视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
深入理解javaScript原型继承
2020-11-27 20:24:58 责编:小采
文档

继承的本质:重用

在探讨 javaScript 的原型继承之前,先不妨想想为什么要继承?

考虑一个场景,如果我们有两个对象,它们一部分属性相同,另一部属性不同。通常一个好的设计方案是将相同逻辑抽出来,实现重用。

以 xiaoMing liLei 两位同学举例。这两位同学有自己的名字,并且会介绍自己。抽象为程序对象,可以做如下表示。

var xiaoMing = {
 name : "xiaoMing",
 hello : function(){
 console.log( 'Hello, my name is '+ this.name + '.');
 }
}

var liLei = {
 name : "liLei",
 hello : function(){
 console.log( 'Hello, my name is '+ this.name + '.');
 }
}

使用过 java 的同学,可能第一眼就想到了用面向对象来解决这个问题。创造一个 Person 的类,然后实例化 xiaoMing 和 liLei 两个对象。在 ES6 中也有类似于 java 中类的概念: class 。

下面使用 ES6 的语法,用面向对象的思路来重构上面的代码。

class Person {
 constructor(name){
 this.name = name
 }

 hello(){
 console.log(this.name);
 }
}

var xiaoMing = new Person('xiaoMing');
var liLei = new Person('liLei');

可以看到,使用类创建对象,达到了重用的目的。它基于的逻辑是,两个或多个对象的结构功能类似,可以抽象出一个模板,依照模板复制出多个相似的对象。

使用类创建对象,就像自行车制造商一遍一遍地重用相同的蓝图来制造大量的自行车。

然解决重用问题的方案,当然不止一种。传统面向对象的类,只是其中的一种方案。下面轮到我们的主角“原型继承”登场了,它从另一个角度解决了重用的问题。

原型继承的原理

原型对象

javaScript 中的 object 由两部分组成,普通属性的集合,和原型属性。

var o = {
 a : 'a',
 ...
 __proto__: prototypeObj
}

普通属性指的就是 a ; 原型属性 指的是 __proto__ 。这本不属于规范的一部分,后来 chrome 通过 __proto__ 将这个语言底层属性给暴露出来了,慢慢的被大家所接受,也就添加到 ES6 规范中了。 o.__proto__ 的值 prototypeObj 也就是 原型对象 。原型对象其实也就是一个普通对象,之所以叫原型对象的原因,只是因为它是原型属性所指的值。

原型对象所以特殊,是因为它拥有一个普通对象没有的能力:将它的属性共享给其他对象。

在 ES6 规范 中,对它是如下定义的:

object that provides shared properties for other objects

属性读操作

回到最开始的例子,看看如何利用原型继承实现重用的目的。

var prototypeObj = {
 hello: function(){
 console.log( 'Hello, my name is '+ this.name + '.');
 }
 // ...
}

var xiaoMing = {
 name : "xiaoMing",
 __proto__ : prototypeObj
}

var liLei = {
 name : "liLei",
 __proto__ : prototypeObj
}

xiaoMing.hello(); // Hello, my name is xiaoMing.
liLei.hello(); // Hello, my name is liLei.

xiaoMing liLei 对象上,并没直接拥有 hello 属性(方法),但是却能读取该属性(执行该方法),这是为什么?

想象一个场景,你在做数学作业,遇到一个很难的题目,你不会做。而你有一个好兄弟,数学很厉害,你去请教他,把这道题做出来了。

xiaoMing 对象上,没有 hello 属性,但是它有一个好兄弟, prototypeObj 。属性读操作,在 xiaoMing 身上没有找到 hello 属性,就会去问它的兄弟 prototypeObj 。所以 hello 方法会被执行。

原型链

还是做数学题的例子。你的数学题目很难,你的兄弟也没有答案,他推荐你去问另外一个同学。这样直到有了答案或者再也没有人可以问,你就不会再问下去。这样就好像有一条无形链条把你和同学们牵在了一起。

在 JS 中,读操作通过 __proto__ 会一层一层链下去的结构,就叫 原型链 。

var deepPrototypeObj = {
 hello: function(){
 console.log( 'Hello, my name is '+ this.name + '.');
 }
 __proto__ : null
}

var prototypeObj = {
 __proto__ : deepPrototypeObj
}

var xiaoMing = {
 name : "xiaoMing",
 __proto__ : prototypeObj
}

var liLei = {
 name : "liLei",
 __proto__ : prototypeObj
}

xiaoMing.hello(); // Hello, my name is xiaoMing.
liLei.hello(); // Hello, my name is liLei.

原型继承的实现

在上面的例子中,通过直接修改了 __proto__ 属性值,实现了原型继承。但是在实际生产中,

代替的方式是使用 Object.create() 方法。

调用 Object.create() 方法会创建一个新对象,同时指定该对象的原型对象为传入的第一个参数。

我们将上面的例子改一下。

var prototypeObj = {
 hello: function(){
 console.log( 'Hello, my name is '+ this.name + '.');
 }
 // ...
}

var xiaoMing = Object.create(prototypeObj);
var liLei = Object.create(prototypeObj);

xiaoMing.name = "xiaoMing";
liLei.name = "liLei";

xiaoMing.hello(); // Hello, my name is xiaoMing.
liLei.hello(); // Hello, my name is liLei.

You-Dont-Know-JS 的作者,对这种原型继承的实现取了一个很好玩的名字 OLOO (objects-linked-to-other-objects) ,这种实现方式的优点是没有使用任何类的概念,只有 object ,所以它是很符合 javaScript 的特性的。

因为JS 中本无类,只有 object 。

无奈的是,喜欢类的程序员是在太多,所以在 ES6 新增了 class 概念。下一篇会讲 class 在 JS 中的实现原理

小结

类创建对象,达到了重用的目的。它基于的逻辑是,两个或多个对象的结构功能类似,可以抽象出一个模板,依照模板 复制 出多个相似的对象。就像自行车制造商一遍一遍地重用相同的蓝图来制造大量的自行车。

使用原型继承,同样可以达到重用的目的。它基于的逻辑是,两个或多个对象的对象有一部分共用属性,可以将共用的属性抽象到另一个公共对象上,通过特殊的原型属性,将公共对象和普通对象链接起来,再利用属性读(写)规则进行遍历查找,实现属性 共享 。

下载本文
显示全文
专题