主页»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   翻译: 伯乐在线 - 埃姆杰

QQ群:凯发娱乐官网官方群(515171538),验证音讯:10000
微信群:加小编微信 849023636 邀请您参加,验证音讯:10000
提示:更多精彩内容重视微信大众号:全栈开发者中心(fsder-com)
网友谈论(共7条谈论) 正在载入谈论......
沉着谈论文明上网,回绝歹意咒骂 宣布谈论 / 共7条谈论
登录会员中心