javascript
JavaScript原型彻底理解2---继承中的原型链
一、繼承的概念
繼承是所有的面向對象的語言最重要的特征之一。大部分的oop語言的都支持兩種繼承:接口繼承和實現繼承。比如基于類的編程語言Java,對這兩種繼承都支持。從接口繼承抽象方法 (只有方法簽名),從類中繼承實例方法。
? 但是對JavaScript來說,沒有類和接口的概念(ES6之前),所以只支持實現繼承,而且繼承在 原型鏈 的基礎上實現的。等了解過原型鏈的概念之后,你會發現繼承其實是發生在對象與對象之間。這是與其他編程語言很大的不同。
二、原型鏈的概念
在JavaScript中,將原型鏈實現繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。再回顧下,構造函數、原型(對象)和對象之間的關系。每個構造函數都有一個屬性 prototype 指向一個原型對象,每個原型對象也有一個屬性 constructor 指向函數,通過new 構造函數() 創建出來的對象內部有一個不可見的屬性[[prototype]]指向構造函數的原型。當每次訪問對象的屬性和方法的時候,總是先從p1中找,找不到則再去p1指向的原型中找。
1、更換構造函數的原型
原型其實就是一個對象,只是默認情況下原型對象是瀏覽器會自動幫我們創建的,而且自動讓構造函數的 prototype 屬性指向這個自動創建的原型對象。
? 其實我們完全可以把原型對象更換成一個我們自定義類型的對象。
看下面的代碼:
<script type="text/javascript">//定義一個構造函數。function Father () {// 添加name屬性. 默認直接賦值了。當然也可以通過構造函數傳遞過來this.name = "馬云";}//給Father的原型添加giveMoney方法Father.prototype.giveMoney = function () {alert("我是Father原型中定義的方法");}//再定義一個構造函數。function Son () {//添加age屬性this.age = 18;}//關鍵地方:把Son構造方法的原型替換成Father的對象。Son.prototype = new Father();//給Son的原型添加getMoney方法Son.prototype.getMoney = function () {alert("我是Son的原型中定義的方法");}//創建Son類型的對象var son1 = new Son();//發現不僅可以訪問Son中定義屬性和Son原型中定義的方法,也可以訪問Father中定義的屬性和Father原型中的方法。//這樣就通過繼承完成了類型之間的繼承。 // Son繼承了Father中的屬性和方法,當然還有Father原型中的屬性和方法。son1.giveMoney();son1.getMoney();alert("Father定義的屬性:" + son1.name);alert("Son中定義的屬性:" + son1.age);</script> 上面的代碼其實就完成了Son繼承Father的過程。那么到底是怎么完成的繼承呢? 看下面的示意圖:
說明:
2、默認頂端原型
? 其實上面原型鏈還缺少一環。
? 在 JavaScript 中所有的類型如果沒有指明繼承某個類型,則默認是繼承的 Object 類型。這種 默認繼承也是通過原型鏈的方式完成的。
下面的圖就是一個完整的原型鏈:
說明:
3、測試數據的類型
到目前為止,我們有3中方法來測試數據的類型。
3.1、typeof:一般用來測試簡單數據類型和函數的類型。如果用來測試對象,則會一直返回object,沒有太大意義。
<script type="text/javascript">alert(typeof 5); // numbervar v = "abc";alert(typeof v); // stringalert(typeof function () {}); //funcionfunction Person () {}alert(typeof new Person()); //object</script>3.2、instanceof: 用來測試一個對象是不是屬于某個類型。結果為boolean值。
<script type="text/javascript">function Father () {}function Son () { }Son.prototype = new Father();var son = new Son();alert(son instanceof Son); // true// Son通過原型繼承了Fatheralert(son instanceof Father); // true//Father又默認繼承了Objcetalert(son instanceof Object); // true </script>3.3、isPrototypeOf( 對象 ) : 這是個原型的方法,參數傳入一個對象,判斷參數對象是不是由這個原型派生出來的。 也就是判斷這個原型是不是參數對象原型鏈中的一環。
<script type="text/javascript">function Father () {}function Son () {}Son.prototype = new Father();var son = new Son();alert(Son.prototype.isPrototypeOf(son)); // truealert(Father.prototype.isPrototypeOf(son)); // truealert(Object.prototype.isPrototypeOf(son)); // true </script>4、原型鏈在繼承中的缺陷
原型鏈并非完美無缺,也是存在一些問題的。
4.1、父類型的屬性共享問題
在原型鏈中,父類型的構造函數創建的對象,會成為子類型的原型。那么父類型中定義的實例屬性,就會成為子類型的原型屬性。對子類型來說,這和我們以前說的在原型中定會以方法,構造函數中定義屬性是違背的。子類型原型中的屬性被所有的子類型的實例所共有,如果有個一個實例去更改,則會很快反應的其他的實例上。
看下面的代碼:
<script type="text/javascript">function Father () {this.girls = ["志玲", "鳳姐"];}function Son () {}// 子類的原型對象中就有一個屬性 girls ,是個數組Son.prototype = new Father(); var son1 = new Son();var son2 = new Son();//給son1的girls屬性的數組添加一個元素son1.girls.push("亦非");//這時,發現son2中的girls屬性的數組內容也發生了改變alert(son2.girls); // "志玲", "鳳姐", "亦非" </script>4.2 向父類型的構造函數中傳遞參數問題
在原型鏈的繼承過程中,只有一個地方用到了父類型的構造函數,Son.prototype = new Father();。只能在這個一個位置傳遞參數,但是這個時候傳遞的參數,將來對子類型的所有的實例都有效。
? 如果想在創建子類型對象的時候傳遞參數是沒有辦法做到的。
? 如果想創建子類對象的時候,傳遞參數,只能另辟他法。
三、借用構造函數調用繼承
1、借用的方式
借用構造函數調用繼承,又叫偽裝調用繼承或冒充調用繼承。雖然有了繼承兩個子,但是這種方法從本質上并沒實現繼承,只是完成了構造方法的調用而已。使用call或apply這兩個方法完成函數借調。這兩個方法的功能是一樣的,只有少許的區別(暫且不管)。功能都是更改一個構造方法內部的this指向到指定的對象上。
看下面的代碼:
<script type="text/javascript">function Father (name,age) {this.name = name;this.age = age;}//這樣直接調用,那么father中的this只的是 window。 因為其實這樣調用的: window.father("李四", 20)// name 和age 屬性就添加到了window屬性上Father("李四", 20);alert("name:" + window.name + "\nage:" + window.age);//使用call方法調用,則可以改變this的指向function Son (name, age, sex) {this.sex = sex;//調用Father方法(看成普通方法),第一個參數傳入一個對象this,則this(Son類型的對象)就成為了Father中的thisFather.call(this, name, age);}var son = new Son("張三", 30, "男");alert("name:" + son.name + "\nage:" + son.age + "\nsex:" + son.sex);alert(son instanceof Father); //false </script> 函數借調的方式還有別的實現方式,但是原理都是一樣的。但是有一點要記住,這里其實并沒有真的繼承,僅僅是調用了Father構造函數而已。也就是說,son對象和Father沒有任何的關系。2、借用的缺陷
Father的原型對象中的共享屬性和方法,Son沒有辦法獲取。因為這個根本就不是真正的繼承。
四、組合繼承
?組合函數利用了原型繼承和構造函數借調繼承的優點,組合在一起。成為了使用最廣泛的一種繼承方式。
<script type="text/javascript">//定義父類型的構造函數function Father (name,age) {// 屬性放在構造函數內部this.name = name;this.age = age;// 方法定義在原型中if((typeof Father.prototype.eat) != "function"){Father.prototype.eat = function () {alert(this.name + " 在吃東西");}} }// 定義子類類型的構造函數function Son(name, age, sex){//借調父類型的構造函數,相當于把父類型中的屬性添加到了未來的子類型的對象中Father.call(this, name, age);this.sex = sex;}//修改子類型的原型。這樣就可以繼承父類型中的方法了。Son.prototype = new Father( );var son1 = new Son("志玲", 30, "女");alert(son1.name);alert(son1.sex);alert(son1.age);son1.eat(); </script>說明:
總結
以上是生活随笔為你收集整理的JavaScript原型彻底理解2---继承中的原型链的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VCSA中配置时间和时区,实测至6.5适
- 下一篇: 正态随机分布 C++实现