Skip to content

原型链

一、原型链

原型链图片

  • 每一个构造函数(实际上就是普通函数,只不过通过new关键字调用的时候,就成了构造函数)都对应有一个原型对象(F.prototype);
  • 每一个实例都对应有一个__proto__属性指向它的原型对象;
  • 每一个原型对象(F.prototype)都是Object的一个实例(可以看作同其他普通对象一样通过 new Object()生成),所以每一个原型对象的__proto__均指向Object.prototype;
  • 每一个构造函数(包括自定义函数,内置函数(Object()、Number()、String()...))都可以看作是通过new Function()方式而来,它们都算是Function的实例,所以它们的__proto__属性均指向Function.prototype

二、_proto_([[prototype]]) 和 prototype

  • __proto__是每个对象都有的一个属性,而prototype函数才会有的属性。
  • 对象 具有属性_proto_,也称为隐式原型。 (一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法)。
  • 构造函数 具有属性 prototype
  • 方法(Function)比较特殊,既是函数也是对象,所以除了有属性__proto__,还有属性prototype
  • ES5中用Object.getPrototypeOf函数获得一个对象的[[prototype]]。ES6中,使用Object.setPrototypeOf可以直接修改一个对象的[[prototype]]

三、instanceOf操作符

  • instanceof的左值一般是一个对象,右值一般是一个构造函数,用来判断左值是否是右值的实例。它的内部实现原理是这样的:
js
// L instanceof R 
//通过判断
 L.__proto__.__proto__ ..... === R.prototype
//最终返回true or false

/**
 * 模拟实现instanceof
 */
function instanceFn(L, R) {
    //#1 取出R的原型
    const O = R.prototype;

    //#2 取L的隐式原型,依次在原型链上判断
    L = L.__proto__;
    while(true) {
        if (L === null) {
            //#3 当L为空时,返回false
            return false;
        } else {
            //#4 当O与L相等是,说明继承于R
            if (O === L) {
                return true;
            }
            L = L._proto_;
        }
    }
}
// L instanceof R 
//通过判断
 L.__proto__.__proto__ ..... === R.prototype
//最终返回true or false

/**
 * 模拟实现instanceof
 */
function instanceFn(L, R) {
    //#1 取出R的原型
    const O = R.prototype;

    //#2 取L的隐式原型,依次在原型链上判断
    L = L.__proto__;
    while(true) {
        if (L === null) {
            //#3 当L为空时,返回false
            return false;
        } else {
            //#4 当O与L相等是,说明继承于R
            if (O === L) {
                return true;
            }
            L = L._proto_;
        }
    }
}

也就是沿着L的__proto__一直寻找到原型链末端,直到等于R.prototype为止。知道了这个也就知道为什么以下这些奇怪的表达式为什么会得到相应的值了.

js
Function instanceof Object // true 
 Object instanceof Function // true 
 Function instanceof Function //true
 Object instanceof Object // true
 Number instanceof Number //false
Function instanceof Object // true 
 Object instanceof Function // true 
 Function instanceof Function //true
 Object instanceof Object // true
 Number instanceof Number //false

四、Function与Object原型关系

Function与Object原型关系

  • ECMAScript标准中的函数是对象,因此每定义一个函数,也就是实例化了一个对象,所以它的隐式原型__proto__指向Object.prototype
  • 每一个构造函数(包括自定义函数,内置函数(Object()、Number()、String()...))都可以看作是通过new Function()方式而来,它们都算是Function的实例,所以它们的__proto__属性均指向Function.prototype

继承

1. 原型链继承

js
function SuperType() {
  this.property = true;
}
Supertype.prototype.getSuperValue = function() {
  return this.property;
}

function Subtype() {
  this.subproperty = false
}

// 继承Supertype
Subtype.prototype = new SuperType();

const instance = new Subtype();
function SuperType() {
  this.property = true;
}
Supertype.prototype.getSuperValue = function() {
  return this.property;
}

function Subtype() {
  this.subproperty = false
}

// 继承Supertype
Subtype.prototype = new SuperType();

const instance = new Subtype();

实现的本质是重写原型对象,代之以一个新类型的实例

原型关系如图:

继承

存在的问题:
  1. 包含 引用类型值 的原型属性会被所有实例共享
js
function SuperType() {
  this.colors = ['red'];
}

function SubType() {}

SubType.prototype = new SuperType();

const instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); // red, black

const instance2 = new SubType();
console.log(instance2.colors); // red, black
function SuperType() {
  this.colors = ['red'];
}

function SubType() {}

SubType.prototype = new SuperType();

const instance1 = new SubType();
instance1.colors.push('black');
console.log(instance1.colors); // red, black

const instance2 = new SubType();
console.log(instance2.colors); // red, black
  1. 在创建子类型的实例时,不能向超类型的构造函数中传递参数。

有鉴于此,实践中很少会单独使用原型链继承。

2. 构造函数继承

js
function Parent(name) {
   this.colors = ["red", "blue", "yellow"]
}

function Child(name, age) {
   // 继承父类属性
   Parent.call(this, name)
}

// Child的每个实例就都会具有自己的colors属性的副本了。
function Parent(name) {
   this.colors = ["red", "blue", "yellow"]
}

function Child(name, age) {
   // 继承父类属性
   Parent.call(this, name)
}

// Child的每个实例就都会具有自己的colors属性的副本了。
存在问题:
  1. 方法都需要在构造函数中定义,方法函数无法复用;
  2. 在超类型的原型中定义的方法,对子类型而言也是不可见的,结果所有类型都只能使用构造函数模式。

考虑到这些问题,借用构造函数的技术也是很少单独使用的。

3. 组合式继承(原型链继承 + 构造函数继承)

使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

js
function Parent(name) {
   this.name = name
   this.colors = ["red", "blue", "yellow"]
}
Parent.prototype.sayName = function () {
   console.log(this.name);
}

function Child(name, age) {
   // 继承父类属性(构造函数继承)
   Parent.call(this, name)
   this.age = age;
}
// 继承父类原型属性或方法(原型链继承)
Child.prototype = new Parent();

Child.prototype.sayAge = function () {
   console.log(this.age);
}
function Parent(name) {
   this.name = name
   this.colors = ["red", "blue", "yellow"]
}
Parent.prototype.sayName = function () {
   console.log(this.name);
}

function Child(name, age) {
   // 继承父类属性(构造函数继承)
   Parent.call(this, name)
   this.age = age;
}
// 继承父类原型属性或方法(原型链继承)
Child.prototype = new Parent();

Child.prototype.sayAge = function () {
   console.log(this.age);
}

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优点,成为JavaScript中 最常用的继承模式

4. 寄生组合式继承

所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。

js
// Object.create()模拟实现
function objectCopy(obj) {
  function Fun() { };
  Fun.prototype = obj;
  return new Fun();
}

function inheritPrototype(child, parent) {
  // 创建对象:对象的隐式原型指向parent.prototype
  const prototype = objectCopy(parent.prototype); 
  // 或者
  // const prototype = Object.create(parent.prototype) 
  // 为创建的副本添加constructor属性
  prototype.constructor = child; 
  // 将新创建的对象(即副本)赋值给子类型的原型
  child.prototype = prototype; 
}

function Parent(name) {
  this.name = name;
  this.friends = ["rose", "lily", "tom"]
}

Parent.prototype.sayName = function () {
  console.log(this.name);
}

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}

inheritPrototype(Child, Parent);
Child.prototype.sayAge = function () {
  console.log(this.age);
}

let child1 = new Child("yhd", 23);
child1.sayAge(); // 23
child1.sayName(); // yhd
child1.friends.push("jack");
console.log(child1.friends); // ["rose", "lily", "tom", "jack"]

let child2 = new Child("yl", 22)
child2.sayAge(); // 22
child2.sayName(); // yl
console.log(child2.friends); // ["rose", "lily", "tom"]
// Object.create()模拟实现
function objectCopy(obj) {
  function Fun() { };
  Fun.prototype = obj;
  return new Fun();
}

function inheritPrototype(child, parent) {
  // 创建对象:对象的隐式原型指向parent.prototype
  const prototype = objectCopy(parent.prototype); 
  // 或者
  // const prototype = Object.create(parent.prototype) 
  // 为创建的副本添加constructor属性
  prototype.constructor = child; 
  // 将新创建的对象(即副本)赋值给子类型的原型
  child.prototype = prototype; 
}

function Parent(name) {
  this.name = name;
  this.friends = ["rose", "lily", "tom"]
}

Parent.prototype.sayName = function () {
  console.log(this.name);
}

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}

inheritPrototype(Child, Parent);
Child.prototype.sayAge = function () {
  console.log(this.age);
}

let child1 = new Child("yhd", 23);
child1.sayAge(); // 23
child1.sayName(); // yhd
child1.friends.push("jack");
console.log(child1.friends); // ["rose", "lily", "tom", "jack"]

let child2 = new Child("yl", 22)
child2.sayAge(); // 22
child2.sayName(); // yl
console.log(child2.friends); // ["rose", "lily", "tom"]

普遍认为寄生组合式继承是引用类型最理想的继承范式。

ES5定义类

js
//定义Animal类
function Animal(name) {
    this.name = name;
    this.sleep = function() {
        console.log(this.name + '正在睡觉');
    }
}
Animal.prototype = {
    eat: function(food) {
        console.log(this.name + "正在吃" + food);
    }
}
function Cat() {}
Cat.prototype = new Animal('Tom');  //继承
var Tom = new Cat('Tom'); //Cat实例对象
//原型链:Tom(Cat实例对象)--->Cat.prototype(Animal实例对象)--->Animal.prototype--->Object.prototype--->null
//定义Animal类
function Animal(name) {
    this.name = name;
    this.sleep = function() {
        console.log(this.name + '正在睡觉');
    }
}
Animal.prototype = {
    eat: function(food) {
        console.log(this.name + "正在吃" + food);
    }
}
function Cat() {}
Cat.prototype = new Animal('Tom');  //继承
var Tom = new Cat('Tom'); //Cat实例对象
//原型链:Tom(Cat实例对象)--->Cat.prototype(Animal实例对象)--->Animal.prototype--->Object.prototype--->null

ES6定义类

定义一个类的方法实际上也是上面所说的定义一个对象的方法,类本身就是一个对象,只不过这个对象里面的方法属性可以供许多实例对象调用而已。类实质上是 JavaScript 现有的基于原型的继承的语法糖

js
class Animal {
  constructor(name) {
    this.name = name;
  }

  sleep() {
    console.log(this.name + ' 正在睡觉');
  }
  eat(food){
    console.log(this.name+'正在吃'+food)  
  }
}

class Cat extends Animal {  //继承

}

const Tom = new Cat('Tom');
class Animal {
  constructor(name) {
    this.name = name;
  }

  sleep() {
    console.log(this.name + ' 正在睡觉');
  }
  eat(food){
    console.log(this.name+'正在吃'+food)  
  }
}

class Cat extends Animal {  //继承

}

const Tom = new Cat('Tom');

class继承与ES5继承的区别?

  • 类的内部定义的所有方法,都是不可枚举的;
  • ES6的class类必须用new命令操作,而ES5的构造函数不用new也可以执行;
  • ES6的class类不存在变量提升,必须先定义class之后才能实例化,不像ES5中可以将构造函数写在实例化之后;
  • ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this

class类传统的基于函数的类this指向的不同

基于class,如果该对象没有this值(或this作为布尔,字符串,数字,未定义或null) ,那么this值在被调用的函数内部将为undefined。不会发生自动包装

js
class Animal { 
  speak() {
    return this;
  }
  static eat() {
    return this;
  }
}

let obj = new Animal();
obj.speak(); // Animal {}
let speak = obj.speak;
speak(); // undefined

Animal.eat() // class Animal
let eat = Animal.eat;
eat(); // undefined
class Animal { 
  speak() {
    return this;
  }
  static eat() {
    return this;
  }
}

let obj = new Animal();
obj.speak(); // Animal {}
let speak = obj.speak;
speak(); // undefined

Animal.eat() // class Animal
let eat = Animal.eat;
eat(); // undefined

基于传统的类,基于调用该函数的this值将发生自动装箱。

js
function Animal() { }

Animal.prototype.speak = function() {
  return this;
}

Animal.eat = function() {
  return this;
}

let obj = new Animal();
let speak = obj.speak;
speak(); // Window

let eat = Animal.eat;
eat(); // Window
function Animal() { }

Animal.prototype.speak = function() {
  return this;
}

Animal.eat = function() {
  return this;
}

let obj = new Animal();
let speak = obj.speak;
speak(); // Window

let eat = Animal.eat;
eat(); // Window

类不能继承常规(非可构造)对象。如果要继承常规对象,可以改用Object.setPrototypeOf()

js
var Animal = {
  speak() {
    console.log(this.name + ' makes a noise.');
  }
};

class Dog {
  constructor(name) {
    this.name = name;
  }
}

Object.setPrototypeOf(Dog.prototype, Animal);

var d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise.
var Animal = {
  speak() {
    console.log(this.name + ' makes a noise.');
  }
};

class Dog {
  constructor(name) {
    this.name = name;
  }
}

Object.setPrototypeOf(Dog.prototype, Animal);

var d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise.

Last updated: