原型(prototype)在js中可是擔當着舉足輕重的作用,原型的實現則是在原型鏈的基礎上,理解原型鏈的原理后,對原型的使用會更加自如,也能體會到js語言的魅力。
本文章會涉及的內容
- 原型及原型對象
- 原型鏈(JavaScript核心部分)
- 類的繼承
- instanceof
- constructor
我們先用一個構造器來實現一個構造函數:
function A(){ this.mark = "A"; this.changeMark = function(){ this.mark += "_changed"; } } A.prototype.mark2 = "A2"; A.prototype.changeMark2 = function(){ this.mark2 += "_changed"; } var a = new A(); var a2 = new A(); //下面則說明構造函數實例化后,分配着不同的實例對象,互不相關 console.log(a.mark); //"A" console.log(a2.mark); //"A" a.changeMark(); //使用實例對象中的方法 console.log(a.mark); //"A_changed" console.log(a2.mark); //"A" //下面則說明了new操作符的一項作用,即將原型中的this指向當前對象, //在a.changeMark2執行時,changMark2中的方法先找 this.mark2 的值, //但是實例對象this中沒有mark2值,則在原型鏈(后面會介紹)向上尋找,得到A原型對象中的mark2值, //在賦值時,將修改后的值添加在了a實例中。 //總:雖然調用的是prototype方法,但是不會對prototype屬性做修改,只會說是在實例中新增屬性,但是在使用時,會最使用最近得到的屬性(在后面原型鏈中可以加以理解) console.log(a.mark2); //"A2" console.log(a2.mark2); //"A2" a.changeMark2(); //使用原型鏈中的方法 console.log(a.mark2); //"A2_changed" console.log(a2.mark2); //"A2"
為什么a可以使原型中的changeMark2方法?這就和js巧妙的原型鏈相關,在Firefox中我們可以打印出對象並可查看到對象下面的__proto__。
我們把上面的過程用流程圖來表示:
只有構造函數才會有prototype屬性,而實例化出來的對象會擁有__proto__,而不會有prototype。
就像上圖畫的那樣,兩個實例化的對象都通過__proto__屬性指向了A.prototype(即構造函數的原型對象)
而原型對象的__proto__指向Object對象,就像a.toString()的toString方法就是存在於Object原型對象(Object.prototype)中。
so:當使用對象的方法或屬性時,對象會在一步一步通過__proto__向上尋找,找到最近的則是最終的獲取到的方法或屬性。
————這就是js中的原型鏈。
就像圖上看到的一樣,所有對象的原型鏈最終都指向了Object對象,而Object的原型對象(Object.prototype)是為數不多的不繼承自任何屬性的對象,即Object.prototype沒有__proto__,是原型鏈的頂峰。
通過上面我們可以了解到,當我們對A.prototype或Object.prototype添加屬性或方法時,在a和a2實例中都會查看到該屬性或方法,因為這兩個實例都通過原型鏈與A和Object的原型對象相連。
再來看看原型對象和原型鏈在繼承方面的實現:
再構造一個函數A和一個函數B,並讓B繼承A,如下:
function A(mark){ this.mark = mark; } A.prototype.getMark = function(){ return this.mark; } function B(mark){ this.mark = mark } //var temp = new A("A"); //B.prototype = temp; //上面語句和下語句作用相同 B.prototype = new A("A"); //實例化一個A,其賦值於B.prototype
//我們知道了原型鏈,這樣做就是將B的原型對象與A的原型對象通過原型鏈(__proto__)連接起來
//以實現繼承
var b = new B("B"); console.log(b.mark); //B, 結果如上面原型鏈分析的那樣,向上找到最近的屬性,則為b實例中的mark:"B"
其中的結構示意大概如下圖:
這時我們可以看到,在B.prototype中是沒有constructor的,因為B.prototype只是簡單的new A("A")對象賦值的結果。
在js中的constructor有什么作用呢?如:
var arr = new Array(); arr instanceof Array; //true arr.constructor === Array; //true function TEMP(){ } var temp = new TEMP(); temp instanceof TEMP; //true temp.constructor === TEMP; //true
引用《JavaScript權威指南》中的對於constructor的解釋為:對象通常繼承的constructor均指代它們的構造函數,而構造函數是類的“公共標識”。即constructor可用來判斷對象所屬的類。
在上面的小例子中,用instanceof也可判斷對象的類,但是有自身的缺陷,instanceof的實現方法為:
instanceof不會去檢查temp是不是由TEMP()構造函數初始化的,面是判斷temp是否繼承自TEMP.prototype,這樣,范圍就寬了很多。
如在上面的大例中,使用
b instaceof B //true 因為在b的原型鏈中可以找到B.prototype對象 b instaceof A //true 在b的原型鏈中也可以找到A.prototype對象
可以說instanceof是用來檢測繼承關系的。
而當
console.log(b.constructor) //function A() //因為在b的原型鏈中,最近的constructor就是A.prototype中有constructor指向了構造函數A();
但我們知道的b是屬於B類的,那最后所以要做的就是:
B.prototype.constructor = B; //將constructor指向自身的構造函數 var new_b = new B("B"); console.log(new_b.constructor) //function B()
這樣,一個完整的類繼承才完成了。
最后附上一個完整繼承后的結果圖:
第一次寫長文,如有問題,還請大家指出。
轉載請注明出處,謝謝。
Finish.