对于原型咱们经过[[prototype]]
、proto 以及 prototype 这三个概念理解其实现继承的思路。数组
在 ECMAScript 标准中规定每一个对象都有一个内置的属性[[prototype]]
,它指向一个对象的原型对象。当查找一个对象的属性或方法时,若是在当前对象找不到,则在其原型对象上寻找,若是原型对象上也没有,则在原型对象的原型对象上寻找,如此继续直到一个对象的原型对象为 null(null 没有原型)。能够看到,这样一种层层向上的查找是一种链式查找,在每一层上的对象都有一个指向其原型对象的连接([[prototype]]
),由这些连接组成的整个链条就叫作原型链。浏览器
如图 1 所示,原型链查找的思路大体为:bash
__proto__
([[prototype]]
)中查找,此时__proto__
指向 object2。__proto__
中查找,若是没有则继续向上查找。__proto__
为 null,则再也不查找。说明: 图中 builts-in 为构建内置函数好比 toString()、valueOf 等。函数
前述中的[[Prototype]]
是一个内置属性,咱们并不能直接获取,为了操做属性的便利性不少浏览器都实现了 Object.prototype.__proto__
,所以能够经过 obj.__proto__
来访问对象的原型对象[[Prototype]]
,因此__proto__
和[[Prototype]]
本质上是一个东西,都指向一个对象的原型对象。 另外一方面,设置[[Prototype]]
是一个缓慢的操做,影响性能,所以使用 __proto__
是有争议的,更推荐使用 Object.getPrototypeOf 和 Object.setPrototypeOf 来访问原型对象(尽管如此,若是性能是个问题,也应尽可能避免使用)。性能
prototype 是构造函数(一个拥有 [[Construct]]
内部方法的对象)才有的属性,好比函数(非箭头函数),实例对象是没有这个属性的。这个所谓的 prototype,其实能够认为是构造函数内部一个普通的对象(或者说指向一个普通对象),只是很不幸地也叫作 prototype(原型对象)而已,当构造函数执行时,会自动将构造函数的 prototype 赋值给 __proto__
(构造函数内部没有显示返回对象的状况下),这样在新的实例上经过原型链就能够共享构造函数 prototype 及其原型链上的属性了。prototype 和前述的__proto__
、[[Prototype]]
是彻底不一样的概念,咱们一般的混淆,主要就来自于用原型对象一词来指代了不一样的它们。ui
来看下面的例子: 函数 Animal 经过 new 实例化的对象可以访问到函数 prototype 属性的 food 和 eat,这是如何作到的呢?this
var Animal = function(name) {
this.name = name;
};
Animal.prototype.food = 'meat';
Animal.prototype.eat = function() {
console.log(this.name + ' eat ' + this.food);
};
var panda = new Animal('panda');
var dog = new Animal('dog');
console.log(panda.eat()); // panda eat meat
console.log(dog.eat()); // dog eat meat
console.log(panda.__proto__=== Animal.prototype); // true
复制代码
以下图所示,实例对象 panda 和 dog 之因此可以访问 Animal 原型上的 food 和 eat 属性是由于在调用构造函数时 Animal 的 prototype 对象赋值给了实例对象的 __proto__
属性,实例对象在访问本身的方法(panda.eat)时,若是没有找到,则在__proto__
对象中寻找,而这个对象正好是 Animal 的 prototype 对象,它拥有 eat 方法,因此能够成功访问 eat 方法。spa
来看另外一个例子: 以下将函数 Fish 的 prototype 赋值为 Animal,以此,经过 fish 的实例来访问 Animal 原型 prototype 上的方法,可结果是 Uncaught TypeError: nimo.eat is not a function,为何会这样呢?之因此会出现这样的错误,是由于咱们错误的把原型对象(__proto__
)当原型对象(prototype)。前述咱们已经知道继承是经过原型链来实现的,而原型链又是经过 __proto__
来串联的。当函数 Fish 的 prototype 赋值为 Animal 后,生成的实例对象 nimo 的 __proto__
为 Animal,因此访问 nimo.eat 会先在 Animal 上寻找 eat 方法,如图 3,Animal 函数并无 eat 方法,从而经过 Animal 的__proto__
继续向上寻找,直到顶层对象 Object,结果仍是没有,所以报错。prototype
var Animal = function(name) {
this.name = name;
};
Animal.prototype.food = 'meat';
Animal.prototype.eat = function() {
console.log('I can eat' + this.food);
};
var Fish = function(name) {
this.name = name;
};
Fish.prototype = Animal;
var nimo = new Fish('nimo');
console.log(nimo.eat()); // Uncaught TypeError: nimo.eat is not a function
复制代码
语法结构建立对象code
对象字面量
经过对象字面量建立的对象其原型链为 obj --> Object.prototype --> null
var obj = { a: 1 };
复制代码
数组字面量
经过数组字面量建立的对象其原型链为 arr --> Array.prototype --> Object.prototype --> null
var arr = [1, 2];
复制代码
函数字面量
经过函数字面量建立的对象其原型链为 f --> Function.prototype --> Object.prototype --> null
function f(){ console.log('func');}
复制代码
构造器建立对象
经过构造函数建立的对象其原型链为 instance --> func.prototype --> Object.prototype --> null
var Animal = function(name) {
this.name = name;
};
Animal.prototype.food = 'meat';
Animal.prototype.eat = function() {
console.log('I can eat' + this.food);
};
//实例对象panda的__proto__指向Animal.prototype
var panda = new Animal('panda');
复制代码
Object.create 建立对象
在 ES5 中引入了一个新的方法来建立对象,就是 Object.create,新对象的原型就是该方法传入的第一个参数。
var a = { x: 1 };
// a --> Object.prototype --> null
var b = Object.create(a);
// b --> a --> Object.prototype --> null
console.log(b.__proto__ === a); // true
console.log(b.x); // 1
var c = Object.create(b);
// c --> b --> a --> Object.prototype --> null
console.log(c.__proto__ === b); // true
复制代码
__proto__
指向的对象)。[[Prototype]]
为一个对象的指向原型对象的内置属性,不能直接访问。__proto__
为一个非标准的,只是为了方便访问原型对象而实现的一个属性,它和[[Prototype]]
本质上同样都 指向原型对象,是全部对象都有的属性。[[construct]]
内部方法的对象才有的属性,它自己只是一个普通对象,只是正好叫作原型对象,它的做用是在构造函数生成新的实例时将这个所谓的原型对象赋值给实例的 __proto__
属性,这样新的实例就能够经过 __proto__
来继承构造函数原型里的方法。能够看到,prototype 和 __proto__
所指的原型对象是彻底不一样的概念。