原型及原型链

proto 和 prototype 分别是什么

  • proto 为 JavaScript 的对象特殊的内置属性,是对于其他对象的引用,就是说谁创建了这个对象,该属性就指向谁。官方写法为:[[Prototype]],不可直接获取,浏览器实现为:__proto__
  • prototype 当通过构造函数调用方式创建对象实例时,生成的实例对象的__proto__属性指向(函数.prototype)这个对象,Foo.prototype:这是一个对象

object 再往上是 null

Object.prototype.__proto__ === null; // true

以下情况,__proto__ 和 prototype 指向同一个

  • Function.__proto__ === Function.prototype
  • arr.__proto__ === Array.prototype;
  • 其他手动修改的方式

原型链断裂

原型链断裂之后将无法通过原型链获取原型链上的属性、方法

js 原型链继承及实现

js prototype 对象实现原型链及继承

js 的继承其实是委托,实例只是指向父对象,本身并不具有父对象的属性和方法,不像其他语言那样通过类似拷贝的方式创建实例

父类

function Father(name) {
    this.name = name;
    this.sum = function () {
        console.log(this.name);
    };
}
Father.prototype.age = 10;

原型链继承

特点

  • 实例继承:实例的构造函数的属性、父类构造函数属性、父类原型属性
  • 父类新增原型方法、属性,所有子类都能访问

缺点

  • 创建实例时无法向父类构造函数传参
  • 所有实例都共享父类原型的属性,一旦修改原型属性,则所有实例都会变化
function Son() {
    this.name = 'ker';
}
// 主要
Son.prototype = new Father();
var son1 = new Son();
son1.age; // 10
son1 instanceof Father; // true

构造函数继承

特点

  • 只继承父类构造函数的属性,没有继承父类原型的属性
  • 解决了原型链继承的缺点
  • 可以继承多个构造函数属性(call 多个)
  • 在子实例中可以向父实例传参

缺点

  • 只能继承父类构造函数的属性
  • 无法实现构造函数的复用,每个新实例都有父类构造函数的副本
  • 实例并不是父类的实例,只是子类的实例
function Son2() {
    // 重点
    Father.call(this);
    this.name = 'Tom';
    this.age = 12;
}
var son2 = new Son2();
son2.name; // Tom
son2 instanceof Father// false

组合继承(组合原型链继承和构造函数继承,常用)

特点

  • 可以继承父类原型上的属性,可传参,可复用
  • 每个实例引入的构造函数属性是私有的
  • 即是子类的实例,也是父类的实例

缺点:调用了两次父类构造函数,生成两份实例,子类构造函数会代替原型上的父类构造实例

function Son3(name) {
    // 借用构造函数继承
    Father.call(this);
    this.name = name;
}
// 原型链继承
// 第一次调用父类构造函数
Son3.prototype = new Father();
// new Son3 的时候,构造函数内部第二次调用父类构造函数
var son3 = new Son3('gar');
son3.name; // gar
son3 instanceof Father; // true

原型式继承(Object.create() 就是这个原理)

特点:类似于复制一个对象,用函数来包装

缺点

  • 所有实例都会继承原型上的属性
  • 无法实现复用
// 重点,封装一个函数容器,用来输出对象和承载继承的原型
function content(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}
// 创建父类实例
var son = new Father();
var son4 = content(son);
son4.age; // 10
son4 instanceof Father; // true

寄生式继承(就是给原型式继承在外面套个壳子)

特点:没有创建自定义类型,只是套个壳子返回对象,这个函数就创建了新对象

缺点:没有用到原型,无法复用

function content(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}
var son = new Father();
// 以上为原型式继承,给原型式继承再套个壳子传递参数
function Son5(obj) {
    var sub = content(obj);
    sub.name = 'gar';
    return sub;
}
var son5 = Son5(son);
son5 instanceof Father; // true
son5 instanceof Son5; // false

寄生组合式继承(常用)

解决了组合式继承两次调用构造函数属性的缺点

// 寄生
function content(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}
// con 实例的原型继承了父类函数的原型,更像原型链继承,只不过只继承了原型属性
var con = content(Father.prototype);
// 组合
function Son6() {
    // 继承了父类构造函数的属性
    Father.call(this);
}
// 重点
// 继承了 con 实例
Son6.prototype = con;
// 修复 constructor 指向
con.constructor = Son6;
// Son6 的实例就继承了构造函数属性,父类实例,con 的函数属性
var son6 = new Son6();
son6 instanceof Father; // true
son6 instanceof Son6; // true
Last Updated:
Contributors: zhangfei