Javascript 原型

89 阅读6分钟

image.png

定义

在规范里,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

1298cff5dbdfc4868312eee9ec7f2e9a.jpg

原型对象就是通过 Object 构造函数生成的,实例的 proto 指向构造函数的 prototype

每个对象都有 __proto__ 属性,但只有函数对象才有 prototype 属性,原型(prototype属性)应该指向一个对象,而不是函数

223bdfcde255f34b405f9b15f8dfbdee.jpg

当读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止。

8c6eb5508fe28b45fd91ae5b353491d9.jpg

图中由相互关联的原型组成的链状结构就是原型链,也就是蓝色的这条线。

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);

github.com/jayli/javas…