摘要:創(chuàng)建自定義的構(gòu)造函數(shù)之后,其原型對象只會取得屬性,其他方法都是從繼承來的。優(yōu)缺點(diǎn)寄生式繼承在主要考慮對象而不是創(chuàng)建自定義類型和構(gòu)造函數(shù)時,是十分有用的。
1.原型鏈原文鏈接:https://kongchenglc.coding.me...
??js的繼承機(jī)制不同于傳統(tǒng)的面向?qū)ο笳Z言,采用原型鏈實現(xiàn)繼承,基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。理解原型鏈必須先理解原型,以下是對于原型的一些解釋:
無論什么時候,只要創(chuàng)建了一個新函數(shù),就會根據(jù)一組特定規(guī)則為該函數(shù)創(chuàng)建一個prototype屬性。這個屬性指向函數(shù)的原型對象,所有原型對象都會自動獲得一個constructor屬性,這個屬性是一個指向prototype屬性所在函數(shù)的指針。創(chuàng)建自定義的構(gòu)造函數(shù)之后,其原型對象只會取得constructor屬性,其他方法都是從Object繼承來的。當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個新實例之后,該實例的內(nèi)部包含一個指針,指向構(gòu)造函數(shù)的原型對象,即[[Prototype]],在瀏覽器中為_proto_
??也就是說,構(gòu)造函數(shù)和實例實際上都是存在一個指向原型的指針,構(gòu)造函數(shù)指向原型的指針為其prototype屬性。實例也包含一個不可訪問的指針[[Prototype]](實際在瀏覽器中可以用_proto_訪問),而原型鏈的形成真正依賴的是_proto_而非[[Prototype]]。
舉個例子下邊是一個最簡單的繼承方式的例子:用父類實例充當(dāng)子類原型對象。
function SuperType(){ this.property = true; this.arr = [1]; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } SubType.prototype = new SuperType(); //在此繼承,SubType的prototype為SuperType的一個實例 SubType.prototype.getSubValue = function(){ return this.subproperty; }; var instance = new SubType(); var instance2 = new SubType(); c(instance.getSuperValue()); //true c(instance.getSubValue()); //false c(instance.__proto__.prototype); //undefined //SubType繼承了SuperType,SuperType繼承了Object。 //instance的_proto_是SubType的原型對象,即SubType.prototype。 //而SubType.prototype又是SuperType的一個實例。 //則instance._proto_.prototype為undefined, //因為SuperType的實例對象不包含prototype屬性。 instance.arr.push(2); c(instance.arr); //[1,2] c(instance2.arr); //[1,2] //子類們共享引用屬性
優(yōu)缺點(diǎn)需要注意的一點(diǎn):無論以什么方式繼承,請謹(jǐn)慎使用將對象字面量賦值給原型的方法,這樣會重寫原型鏈。
??原型鏈繼承方式的優(yōu)點(diǎn)在于簡單,而缺點(diǎn)也十分致命:
子類之間會共享引用類型屬性
創(chuàng)建子類時,無法向父類構(gòu)造函數(shù)傳參
2.借用構(gòu)造函數(shù)??又叫經(jīng)典繼承,借用構(gòu)造函數(shù)繼承的主要思想:在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型構(gòu)造函數(shù),即用call()或apply()方法給子類中的this執(zhí)行父類的構(gòu)造函數(shù),使其擁有父類擁有的屬性實現(xiàn)繼承,這種繼承方法完全沒有用到原型。下邊是借用構(gòu)造函數(shù)的實現(xiàn):
function SuperType(){ this.colors = ["red","blue","green"]; } function SubType(){ SuperType.call(this); //借用構(gòu)造函數(shù) } var instance1 = new SubType(); instance1.colors.push("black"); c(instance1.colors); //["red","blue","green","black"] var instance2 = new SubType(); c(instance2.colors); //["red","blue","green"]舉個例子
??借用構(gòu)造函數(shù),相當(dāng)于將父類擁有的屬性在子類的構(gòu)造函數(shù)也寫了一遍,使子類擁有父類擁有的屬性,這種方法在創(chuàng)建子類實例時,可以向父類構(gòu)造函數(shù)傳遞參數(shù) 。
function SuperType(name){ this.name = name; } function SubType(name){ SuperType.call(this,name); //借用構(gòu)造函數(shù)模式傳遞參數(shù) this.age = 29; } var instance = new SubType("something"); c(instance.name); //something c(instance.age); //29優(yōu)缺點(diǎn)
??借用構(gòu)造函數(shù)模式,不同于原型式繼承和原型模式,它不會共享引用類型屬性,而且也可以向超類型構(gòu)造函數(shù)傳遞參數(shù)。但是相對的,由于不會共享屬性,也無法實現(xiàn)代碼復(fù)用,相同的函數(shù)在每個實例中都有一份。為了實現(xiàn)代碼復(fù)用,提示效率,大神們又想出了下邊的繼承方法。
3.組合繼承組合繼承有時也叫偽經(jīng)典繼承,是將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合到一塊,從而發(fā)揮二者之長的一種繼承模式。
??即用原型鏈實現(xiàn)對原型屬性和方法的繼承(需要共享的),通過借用構(gòu)造函數(shù)實現(xiàn)對實例屬性的繼承(不共享的)。這樣的方法實現(xiàn)了函數(shù)復(fù)用,而且每個實例擁有自己的屬性。
舉個例子function SuperType(name) { //父類的實例屬性 this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { //父類的原型屬性 c(this.name); }; function SubType(name, age) { //借用構(gòu)造函數(shù)繼承實例屬性 SuperType.call(this, name); this.age = age; } SubType.prototype = new SuperType(); //原型鏈繼承原型屬性 SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { c(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); c(instance1.colors); //"red,blue,green,black" delete instance1.colors; //刪除從實例屬性繼承來的colors,讀取colors會成為從原型繼承來的實例屬性 c(instance1.colors); //"red,blue,green" instance1.sayName(); //Nicholas instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27); c(instance2.colors); //"red,blue,green" instance2.sayName(); //Greg instance2.sayAge(); //27優(yōu)缺點(diǎn)
這是所有繼承方式中最常用的,它的優(yōu)點(diǎn)也十分明顯:
可以在創(chuàng)建子類實例時向父類構(gòu)造函數(shù)傳參。
引用類型屬性的值可以不共享。
可以實現(xiàn)代碼復(fù)用,即可以共享相同的方法。
但是這種方法依然有一點(diǎn)不足,調(diào)用了兩次父類的構(gòu)造函數(shù),最后會講到一種理論上接近完美的繼承方式,即寄生組合式繼承。
4.原型式繼承??原型式繼承借助原型基于已有對象創(chuàng)建新對象,需要一個對象作為另一個對象的基礎(chǔ):
function object(o) { function F() {} F.prototype = o; return new F(); }
??上邊段代碼就是原型式繼承的核心代碼,先創(chuàng)建一個臨時性的構(gòu)造函數(shù),然后將傳入的對象作為這個構(gòu)造函數(shù)的原型,最后返回這個臨時類型的一個新實例。
舉個例子var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = object(person); //在此繼承 yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); c(person.friends); //["Shelby","Court","Van","Rob","Barbie"] c(person.name); //Nicholas c(anotherPerson.name); //Greg c(yetAnotherPerson.name); //Linda delete yetAnotherPerson.name; //刪除子類的屬性,就會解除對父類屬性的屏蔽,暴露出父類的name屬性 c(yetAnotherPerson.name); //Nicholas
??從上邊的代碼顯示,由object(注意首字母小寫,不是對象的構(gòu)造函數(shù))產(chǎn)生的兩個子類會共享父類的引用屬性,其中friends數(shù)組是共享的,anotherPerson和yetAnotherPerson都是繼承自person。實際上相當(dāng)于創(chuàng)建了兩個person對象的副本,但可以在產(chǎn)生之后擁有各自的實例屬性。
ECMAScript5新增了Object.create()方法規(guī)范化了原型式繼承,這個方法接受兩個參數(shù):
一個作為新對象原型的對象(可以是對象或者null)
另一個為新對象定義額外屬性的對象(可選,這個參數(shù)的格式和Object.defineProperties()方法的第二個參數(shù)格式相同,每個屬性都是通過自己的描述符定義的)
??下邊是一個例子:
var person = { //原型式繼承規(guī)范化為create()函數(shù) name: "Nicholas", friends: ["Shelby","Court","Van"] }; var anotherPerson = Object.create(person, { name: { value: "Greg" } }); c(anotherPerson.name); //"Greg"優(yōu)缺點(diǎn)
??如果想讓一個對象與另一個對象保持類似,原型式繼承是很貼切的,但是與原型模式一樣,包含引用類型的值得屬性會共享相應(yīng)的值。
5.寄生式繼承舉個例子寄生式繼承與原型式繼承緊密相關(guān)的一種思路,與寄生構(gòu)造函數(shù)和工廠模式類似,即創(chuàng)建一個僅用于封裝繼承過程的函數(shù),函數(shù)內(nèi)部以某種方式來增強(qiáng)對象,最后再像真的做了所有工作一樣返回對象。
function createAnother(original) { var clone = object(original); //此處用到了原型式繼承 clone.sayHi = function() { c("Hi"); }; return clone; } var person = { //父類實例 name: "Nicholas", friends: ["Shelby","Court","Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi();
??上邊的寄生式繼承用到了原型式繼承,向?qū)崿F(xiàn)繼承的函數(shù)傳入一個父類對象實例,再用原型式繼承得到一個父類對象實例的副本,再給這個副本添加屬性,即增強(qiáng)這個對象,最后返回這個副本對象。由于用到了原型式繼承,這個對象的原型指向傳入的父類對象實例。上邊例子用到的object()函數(shù)(原型式繼承)并不是必須的,任何能夠返回新對象的函數(shù)都適用于寄生式繼承模式。
優(yōu)缺點(diǎn)??寄生式繼承在主要考慮對象而不是創(chuàng)建自定義類型和構(gòu)造函數(shù)時,是十分有用的。但是如果考慮到用寄生式繼承為對象添加函數(shù)等,由于沒有用到原型,做不到函數(shù)復(fù)用,會導(dǎo)致效率降低。
6.寄生組合式繼承??這個名字并不是很貼切,雖然叫寄生組合式繼承,但是和寄生式繼承關(guān)系不是很大,主要是用原型式繼承來實現(xiàn)原型屬性的繼承,用借用構(gòu)造函數(shù)模式繼承實例屬性。寄生組合式繼承和組合繼承的區(qū)別在于:
在繼承原型屬性時,組合繼承用原型鏈繼承了整個父類(通過將父類實例賦值給子類構(gòu)造函數(shù)的原型對象來實現(xiàn)),這使子類中多了一份父類的實例屬性。而寄生組合式繼承用原型式繼承只繼承了父類的原型屬性(把父類構(gòu)造函數(shù)的原型對象用原型式繼承復(fù)制給子類的構(gòu)造函數(shù)的原型對象)。
組合繼承調(diào)用了兩次超類型構(gòu)造函數(shù),寄生組合式繼承調(diào)用了一次。
舉個例子function inheritPrototype(subType, superType) { //寄生式繼承 var prototype = Object.create(superType.prototype); //創(chuàng)建對象 prototype.constructor = subType; //增強(qiáng)對象 subType.prototype = prototype; //指定對象 } function SuperType(name) { this.name = name; this.colors = ["red","blue","green"]; } SuperType.prototype.sayName = function(){ c(this.name); }; function SubType(name, age) { SuperType.call(this, name); //借用構(gòu)造函數(shù) this.age = age; //添加子類獨(dú)有的屬性 } inheritPrototype(SubType, SuperType); //此處調(diào)用實現(xiàn)寄生組合繼承的函數(shù) SubType.prototype.sayAge = function() { //添加子類獨(dú)有原型屬性 c(this.age); }; var son = new SubType("erzi",16); var father = new SuperType("baba"); c(typeof father.sayName); //function c(typeof father.sayAge); //SubType獨(dú)有的方法,返回undefined SubType.prototype.sayName = function() { c("This function has be changed"); } //更改子類的方法只會影響子類,prototype是對象,添加新屬性和更改屬性不會影響父類的prototype father.sayName(); //baba son.sayName(); //This function has be changed SuperType.prototype.sayName = function() { //更改父類的原型屬性 c("This function has be changed"); } father.sayName(); //This function has be changed son.sayName(); //This function has be changed優(yōu)缺點(diǎn)
??這種繼承方式理論上是完美的,但是由于出現(xiàn)的較晚,人們大多數(shù)使用的是組合繼承模式。
??以上就是我對于js繼承的一些理解,如有不足歡迎指出。
參考資料:《JavaScript高級程序設(shè)計》
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/83014.html
摘要:相當(dāng)于在用原型繼承編寫復(fù)雜代碼前理解原型繼承模型十分重要。同時,還要清楚代碼中原型鏈的長度,并在必要時結(jié)束原型鏈,以避免可能存在的性能問題。 js是一門動態(tài)語言,js沒有類的概念,ES6 新增了class 關(guān)鍵字,但只是語法糖,JavaScript 仍舊是基于原型。 至于繼承,js的繼承與java這種傳統(tǒng)的繼承不一樣.js是基于原型鏈的繼承. 在javascript里面,每個對象都有一...
摘要:理解繼承在中對繼承有了更友好的方式??偟膩碚f的的實質(zhì)和以前的繼承方式是一致的,但是有了更好的,更清晰的表現(xiàn)形式。 理解ES6繼承extends 1.在es6中對繼承有了更友好的方式。在下面的繼承中那到底在extends的時候做了什么,super()又是代表什么意思。 class People{ constructor(name, age) { this.name = name; ...
摘要:關(guān)于中面向?qū)ο蟮睦斫饷嫦驅(qū)ο缶幊趟且环N編程思想我們的編程或者學(xué)習(xí)其實是按照類實例來完成的學(xué)習(xí)類的繼承封裝多態(tài)封裝把實現(xiàn)一個功能的代碼封裝到一個函數(shù)中一個類中以后再想實現(xiàn)這個功能,只需要執(zhí)行這個函數(shù)方法即可,不需要再重復(fù)的編寫代碼。 關(guān)于js中面向?qū)ο蟮睦斫?面向?qū)ο缶幊?oop) 它是一種編程思想 (object-oriented programming ), 我們的編程或者學(xué)習(xí)其...
摘要:關(guān)于中面向?qū)ο蟮睦斫饷嫦驅(qū)ο缶幊趟且环N編程思想我們的編程或者學(xué)習(xí)其實是按照類實例來完成的學(xué)習(xí)類的繼承封裝多態(tài)封裝把實現(xiàn)一個功能的代碼封裝到一個函數(shù)中一個類中以后再想實現(xiàn)這個功能,只需要執(zhí)行這個函數(shù)方法即可,不需要再重復(fù)的編寫代碼。 關(guān)于js中面向?qū)ο蟮睦斫?面向?qū)ο缶幊?oop) 它是一種編程思想 (object-oriented programming ), 我們的編程或者學(xué)習(xí)其...
閱讀 3705·2021-10-13 09:40
閱讀 3164·2021-10-09 09:53
閱讀 3563·2021-09-26 09:46
閱讀 1866·2021-09-08 09:36
閱讀 4258·2021-09-02 09:46
閱讀 1327·2019-08-30 15:54
閱讀 3190·2019-08-30 15:44
閱讀 1036·2019-08-30 11:06