日期:2014-05-16  浏览次数:20351 次

js构造函数原型(prototype)问题分析(一段烂代码引发的博客)
Email:longsu2010 at yeah dot net
很久以前有人发了我如下的代码, 问我有什么问题。今天旧话重提,我索性就写一篇博客分享给大家。事先声明,本博只分析问题,并不提供解决方案(我并不清楚写这段代码的人的真正意图)。

代码咋一看很头疼,仔细看更头疼,反正我不会这么写代码。代码如下:

function aaa(sColor){
	this.color = sColor;
	if(typeof aaa._is_ == "undefined"){
		aaa.prototype.sayColor = function(){
			alert(this.color);
		};
	}
	aaa._is_ = true;
}

function bbb(sColor,name){
	this.name = name;
	if(typeof bbb._iss_ == "undefined"){
		bbb.prototype = new aaa();
		bbb.prototype.sayName = function(){
			alert(this.name);
		};
	}
	bbb._iss_ = true;
}

看完代码之后发现这是两个构造函数。先说第一个,在使用该函数创建第一个实例或者直接调用该函数(会污染全局对象)时向其原型中添加一个操作color属性的方法。代码正确性上没啥问题,用如下代码测试:
var a1 = new aaa("red");
var a2 = new aaa("green");
console.dir(a1);
console.dir(a2); 
console.log(a1.__proto__ === a2.__proto__); // 输出true

现在说说第二个构造函数,在使用该函数创建第一个实例或者直接调用该函数(会污染全局对象)时会将该函数的原型重新赋值并在新原型中增加一个操作那么属性的方法,有继承的意思。但是问题来了,只有在创建第一个实例并且执行到if语句的时候才开始改变原型,这时已经来不及了,第一个实例使用的将会是默认的原型。测试代码如下:
var bbb_proto = bbb.prototype; // 先记录bbb的原型
var b1 = new bbb("red", "liuchen1");
var b2 = new bbb("green", "liuchen2");
console.dir(b1);
console.dir(b2);
console.log(b1.__proto__ === b2.__proto__); // 输出false

bbb.prototype = bbb_proto;
var b3 = new bbb("green", "liuchen3");
console.log(b1.__proto__ === b3.__proto__); // 输出true

写到这里问题就分析完了,那总结一下吧。
总结:改变构造函数原型的时机很重要,动态改变原型要慎重,特别是已经创建了一些实例以后。已经创建的对象不会使用新原型对象。



多说几句,做如下一个有趣的实验(实验在chrome中进行),请大家仔细反复阅读代码。

function a(){}
function b(){}
// Object.prototype.xxx = '--00--'; // IE这样写
new a().__proto__.__proto__.xxx = "--00--"; // 非IE这样写
console.log(new a().__proto__ === new b().__proto__); //false // IE会打印true,因为 undefined === undefined
console.log(new a().__proto__.__proto__ === new b().__proto__.__proto__); //true  // IE会报错,测试请删除
console.log(Object.prototype === new a().__proto__.__proto__); //true  // IE会报错,测试请删除
console.log(a.prototype instanceof Object); //true
console.log(new a().__proto__ instanceof Object); //true   // 同样注意IE的问题
console.log(new a() instanceof Object) //true
console.log(Object.prototype instanceof Object) //false
console.log(a.prototype === b.prototype); //false
console.log(new a().__proto__ === a.prototype); //true
console.log(typeof(a.prototype)); //object
console.log(typeof(Object.prototype)); //object
console.log(new a().xxx); //--00--
console.log(new b().xxx); //--00--
console.log(Object.xxx); //--00--
console.log(new Object().xxx); //--00--
console.log(window.xxx); //--00--
console.log(document.xxx); //--00--
console.log(document.head.xxx); //--00--
console.log(document.getElementById("div").xxx); //--00-- //请确保dom节点已经存在
console.log("global", xxx); //global --00--
yyy = "0000";
delete window.yyy;
console.log(window.yyy); //undefined 
写了一些乱其乱七八糟的代码,下面做写说明:
1、所有js对象原型链的尽头是同一对象,即Object的prototype属性,这是为什么都会输出--00--的原因。
2、构造函数默认prototype是Object的一个实例,所有该函数创建的实例共享同一个对象。
3、修改Object的prototype功能很强大,后果很严重,慎重。
4、之所以输出global --00--是因为在查找变量的时候在window的原型链上找到了xxx,详情请研究js变量查找规则。
5、每个函数都有prototype属性,同时函数也是对象,所以也拥有原型链。如果将函数做为对象使用那么属性查找在原型链上进行,如果将函数作为构造函数使用(即new 函数名()这种形式)则会新创建一个对象,并且该对象使用函数的prototype作为原型链。
6、以上实验使用chrome25版本,IE浏览器会有一定出入,特别是可爱的IE6、7、8。好消息是win xp要退出历史舞台了,IE6、7、8应该也一起走了吧。

longsu2010 at yeah dot net 我的邮箱,有事儿别发邮件给我,:)。
关于原型还有很多内容可写,但与本文关系不那么大了,不能再写了。