定义
在规范里,prototype 被定义为:给其它对象提供共享属性的对象。只是一个约定。
当某个对象,承担了为其它对象提供共享属性的职责时,它就成了该对象的 prototype。当它失去这个职能(比如子对象的原型被设置为其它对象),它就不叫该对象的 prototype。
当我们说 prototype 对象时,是在做一个简略描述,实际上说的是 “xxx 对象的 prototype 对象”。
ECMAScript 规范说 prototype 应当是一个隐式引用:
1)通过 Object.getPrototypeOf(obj) 间接访问指定对象的 prototype 对象。
2)通过 Object.setPrototypeOf(obj, anotherObj) 间接设置指定对象的 prototype 对象。
3)部分浏览器提前开了 proto 的口子,使得可以通过 obj.proto 直接访问原型,通过 obj.proto = anotherObj 直接设置原型。
4)ECMAScript 2015 规范只好向事实低头,将 proto 属性纳入了规范的一部分。
详解
每一个JavaScript对象(除了 null )都具有的一个属性,叫__proto__,这个属性会指向该对象的原型
每个原型都有一个 constructor 属性指向关联的构造函数。
注意: 在默认情况下,所有的原型对象都会自动获得一个 constructor(构造函数)属性,这个属性(是一个指针)指向 prototype 属性所在的函数(Person)
function Person() { }
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
console.log(person.constructor == Person)//true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
原型对象就是通过 Object 构造函数生成的,实例的 proto 指向构造函数的 prototype
每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性,原型(prototype属性)应该指向一个对象,而不是函数
当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。
图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。
Tips
补充三点大家可能不会注意的地方:
首先是 constructor 属性
function Person() { }
var person = new Person();
console.log(person.constructor === Person); // true
当获取 person.constructor 时,其实 person 中并没有 constructor 属性,当不能读取到constructor 属性时,会从 person 的原型也就是 Person.prototype 中读取,正好原型中有该属性,所以:
person.constructor === Person.prototype.constructor
其次是 proto
绝大部分浏览器都支持这个非标准的方法访问原型,然而它并不存在于 Person.prototype 中,实际上,它是来自于 Object.prototype ,与其说是一个属性,不如说是一个 getter/setter,当使用 obj.proto 时,可以理解成返回了 Object.getPrototypeOf(obj)。
最后是关于继承
真的是继承吗?
“每一个对象都会从原型‘继承’属性”,实际上,继承是一个十分具有迷惑性的说法,引用《你不知道的JavaScript》中的话,就是:
继承意味着复制操作,然而 JavaScript 默认并不会复制对象的属性,相反,JavaScript 只是在两个对象之间创建一个关联,这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。
测试
person1.proto 是什么?
Person.proto 是什么?
Person.prototype.proto 是什么?
Object.proto 是什么?
Object.prototype.proto 是什么?
答案:
第一题:
因为 person1.proto === person1 的构造函数.prototype
因为 person1的构造函数 === Person
所以 person1.proto === Person.prototype
第二题:
因为 Person.proto === Person的构造函数.prototype
因为 Person的构造函数 === Function
所以 Person.proto === Function.prototype
第三题:
Person.prototype 是一个普通对象,我们无需关注它有哪些属性,只要记住它是一个普通对象。
因为一个普通对象的构造函数 === Object
所以 Person.prototype.proto === Object.prototype
第四题,参照第二题,因为 Person 和 Object 一样都是构造函数
第五题:
Object.prototype 对象也有proto属性,但它比较特殊,为 null 。因为 null 处于原型链的顶端,这个只能记住。
Object.prototype.__proto__ === null
JavaScript中有内置(build-in)构造器/对象共计12个(ES5中新加了JSON),这里列举了可访问的8个构造器。剩下如Global不能直接访问,Arguments仅在函数调用时由JS引擎创建,Math,JSON是以对象形式存在的,无需new。它们的proto是Object.prototype。
Number.__proto__ === Function.prototype // true
Number.constructor == Function //true
Boolean.__proto__ === Function.prototype // true
Boolean.constructor == Function //true
String.__proto__ === Function.prototype // true
String.constructor == Function //true
// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身 Object.__proto__ === Function.prototype // true
Object.constructor == Function // true
// 所有的构造器都来自于Function.prototype,甚至包括根构造器Object及Function自身 Function.__proto__ === Function.prototype // true
Function.constructor == Function //true
Array.__proto__ === Function.prototype // true
Array.constructor == Function //true
RegExp.__proto__ === Function.prototype // true
RegExp.constructor == Function //true
Error.__proto__ === Function.prototype // true
Error.constructor == Function //true
Date.__proto__ === Function.prototype // true
Date.constructor == Function //true
Math.__proto__ === Object.prototype // true
Math.construrctor == Object // true
JSON.__proto__ === Object.prototype // true
JSON.construrctor == Object //true
Function.prototype也是唯一一个typeof XXX.prototype为 function的prototype。其它的构造器的prototype都是一个对象
console.log(typeof Function.prototype) // function
console.log(typeof Object.prototype) // object
console.log(typeof Number.prototype) // object
console.log(typeof Boolean.prototype) // object
console.log(typeof String.prototype) // object
console.log(typeof Array.prototype) // object
console.log(typeof RegExp.prototype) // object
console.log(typeof Error.prototype) // object
console.log(typeof Date.prototype) // object
console.log(typeof Object.prototype) // object
所有构造器(含内置及自定义)的__proto__都是Function.prototype,那Function.prototype的__proto__是谁呢?
相信都听说过JavaScript中函数也是一等公民,那从哪能体现呢?如下
console.log(Function.prototype.proto === Object.prototype) // true
这说明所有的构造器也都是一个普通 JS 对象,可以给构造器添加/删除属性等。同时它也继承了Object.prototype上的所有方法:toString、valueOf、hasOwnProperty等
最后Object.prototype的proto是谁?
Object.prototype.__proto__ === null // true
已经到顶了,为null。
所有对象的 __proto__ 都指向其构造器的 prototype
所有函数对象proto都指向 Function.prototype,它是一个空函数(Empty function)
function Person(name) {
this.name = name
} // 修改原型
Person.prototype.getName = function() {}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
可以看到p.__proto__与Person.prototype,p.constructor.prototype都是恒等的,即都指向同一个对象。
如果换一种方式设置原型,结果就有些不同了:
function Person(name) { this.name = name }
// 重写原型
Person.prototype = {
getName: function() {}
}
var p = new Person('jack')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false
这里直接重写了 Person.prototype(注意:上一个示例是修改原型)。输出结果可以看出p.__proto__仍然指向的是Person.prototype,而不是p.constructor.prototype。
这也很好理解,给Person.prototype赋值的是一个对象直接量{getName: function(){}},使用对象直接量方式定义的对象其构造器(constructor)指向的是根构造器Object,Object.prototype是一个空对象{},{}自然与{getName: function(){}}不等。
代码复用
继承
终极目标——代码复用,继承是达到这个目标的一种方法,但是不是唯一的。
在做代码复用的工作的时候,谨记Gang of Four 在书中给出的关于对象创建的建议:“优先使用对象创建而不是类继承”。(译注:《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)是一本设计模式的经典书籍,该书作者为Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides,被称为“Gang of Four”,简称“GoF”。)
//parent构造函数
function Parent(name) {
this.name = name || 'Adam';
}
//给原型增加方法
Parent.prototype.say = function () {
return this.name;
};
//空的child构造函数
function Child(name) {}
//继承
inherit(Child, Parent);