乐博娱乐»JavaScript»JavaScript中的原型和继续

JavaScript中的原型和继续

来源:jobbole 宣布时间:2014-05-04 阅读次数:乐博

  请在此暂时忘记之前学到的面向工具的一切知识。这里只需要考虑赛车的情况。是的,就是赛车。

  最近我正在寓目 24 Hours of Le Mans ,这是法国流行的一项赛事。最快的车被称为 Le Mans 原型车。这些车虽然是由“奥迪”或“标致”这些厂商制造的,可它们并不是你在街上或速公路上所见到的那类汽车。它们是专为加入高速耐力赛事而制造出来的。

  厂家投入巨额资金,用于研发、设计、制造这些原型车,而工程师们总是努力实验将这项工程做到极致。他们在合金、生物燃料、制动技术、轮胎的化合物身分和宁静特性上进行了各种实验。随着时间的推移,这些实验中的某些技术经过重复革新,随之进入到车辆的主流产品线中。你所驾驶车辆的某些技术,有可能是在赛车原型上第一次亮相的。

  你也可以说,这些主流车辆继续了来自赛车的技术原型

  到现在,我们就有讨论 JavaScript 中的原型和继续问题的基础了。它虽然并不像你在 C++、Java 或 C# 中了解的经典继续模式一样,但这种方式同样强大,而且有可能会越发灵活。

 有关工具和类

  JavaScript 中全是工具,这指的是传统意义上的工具,也就是“一个包罗了状态和行为的单一实体”。例如,JavaScript 中的数组是含有数个值,而且包罗 push、reverse 和 pop 要领的工具。

var myArray = [1, 2];
myArray.push(3);
myArray.reverse();
myArray.pop();
var length = myArray.length;

  现在问题是,push 这样的要领是从何而来的呢?我们前面提到的那些静态语言使用“类语法”来界说工具的结构,但是 JavaScript 是一个没有“类语法”的语言,无法用 Array“类”的语法来界说每个数组工具。而因为 JavaScript 是动态语言,我们可以在实际需要的情况下,将要领任意放置到工具上。例如下面的代码,就在二维空间中,界说了用来体现一个点的点工具,同时还界说了一个 add 要领。

var point = {
    x : 10,
    y : 5,
    add: function(otherPoint) {
        this.x += otherPoint.x;
        this.y += otherPoint.y;
    }
};

  但是上面的做法可扩展性并欠好。我们需要确保每一个点工具都含有一个 add 要领,同时也希望所有点工具都共享同一个 add 要领的实现,而不是这个要领手工添加每一个点工具上。这就是原型发挥它作用的地方。

 有关原型

  在 JavaScript 中,每个工具都保持着一块隐藏的状态 —— 一个对另一个工具的引用,也被称作原型。我们之前创建的数组引用了一个原型工具,我们自行创建的点工具也是如此。上面说原型引用是隐藏的,但也有 ECMAScript(JavaScript 的正式名称)的实现可以通过一个工具的__proto__属性(例如谷歌浏览器)会见到这个原型引用。从看法上讲,我们可以将工具看成类似于 图1 所体现的工具 —— 原型的关系。

 乐博

1

  展望未来,乐博娱乐开发者将能够使用 Object.getPrototypeOf 函数,取代__proto__属性,取得工具原型的引用。在本文写出的时候,已经可以在 Google Chrome,FIrefox 和 IE9 浏览器中使用 Object.getPrototypeOf 函数。更多浏览器在未来会实现此功效,因为它已经是 ECMAScript 尺度的一部门了。我们可以使用下面的代码,来证明我们建树的 myArray 和点工具引用的是两个差异的原型工具。

  1. Object.getPrototypeOf(point) != Object.getPrototypeOf(myArray);

  对于本文的其余部门,我将交织使用 __proto__和Object.getPrototypeOf 函数,主要是因为 __proto__ 在图和句子中更容易识别。需要记着的是它(__proto__)不是尺度,而 Object.getPrototypeOf 函数才是检察工具原型的推荐要领。

  是什么让原型如此特别?

  我们还没有回覆这个问题:数组中 push 这样的要领是从何而来的呢?答案是:它来源于 myArray 原型工具。图 2 是 Chrome 浏览器中脚本调试器的屏幕截图。我们已经调用 Object.getPrototypeOf 要领检察 myArray 的原型工具。

 乐博

2

  注意 myArray 的原型工具中有许多要领,包罗那些在代码示例中调用的 push、pop 和 reverse 要领。因此,原型工具中简直包罗 push 要领,但是 myArray 要领如何引用到呢?

myArray.push(3);

  了解其事情原理的第一步,是要认识到原型并不是特此外。原型只是普通的工具。可以给原型添加要领,属性,并把他们看成其他 JavaScript 工具一样看待。然而,套用乔治·奥威尔的小说《动物农场》中“猪”的说法 —— 所有的工具应当是平等的,但有些工具(遵守规则的)比其他人越发平等。

  JavaScript 中的原型工具简直是特殊的,因为他们遵从以下规则。当我们告诉 JavaScript 我们要调用一个工具的 push 要领,或读取工具的 x 属性时,运行时会首先查找工具自己。如果运行时找不到想要的工具,它就会循着 __proto__ 引用和工具原型寻找该成员。当我们  调用 myArray 的 push 要领时,JavaScript 并没有在 myArray 工具上发现 push 要领,而是在 myArray 的原型工具上找到了,于是 JavaScript 调用此要领(见图 3)。

乐博

3

  上面所描述的行为是指一个工具自己继续了原型上的任何要领或属性。JavaScript 中其实不需要使用类语法也能实现继续。就像从赛车原型上继续了相应的技术的车,一个 JavaScript 工具也可以从原型工具上继续功效特性。

  图 3 还展示了每个数组工具同时也可以维护自身的状态和成员。在请求获得 myArray 的 length 属性的情况下,JavaScript 会取得 myArray 中 length 属性的值,而不会去读取原型中的对应值。我们可以通过向工具上添加 push 这样的要领来“重写”push 要领。这样就会有效地隐藏原型中的 push 要领实现。

 共享原型

  JavaScript 中原型的真正神奇之处是多个工具如何维持对同一个原型工具的引用。例如,如果我们创建了这样的两个数组:

var myArray = [1, 2];
var yourArray = [4, 5, 6];

  那么这两个数组将共享同一个原型工具,而下面的代码盘算结果为 true:

Object.getPrototypeOf(myArray) === Object.getPrototypeOf(yourArray);

  如果我们引用两个数组工具上的 push 要领,JavaScript 会去寻找原型上共享的 push 要领。

乐博

4

  JavaScript 中的原型工具提供继续功效,同时也就实现了该要领实现的共享。原型也是链式的。换句话说,因为原型工具只是一个工具,所以一个原型工具可以维持到另一个原型工具的引用。如果你重新审视图 2 便可以看到,原型的 __proto__ 属性是一个指向另一个原型的非空值。当 JavaScript 查找像 push 要领这样的成员时,它会循着原型引用链检查每一个工具,直到找到该成员,或者抵达原型链的末端。原型链为继续和共享开辟了一条灵活的途径。

  你可能会问的下一个问题是:我该如何设置那些自界说工具的原型引用呢?例如前面所使用的点工具,如何才气将 add 要领添加到原型工具中,并从多个点工具中继续要领呢?在回覆这个问题之前,我们需要看看函数。

 有关函数

  JavaScript 中的函数也是工具。这样的表述带来了几个重要的结果,而我们并不会在本文中涉及所有的事项。这其中,能将一个函数赋值给一个变量,而且将一个函数作为参数通报给另一个函数的能力组成了现代 JavaScript 编程表达的基本范式。

  我们需要关注的是,函数自己就是工具,因此函数可以有自身的要领,属性,而且引用一个原型工具。让我们来讨论下面的代码的寄义。

// 这将返回 true:
typeof (Array) === "function"
// 这样的表达式也是:
Object.getPrototypeOf(Array) === Object.getPrototypeOf(function () { })
// 这样的表达式同样:
Array.prototype != null

  代码中的第一行证明, JavaScript 中的数组是函数。稍后我们将看到如何调用 Array 函数创建一个新的数组工具。下一行代码,证明了 Array 工具使用与任何其他函数工具相同的原型,就像我们看到数组工具间共享相同的原型一样。最后一行代码证明了 Array 函数都有一个 prototype 属性,而这个 prototype 属性指向一个有效的工具。这个 prototype 属性十分重要。

  JavaScript 中的每一个函数工具都有 prototype 属性。千万不要混淆这个 prototype 属性的 __proto__ 属性。他们用途并不相同,也不是指向同一个工具。

// 返回 true
Object.getPrototypeOf(Array) != Array.prototype

  Array.__proto__ 提供的是 数组原型 – 请把它看成 Array 函数所继续的工具。

  而 Array.protoype,提供的的是 所有数组的原型工具。也就是说,它提供的是像 myArray 这样数组工具的原型工具,也包罗了所有数组将会继续的要领。我们可以写一些代码来证明这个事实。

// true
Array.prototype == Object.getPrototypeOf(myArray)
// 也是 true
Array.prototype == Object.getPrototypeOf(yourArray);

  我们也可以使用这项新知识重绘之前的示意图。

乐博

5

  基于所知道的知识,请想象创建一个新的工具,并让新工具体现地像数组的历程。一种要领是使用下面的代码。

// 创建一个新的空工具
var o = {};
// 继续自同一个原型,一个数组工具
o.__proto__ = Array.prototype;
// 现在我们可以调用数组的任何要领...
o.push(3);

  虽然这段代码很有趣,也能事情,可问题在于,并不是每一个 JavaScript 情况都支持可写的 __proto__ 工具属性。幸运的是,JavaScript 确实有一个创建工具内建的尺度机制,只需要一个操作符,就可以创建新工具,而且设置新工具的 __proto__ 引用 – 那就是“new”操作符。

var o = new Array();
o.push(3);

  JavaScript 中的 new 操作符有三个基本任务。首先,它创建新的空工具。接下来,它将设置新工具的 __proto__ 属性,以匹配所调用函数的原型属性。最后,操作符调用函数,将新工具作为“this”引用通报。如果要扩展最后两行代码,就会酿成如下情况:

var o = {};
o.__proto__ = Array.prototype;
Array.call(o);
o.push(3);

  函数的 call 要领允许你在调用函数的情况下在函数内部指定“this”所引用的工具。虽然,函数的作者在这种情况下需要实现这样的函数。一旦作者创建了这样的函数,就可以将其称之为结构函数。

  结构函数

  结构函数和普通的函数一样,但是具有以下两个特殊性质。

  1. 通常结构函数的首字母是大写的(让识别结构函数变得更容易)。
  2. 结构函数通常要和 new 操作符结合,用来结构新工具。

  Array 就是一个结构函数的例子。Array 函数需要和 new 操作符一起使用,而且 Array 的首字母是大写的。JavaScript 将 Array 作为内置函数包罗在内,而任何人都可以写出自己的结构函数。事实上,我们最后可以为先前创建的点工具编写出结构函数。

var Point = function (x, y) {
    this.x = x;
    this.y = y;
    this.add = function (otherPoint) {
        this.x += otherPoint.x;
        this.y += otherPoint.y;
    }
}
var p1 = new Point(3, 4);
var p2 = new Point(8, 6);
p1.add(p2);

  在上面的代码中,我们使用了 new 操作符和 Point 函数来结构点工具,这个工具带有 x 属性和 y 属性和一个 add 要领。你可以将最后的结果想象成图 6 的样子。

乐博

6

  现在的问题是我们的每个点工具中仍然有单独的 add 要领。使用我们学到的原型和继续的知识,我们更希望将点工具的 add 要领从每个点实例中转移到 Point.prototype 中。要到达继续 add 要领的效果,我们所需要做的,就是修改 Point.prototype 工具。

var Point = function (x, y) {
    this.x = x;
    this.y = y;
}
Point.prototype.add = function (otherPoint) {
    this.x += otherPoint.x;
    this.y += otherPoint.y;
}
var p1 = new Point(3, 4);
var p2 = new Point(8, 6);
p1.add(p2);

  大功告成!我们刚刚在 JavaScript 中完成原型式的继续模式!

乐博

7

  总结

  我希望这篇文章能够帮助你揭开 JavaScript 原型看法的神秘面纱。开始看到的是原型怎样让一个工具从其他工具中继续功效,然后看到怎样结合 new 操作符和结构函数来构建工具。这里所提到的,只是开启工具原型力量和灵活性的第一步。本文勉励你自己发现学习有关原型和 JavaScript 语言的新信息。

  同时,请小心驾驶。你永远不会知道这些行驶在路上的车辆会从他们的原型继续到什么(有缺陷)的技术。

  原文链接: Script Junkie   翻译: 伯乐在线 - 埃姆杰