在软件工程中,代码重用的模式极为重要,由于他们能够显著地减小软件开发的成本。在那些主流的基于类的语言(好比Java,C++)中都是经过继承(extend)来实现代码复用,同时类继承引入了一套类型规范。而JavaScript是一门弱类型的语言,历来不须要类型装换,在JavaScript中变量能够指向任何类型的value(ES6规范中的类也只是语法糖,基于类的继承本质上也是经过原型实现)。而基于原型的继承模式能够说提供了更加丰富的代码重用模式(后面再详细讲解JavaScript中的经常使用继承模式,本文只专一于JavaScript中的原型),一个对象能够直接继承另一个对象,从而得到新的方法和属性。javascript
要理解JavaScript中的原型关系,首先必须弄清楚对象的基本概念。ECMAScript 5.1规范中描述的对象java
An object is a collection of properties and has a single prototype object. The prototype may be the null value.
直译就是:对象是属性的集合而且拥有一个原型对象。原型多是null(除非故意设置一个对象的原型为null,不然只有Object.prototype的原型为null)。咱们能够简单把对象想象成hash表。有一种说法是JavaScript中一切都是对象,这种说法并不许确。How is almost everything in Javascript an object?app
JavaScript的原型存在着诸多矛盾。它的某些复杂的语法看起来就像那些基于类的语言,这些语法的问题掩盖了它的原型机制。它不直接让对象从其余对象继承,反而插入一个多余的间接层:经过构建器函数产生对象。——JavaScript语言精粹第5章节(继承)
虽然能够直接设置一个对象的原型为另一个对象,从而得到新的方法和属性。以下所示:ecmascript
// Generic prototype for all letters. let letter = { getNumber() { return this.number } } // 在ES6规范中,已经正式把__proto__属性添加到规范中 // 也能够经过Object.setPrototypeOf(obj, prototype) Object.getPrototypeOf(obj) // 设置和获取对象的原型对象 let a = { number: 1, __proto__: letter } let b = { number: 2, __proto__: letter } // ... let z = { number: 26, __proto__: letter } console.log( a.getNumber(), // 1 b.getNumber(), // 2 z.getNumber() // 26 )
对象之间的关系能够用下图来表示函数
但规范主要介绍了如何利用构造函数去构建原型关系。因此JavaScript语言精粹的做者Douglas Crockford才会认为:不让对象直接继承另一个对象,而经过中间层(构造函数)去实现显得有些复杂并且存在一些弊端。调用构造器函数忘记new关键字,this将不会绑定到一个新对象上。悲剧的是,this将会绑定到全局对象上。详情能够阅读JavaScript语言精粹继承章节。ui
下面利用构造函数来实现上述一样功能this
function Letter(number) { this.number = number } Letter.prototype.getNumber = function() { return this.number } let a = new Letter(1) let b = new Letter(2) let z = new Letter(26) console.log( a.getNumber(), // 1 b.getNumber(), // 2 z.getNumber() // 26 )
其中原型关系能够下图表示spa
prototype
和__proto__
属性咱们看下规范中有关原型介绍的核心,更多详情请阅读ECMAScript 5.1 4.2.1章节 prototype
...Each constructor is a function that has a property named “prototype” that is used to implement prototype-based inheritance and shared properties...Every object created by a constructor has an implicit reference (called the object’s prototype) to the value of its constructor’s “prototype” property. Furthermore, a prototype may have a non-null implicit reference to its prototype, and so on; this is called the prototype chain...设计
经过上面的描述咱们能够得出如下结论
构造函数说到底本质上也是一个普通函数,只是该函数专门经过new
关键字来生成对象。因此JavaScript语言没法肯定哪一个函数是打算用来作构造函数的。因此每一个函数都会获得一个prototype属性,该属性值是一个包含constructor属性且constructor属性值为该函数的对象,以下所示。
只有函数才拥有prototype属性用来实现原型的继承,其余对象并无。对象拥有__proto__指向其原型对象,JavaScript引擎可经过内部属性[[prptotype]]
获取对象的原型对象。
关于这两个属性联系能够用一句话归纳:__proto__
is the actual object that is used in the prototype chain to resolve field,methods, etc. prototype is the object that is used to build __proto__
when you create an object with new.
若是你已经了解JavaScript原型,那咱们能够来说讲JavaScript语法为何要设计构造函数。
首先来加深一遍概念:JavaScript是一门基于原型继承的语言,这意味着对象能够直接从其余对象继承属性,该语言是无类型的。
然而这种设计是偏离主流方向的,当时主流语言JavaScript,C++都是经过 new Class 的语法来建立对象。JavaScript显然对它的原型本质缺少信心,因此它提供了一套和class
语法相似的对象构建语法——也就是构造函数。经过instanceof
操做符来判断对象是否属于某一类型。
MDN介绍了其内部原理
The instanceof operator tests whether the prototype property of a constructor appears anywhere in the prototype chain of an object.
instanceof操做符的语法
object instanceof constructor
简单来讲:instanceof 操做符就是判断构造函数的prototype属性值是否能在object对象的原型链中被找到,对就是这么简单。这样经过构造函数语法JavaScript引入了类的概念(伪类)。
知乎用户wang z在其专栏中发布一张有关JavaScript原型链图,能够说看懂了图片也就清楚了JavaScript中的原型关系,感兴趣的用户能够直接浏览详情。以下图所示
笔者就可能读者遇到的问题备注以下:
若是你最后仍是没有弄清楚JavaScript中的原型关系,能够在评论中进行描述我将尽我所能帮你答疑解惑。
或许你也能够看看参考文献中的引用连接。