JS中的继承与原型链

对于原型咱们经过[[prototype]]、proto 以及 prototype 这三个概念理解其实现继承的思路。数组

[[prototype]]

在 ECMAScript 标准中规定每一个对象都有一个内置的属性[[prototype]],它指向一个对象的原型对象。当查找一个对象的属性或方法时,若是在当前对象找不到,则在其原型对象上寻找,若是原型对象上也没有,则在原型对象的原型对象上寻找,如此继续直到一个对象的原型对象为 null(null 没有原型)。能够看到,这样一种层层向上的查找是一种链式查找,在每一层上的对象都有一个指向其原型对象的连接([[prototype]]),由这些连接组成的整个链条就叫作原型链。浏览器

如图 1 所示,原型链查找的思路大体为:bash

  • 当前对象 object1 在查找一个属性时,首先查找本身拥有的属性,若是没有,则在 object1 对象的__proto__([[prototype]])中查找,此时__proto__指向 object2。
  • 若是对象 object2 中没有要查找的属性,则在 object2 对象的__proto__中查找,若是没有则继续向上查找。
  • 直到 Object 对象的 prototype,此时 Object 对象的__proto__为 null,则再也不查找。

链式查找示意图
图1. 原型链查找示意图

说明: 图中 builts-in 为构建内置函数好比 toString()、valueOf 等。函数

__proto__

前述中的[[Prototype]]是一个内置属性,咱们并不能直接获取,为了操做属性的便利性不少浏览器都实现了 Object.prototype.__proto__,所以能够经过 obj.__proto__ 来访问对象的原型对象[[Prototype]],因此__proto__[[Prototype]]本质上是一个东西,都指向一个对象的原型对象。 另外一方面,设置[[Prototype]]是一个缓慢的操做,影响性能,所以使用 __proto__ 是有争议的,更推荐使用 Object.getPrototypeOf 和 Object.setPrototypeOf 来访问原型对象(尽管如此,若是性能是个问题,也应尽可能避免使用)。性能

prototype

prototype 是构造函数(一个拥有 [[Construct]] 内部方法的对象)才有的属性,好比函数(非箭头函数),实例对象是没有这个属性的。这个所谓的 prototype,其实能够认为是构造函数内部一个普通的对象(或者说指向一个普通对象),只是很不幸地也叫作 prototype(原型对象)而已,当构造函数执行时,会自动将构造函数的 prototype 赋值给 __proto__(构造函数内部没有显示返回对象的状况下),这样在新的实例上经过原型链就能够共享构造函数 prototype 及其原型链上的属性了。prototype 和前述的__proto__[[Prototype]]是彻底不一样的概念,咱们一般的混淆,主要就来自于用原型对象一词来指代了不一样的它们。ui

__proto__与prototype关系示意图
图2. __proto__与prototype关系示意图

来看下面的例子: 函数 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

prototype继承示意图
图3. panda/dog实例继承示意图

来看另外一个例子: 以下将函数 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]]本质上同样都 指向原型对象,是全部对象都有的属性。
  • prototype 为拥有 [[construct]] 内部方法的对象才有的属性,它自己只是一个普通对象,只是正好叫作原型对象,它的做用是在构造函数生成新的实例时将这个所谓的原型对象赋值给实例的 __proto__ 属性,这样新的实例就能够经过 __proto__ 来继承构造函数原型里的方法。能够看到,prototype 和 __proto__ 所指的原型对象是彻底不一样的概念。
  • 实例对象没有 prototype 属性,
相关文章
相关标签/搜索