这一篇鸽了两个周,实在不能再拖下去了。之因此会拖这么久,除了一方面这一块不像以前那样只是一个小知识点,另外一方面是总想着能写出点什么新东西。如今发现,我还只是一名技术领域的追随者,可以勉强跟得上技术的潮流就已经值得庆幸了;我所学习的东西,大可能是五六年前的标准,七八年前的框架,十几年前的思想。因此,看清本身的位置,立足当下,把它看成本身的学习总结。javascript
JavaScript 是一门动态语言,动态语言的哲学决定了咱们很难用一些静态语言类的思想去操纵 JavaScript 的对象。虽然 es6 给出了不少新特性,这些新特性能够帮助咱们在必定程度上模拟类,但不能忽视的是,它们是创建在原型的基础之上的。深刻地了解原型,而不是一味地回避,可让咱们更好地使用 JavaScript。java
接下来我会按照本身的理解,整理关于原型的线索,内容以下:es6
先来了解下原型。之因此把原型放在对象前面,是由于我在学习 JavaScript 对象的相关知识时发现,它彻底绕不开原型。先对原型创建个大概印象,再以它为工具,能够更好地发现隐藏在JS语法背后的奥秘。浏览器
因此,原型是什么?大概你已经在不一样的场合见识过原型的介绍了,这里请看MDN官方文档关于对象原型的介绍:bash
JavaScript 常被描述为一种基于原型的语言 (prototype-based language)——每一个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为什么一个对象会拥有定义在其余对象中的属性和方法。markdown
准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的
prototype
属性上,而非对象实例自己。框架在传统的 OOP 中,首先定义“类”,此后建立对象实例时,类中定义的全部属性和方法都被复制到实例中。在 JavaScript 中并不如此复制——而是在对象实例和它的构造器之间创建一个连接(它是
__proto__
属性,是从构造函数的prototype
属性派生的),以后经过上溯原型链,在构造器中找到这些属性和方法。函数注意:理解对象的原型(能够经过
Object.getPrototypeOf(obj)
或者已被弃用的__proto__
属性得到)与构造函数的prototype
属性之间的区别是很重要的。前者是每一个实例上都有的属性,后者是构造函数的属性。也就是说,Object.getPrototypeOf(new Foobar())
和Foobar.prototype
指向着同一个对象。工具
初学者看这段介绍确定是懵的,不要紧接下来一一做介绍。学习
这里仍是先沿用官方的说明和例子。
在javascript中,函数能够有属性(注:能够认为JS中函数是特殊的对象)。每一个函数都有一个特殊的属性叫做 prototype
(注,mdn中文文档这里翻译为【原型】,但我认为不翻译比较好,后面也是如此),正以下面所展现的。
function Foo(){}
console.log( Foo.prototype );
// 你如何声明函数并不重要,
// 在javascript中函数都会有一个默认的
// prototype 属性。
var Foo = function(){};
console.log( Foo.prototype );
复制代码
它们都会返回同一个对象。是的没错,这些函数的 prototype
指向了同一个特殊的对象,通常称为【原型对象】:
{
constructor: ƒ Foo(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
复制代码
上面那些奇奇怪怪的函数,有些等会会说起到,有些本文没法顾及。咱们先重点来看看该原型对象的两个属性名:constructor
和 __proto__
。
前者 constructor
,能够翻译成【构造器】,看起来它又指回原来的函数了。它们的关系彷佛是这样的:
因此,是否是就意味着一个函数的原型对象的 constructor
必定指向该函数呢?确定不是,既然它是一个可访问属性,那么它的对象确定就能够修改。具体怎么修改,这里暂且不提,若是有读者感兴趣能够留言,或者自行阅读《你不知道的 JavaScript(上)》第二部分第5、六章。
后者 __proto__
,看起来是一个很奇怪的属性名,它有另外一个称呼你可能见过,[[Prototype]]
。嗯?这个怎么看起来和以前的 prototype
属性那么像啊。二者有什么关联吗?
这里我暂时找不到关于 [[Prototype]]
的官方定义,ECMAScript 可能有但我懒得找了。不过,不管是 MDN 仍是《你所不知道的 JavaScript》都提到它是一个内部属性。什么意思呢?虽然JS中没有私有属性的概念,可是每一个对象都有一些内部属性,其中就有 [[Prototype]]
。在 ES 标准中,该属性你是没法经过常规的访问方式访问和设置的——包括点访问法和括号访问法(.__proto__
不是标准实现,它只是个别浏览器厂商的内部实现)。甚至在 ES5 以前,除了 new
操做外没法经过其它途径操做该属性(后文会介绍 ES5 支持的新方法)。
上面说到,__proto__
,即 [[Prototype]]
是一个属性,也指向了一个对象。该对象也有一个 construcotr
属性,难道它也是原型对象?没错,而且JS 还有不少内置函数,包括 Function
的 __proto__
都指向这个原型对象。后文会提到,它其实就是 Object
的原型对象。
你可能看得云里雾里,不要紧看看下面代码你就清楚了:
console.log(Foo.prototype.__proto__ === Object.prototype) // 谷歌浏览器下
// true
复制代码
再回到 MDN 官方文档的介绍,里面有一句话提到JS原型的复制机制:
在对象实例和它的构造器之间创建一个连接(它是
__proto__
属性,是从构造函数的prototype
属性派生的)
这句话怎么理解呢?请看下面这个代码示例,环境是谷歌开发者工具:
var Foo = function(){}
// undefined
var foo = new Foo // 无参数时可省略括号
// undefined
Foo.prototype === foo.__proto__
// true
复制代码
看起来它们之间的关系是这样的:
须要注意的是,foo
没有属性 prototype
。这里官方文档也有提到:**[[Prototype]]
是每一个实例上都有的属性,prototype
是构造函数的属性。**这里,每一个实例应是指对象(包括函数),也就是说JS中每一个对象都有 [[Prototype]]
属性(并且是内部属性)。你可能会好奇,那 Foo
的 [[Prototype]]
属性指向什么呢?
这里其实就涉及到原型链的知识了。
那么,Foo
的 [[Prototype]]
属性指向什么呢?前文说到,Foo
的原型对象的 [[Prototype]]
属性指向 Object
的原型对象。那么若是不是该函数自己是否也指向 Object
的原型对象呢?尝试下看看:
console.log(Foo.__proto__ === Object.prototype) // 谷歌浏览器下
// false
复制代码
看起来不是。这里直接给出答案吧,它其实指向 Function
的原型对象,你能够用一样的方法检测一下:
console.log(Foo.__proto__ === Function.prototype) // 谷歌浏览器下
// true
复制代码
估计有些人会满脑子疑问,那 Function
的 [[Prototype]]
,及该函数原型对象的 [[Prototype]]
都指向什么呢(包括 Object
、foo
等等)?有两个方法,一个是你本身一个一个找,另外一个是请你看下面这幅图,而且试着作下验证:
你能够着重看下 Function
、Object
和 Foo
这几个函数的原型对象之间的关系。看的时候确定有不少疑问,**为何光线条的样式就有三种呢?**不要紧先放下继续看下文。
不知道看完上文,特别是上图,你对【原型链】是否有必定的认识?也许你脑中会反应过来刚刚说起的函数的 prototype
,及全部实例即对象的 [[Prototype]]
(请记住,二者不一样一回事)。没错,当咱们谈及原型链时,确定绕不开这两个属性。
工欲善其事,必先利其器。咱们先了解下操纵这两个属性的工具。前者其实不用说了,它是一个可以直接修改的属性;后者前文说过了,它是一个内部属性,ES5 后有三个标准实现能够操纵它:
除此以外,在 ES6 以前没有 setPrototypeOf
方法时,有两个替代性的方法:
[[Prototype]]
的方法)前文说过,__proto__
是个别浏览器厂商的内部实现,还不是标准。综上,这些方法,基本上就是 es6 后可以了解 [[Prototype]]
的工具了。它们的做用应该很好猜,若是不肯定的话还请自行搜索一下吧。
那么,咱们如今再回到最开始的问题,原型链是什么?
这里用一些很容易搞错的问题,做为引子。请看下面代码(后面不特殊说明,环境都是谷歌开发者工具):
var Foo = function(){}
console.log(Foo.constructor === Function)
// true
console.log(Object.constructor === Function)
// true
console.log(Function.constructor === Function)
// true
复制代码
看起来很奇怪,尤为是最后一个。嗯,前文的原型对象彷佛有提到这个 constructor
,像 Foo
的原型对象的 constructor
正是它自己。前文也提到,该属性是可直接访问和修改的。看起来,该属性应该只有原型对象才有的,这些构造函数应该不可能有。嗯,这话说对了一半,请看下例:
Foo.hasOwnProperty("constructor")
// false
Object.hasOwnProperty("constructor")
// false
Function.hasOwnProperty("constructor")
// false
复制代码
问题就来了,既然这三个函数都没有该属性,为何以前的例子又是那样输出的呢?浏览器确定没犯毛病,问题的根源就在原型链上。
回到原型链,咱们先找找 MDN 官方文档中对它的描述:
每一个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为什么一个对象会拥有定义在其余对象中的属性和方法。
简单点说,原型链可让一个对象访问定义在其它对象中的属性和方法。具体的访问过程是怎样的,这里不细讲,下一篇博客【对象部分】会讨论这个问题。你这里就能够先按照你的喜爱简单理解。
那么,咱们用这个刚认识的原型链来剖析一下上面的问题吧。咱们都知道了,那三个函数确定是没有 constructor
属性的。那么根据原型链的定义,它们确定是访问了其它对象的该属性,并且极可能仍是同一个对象。那是哪一个对象呢?我不妨再放一次图(但是辛辛苦苦作了两个小时的),这一副重点描了一个红框,它其实就同时是这三个函数所访问的那个“受害人”,以及三个红圈,它们所在的三条线其实就是形成上述问题的“罪魁祸首”。
嗯,原型链简直是魔鬼。这里我为了方便你们理解,将制做的这幅图一部分线条用红色和蓝色描出来。红线是关于 Object
的原型链,蓝线是关于 Function
的原型链。
当你理解这幅图时,你也就理解了,为何函数能够访问一些特殊方法,对象又能够访问一些特殊方法。其实都是原型链的功劳。至于都有哪些特殊方法,我贴一副《你不知道的 JavaScript》书中的插图:
其中左边的红色椭圆圈住 Function
的原型对象,右边的红色方框圈住 Object
的原型对象,省略号部分可自行搜索。另外,上面红色方框圈住的 construct
是我认为有问题之处。根据以前的代码示例,Object
没有 constructor
,这里应该指的是原型委托,但委托的对象又出了差错。总之,还请读者自行辨认。
短短一篇博客还分前言后记是挺搞笑的。只不过我这篇确实写了两三天,工做量挺大的,光是例图可能就花了三个多小时,因此也想请看了此篇后,以为有帮助的读者给我点个赞吧~
另外作一个预告,下一篇应该是关于 JS 的对象了,它与原型本紧密结合,应放一块儿写才对;只不过此篇就写了七千多字(markdown格式的统计,包括字母),再写下去太长了。嗯,就这样吧,感谢慧鉴。