11

我想首先说我了解 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变量的方法,然后发现 usingvar而不是thisessential 使得它在constructor.能够定义允许我这样做的 getter 和 setter。

现在终于到了我的问题,对长时间的结束感到抱歉。对于经验丰富的 JavaScript 程序员的最佳实践,您是让所有变量private都遵循其他 OOP 语言的标准,并设置 getter 和 setter(不能为每个 Object 强制创建原型),还是尽可能避免它们,因为this关键字基本上可以让您获取和设置您想要的所有内容,并且仅private用于对课程所需的一些内部数据进行硬编码?

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

4

3 回答 3

23

一般面向对象

我认为无论你用什么语言编写代码,getter 和 setter 基本上都是毫无意义和愚蠢的。

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

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

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

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

JS面向对象

我的建议是忘记 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 关键字。

于 2012-12-05T16:56:07.673 回答
1

我们需要意识到我们倾向于希望我们学习的每一种新语言的行为都与我们学习的最后一种语言相同。还是第一个。等等。道格拉斯·克罗克福德(Douglas Crockford)有一个很棒的(虽然有点过时)谷歌谈话,他在其中沉思道,“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"
于 2012-12-05T15:48:19.773 回答
0

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

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

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

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 设计模式),它将提供一些有关 avascript 功能和设计模式的有用信息。

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

于 2017-08-22T09:39:04.707 回答