建站教程

建站教程

Products

当前位置:首页 > 建站教程 >

JS精粹,原型链继承和构造函数继承的“毛病”(JavaScript对象原型链原理解析)

GG网络技术分享 2025-03-18 16:13 7


JS精粹,原型链继承和构造函数继承的“毛病”

先从面向对象讲起,本瓜认为:面向对象编程,它的最大能力就是:复用!

咱常说,面向对象三大特点,封装、继承、多态。

这三个特点,以“继承”为核心。封装成类,是为了继承,继承之后再各自发展(重写),可理解为多态。所以,根本目的是为了继承,即“复用“!

如果你用 JavaScript 面向对象的能力来编程的话,能想到的,也只供使用的就是:基于原型

因为这门语言设计就是这样,我们之前也提过:JavaScript的语言设计主要受到了Self(一种基于原型的编程语言)和 Scheme(一门函数式编程语言)的影响;

它复用的能力就是来自原型!

好了,有这个认知基础,我们再看原型继承。

原型链继承

原型继承最直接的一种实现就是:原型链继承

我们来看看原型链继承的代码实现:

function SuperType() {

this.property = true;

}

function SubType() {

this.subproperty = false;

}

SuperType.prototype.getSuperValue = function() {

return this.property;

};

SubType.prototype.getSubValue = function () {

return this.subproperty;

};

SubType.prototype = new SuperType(); // 对 SubType 得原型链重新指定,是原型链继承

let instance = new SubType();

console.log(instance.getSuperValue()); // true

复制代码

还需要再额外说明查找关系吗??不懂得工友可见这篇 《歪理解?原型链中的函数和对象》

这里还是用代码展示下它们的指向关系吧:

上面例子中有 1 个对象 instance , 两个函数,SuperType 和 SubType 。函数是上帝,对象是基本物质。继承来自两方面:1. 继承自祖先(遗产);2. 继承自上帝(天赋);

// 继承自祖先(遗产)

instance.__proto__ === SubType.prototype // true

SubType.prototype.__proto__ === SuperType.prototype // true

// 继承自上帝(天赋)

SuperType.__proto__ === Function.prototype // true

SubType.__proto__ === Function.prototype // true

SuperType.prototype.__proto__ === Object.prototype // true

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

当然,我们并不是来讲原型链的。重点是:点出原型链继承的“问题”!!

它的主要问题出现在:原型中包含引用值的时候,原型中包含的引用值会在所有实例间共享。

function SuperType() {

this.colors = ["red", "blue", "green"];

}

function SubType() {}

SubType.prototype = new SuperType() // 原型链继承

let s1 = new SubType()

let s2 = new SubType()

s1.colors.push("yellow")

console.log(s1.colors) // ['red', 'blue', 'green', 'yellow']

console.log(s2.colors) // ['red', 'blue', 'green', 'yellow']

colors 是个数组,引用值,当它共享给 SubType 的时候,用的是引用值,当我们实例化的时候,如果其中一个实力对它做出了修改,将会影响到其它实例的引用。

其实,我们也知道,很少在业务代码中这样去写继承:SubType.prototype = new SuperType() ,原型链继承会造成复用的混乱,所以它基本不会被单独使用。

构造函数继承

构造函数继承,也叫做:“盗用构造函数”,“对象伪装”或“经典继承”。

基本思路:在子类构造函数中用 apply()和 call()方法调用父类构造函数。

上一小节的例子改造为:

function SuperType() {

this.colors = ["red", "blue", "green"];

}

function SubType() {

SuperType.call(this) // 构造函数继承

}

let s1 = new SubType()

let s2 = new SubType()

s1.colors.push("yellow")

console.log(s1.colors) // ['red', 'blue', 'green', 'yellow']

console.log(s2.colors) // ['red', 'blue', 'green']

完美解决原型链继承的问题,但是它也有它的问题,也是使用构造函数模式自定义类型的问题,

即:必须在构造函数中定义方法(在原型上定义方法,子类是访问不到的),函数不能重用

function SuperType() {

}

function SubType() {

SuperType.call(this) // 构造函数继承

}

SuperType.prototype.fn = ()=>{}

let s1 = new SubType()

console.log(s1.fn) // undefined

function SuperType() {

this.fn=()=>{}

}

function SubType() {

SuperType.call(this) // 构造函数继承

}

let s1 = new SubType()

let s2 = new SubType()

console.log(s1.fn === s2.fn) // false

而这一点,在原型链继承中,又是可以的。。。

function SuperType() {}

function SubType() {}

SuperType.prototype.fn = ()=>{}

SubType.prototype = new SuperType() // 原型链继承

let s1 = new SubType()

console.log(s1.fn) // ()=>{}

复制代码

function SuperType() {

this.fn=()=>{}

}

function SubType() {}

SubType.prototype = new SuperType() // 原型链继承

let s1 = new SubType()

let s2 = new SubType()

console.log(s1.fn === s2.fn) // true

所以,综上,原型链继承和构造函数继承的 “毛病” 分别是:

  1. 原型链继承:所有继承的属性和方法都会在对象实例间共享,无法做到实例私有。
  2. 构造函数继承:子类不能访问父类原型上的方法。

咱就是说,这东西怎么这么拧巴呢。。。

于是乎一个规避二者“毛病”的继承方式出现了:组合继承~~

组合继承

目前最流行的继承模式是组合继承!

思路是:使用原型链继承原型上的属性和方法,而通过构造函数继承实例属性。

function SuperType(name){

this.name = name;

this.colors = ["red", "blue", "green"];

}

function SubType(name, age){

SuperType.call(this, name) // 构造函数继承

this.age = age;

}

SuperType.prototype.sayName = function() {

console.log(this.name);

}

SubType.prototype = new SuperType() // 原型链继承

SubType.prototype.sayAge = function() {

console.log(this.age);

}

let s1 = new SubType("Nicholas", 29)

let s2= new SubType("Greg", 27)

s1.colors.push("yellow")

console.log(s1.colors) // ['red', 'blue', 'green', 'yellow']

console.log(s2.colors) // ['red', 'blue', 'green']

s1.sayName() // Nicholas

s2.sayName() // Greg

s1.sayAge() // 29

s2.sayAge() // 27

组合继承,总结起来就是,属性(特别是引用值)通过构造函数去继承,而公用的、需要复用的方法用原型链去继承!!

说实话,JS 继承真的很奇怪。。。并不是面向对象语言,又要通过原型链去模拟面向对象,真的很多小坑的点需要去注意。(哈哈哈,想想还是函数式好,清晰)

JavaScript对象原型链原理解析

介绍了JavaScript对象原型链原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下。

一个js对象,除了自己设置的属性外,还会自动生成proto、class、extensible属性,其中,proto属性指向对象的原型。

对象的属性也有writable、enumerable、configurable、value和get/set的配置方法。

JavaScript对象原型链原理解析 (https://www.wpmee.com/) javascript教程 第1张

对象的创建方式有三种:

一、使用字面量直接创建。

二、基于原型链创建。

JavaScript对象原型链原理解析 (https://www.wpmee.com/) javascript教程 第2张

分析上图,要点如下:

1.可以new运算符新建对象,foo为自定义函数,即是对象。

2.可以设置foo.prototype上的属性。

3.变量z在原型链上,为foo.prototype的属性,并非obj的自有属性。

4.原型链为obj->foo.prototype->Object.prototype->null。

JavaScript对象原型链原理解析 (https://www.wpmee.com/) javascript教程 第3张

分析上图,要点如下:

1.若定义与原型链上同名的变量,则不会覆盖原型链上的变量,而是在对象本身新增副本。

2.delete运算符不会影响原型链上已有的变量,只会删除对象自身的属性。

三、使用Object.create创建。

JavaScript对象原型链原理解析 (https://www.wpmee.com/) javascript教程 第4张

分析上图,要点如下:

1.Object.create是基于传入的参数产生新的对象,并且入参会成为其原型链上的一部分。

2.Object.create(null)创建的对象直接指向null。

以上就是本文的全部内容,希望对大家的学习有所帮助。

标签:

提交需求或反馈

Demand feedback