了解JavaScript原型链

本文深入探讨JavaScript中构造函数实例的原型链特性及其对继承的影响。解析如何通过构造函数创建的实例可以访问到原型对象的方法及属性,并讨论了在修改原型后对不同实例的影响。此外,还介绍了如何正确设置构造函数的原型以避免常见的问题。

一个原型链

  • 通过 构造函数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{
//...
}复制代码

转载于:https://juejin.im/post/5ae6d3c16fb9a07ac90cfba9

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值