你是否曾思考为何咱们能使用 JS 中的一些内置属性和方法,好比 .length
,.split()
,.join()
?咱们并无显式地声明它们,那么究竟它们从哪里来的呢? 可不要说什么“那是 JS 中的魔法!”。其实这些都由于一个叫作 原型继承(prototypal inheritance) 的东西。它太棒啦,你平时也常常会用到,只不过可能没有注意!javascript
咱们常常须要建立不少相同类型的对象。想象一下咱们有个网站,上面都是狗狗~java
对于每一个狗狗来讲,咱们须要一个对象来表示它!为了避免每次都新建立一个对象,就须要写一个构造函数(稍后再说 ES6 中的类哈~)。有了构造函数,就能够用 new
关键字来建立狗狗的 实例(instance) 了。es6
每只狗都有一个 名字(name),品种(breed),颜色(color) 和一个 吠(bark) 方法。segmentfault
当咱们建立了这个 Dog
构造函数,它并非咱们建立的惟一对象(要知道函数也是对象)。自动地,咱们建立了另外一个 prototype
对象。默认状况下,这个对象有一个 constructor
属性,指向的就是咱们的 Dog
构造器。函数
这个在 Dog
构造器上的 prototype
属性是不可枚举的,意味着当你尝试访问对象属性时,该属性不会显示。可是它仍然在那里!oop
好吧~那么为何须要有该属性对象呢?首先,让咱们来建立几只狗狗。简单起见,咱们就叫它们 dog1
和 dog2
。dog1
叫 Daisy
,是一只可爱的拉布拉多(Labrador)!dog2
叫 Jack
,是一只勇敢的杰克罗素犬(Jack Russell)~学习
让咱们将 dog1
打印到控制台,而后展开它的属性:网站
能够看到咱们添加的属性有:name
,breed
,color
和 bark
,可是快看,还有一个 __proto__
属性!它也是不可枚举的,因此一般咱们在获取对象属性的时候也看不到它。让咱们展开看看:spa
是否是看起来和 Dog.prototype
同样哈!你猜怎么着,这个 __proto__
就是对 Dog.prototype
的引用。这就是 原型继承 的所有内容:构造函数创造的每一个实例都可以访问构造函数的原型。prototype
那么为何这很酷?有时候咱们拥有每一个实例共享的属性。好比 bark
方法:它在每一个实例中都是相同的,那为何每次建立新实例都要新建一个 bark
方法,很明显这样会浪费内存。相反地,咱们能够将 bark
方法添加到 Dog.prototype
上去!
这样每当咱们访问实例的属性时,引擎首先检查该属性在实例上是否认义,若是没有找到,就会经过 __proto__
属性,顺着原型链 继续查找。
这只是一个步骤,其实能够包含多个步骤!若是继续进行下去,你可能会注意到,当我展开 Dog.prototype
的 __proto__
对象时,我没有包含一个属性。因为 Dog.prototype
本身也是一个对象,这意味着它其实是 Object
构造函数的实例。这意味着 Dog.prototype
也有一个 __proto__
属性,而且指向了 Object.prototype
。
好比说 .toString()
这个方法。它是定义在 dog1
上么?明显不是。那么在 Dog.prototype
上有么?也没有!其实它是定义在 Dog.prototype.__proto__
上,即 Object.prototype
上。
前面咱们使用的是构造函数的方式(function Dog() { ... }
),实际上 ES6 中提供了构造函数和原型的更简单的语法:类(Classes)
类 只是 构造函数 的 语法糖。一切都是以相同的方式工做!
咱们使用 class
关键字来定义类。每一个类都有一个 constructor
函数,基本上对应了 ES6 中构造函数的写法。而咱们想要添加到原型 prototype
上的属性和方法,均可以在类中直接定义。
关于类的另外一个好处就是,咱们能够轻松地 扩展(extend) 其余的类。
假如咱们要添加另外一种狗,吉娃娃(Chihuahuas)狗。为了便于理解,咱们只添加一个 name
属性。可是吉娃娃也能够有本身特殊的叫声!和普通的叫声不一样,吉娃娃的叫声可能比较小~
在子类中,咱们能够经过 super
关键字访问到父类的构造方法。参数固然也参考父类的构造方法传入。
myPet
能够访问到 Chihuahua.prototype
和 Dog.prototype
(固然也有 Object.prototype
,由于 Dog.prototype
是个对象)。
因为 Chihuahua.prototype
上有一个 smallBark
方法,Dog.prototype
上有一个 bark
方法,因此咱们能够在 myPet
实例上同时访问到 smallBark
和 bark
方法。
如今,你能够想象,原型链不会永远持续下去。最终会有一个原型等于 null
的对象:它就是 Object.prototype
。若是咱们试图访问在本地或者原型链上都不存在的属性,最终会返回 undefined
。
尽管上面已经解释了构造函数和类,其实还有一个为对象添加原型的方式是使用 Object.create
方法。经过这个方法,咱们建立了一个新对象,而且指明了这个对象的原型是什么。
只须要将一个已经存在的对象传入 Object.create
方法中。建立出来的对象就是以咱们传入的对象做为原型。看例子:
咱们打印一下 me
,能够看到:
咱们并无为 me
对象添加其余的属性,可是访问它却有一个 __proto__
属性,而且这个属性指向的是具备 name
和 age
的对象 person
。而 person
这个对象的 __proto__
属性指向的是 Object.prototype
。
全文就到这里啦,但愿对你学习原型继承有帮助~
本文是翻译的系列文章:
本文首发于公众号:码力全开(codingonfire)
本文随意转载哈,注明原文连接便可,公号文章转载联系我开白名单就好~