06月16, 2017

JavaScript: 为初学者介绍 new 运算符

原文:https://hackernoon.com/javascript-for-beginners-the-new-operator-cee35beb669e

alt

四条规则

理解 new 运算符的最简单的方式就是去理解它做什么。当我们用 new 时,发生了四件事:

  1. 创建了一个新的空对象。
  2. this 绑定到新创建的对象。
  3. 给新创建的对象添加了一个名为 __proto__ 的属性,该属性指向构造器函数的原型对象。
  4. 给函数末尾添加一条 return this 语句,这样被创建的对象就被从函数返回。

等等,这是啥玩意啊?

如果还不明白这些规则,没关系。我们从简单的开始。创建一个名为 Student 的构造器函数。该函数带有两个参数,nameage。然后,将这些属性设置在 this 的值上面。

function Student(name, age) {
    this.name = name;
    this.age = age;
}

不错。下面我们用 new 运算符调用我们的构造器函数。我们打算传递进两个参数:'John'26

var first = new Student('John', 26);

那么,当执行上述代码时,会发生什么呢?

  1. 创建了一个新对象 — first 对象。
  2. this 被绑定到 first 对象。于是引用 this 就会指向 first
  3. __proto__ 被添加到 first 上。现在 first.__proto__ 会指向 Student.prototype
  4. 一切都做完后,崭新的 first 对象被返回给新的 first 变量。

现在我们可以运行几个简单的 console.log 语句来检测一下它是否起作用了:

console.log(first.name);
// John

console.log(first.age);
// 26

棒极了。下面我们更深入研究一下 new 关键字的 __proto__ 部分。

原型

每个 JavaScript 对象都有一个原型(prototype)。JavaScript 中所有对象都从它们的原型中继承方法和属性。

下面我们用示例验证一下。打开 Chrome 开发者控制台(Windows: Ctrl + Shift + J)(Mac: Cmd + Option + J),键入本文早前的 Student 函数:

function Student(name, age) {  
    this.name = name;  
    this.age = age;
}

为证实每个对象都有一个原型,现在我们键入:

Student.prototype;
// Object {...}

酷!返回了一个对象。现在我们试着创建一个新学生:

var second = new Student('Jeff', 50);

我们用 Student 构造器函数来创建第二个名为 Jeff 的学生。并且,因为我们使用 new 运算符,那么 __proto__ 也应该已经被添加到 second 对象上。它应该指向父构造器。我们来试试看它们是否相等:

second.__proto__ === Student.prototype;
// true

最后,Student.prototype.constructor 应该指向 Student 构造器函数:

Student.prototype.constructor;

//  function Student(name, age) {
//    this.name = name;
//    this.age = age;
//  }

这很快就变得复杂了。我们来看看能否用一个图像帮助表示发生了什么:

alt

如上所见,Student 构造器函数(以及所有其它构造器函数)有一个名为 .prototype 的属性。这个原型上有一个对象叫 .constrcutor,往回指向构造器函数。这是一个漂亮的小回环。然后,在我们使用 new 运算符创建新对象时,每个对象都有 .__proto__ 属性将新对象与 Student.prototype 连接起来。

那么,为什么这很重要呢?

因为继承,所以它重要。原型对象是在用该构造器函数创建的所有对象之间共享的。也就是说可以给原型添加所有对象都可以用的函数和属性。

在上例中,我们只创建了两个 Student 对象,但是如果不是两个对象,而是两万个对象呢?把共享的函数放在原型上,而不是在每个学生对象上,一下子就省了大把力气。

下面我们看一个例子,让这种理念落地。在控制台中,添加如下行:

Student.prototype.sayInfo = function(){
  console.log(this.name + ' is ' + this.age + ' years old');
}

我们在这里所做的,还是给 Student 原型添加一个函数 - 每个我们要创建的或者已经创建的任何学生都可以访问这个崭新的 .sayInfo 函数了!我们来检验一下:

second.sayInfo();
// Jeff is 50 years old

加一个新学生,再试试:

var third = new Student('Tracy', 15);

// 现在如果输出第三个学生,我们会看到该对象只有两个属性,
// age 和 name。不过还是能访问 sayInfo 函数:

third;
// Student {name: "Tracy", age: 15}

third.sayInfo();
// Tracy is 15 years old

起作用了!而且它能起作用是因为继承。使用 JavaScript 对象时,对象会先找找它本身是否有我们调用的属性。如果没有,就会向上,对它的原型说 '喂,你有没有这个属性啊?'。这种同样的模式一直持续到要么找到该属性,要么到达全局对象上原型链的末尾。

过去我们能使用 .toString() 这种函数,也是因为有了继承!想一想,我们从来就没有写过 toString() 方法,却依然可以很好地用它。这是因为该方法,以及其它 JS 内置的方法都定义在 Object 对象的原型上。我们创建的每个对象最终都会委托给 Object 的原型。当然,我们可以像下面这样重写这些方法:

var name = {
  toString: function(){
    console.log('Not a good idea');
  }
};

name.toString();
// Not a good idea

对象会在转到原型之前,先检查它自己是否有这个方法。既然我们确实有这个方法,那就执行它好了,不需要继承。不过,这并不是个好主意。最好保留全局方法,把自己的函数改个名字。

总结

对于开发新手来说,这可能是一个很难理解的概念,不过一旦你掌握了,就可以编写更好更干净的代码。通过使用原型,我们可以快速而高效地在成百上千个对象之间共享相同的代码。跟我所有其它文章一样,本文的理念是让你掌握能让你起步的基础知识,然后自己去继续学习更多东西。

本文链接:http://www.xiaojichao.com/post/new.html

-- EOF --

Comments