这篇文章的重点讲的是关于JavaScript中对象的继承,这是面向对象编程很重要的一个方面。A 对象继承自B 对象,就能直接拥有 B 对象的所有属性和方法,作用是避免了代码的复用,节省代码量。
而大部分面向对象的编程语言,都是通过“类”(class)来实现对象的继承。传统上,JavaScript 语言的继承不通过 class(ES6 引入了 class 语法,基于 class 的继承不在这里介绍),而是通过“原型对象”(prototype
)实现,因此,这里把“继承”着重拿出来讲,就为了体现这个不同。
复习原型/原型链
JavaScript的继承得靠原型/原型链来实现,当然原型/原型链不是这篇文章的重点,之前的文章也已经介绍过了,所以这里我们来复习一下即可。
其实原型的概念很简单,我觉得用以下简短的几句话就能概括完全了:
- 所有对象都有一个属性
__proto__
指向一个对象,也就是原型 - 每个对象的原型都可以通过
constructor
找到构造函数,构造函数也可以通过prototype
找到原型 - 所有函数都可以通过
__proto__
找到Function
对象 - 所有对象都可以通过
__proto__
找到Object
对象 - 对象之间通过
__proto__
连接起来,这样称之为原型链。当前对象上不存在的属性可以通过原型链一层层往上查找,直到顶层Object
对象,顶层Object
对象最终指向null
我觉得原型中最重要的内容其实就这些了,没必要看太多关于原型的文章,到头来只是会越来越糊涂,如果硬要推荐原型参阅资料的话,《JavaScript高级程序设计》这本书,当之无愧!
继承
通过上面的介绍,我们知道JavaScript中的继承是通过原型/原型链来体现的,先看几行代码:
function Foo() { }
var f1 = new Foo();
f1.a = 10;
Foo.prototype.a = 100;
Foo.prototype.b = 200;
console.log(f1.a); // 10
console.log(f1.b); // 200
console.log(f1.c); // undefined
复制代码
以上代码中,f1
是Foo
函数通过new
构造出来的对象,f1.a
是f1
对象的基本属性,而f1.b
是从Foo.prototype
继承得到的,因为f1.__proto__
指向的是Foo.prototype
。
这里有一个重要的规则:当访问一个对象的属性时,首先在基本属性中查找,如果没有,再沿着__proto__
这条链往上找,看是否在链上,有的话就能继承这一属性,如果没有,就返回undefined
,这就是原型链,又复习一遍咯。
看图直观一些,这里还是采用反复用的原型/原型链经典图:
上图中,访问f1.a
时,f1
的基本属性中有a
,则不会继续沿着__proto__
找,直接读出基本属性a
的值;而访问f1.b
时,f1
的基本属性中没有b
,于是沿着__proto__
找到了Foo.prototype.b
。
那我们如何在实际应用中区分一个属性到底是基本属性还是在原型链中的公有属性呢?这里可以好好利用一下这个属性——hasOwnProperty
,一下就能测出谁是基本属性,当在for…in…
循环中,需要额外注意。
hasOwnProperty()
方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性,所以上图中,右边的只打印出a
一个值,因为b
是在Foo.prototype
上的,不属于自身属性。
那f1
的这个hasOwnProperty()
方法,f1
自身没有,Foo.prototype
中也没有,又是从何而来呢?
还是引用那张原型/原型链经典图,从图上来看,hasOwnProperty()
方法是从Object.prototype
中继承来的:
所以对象的原型链是沿着__proto__
这条线走的,因此在查找f1.hasOwnProperty
属性时,因为自身没有这一属性,就会沿着原型链一直查找到Object.prototype
上有这一属性,如果没找到则返回undefined
。
由于所有的对象的原型链都会最终找到Object.prototype
,因此所有的对象都会有Object.prototype
中的方法,比如toString
、valueOf
等这些公有属性,这就是所谓的“继承”。
当然这只是一个例子,你可以自定义函数和对象来实现自己的继承,这一点后续文章会有专门介绍。
这里再说一个函数的例子来加深理解吧。
我们都知道每个函数都有apply
,call
方法,都有length
,arguments
,caller
等属性。为什么每个函数都有?这肯定是“继承”来的。在介绍instanceof
这篇文章中也提到,函数是由Function
函数创建,都继承自Function.prototype
中的方法。不信可以在Chrome中打印出:
直接可以看到了吧,有call
、length
等这些属性。
那怎么还有hasOwnProperty
呢?上图中hasOwnProperty
右边显示Object
,代表Function.prototype
继承自Object.prototype
。有疑问可以再看看这张原型/原型链经典大图,Function.prototype.__proto__
会指向Object.prototype
。
最后还是那句话,当你完全搞懂上面这张图的时候,就是你掌握原型、原型链的时候了。
如果觉得文章对你有些许帮助,欢迎在我的GitHub博客点赞和关注,感激不尽!