对于那些熟悉基于类的面向对象语言(Java 或者 C++)的开发者来讲,JavaScript 的语法是比较怪异的,这是因为 JavaScript 是一门动态语言,并且它没有类的概念
(虽然 class
是个保留字,不能做为变量名来使用)。web
继承方面,JavaScript 中的每一个对象都有一个内部私有的连接指向另外一个对象,这个对象就是原对象的原型。这个原型对象也有本身的原型,直到对象的原型为 null
为止(也就是没有原型)。这种一级一级的链结构就称为原型链。数组
虽然这一般会被称做 JavaScript 的弱点之一,实际上这种原型继承的模型要比经典的继承模型还要强大。虽然在原型模型上构建一个经典模型是至关琐碎的,但若是采起其余方式实现则会更加困难。框架
JavaScript 对象有两种不一样的属性,一种是对象自身的属性,另一种是继承于原型链上的属性。下面的代码则演示了当访问一个对象的属性时发生的行为:函数
// 假定咱们有个对象o,而且o所在的原型链以下: // {a:1, b:2} ---> {b:3, c:4} ---> null // 'a'和'b'是o自身的属性. // 该例中,用"对象.[[Prototype]]"来表示这个对象的原型. // 这只是一个纯粹的符号表示(ECMAScript标准中也这样使用),不能在实际代码中使用. console.log(o.a); // 1 // a是o的自身属性吗?是的,该属性的值为1 console.log(o.b); // 2 // b是o的自身属性吗?是的,该属性的值为2 // o.[[Prototype]]上还有一个'b'属性,可是它不会被访问到.这种状况称为"属性遮蔽". console.log(o.c); // 4 // c是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有. // c是o.[[Prototype]]的自身属性吗?是的,该属性的值为4 console.log(o.d); // undefined // d是o的自身属性吗?不是,那看看o.[[Prototype]]上有没有. // d是o.[[Prototype]]的自身属性吗?不是,那看看o.[[Prototype]].[[Prototype]]上有没有. // o.[[Prototype]].[[Prototype]]为null,原型链已到顶端,没有d属性,返回undefined
JavaScript 并无真正的“方法”,JavaScript 只有函数,并且任何函数均可以添加到对象上做为对象的属性。继承的函数与其余的属性是基本没有差异的,包括“属性遮蔽”(这种状况至关于其余语言的方法重写)。性能
当继承的函数被调用时,this
指向的是当前继承原型的对象,而不是继承的函数所在的原型对象。this
var o = { a: 2, m: function(){ return this.a + 1; } }; console.log(o.m()); // 3 // 当调用 o.m 时,'this'指向了o. var p = Object.create(o); // p是一个对象, p.[[Prototype]]是o. p.a = 12; // 建立p的自身属性a. console.log(p.m()); // 13 // 调用p.m时, 'this'指向 p. 'this.a'则是12.
var o = {a: 1}; // o这个对象继承了Object.prototype上面的全部属性 // 因此能够这样使用 o.hasOwnProperty('a'). // hasOwnProperty 是Object.prototype的自身属性。 // Object.prototype的原型为null。 // 原型链以下: // o ---> Object.prototype ---> null var a = ["yo", "whadup", "?"]; // 数组都继承于Array.prototype (indexOf, forEach等方法都是从它继承而来). // 原型链以下: // a ---> Array.prototype ---> Object.prototype ---> null function f(){ return 2; } // 函数都继承于Function.prototype(call, bind等方法都是从它继承而来): // f ---> Function.prototype ---> Object.prototype ---> null
在 JavaScript 中,构造方法其实就是一个普通的函数。当使用 new 操做符 来做用这个函数时,它就能够被称为构造方法(构造函数)。spa
function Graph() { this.vertexes = []; this.edges = []; } Graph.prototype = { addVertex: function(v){ this.vertexes.push(v); } }; var g = new Graph(); // g是生成的对象,他的自身属性有'vertexes'和'edges'. // 在g被实例化时,g.[[Prototype]]指向了Graph.prototype.
ECMAScript 5 中引入了一个新方法:Object.create。能够调用这个方法来建立一个新对象。新对象的原型就是调用 create
方法时传入的第一个参数:prototype
var a = {a: 1}; // a ---> Object.prototype ---> null var b = Object.create(a); // b ---> a ---> Object.prototype ---> null console.log(b.a); // 1 (继承而来) var c = Object.create(b); // c ---> b ---> a ---> Object.prototype ---> null var d = Object.create(null); // d ---> null console.log(d.hasOwnProperty); // undefined, 由于d没有继承Object.prototype
在原型链上查找属性比较耗时,对性能有反作用,这在性能要求苛刻的状况下很重要。另外,试图访问不存在的属性时会遍历整个原型链。code
遍历对象的属性时,原型链上的每一个属性都是可枚举的。orm
检测对象的属性是定义在自身上仍是在原型链上,有必要使用 hasOwnProperty 方法,该方法由全部对象继承自 Object.proptotype
。
hasOwnProperty 是 JavaScript 中惟一一个只涉及对象自身属性而不会遍历原型链的方法。
注意:仅仅经过判断值是否为 undefined 还不足以检测一个属性是否存在,一个属性可能存在而其值刚好为 undefined
。
一个常用的很差实践是扩展 Object.prototype
或者其余内置对象的原型。
该技术被称为 monkey patching,它破坏了对象的封装性。虽然一些流行的框架(如 Prototype.js)在使用该技术,可是该技术依然不是好的实践,附加的非标准的方法使得内置的类型混乱。
扩展内置对象原型的惟一正当理由是移植较新 JavaScript 引擎的特性,如 Array.forEach
。
在编写使用到原型继承模型的复杂代码前理解原型继承模型十分重要。同时,还要清楚代码中原型链的长度,并在必要时结束原型链,以免可能存在的性能问题。更进一步,除非为了兼容新 JavaScript 特性,不然永远不要扩展原生对象的原型。