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("p").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 我的郵箱,有事兒別發郵件給我,:)。
關於原型還有很多內容可寫,但與本文關系不那麼大瞭,不能再寫瞭。

 

 

發佈留言