了解JavaScript原型链

190 阅读3分钟

一个原型链

  • 通过 构造函数A 创建的实例A-instance的`[[proto]]`属性会自动指向 构造函数A的原型对象`prototype`,这种关系是在实例被创建时便自动创建的。
  • 而我们的 构造函数A 的原型对象 `prototype` 本身就是一个 `Object`的实例。


JavaScript动态特性的副作用


function car(){
this.broken = true;
}
const carA = new car();

car.prototype.WhetherBroken = function(){
return this.broken;
}
console.log(carA.WhetherBroken()); // output:

car.prototype = {  //line 1
expensive: function(){
return true;
 }
}

console.log(carA.WhetherBroken()); // output: true


const carB = new car();
console.log(carB.expensive()); // output: true;
console.log(carB.WhetherBroken()); // Uncaught TypeError: carB.WhetherBroken is not a function


  • 当我们创建实例carA时,原型链的状态


  • 当我们在 line 1修改了构造函数car的原型,例carB时原型链的样子。



  • 当我们对构造函数car原型上的属性和方法进行删改时, car的所有实例都可以访问新的方法或属性。
  • 当我们完全更换构造函数car的原型时(如line1), 之前创建的car的实例会保留旧的原型的引用, 而在更换完原型之后通过构造函数创建的实例将会保留新的原型的引用。对新的原型上的属性的删改在旧的实例(保留旧的原型的引用的实例)上无法体现。


通过instanceof判断实例类型


function Car(){};
const carA = new Car();
console.log(carA instanceof Car); // line 1  output: true
Car.prototype = {}; // line 2
console.log(carA instanceof Car); // line 3 output:  false


  • 用法: object instanceof constructor
  • 原理: instanceof 通过判断构造函数constructor的prototype是否在object的原型链上。即检测右边函数原型是否在左侧对象的原型链上。


  • line 2 之前



  • line2 改变构造函数`Car`的原型之后原型链的样子


  • 如图所示, 显然构造函数Car的原型是不在CarA的原型链上的
  • 同时我们还发现, 尽管我们在 line 2 重新定义了构造函数Car的原型, 但是新定义的原型对象并没有consturctor属性, 而原来的原型对象的constructor属性仍然指向构造函数Car。
  • 这就会产生如下问题:


console.log(carA.constructor === Car);// output:true
console.log(carA instanceof Car); // output:false


  • 而通过原型链实现继承时, 也会出现类似的问题:


function Car(){}
function miniCar(){}
miniCar.prototype = new Car();// line 1

let miniA = new miniCar();


console.log( miniA.constructor === miniCar); // false
console.log( miniA instanceof miniCar); //true
console.log( miniA.constructor); //Car


  • 原型继承的图示


  • 由于javascript 的垃圾回收机制, 实际情况应是如下图所示



  • 为了解决上述问题, 我们应该在上述代码 line1 之后设置新原型的constructor属性


function Car(){}
function miniCar(){}
miniCar.prototype = new Car();// line 1
Object.defineProperty(miniCar.prototype, 'constructor', {
enumerable: false,
value: miniCar,
writable: true
}) // added line

let miniA = new miniCar();


console.log( miniA.constructor === miniCar); // true
console.log( miniA instanceof miniCar); //true
console.log( miniA.constructor); //miniCar
  • 由此我们也通过JavaScript实现了原型继承


function SuperClass(){};
function SubClass(){};
SubClass.prototype = new SuperClass();
Object.defineProperty(SubClass.prototype, 'constructor', {
enumerable: false,
value: SubClass,
writable: true
})

let instance = new SubClass();

ES6 Class


  • 由于ES5实现原型继承的过程繁琐复杂, ES6引入class语法糖, 来模拟类继承。 但其底层仍然是基于原型的实现。
// before ES6
function Car(name){ //构造函数
this.name;
}
Car.showName = function(car){//静态方法
return car.name;
}
Car.prototype.run = function(){ //原型方法
return true;
}

function miniCar(){};
miniCar.prototype = new Car('BMW');
Object.defineProperty(SubClass.prototype, 'constructor', {
eumerable: false,
value:SubClass,
writable: true,
})

// after ES6

class Car {
constructor(name){ // 构造函数
this.name;
 }

// 静态方法
static showName(car){
return car.name;
 }

// 如下为原型方法

 run(){
return true;
 }
}
// 通过 extends 关键字来实现继承。
class miniCar extends Car{
//...
}