一、 说明
由于proto仅谷歌等部分浏览器支持,而IE低版本不支持,所以为了表述方便,下面说对象的原型时,直接就用prototype(如果偶尔看到proto,可能是笔误,也请不要深究,原理能明白即可)
绘图说明:像下面这样的,表示t2的原型prototype指向SuperType的原型
二、 初识原型
JS的function类型会内置一个特殊的属性prototype。通常prototype是Object
对象。而object有proto,等于将其new出来的function的prototype。即,如
果有类
function Test(){}
var t=new Test();
则Test.prototype==t.proto将会是true。也可以这样表述:
t.constructor.prototype== t.proto,结果为true
注:proto并不是js的标准属性,所以仅谷歌火狐等部分浏览器支持此属性,IE低版本并不支持这个属性。所以IE低版本下,t.proto是会报错的,不过可以通过Object.getPrototypeOf进行获取。
示例:
function SuperType(){};
SuperType.age=18;
SuperType.prototype.color=[“red”,”blue”];
var t1=new SuperType ();
t1.name=”hh”;
var t2=new SuperType ();
t2.qq=”aa”;
SuperType,t1,t2原型关系示意图:
定义function SuperType时,编译器会为其分配一个prototype属性,并在内存
中开辟一片区域用于存放SuperType的原型的数据,假设这片区域的地址为0xA。
然后让SuperType的prototype指向0xA。由SuperType创建出来的t1和t2的
prototype也指向内存地址0xA。如果有SuperType.prototype.color.push(“green”),则
t1和t2的color也会变为[“red”,”blue”,”green”]。因为SuperType,t1,t2的原型指
向的是同一个地址的数据。这个过程可以这么比喻:把原型比喻为你的银行卡账户,
假设有一万块钱。当有人盗刷你的银行卡,刷走了4000,然后你去银行查看你账
户余额,当然的会剩下6000。因为你跟盗刷你银行卡的人所持的银行卡指向的是
同一个账户。
SuperType.prototype.color.push(“green”)后,示意图如下:
另外要注意的是,prototype也是object类型的,是Object的一个实例。所以完整示意图如下:
三、 原型链
其实在上面的示意图中,顺着箭头走,如Test—>Test的原型—>Object的原型,就是一条原型链了。当然t2—>Test的原型—>Object的原型也是一条原型链。下面对原形链进行补充,说明原型链在继承体系中是怎么工作的。
示例:
function SuperType(){};
SuperType.age=18;
SuperType.prototype.color=[“red”,”blue”];
function SubType(){}
SubType.property=”property”;
SubType.prototype=new SuperType();
SubType.prototype.test=function(){alert(“test”);}
function Child(){}
Child.prototype=new SubType();
var t1=new SuperType ();
t1.name=”hh”;
var t2=new SuperType ();
t2.qq=”aa”;
var s=new SubType();
var c=new Child();
注意,SubType的原型被重新赋值了,SubType.prototype=new SuperType();
所以SubType的原型是SuperType的一个对象实例。
上述代码的原型链示意图如下:
说到这里,原型链已经呼之欲出了。假设有这样的调用,c.toString();于是就会从上图的c开始沿着箭头到Child的原型,再到SubType的原型,到最后Object的原型,找到toString,然后执行。如果到Object的原型还没找到toString,那么就会报错。如果是这么调用,c.test(),那么就会沿着原型链,找到SubType的原型,从中找到test,然后执行。
从上图也可以看到,SuperType的age,只能SuperType自己引用。t2的qq也只能t2引用。它们都是实例属性。这便是原形链的工作机制。
四、重置原型
要重置原型,只要对原型重新赋值即可。例如:
function Person(){}
Person.prototype={
name:”Leo”
}
需要注意的是,Person原型重写后,Person的原型为{ name:”Leo”}。{ name:”Leo”}是一个匿名的Object实例,所以其constructor,为Object。也就是说重写后Person的原型的constructor为Object。如果constructor很重要,可以为其增加一个constructor属性,如下:
Person.prototype={
name:”Leo”,
constructor:Person
}
不过此时还会有一个问题,这样设置的constructor将会使constructor变成是可枚举的。所以,如果想让它变为不可枚举的,可用Object.defineProperty进行设置
五、原型的动态性
示例:
function Person(){}
var p=new Person();
Person.prototype.sayHi=function(){alert(“Hi”);};
p.sayHi();//这里没问题,因为p在调用sayHi时,会先从自身找sayHi,找不到则会沿着原形链去寻找,
//然后在Person的原型中找到了sayHi,于是就执行
原型动态性之——-重写原型的问题
function Person(){}
var p=new Person();
Person.prototype={
sayName: function(){alert(“Hi”);},
constructor:Person
}
p.sayName();//此时将会报错,p没有sayName方法
这里为何p.sayName会报错,Person的原型不是有sayName么?
且看下面的示意图:
从图中可以看出Person重写原型后,又分配了一片内存区域,用于存储新的Person原型,原先的Person原型依然存在,p指向的就是原先的没有sayName属性的Person原型。所以p调用sayName会找不到这个属性,于是报错。
六、原型共享所引发的问题
原型的优点就是共享。例如:
function Person (){}
Person.prototype.sayName=function(){};
var p1=new Person ();
var p2=new Person ();
在原型中定义sayName函数,于是p1,p2对象有同一个sayName。而不会在内存中分配两次内存来分别存放p1的sayName和p2的sayName。而缺点也是由共享所致。共享对于函数而言,是合适的,但是对于其他属性而言,可能就会出问题。
示例如下:
function Person(){}
Person.prototype.color=[“red”,”green”];
var p1=new Person();
var p2=new Person();
alert(p1.color);//输出red,green
alert(p2.color); //输出red,green
p2.color.push(“blue”);
alert(p1.color); //输出red,green,blue。注意,这里我并没有更改p1的color
alert(p2.color); //输出red,green,blue
这里我们可以发现,p2的color进行更改之后,p1的color也跟着更改,这正是原型共享所引发的问题
七、原型链与instanceof实现原理
本节参考:http://www.ibm.com/developerworks/cn/web/1306_jiangjj_jsinstanceof/
7.1 ECMAScript-262 edition 3 中 instanceof 运算符的定义(略复杂,可跳过此节,感兴趣读者可阅读)
11.8.6 The instanceof operator
The production RelationalExpression:
RelationalExpression instanceof ShiftExpression is evaluated as follows:
- Evaluate RelationalExpression.
- Call GetValue(Result(1)).// 调用GetValue方法得到 Result(1) 的值,设为 Result(2)
- Evaluate ShiftExpression.
- Call GetValue(Result(3)).// 同理,这里设为 Result(4)
- If Result(4) is not an object, throw a TypeError exception.
// 如果 Result(4) 不是 object,抛出异常
/* 如果 Result(4) 没有 [[HasInstance]] 方法,抛出异常。规范中的所有 [[…]] 方法或者属性都是内部的,
在 JavaScript 中不能直接使用。并且规范中说明,只有 Function 对象实现了 [[HasInstance]] 方法。
所以这里可以简单的理解为:如果 Result(4) 不是 Function 对象,抛出异常 */ - If Result(4) does not have a [[HasInstance]] method,
throw a TypeError exception.
// 相当于这样调用:Result(4).[HasInstance] - Call the [[HasInstance]] method of Result(4) with parameter Result(2).
Return Result(7).
// 相关的 HasInstance 方法定义
15.3.5.3 [[HasInstance]] (V)
Assume F is a Function object.// 这里 F 就是上面的 Result(4),V 是 Result(2)
When the [[HasInstance]] method of F is called with value V,
the following steps are taken:- If V is not an object, return false.// 如果 V 不是 object,直接返回 false
- Call the [[Get]] method of F with property name “prototype”.
//用[[Get]]方法取F的 prototype 属性 - Let O be Result(2).//O = F.[[Get]](“prototype”)
- If O is not an object, throw a TypeError exception.
- Let V be the value of the [[Prototype]] property of V.//V = V.[[Prototype]]
- If V is null, return false.
// 这里是关键,如果 O 和 V 引用的是同一个对象,则返回 true;否则,到 Step 8 返回 Step 5 继续循环 - If O and V refer to the same object or if they refer to objects
joined to each other (section 13.1.2), return true. - Go to step 5.
7.2 由规范得到的instanceof实现
//注:如果想写得更通用些,下面的L.__proto__可以换成L.constructor.prototype
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式
var O = R.prototype;// 取 R 的显示原型
L = L.__proto__;// 取 L 的隐式原型
while (true) {
if (L === null)
return false;
if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true
return true;
L = L.__proto__;
}
}
7.3 instanceof实现原理说明
假设a instanceof b,那么会从a的原形链中找出是否有跟b的原型相等的原型,如果找到则返回true,否则返回false。
示例如下:
function SuperType(){}
function SubType(){}
function Test(){}
SubType.prototype=new SuperType();
var s=new SubType();
console.log(s instanceof SubType);//打出true,这是因为s的原型等于SubType的原型
SubType.prototype=new SuperType();
console.log(s instanceof SubType);//打出false,这是因为SubType的原型变了,而s还//是原来的SubType的原型
上面代码的工作过程是这样的:
先说s的原型:s由SubType构造而来,SubType原型由SuperType构造而来。所以有
这样的关系(注:对象并没有prototype属性,但是在谷歌火狐等浏览器有proto,
而proto又不是标准属性,IE低版本浏览器并不支持,所以为表述方便,下面表
示s的原型直接用prototype),
①、s.prototype=SubType.prototype= new SuperType();
②、s.prototype.prototype=SuperType.prototype;
于是第一个s instanceof SubType时,会先这样判断s.prototype是否跟
SubType.prototype是同一个对象,用表达式表示的话就是这样,判断
s.prototype==SubType.prototype,如果相等结果返回true。在第二次
s instanceof SubType时SubType.prototype重新new了一次,于是
s.prototype==SubType.prototype就返回false了。但是s的原形链还没到终点,于是再判断s.prototype.prototype==SubType.prototype,此时仍然是false,而s的原形链也已经到了终点,于是返回false,所以第二次s instanceof SubType的运算结果就是false。以此类推,如果是s instanceof SuperType那么会先这样
s.prototype==SuperType.prototype显然是false,于是沿着原形链,继续判断s.prototype.prototype==SuperType.prototype,此时返回true。于是
s instanceof SuperType的结果就是true。
说明:
s是object类型,所以s的原型是不可以直接引用的,即s.prototype是会报错的,这里这么表示只是为了不想画图,又要表述方便,所以采用这种方式进行说明。
八、Function与Object互为实例
1、Object及所有的其他类都是Function的实例
当执行Object instanceof Function,将会为true。这是因为
Object.proto===Function.prototype,实际上所有类的proto都是全等于
Function.prototype,所以所有的function类型都是Function的实例,执行instanceof Function都会返回true
2、Function也是Object的实例
Function.proto.proto===Object.prototype执行结果为true。
所以根据前面instanceof的实现函数,执行结果将会为true,即
Function instanceof Object将会为true
所以Function与Object是互为实例的
九、Function与function
类由Function构造而来,比如定义一个Test类:
function Test(str){console.log(str)}
当你这么定义Test类时,JS底层将会利用Function类将其构造出来,所以上面这句等价于:
var Test = new Function(‘str’,’console.log(str)’);
所以,Test是Function的实例,Test.constructor为Function,既然Test也可以是
实例,那么就会有proto这样的属性,前面已经知道,Test还会有prototype。所
以Test. proto===Function.prototype
十、混沌初开
1、无极
null,undefined
2、无极生太极:
最初的对象,其实就是Object的原型
var prototype={toString等};
prototype.prototype=null;
prototype.proto=undefined;
3、太极生两仪:
Function.prototype=Function.proto;
Function.prototype.prototype=undefined;
Function.prototype.proto= prototype;
有了Function后,再由Function创建Object
function Object(){}
Object.prototype=prototype
4、两仪生四象
function Array(){}
function Boolean(){}
function Date(){}
等系统类,其构造过程跟Object相同,都有Function构造而来,它们的原型都指
向最初那个原型prototype
当你定义其他类的时候,其实也是一样的过程,比如:
function Test(str){console.log(str)}
在JS底层会调用Functon类将Test类创建出来:
var Test= new Function(‘str’,’console.log(str)’);
在创建过程中,会给Test内置一个prototype,这个prototype指向最初的prototype,
也就是Object的原型,实际上,Object除了原型,基本什么都没有。而new操作
会为Test增加一个proto属性,Test.proto=Function.prototype
5、四象生八卦
有了类之后就可以创建各种各样的对象了。对象创建的方式很多,比如new,字
面量,Object.create等。