【问题标题】:JavaScript: Is it worth making variables private and defining getters/setters?JavaScript:是否值得将变量设为私有并定义 getter/setter?
【发布时间】:2012-12-05 14:57:39
【问题描述】:

首先我想说的是,我了解 JavaScript 是一种无类语言。我的背景是 Java、C++ 和 Objective-C,它们都是支持类的经典 OOP 语言。

我正在扩展 Web 开发,并一直在尝试使用 JavaScript 并学习其模式。现在我正在使用在 JavaScript 中模拟类的构造函数模式。

所以这是我的“练习”课:

function Car( model, year, miles ) {
    this.model = model;
    this.year = year;
    this.miles = miles;

    var privateVarTest = 10;

    this.getPrivateVarTest = function() {
        return privateVarTest;
    }

    this.setPrivateVarTest = function( value ) {
        privateVarTest = value;
    }
}

Car.prototype.toString = function() {
    return this.model + " is a " + this.year + " model and has " + 
           this.miles + " miles.";
}

var myCar = new Car( "Ford Focus", "2006", "65,000" );
document.getElementById('notepad').innerHTML += '</br> Testing </br>';
document.getElementById('notepad').innerHTML += myCar.toString() + '</br>';
document.getElementById('notepad').innerHTML += myCar.model + '</br>';
document.getElementById('notepad').innerHTML += myCar.getPrivateVarTest() + '</br>';
myCar.setPrivateVarTest( 20 );
document.getElementById('notepad').innerHTML += myCar.getPrivateVarTest() + '</br>';

现在我喜欢使用prototype 定义函数的方式,因为它不会为每个创建的Car 对象实例化新版本的函数。但是,在经典的 OOP 语言中,我们将变量设为 private 并创建 public 函数/方法来根据需要设置和获取这些变量。

JavaScript 是无类的,没有 privatepublic 关键字用于此用途,所以我想我会尝试一种“伪造”private 变量的方法,然后发现使用 @987654329 @ 而不是 this 基本使它在 constructor 之外无法访问,但我能够定义允许我这样做的 getter 和 setter。

现在终于要回答我的问题了,很抱歉拖了这么久。对于经验丰富的 JavaScript 程序员的最佳实践,您是否会使所有变量 private 遵循其他 OOP 语言的标准,并设置 getter 和 setter(不能为每个 Object 强制创建原型),或者尽可能避免使用它们可能,因为this 关键字基本上可以让你获取和设置你想要的一切,并且只使用private 对类所需的一些内部数据进行硬编码?

感谢您抽出时间阅读本文并参与讨论,我真的只是想了解经验丰富的 Web 开发人员用作最佳实践的标准。

【问题讨论】:

    标签: javascript oop class coding-style web-standards


    【解决方案1】:

    通用 OOP

    无论您使用哪种语言编写代码,我都认为 getter 和 setter 在很大程度上完全没有意义和愚蠢。

    在大多数情况下,暴露的属性应该很少见,因为对象的任何属性通常都应该在对象的域内,因此只有对象实际上应该更改自己的内部结构作为其他操作的副作用,而不是因为其他一些对象直接告诉它改变一些东西。我确定有例外(总是有),但我不记得我上次需要制作一个例外是什么时候。

    此外,当属性被公开时,使用方法公开的唯一原因是,由于语言限制 (Java),您不能只公开属性,或者因为在更改该属性时必须进行一些验证或通知.仅仅添加 Java bean 风格的方法,这些方法除了实际更改或返回属性外,对保留封装没有任何作用。如果可以的话,您不妨将财产公开。

    但是,想要从各处随意获取/设置所有内容的真正问题在于,您基本上只是编写了链式过程代码并将其称为 OOP。您仍然有一系列漫长而曲折的事情,只能根据一个接一个发生的情况来推理。使用 OOP,其想法是避免冗长缠绕的意大利面条链,因此您可以从更大的构造的角度更多地查看您的架构,这些构造拥有在关键点上相互交互的特定域。如果没有这些,您可能会通过至少将您的函数分类到命名空间下来减少意大利面,以便更容易知道在哪里寻找东西,但您并没有真正利用 OOP 可以为您的架构提供的关键优势。

    私有或在 JS 的情况下本地构造函数/工厂关闭变量的真正价值是信号意图。如果它暴露了,那么外部真的应该改变它。如果不是,那么您已经明确表示 var 只是对象的业务。

    JS OOP

    我的建议是忘记 JS 中的类仿真。这是完全没有必要的。一旦你理解了原型,它们就会变得优雅而简单。将构造函数的原型属性视为一种备份对象。如果你在一个实例上调用一个它没有的方法,下一步就是检查实例的构造函数的原型对象属性。如果该对象没有它,则检查其构造函数的原型对象,依此类推,直到您最终到达核心 Object 构造函数的原型。

    正是由于这个查找过程,您可以在运行中向构造函数添加新方法,并让所有实例在构建后“继承”它,但这并不是真正的继承,而是回退过程。

    JS 中的继承非常简单。这并不意味着你应该做很多事情。长链级联继承在任何语言中都被视为一种反模式是有充分理由的,并且由于回调过程的工作方式,如果您通过 18 级原型对调用对象进行锤击,它也可以真正杀死性能JS 中的每一件小事。我会说更喜欢复合对象而不是继承,当继承似乎是一个更明智的选择时,请随时检查自己,如果你想通过链中超过 2-3 个原型链接进行继承。

    哦,还有一个 JS 需要注意构造函数中作为私有属性的局部实例变量:这实际上只是 JS 在函数范围上下文中的闭包规则。在原型中或构造函数外部声明的方法无法访问这些内部变量。使用 new 关键字调用的构造函数会改变 'this' 访问的规则,它们会留下一个实例,但会以其他方式执行 JS 函数。

    JS OOP 中其他一些疯狂但也很疯狂的值得理解的方式是 apply、call 和现在的 bind 方法。我倾向于将这些更多地视为您在工厂中想要的东西,但它们非常强大。

    一旦你掌握了 JS OOP,开始从功能的角度来理解 JS,你会发现它有一个非常强大的 1-2 拳组合。我们几乎可以用最少的 JS 代码轻松地做任何事情。设计权衡是性能(现代 JIT 编译器处理得非常好),并且它为您提供了很多可以吊死自己的绳索。我更喜欢绳子。自我私刑并不好玩,但这是学习/培养更好的直觉过程的一部分,结果发生得更快,并且从长远来看会导致更可维护的代码。 Java 基本上强制实施 OOP,但由于在开发人员对自己做愚蠢的事情方面过于保护主义,导致社区广泛采用与 OOP 的全部观点完全背道而驰的做法。

    简短版:

    • 如果您这样做了,请停止获取/设置很多内容,无论使用何种语言。它从一开始就大大降低了实施 OOP 的成功因素。
    • 原型非常简单、优雅且功能强大。修补他们。学习它们。但请注意。相比之下,课程可能会开始显得陈旧、笨拙和过度劳累(尽管公平地说,在非解释性语言中是完全必要的)。
    • 为了让 JS 为您提供良好的工作,请自学任何您碰巧遇到的问题。在原始优雅的语言能力方面的回报非常值得花时间。 JS 比您列出的熟悉的语言更接近于 Scheme,所以这很奇怪,但它并不是任意奇怪或没有考虑设计原则,而且 JS 在 web UI 中的主导成功并非偶然,不管人们告诉大家我们“卡住了”有了它”会让你相信。
    • 全面披露:我不喜欢 Java。

    更新:

    es6 class 关键字几乎没有改变 JS 中的 OOP。它是 100% 的语法糖。 IMO,使用“类”这个词对新手没有任何好处,但是 JS 中对象构造器/创建和对象实例化的所有三种样式都有优点/缺点,它们都值得了解/理解。这三种方法是作为构造函数的函数、Object.create,以及现在的 class 关键字。

    【讨论】:

    • 这是一个绝妙的答案。谢谢埃里克。
    • 感谢 Erik 的深思熟虑和构建的答案!我最近开始在 Web 领域使用普通语言进行开发,PHP、HTML、XML、SQL(用于数据存储和检索),当然还有 JavaScript。我最初只将 JavaScript 用作最后的手段,但我注意到 JavaScript(尤其是 jQuery)在完成一些非常好的应用程序方面具有很大的功能。再次感谢埃里克!
    • jQuery 在减少/规范化 DOM API 方面非常强大,但我尝试将其隐藏在具有非 UI 专业 Web 开发人员更易于访问的接口的对象中。当你有机会时,试着了解它是如何在幕后工作的。起初它看起来像一团糟,但从 JavaScript 对象工厂的角度来看,在保持对象轻量级方面有一些非常聪明的东西。只要您可以选择数据格式,我肯定会更喜欢 JSON 而不是 XML。 JSON 更加紧凑,它的 JavaScript 在客户端上非常易于阅读/使用。
    【解决方案2】:

    我们需要意识到我们倾向于希望我们学习的每一种新语言的行为都与我们学习的最后一种语言相同。还是第一个。等等。 Douglas Crockford 有一个great (albeit a bit dated) google talk,他在其中沉思道:“Javascript 是我所知道的唯一一种人们觉得在使用它之前不需要学习的语言”。那场演讲会回答很多你从来不知道的问题,包括你在这里提出的问题。

    编写 setter 和 getter 并没有错。为保持自己的理智而工作很少有坏处。说 JS 时,你会碰巧有一个“C 口音”,但至少你会清楚地告诉任何阅读你的代码的人。

    关于跨范围管理“this”的理智保存提示,请始终记住,您可以在进入新上下文之前保存当前的“this”:

    var self = this;
    

    我通过在声明范围内包含我的对象方法来避免使用原型,除非在特殊情况下。

    function MyClass(_arg){
        var self = this;
        this.arg = _arg;
    
        this.returnArg = function(){
             return self.arg; 
        }
    }
    
    var foo = new MyClass("bar");
    foo.returnArg();  //"bar"
    

    【讨论】:

    • 所以我知道保存对原始this 的引用可能很有用,但这如何帮助封装数据并防止访问类的某些方面?您和其他 JavaScript 程序员在使用类时是否不担心限制数据访问?
    • 封装不是关于限制数据访问,而是关于绑定域数据,这样只有对象本身才应该做出改变任何事情的决定。这是我批发获取/设置的基本问题。理想情况下,在大多数情况下,对象会相互请求做某事。他们不会告诉对方如何处理他们的内部数据。
    • ^我应该详细说明。数据访问的限制确实发生了。只需学会将其视为副作用而不是目标,以便深入了解 OOP。人们总是把他们的鸡和鸡蛋放在那个上面。
    • 如果有人指出 JS 需要像任何其他语言一样作为自己的范式学习,我将永远 +1。不过,我不怪人们被愚弄。与看起来和感觉非常相似的语言相比,C 语法很容易思考它不能做什么。
    • 我的回答是强调一种类似 C 的语法,用于在 Javascript 中将数据绑定到域,作为概念证明,并且假设问题本身知道其自身的传统 OO 偏见。 Erik 关于封装意图的特殊性非常值得赞赏,因为它强调了 JS 虽然能够模仿其他语言的功能,但也有自己的功能。
    【解决方案3】:

    在 OOP 的情况下,我不得不说事实上 javascript 提供了某种级别的 oop。

    我的意思是 OOP 设计的 4 个主要概念可以在 javascript 中实现,尽管它不像 Java 或 C++ 那样强大且定义得很好。让我们检查一下这些概念,我将尝试为每个概念提供一个示例。

    1- 抽象 :正如我之前所说,在这里我们可以理解为什么 OOP 不像 Java 中那样定义得很好,在 Java 中我们使用类、变量、接口等来实现抽象概念...但是与其他 OOP 语言(如 Java)相比,javascript 中的抽象是相当隐式定义的。

    2- 封装:我想这里有一个例子就足够了

    function Student (stdName, stdEmail, stdAvg) {
      this.name = theName;
      this.email = theEmail;
      this.avg = stdAvg;
     }
    

    这里也正如你所见,我们使用函数定义了一个类似“类”的概念,事实上,如果获取类型 Student,你会看到它是一个函数。

    3,4 - 继承和多态: JavaScript 实现继承和多态的方式与 Java 或 C++ 不同,因为它采用了原型(老实说我不知道​​任何其他方式可以这么说)方法。

    const Gun = function(soundEffect){
      this.soundEffect = soundEffect;
    };
    
    Gun.prototype.fire = function(){
      console.log(this.soundEffect);
    };
    
    const DesertEagle = function(color,clipSize){
      this.color = color;
      this.clipSize = clipSize;
    };
    
    DesertEagle.prototype = new Gun("pew pew peeeew");
    
    const myWeapon = new DesertEagle("black",15);
    
    myWeapon.fire();
    

    现在为了涵盖变量和函数的公共/私有访问,我们必须使用某种技术来实现这样的概念。检查下面的代码:

    const Student = function(name, stdNumber, avg){
      this.name = name;
      this.stdNumber = stdNumber;
      this.avg = avg;
      var that = this; //NOTE : we need to store a reference to "this" in order for further calls to private members
    
      this.publicAccess = { // a set of functions and variables that we want as public
        describe: function () {
           console.log(that.name + " : " + that.stdNumber);
        },
        avg: this.avg,
      };
    
      return this.publicAccess; // return set of public access members
    };
    
    
    const newStd = new Student("john", "123", "3.4");
    
    newStd.describe();
    // output: john : 123
    console.log(newStd.avg)
    // output: 3.4
    

    在 ES6 中定义一个类要容易得多,但它只是语法糖,它的核心仍然是相同的东西。

    我希望它会有所帮助。 我还向您推荐这篇文章 (Javascript design patterns),它将提供一些有关 avascript 功能和设计模式的有用信息。

    请接受我对我糟糕的英语的道歉。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-03-27
      • 1970-01-01
      • 2013-06-22
      • 1970-01-01
      • 2015-06-21
      • 2012-01-25
      • 2020-11-08
      • 2019-06-10
      相关资源
      最近更新 更多