边城

JavaScript 起步:“new” 运算符 - Hacker Noon

边城 · 2017-06-06翻译 · 576阅读 原文链接

你好!如果你刚来,这是我每周系列,JavaScript 起步的第4期。我推荐你在阅读本文之前,先阅读本系列的其它文章。


四项原则

理解 new 运算符最简单的办法就是搞明白它是干什么的。当你使用 new 的时候,会发生4件事情:

  1. 它创建一个新的空对象。

  2. 将 this 绑定到新创建的对象上。

  3. 在新创建的对象上添加一个叫“proto” 的属性,这个属性指向构造函数的原型(prototype)对象。

  4. 在函数结束前添加return this,这样刚创建的对象可以从这这个函数返回出去。


等等,什么?

如果不能完全理解这些规则也没关系。我们从简单的开始。创建一个叫 Student 的构造函数。这个函数有两个参数,name 和 age。然后会将这两个参数的值设置给 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.proto 现在指向 Studnet.prototype。

  4. 所有事情完成后,我们将新的 first 对象返回出来赋值给新的 first 变量。

现在我们来运行一个简单的 console.log 语句测试代码是否可运行:

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

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

非常棒。现在我们深入 new 关键字的 proto 部分。


原型

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

来看一个示例。打开你的 Chrome 开发者工具 控制台(Windows:Ctrl+Shift+J)(Mac: Cmd+Option+J),然后输入本文前面定义的 Students 函数。

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

为了证明每个对象都有原型,我们现在输入:

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

酷,返回了一个对象。现在尝试创建一个新的学生对象:

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

我们已经使用了 Studnet 构造函数来创建第二个名为 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;
//  }

这很快就得复杂起来。让我们看看下面的简图,看能否让你直观地了解到这个过程:

很漂亮,不是吗?

正如你在上面看到的,我们的 Student 构造函数 (其它构造函数也是一样)有一个叫 .prototype 的属性。这个原型上有一个对象叫 .constrcutor,往回指向构造函数。这是一个漂亮的小回环。然后,在我们使用 new 运算符创建新对象的时候,每个对象都有 .proto 属性将新对象与 Student.prototype 连接起来。

为什么这很重要?

因为继承,它非常重要。原型对象由对应构造函数创建的所有对象共享。也就是说我们可以在原型中添加函数和属性,而所有对象都可以使用。

在我们上面的示例中,我们只创建了两个 Student 对象,但是如果不是 2 个学生,而是 20,000 个呢?我们在原型上定义函数而不是在每个学生对象上定义这种做法一下子就节省了大量的处理能力。

让我们来看一个回到理想之家的例子。在控制台输入下面的代码:

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);

// Now if we log third out, we see the object only has two
// properties, age and name. Yet, we still have access to the 
// sayInfo function:

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

我们的对象首先检查自己是否有需要的方法,然后才会到原型中去找。既然我们已经定义了这个方法,那直接运行就好,不需要使用继承的方法了。但这并不是个好主意。最好保留全局方法,为你自己的方法换个别的名称。

小结

作为新入行的开发者,可能很难理解这些概念,不过一旦理解了,就能写出更好更有效的代码。通过原型我们可以快速而有效地在成百上千个对象中共享相同的代码。和我其它所有文章一样,这篇文章是让你了解一些基础知识,然后你可以自己继续深入学习。


❤如果本文对你有帮助,请点击那颗小小的爱心!还有,不要忘了看看其它我最近写的文章:

相关文章