🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## **原型** 归根结底,原型其实也是一个对象,对象也是 `ECMAScript` 中最基本的概念。 那么我们来看一下对象这个概念,举一个基本 `Object` 的例子,首先我们要清楚,一个 `Object` 的原型是一个内部的 `[[prototype]]` 属性的引用。 不过我们一般会使用 `__proto__` 来来代替 `[[prototype]]`,不过这只是某些浏览器给出的便捷访问的方式,所以并非标准,但不妨碍我们了解原型。 *` Object`* ``` var foo = { x: 10, y: 20 } foo.__proto__ === Object.prototype // true ``` 我们可以看到 `foo` 是一个普通对象,谷歌可以通过 `__proto__` 来访问对象原型。 ## **原型链** 原型对象是一个对象,并且有可能有自己的原型,如果一个原型对象的原型不是 `null`,就有可能还有自己的原型,这就像链条一样,构成了原型链。 > A prototype chain is a finite chain of objects which is used to implemented inheritance and shared properties. 原型链是一个由对象组成的有限对象链由于实现继承和共享属性。 想象一个这种情况,2个对象,大部分内容都一样,只有一小部分不一样,很明显,在一个好的设计模式中,我们会需要重用那部分相同的,而不是在每个对象中重复定义那些相同的方法或者属性。在基于类 `[class-based]` 的系统中,这些重用部分被称为类的继承 – 相同的部分放入 `class A`,然后 `class B` 和 `class C` 从A继承,并且可以声明拥有各自的独特的东西。 `ECMAScript` 没有类的概念。但是,重用 `[reuse]` 这个理念没什么不同(某些方面,甚至比 `[class-based]` 更加灵活),可以由 `prototype chain` 原型链来实现。这种继承被叫做原型继承。 ``` var a = { x: 10, calculate: function (z) { return this.x + this.y + z } } var b = { y: 20, __proto__: a } var c = { y: 30, __proto__: a } // 上面对象表达式中直接声明了__proto__属性为a, 所以可以使用原型a上面的方法 b.calculate(30) // 10 + 20 + 30 = 60 c.calculate(40) // 10 + 30 + 40 = 80 ``` 这种继承方式很直观,`b` 和 `c` 都可以访问他们的 `__proto__` 原型对象里面的属性。 所以原型链有下列关系: ``` // 函数对象实例, 例如: var Foo = function () {} Foo.__proto__ === Function.prototype // 普通对象实例, 例如: var object = {} object.__proto__ === Object.prototype // 因为所有的原型链尽头都是Object.prototype -> null // 所以Function.prototype 也有自己的原型 Function.prototype.__proto__ === Object.prototype Object.prototype.__proto__ === null // 所以总结为: Object.__proto__ === Function.prototype Function.prototype.__proto__ === Object.prototype Object.prototype.__proto__ === null ``` 由上可知:***函数对象实例***比 ***普通对象实例*** 要多一层原型链,而且只有 ***函数*** 才有 `prototype` 属性,与之相对应的实例属性是 `__proto__`,这就是默认的对象原型和原型链。 ## **构造函数** 但是通常我们不会用上面那个方式实现继承,我们会封装构造函数来创建新的实例: ``` // 构造函数 function Foo(y) { // 构造函数将会以特定模式创建对象:被创建的对象都会有"y"属性 this.y = y } // Foo.prototype 存放了新建对象的原型引用 // 所以这里定义继承和共享的属性或方法 Foo.prototype = { x: 10, calculate: function (z) { return this.x + this.y + z } } // this.y = y var b = new Foo(20) var c = new Foo(30) // 调用继承方法 b.calculate(30) // 10 + 20 + 30 = 60 c.calculate(40) // 10 + 30 + 40 = 80 // 实例的 __proto__ 属性 === 构造函数的 prototype 属性 b.__proto__ === Foo.prototype // true c.__proto__ === Foo.prototype // true // Foo.prototype 自动创建了一个特殊的属性 constructor // 这个属性等于构造函数本身 b.constructor === Foo // true c.constructor === Foo // true Foo.prototype.constructor === Foo // true // 下面证明实例继承来的方法和原型上的方法是同一个 b.calculate === b.__proto__.calculate // true b.__proto__.calculate === Foo.prototype.calculate // true ``` ![](https://box.kancloud.cn/a504f06150f594c7af52a7ed2eda45ec_627x392.png) 因为 `__proto__` 并非标准,`Foo.prototype` 才是显式的属性,也就是 `b` 和 `c` 的 `__proto__` 属性。 每个对象都有原型,表现出来的形式是构造函数的 `prototype` 属性,所以我们一般操作原型的时候都会使用构造函数。 可以这么说,所有的对象都有原型,根据不同浏览器访问的方式可能会不同,拿谷歌为例,可以访问对象的 `__proto__` 属性来访问原型,`arguments` 是一个类数组,不是数组却是对象,所以: ``` function Foo () { console.log(arguments.__proto__ === Object.prototype) // true console.log(Object.__proto__ === Function.prototype) // true } ```