从mixin到new和prototype:Javascript原型机制详解
从mixin到new和prototype:Javascript原型机制详解
这是一篇markdown格式的文章,更好的阅读体验请访问我的github,移动端请访问我的博客
继承是为了实现方法的复用,如何实现方法的复用呢?最容易想到的,就是:
```js
//mixin
function extend(optional, base){
for(var prop in base){
if(!prop in optional){
optional[prop] = base[prop]
}
}
return optional
}
```
这种方法俗称`mixin`,它直接从甲对象复制方法和属性和方法到乙对象,乙对象就拥有了甲对象的功能,简单粗暴,易于理解。但是它有一个缺点,就是性能低下,浪费空间,因为每次扩展时都要复制一遍。那么,有没有更优雅的继承方法呢?
当然有。
实际上,`Javascript`天然内置了一种更优雅简单的方法,这就是人们耳熟能详的`prototype`原型链机制,该机制的内容可以描述为:
> 每一个对象内部都有一个`[[prototype]]`属性,该属性是一个指针,当对对象进行取值操作时,如果没有找到,则会尝试从该指针所指向的对象(即该对象的原型)中去查找,如果还没有找到,则会继续它的原型的原型中去查找,这个过程会一直持续下去,直到终点—— `Object.prototype`。
也可以简单图示为:

这种对象扩展的方法十分清新,如果想要乙对象从甲对象扩展方法,只要直接把乙对象的`[[prototype]]`属性指向甲对象即可。也就是说,`prototype`机制是一种在对象层面上的方法复用实现方式,它的思想很简单。而在经典的类式继承语言中,实现继承需要创建父类,然后创建子类继承父类,再实例化子类。
```
//pseudo code
class A{}
class B extends A{}
B foo = new B()
```
令人感到不可思议的是,虽然原型机制是`Javascript`式继承的基石,但在`ECMAScript5`发布以前,`Javascript`竟然并没有提供可以把一个对象的`[[prototype]]`指针指向另一个对象的`API`,反而是用一种十分奇怪的方式模拟了典型类式语言的语法,也就是大家耳熟能详的构造函数和`new`。构造函数和`new`很好地满足了一些程序员对于经典类式语法的追求,但它掩盖了很多细节和原理,容易让人产生误解,这里暂且按下不表。
要实现纯`Javascript`原型式的继承,直接设置`[[prototype]]`指针的`API`是不可或缺的。一些浏览器给对象暴露了一个可读可写的`__proto__`属性,可以用来设置对象的`[[prototype]]`指针,而`ES5`终于提供了一个`Object.create()`的方法,用于设置`[[prototype]]`指针。
使用`Object.create()`方法,可以轻松实现纯`Javascript`式继承,例如:
```js
var person = {
speak: function(what){
console.log(what)
}
}
var artist = Object.create(person)
//I am an artist
artist.speak('I am an artist')
```
`artist`对象由`Object.create(person)`创建而来,因此它内部的`[[prototype]]`指针就指向`person`,当我们查询它的`speak`属性时,在它本身上找不到,于是就自动到它的原型上去查询。整个过程可以图示如下:

这是一种轻量、简洁、易于理解的继承方式,我们可以随便创建新的`artist`对象,让它继承`person`,如果需要给所有的`artist`添加新的方法,只需在`person`添加就可立即生效。
但是这种简单的机制也不是没有缺点。`person`上的属性是被所有的`artist`共享的。在现实世界中,每个`artist`有一些独特的不能和他人共享的属性,比如,他的姓名、流派、代表作等等,于是我的代码可能要改成这样:
```js
//公共属性写在原型上
var person = {
speak: function(){
return this.name
}
}
//创建对象
var artist = Object.create(person)
//自己的属性
artist.name = 'Leonardo da Vinci'
artist.school = 'classical'
artist.works = ['Mona Lisa']
```
但是,每次创建一个`artist`对象都要写这么一大串,我们最好把这个创建的过程用一个函数封装起来,即:
```js
var person = {
getName: function(){
return this.name
}
}
function genArtist(name, school, work){
//设定原型
var artist = Object.create(person)
//自有的属性
artist.name = name
artist.school = school
article.work = work
return artist
}
var vinci = genArtist('Leonardo da Vinci', 'classical', 'Mona Lisa')
//Leonardo da Vinci
console.log(vinci.speak())
```
现在每次通过`genArtist`函数创建新的`artist`时,返回的对象既会生成它自己的独有属性,又会继承`person`的属性。因为新生成的对象的`[[prototype]]`指向了`person`对象。整个`genArtist`函数就是一个批量生产`artist`对象的__工厂__,这个过程可以图示如下:

至此我们已经创建了一个相当令人满意的纯`Javascript`式的对象生成方法:通过函数统一生成一类对象、生成的对象共享原型方法,如果我们愿意,还可以把`person`的`[[prototype]]`指向另一个对象,延长原型链。
但是这个方法也有一个缺点,我们不能利用`instanceof`操作符来检测它属于哪一类,`instanceof`检测的原理是:
> 如果一个函数的`prototype`指针指向的对象和一个对象的原型链上的任何一个对象相同,则返回`true`。

工厂函数方法生成的对象的`[[prototype]]`被指定为一个其他对象,即`person`,并没有指向哪个函数的`prototype`,所以`instanceof`就不可用。那么我们最好来指定一下。(值得注意的是这里的__函数的`prototype`指针__,极易与__对象的`[[prototype]]`指针__混淆,前者是每个函数对象都带有的一个属性,作用是作为通过这个函数`new`出来的对象的[[prototype]]`指针的目标。)
```js
function Artist(name, school, work){
//important
var artist = Object.create(Artist.prototype)
artist.name = name
artist.school = school
artist.work = work
return artist
}
var artist = Artist()
console.log(artist instanceof Artist) //true
```
现在生成的对象的`[[prototype]]`指向了函数`Artist`的`prototype`,`instanceof`终于可以用了。问题又来了:`person`被我们丢掉了,`person`上添加了一些所有对象共用的方法,不能丢掉。但是想想,既然现在所有生成对象的`[[prototype]]`都指向了`Artist.prototype`,那我们直接在`Artist.prototype`上扩展不就好了吗?
```js
Artist.prototype.speak = function(){
return this.name
}
```
到这一步,我们完全实现了`new`和构造函数所达到的所有效果,不同的是,我们的实现中,没有任何黑盒子,每一步都很清晰。上面的过程,用`new`和构造函数__等价__改写一下,大概是像下面这样的:
```js
function Artist(name, school, work){
this.name = name
this.school = school
this.work = work
}
var artist = new Artist()
console.log(artist instanceof Artist) //true
```

比较构造函数和前面的工厂函数,发现有下面几个不同点:
1. 没有显式指定生成对象的`[[prototype]]`。
2. 没有返回一个对象。
3. 调用需要加关键字`new`。
这说明,加关键字`new`调用函数,背后会自动为你完成1、2两步,背后干了这么多事情,都被浓缩到一个__`new`魔法__里面了,也难怪会让很多人困惑。
从mixin到new和prototype:Javascript原型机制详解的更多相关文章
- JavaScript运行机制详解
JavaScript运行机制详解 var test = function(){ alert("test"); } var test2 = function(){ alert(& ...
- [转]JavaScript异步机制详解
原文: https://www.jianshu.com/p/4ea4ee713ead --------------------------------------------------------- ...
- 【移动端兼容问题研究】javascript事件机制详解(涉及移动兼容)
前言 这篇博客有点长,如果你是高手请您读一读,能对其中的一些误点提出来,以免我误人子弟,并且帮助我提高 如果你是javascript菜鸟,建议您好好读一读,真的理解下来会有不一样的收获 在下才疏学浅, ...
- js javascript 原型链详解
看了许多大神的博文,才少许明白了js 中原型链的概念,下面给大家浅谈一下,顺便也是为了巩固自己 首先看原型链之前先来了解一下new关键字的作用,在许多高级语言中,new是必不可少的关键字,其作用是为了 ...
- JavaScript 运行机制详解:再谈Event Loop
原文地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Eve ...
- JavaScript 运行机制详解:深入理解Event Loop
Philip Roberts的演讲<Help, I'm stuck in an event-loop>,详细.完整.正确地描述JavaScript引擎的内部运行机制. 一.为什么JavaS ...
- javascript运行机制详解: 再谈Event Loop(转)
作者: 阮一峰 日期: 2014年10月 8日 一年前,我写了一篇<什么是 Event Loop?>,谈了我对Event Loop的理解. 上个月,我偶然看到了Philip Roberts ...
- JavaScript 运行机制详解:Event Loop
参考地址:http://www.ruanyifeng.com/blog/2014/10/event-loop.html 一.为什么JavaScript是单线程? JavaScript语言的一大特点就是 ...
- JavaScript 运行机制详解
一.为什么JavaScript是单线程? JavaScript语言的一大特点就是单线程,也就是说,同一个时间只能做一件事.那么,为什么JavaScript不能有多个线程呢?这样能提高效率啊. Java ...
随机推荐
- Innodb 表空间传输迁移数据
在mysql5.5之前,mysql实例中innodb引擎表的迁移是个头疼的问题,要么使用mysqldump导出,要么使用物理备份的方法,但是在mysql5.6之后的版本中,可以使用一个新特性,方便地迁 ...
- HTML5:web socket 和 web worker
a:hover { cursor: pointer } 做练习遇到了一个选择题,是关于web worker的,问web worker会不会影响页面性能?补习功课之后,答案是不会影响. 查阅了相关资料学 ...
- java主函数的含义
下面对java中的主函数进行简单的解释,解决可能困惑大家的问题,下面举的例子在实际开发中几乎不会出现,但是为了解决好奇心,大家可以这么去尝试一下! 我们在java中看到的主函数通常是这样的:publi ...
- SunRay4(新蕾4) 定时自动关机方案, Linux后台自动任务crontab实践
目录: 需求和思路分析 具体实现步骤 理解Crontab Crontab具体参数详细说明 最近碰到一个想要实现定时自动关机的功能,关机的指令无非就是: shutdown -h time 调用openw ...
- Symbol not found for architecture arm64 错误
如果Archiectures和Valid Architectures都是对的,需要检查Other Linker Flags是否添加$(inherited),如果还不行,检查Library Search ...
- 复旦高等代数 I(16级)思考题
思考题的说明 一.本学期高代I的思考题面向16级的同学,将不定期地进行更新; 二.欢迎16级的同学通过微信或书面方式提供解答图片或纸质文件给我,优秀的解答可以分享给大家: 三.请大家先独立解答思考题, ...
- JAVA窗口程序实例一
package 甲; import java.awt.Dimension; import java.text.SimpleDateFormat; import java.util.Calendar; ...
- Android runtime Exception 整理
一般面试中java Exception(runtimeException )是必会被问到的问题 常见的异常列出四五种,是基本要求.更多的....需要注意积累了 常见的几种如下: NullPoi ...
- CSS下拉列表错误纠正
上一篇关于CSS制作下来列表的错误纠正. 在上一篇中,用CSS只做了下拉列表,但是鼠标不放在导航栏上的时候,下拉列表也是出来的.具体错误就是 div ul{ list-style:none; max- ...
- javaScript 验证表单提交
<script type="text/javascript"> function $(id) { return document.getEl ...