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