JavaScript原型、原型鏈、對象的創建

JavaScript原型

在JavaScript中,每個函數
都有一個prototype屬性,當一個函數被用作構造函數來創建實例時,這個函數的prototype屬性值會被作爲原型賦值給所有對象實例。
所有函數對象的_proto_最終都指向Function.prototype,它是一個空函數.

代碼:

function Son (name) { this.name = name; }
function Mother () { }

//Mother的原型
Mother.prototype = { 
    age: 18,
    home: ['Beijing', 'Shanghai']
};
//Son的原型設爲Mother
Son.prototype = new Mother(); 

var p1 = new Son('小明'); //p1:'小明'; __proto__:{__proto__:18,['Beijing','Shanghai']}
var p2 = new Son('蛋蛋'); //p2:'蛋蛋'; __proto__:{__proto__:18,['Beijing','Shanghai']}

//實例不會改變原型的基本值屬性
p1.age = 20;      //p1:'小明',20; __proto__:{__proto__:18,['Shenzhen','Shanghai']}

//改變了原型Mother中的屬性
// p1:'小明',20; __proto__:{__proto__:18,['Shenzhen','Shanghai']}
// p2:'蛋蛋';    __proto__:{__proto__:18,['Shenzhen','Shanghai']}
p1.home[0] = 'Shenzhen'; 


//可以理解爲p1.age=20
p1.home = ['Hangzhou', 'Guangzhou'];  //p1:'小明',20,['Hangzhou','Guangzhou']; __proto__:{__proto__:18,['Shenzhen','Shanghai']}

//刪除實例的屬性之後,將會顯示原本被覆蓋的原型值,這就是向上的搜索機制
delete p1.age;    //p1:'小明',['Hangzhou','Guangzhou']; __proto__:{__proto__:18,['Shenzhen','Shanghai']}

//爲Son的原型Monther增加屬性(改寫原型,動態反應到實例中)
// p1:'小明',['Hangzhou','Guangzhou']; __proto__:{'Li',__proto__:18,['Shenzhen','Shanghai']}
// p2:'蛋蛋';                          __proto__:{'Li',__proto__:18,['Shenzhen','Shanghai']}
Son.prototype.lastName = 'Li'; 

//更換Son的原型,就好像換了一個後媽一樣
Son.prototype = { 
    age: 20, 
    address: { city: 'Ji nan' }
};

// p1:'小明',['Hangzhou','Guangzhou']; __proto__:{'Li',__proto__:18,['Shenzhen','Shanghai']}
// p2:'蛋蛋';                          __proto__:{'Li',__proto__:18,['Shenzhen','Shanghai']}
// p3:'小花';__proto__: 20 {city: 'Ji nan'}
var p3 = new Son('小花'); 

//爲Mother的原型增加屬性,即爲姥姥增加了屬性
//但是上面因爲更換了son的原型,所以Mother的變換不會影響p3
// p1:'小明',['Hangzhou','Guangzhou']; __proto__:{'Li',__proto__:18,['Shenzhen','Shanghai'],233}
// p2:'蛋蛋';                          __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai'],233}
// p3: '小花';__proto__: 20 {city: 'Ji nan'}
Mother.prototype.adressnum = 233;

//更換Mother的原型,即更換了姥姥
//但是因爲上面son的原型已經不是mother了,所以Mother怎麼變不會影響Son。
Mother.prototype = { 
    car: 2, 
    hobby: ['run','walk']
};

var p4 = new Son('Tony');  //p4:'Tony';__proto__: 20 {city: 'Ji nan'}

//想讓son應用這些改變的話,需要重新綁定mother
Son.prototype = new Mother(); //再次綁定

var p5 = new Son('小碩'); // p5:'小碩';__proto__:{__proto__: 2, ['run','walk']}

對於p1.home[0] = ‘Shenzhen’; 爲何mother、p1和p2都受影響呢?

這是因爲:p1中並不存在home這個數組。當你使用p1.home[0]時,在本地找不到home這個變量,這時由於原型鏈的向上搜索機制,會到p1的原型mother中尋找,找不到,接着到mother的原型中尋找,這裏找到了home這個屬性,於是就改變了mother原型的home[0]屬性。
因此p1.home[0] = ‘Shenzhen’ 就等同於 Mother.prototype.home[0] = ‘Shenzhen’

原型鏈

原型鏈是實現繼承的主要方法。
原型鏈的終點爲null

原理:利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
核心:屬性共享和獨立的控制

原型鏈

原型鏈繼承的主要問題在於屬性的共享:

組合繼承

function Mother (age) {
    this.age = age;
    this.hobby = ['running','football']
}
Mother.prototype.showAge = function () {
    console.log(this.age); 
};

function Person (name, age) { 
    Mother.call(this, age);  
    this.name = name; 
}
Person.prototype = new Mother();  //第一次執行mother
Person.prototype.constructor = Person;
Person.prototype.showName = function () {
    console.log(this.name);
}

var p1 = new Person('Jack', 20); //第二次執行mother
p1.hobby.push('basketball');  //p1:'Jack'; __proto__:20,['running','football','basketball']
var p2 = new Person('Mark', 18);  //p2:'Mark'; __proto__:18,['running','football']

通過第二次執行原型的構造函數 Mother(),在對象實例中複製了一份原型的屬性,這樣就做到了與原型屬性的分離獨立。但是第一次調用 Mother(),好像什麼用都沒有,能不調用他嗎?當然可以,於是就有了下面的寄生組合式繼承。

寄生組合式繼承

function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}
//避免了new Mother(),所以沒了第一次執行
function inheritPrototype(Person, Mother){
    var prototype = object(Mother.prototype); 
    prototype.constructor = Person;    
    Person.prototype = prototype;    
}

function Mother (age) {
    this.age = age;
    this.hobby = ['running','football']
}
Mother.prototype.showAge = function () {
    console.log(this.age); 
};

function Person (name, age) { 
    Mother.call(this, age);
    this.name = name; 
}

inheritPrototype(Person, Mother);

Person.prototype.showName = function () {
    console.log(this.name);
}

var p1 = new Person('Jack', 20); 
p1.hobby.push('basketball');//p1:'Jack'; __proto__:20,['running','football','basketball']
var p2 = new Person('Mark', 18); //p2:'Mark'; __proto__:18,['running','football']

關鍵點在於 object(o) 裏面,這裏借用了一個臨時對象來巧妙避免了調用new Mother(),然後將原型爲 o 的新對象實例返回,從而完成了原型鏈的設置。

Js創建對象的方法

//最原始模式,對象字面量方式
var person = { 
    name: 'Sun Miao',
    age: 22,
    sayName: function () { alert(this.name); }
};

最原始的模式不適合批量創建對象,批量創建可以考慮工廠模式

//工廠模式,定義一個函數創建對象
function creatPerson (name, age) {
    var person = new Object(); 
    person.name = name;
    person.age = age;
    person.sayName = function () {
        alert(this.name);
    };
    return person; 
}

工廠模式缺點是每次創建對象都會產生一個臨時對象,而且你無法缺點創建的對象具體是什麼類型,因爲new Object().
這是可以考慮構造函數模式。

//構造函數模式,定義一個構造函數
function Person (name, age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        alert(this.name);
    };    
}
var p1 = new Person('Sun Miao', 22);

通過構造函數創建的對象,其中的方法(這裏把方法看成是和name一樣的變量就好)也都是各自獨立的,比如sayName方法,如果我們想讓多個對象共用一個sayName怎麼辦?
那就得用到原型模式了。

//原型模式1,直接定義prototype屬性方式
function Person () {}
Person.prototype.name = 'Sun Miao';
Person.prototype.age = 22;
Person.prototype.sayName = function () { alert(this.name); };

//原型模式2,字面量定義方式
function Person () {}
Person.prototype = {
    name: 'Sun Miao',
    age: 22,
    sayName: function () { alert(this.name); }
};
var p1 = new Person(); 

需要注意的是原型屬性和方法的共享,即所有實例中都只是引用原型中的屬性方法,任何一個地方產生的改動會引起其他實例的變化。

但我們只想共享某些方法,其他變量仍保持獨立,那怎麼辦?
那就把原型和構造組合起來唄~

//原型+構造 模式
//對於要求獨立的變量,使用構造模式
function Person (name, age) {
    this.name = name;
    this.age = age;
};
//對於要求共享的變量,使用原型模式
Person.prototype = {
    hobby: ['warking','football'];
    sayName: function () { alert(this.name); },
    sayAge: function () { alert(this.age); }
};

var p1 = new Person('Sun Miao', 22); 
//p1:'Sun Miao',22; __proto__: ['warking','football'],sayName,sayAge

var p2 = new Person('Wen Shuo', 23); 
//p1:'Wen Shuo',23;__proto__: ['warking','football'],sayName,sayAge

這樣做既節省了內存開銷又保留了對象實例的獨立性,一舉兩得。

參考文章

非常感謝茄果寫的這篇關於原型理解的文章,看了之後很多模糊的地方都通了,例子比喻的很形象,通俗易懂。不像某些文章扯一堆官話,讓人看一眼就沒看下去的慾望了O(∩_∩)O哈哈~
http://www.cnblogs.com/qieguo/p/5451626.html
還有它寫的關於閉包的文章也很出色,絕對是最好理解的文章。沒有之一:
http://www.cnblogs.com/qieguo/p/5457040.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章