摘要:舉例說明組合繼承組合繼承利用原型鏈借用構造函數的模式解決了原型鏈繼承和類式繼承的問題。示例組合式繼承是比較常用的一種繼承方法,其背后的思路是使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。
對js原型和繼承的理解一直處于“不懂-懂-不懂-懂-不懂。。。”的無限循環之中,本來打算只是簡單總結下js繼承方式,可看了些網上的資料后,發現又不懂繼承了。。。這篇文章只是一個閱讀筆記,總結了我所看到的js文章,參考見文末。
一、原型與構造函數 1、prototype屬性? ? ? ?Js所有的函數都有一個prototype屬性,這個屬性引用了一個對象,即原型對象,也簡稱原型。這個函數包括構造函數和普通函數,我們講的更多是構造函數的原型,但是也不能否定普通函數也有原型。譬如普通函數:
function F(){}; alert(F.prototype instanceof Object);//true
默認情況下,原型對象也會獲得一個constructor屬性,該屬性包含一個指針,指向prototype屬性所在的函數
Person.prototype.constructor === Person
在面向對象的語言中,我們使用類來創建一個自定義對象。然而js中所有事物都是對象,那么用什么辦法來創建自定義對象呢?這就需要用到js的原型:
我們可以簡單的把prototype看做是一個模版,新創建的自定義對象都是這個模版(prototype)的一個拷貝 (實際上不是拷貝而是鏈接,只不過這種鏈接是不可見,新實例化的對象內部有一個看不見的__Proto__指針,指向原型對象)。
_proto_是對[[propertyName]]屬性的實現(是只能對象可以擁有的屬性,并且是不可訪問的內部屬性),它指向對象的構造函數的原型對象。如下:
function Person(name) { this.name = name; } var p1 = new Person(); p1._proto_ === Person.prototype;//true
__proto__只是瀏覽器的私有實現,目前ECMAScript標準實現方法是Object.getPrototypeOf(object),如下:
function Person(name) { this.name = name; } var p1 = new Person(); Object.getPrototypeOf(p1) === Person.prototype;//true2、通過構造函數實例化對象過程
? ? ? ?構造函數,也即構造對象。首先了解下通過構造函數實例化對象的過程
function A(x){ this.x=x; } var obj = new A(1);
實例化obj對象有三步:
(1).創建obj對象:obj=new Object();
(2).將obj的內部__proto__指向構造他的函數A的prototype,同時obj.constructor===A.prototype.constructor(這個是永遠成立的,即使A.prototype不再指向原來的A原型,也就是說:類的實例對象的constructor屬性永遠指向"構造函數"的prototype.constructor),從而使得obj.constructor.prototype指向A.prototype(obj.constructor.prototype===A.prototype,當A.prototype改變時則不成立,下文有遇到)obj.constructor.prototype與的內部_proto_是兩碼事,實例化對象時用的是_proto_,obj是沒有prototype屬性的,但是有內部的__proto__,通過__proto__來取得原型鏈上的原型屬性和原型方法,FireFox公開了__proto__,可以在FireFox中alert(obj.__proto__);
(3).將obj作為this去調用構造函數A,從而設置成員(即對象屬性和對象方法)并初始化。
當這3步完成,這個obj對象就與構造函數A再無聯系,這個時候即使構造函數A再加任何成員,都不再影響已經實例化的obj對象了。此時,obj對象具有了x屬性,同時具有了構造函數A的原型對象的所有成員,當然,此時該原型對象是沒有成員的。
3、原型對象原型對象初始是空的,也就是沒有一個成員(即原型屬性和原型方法)。可以通過如下方法驗證原型對象具有多少成員。
function A(x){ this.x=x; } var num = 0; for(o in A.prototype){ alert(o);// alert 原型屬性名字 num++; } alert(num);//0
但是,一旦定義了原型屬性或原型方法,則所有通過該構造函數實例化出來的所有對象,都繼承了這些原型屬性和原型方法,這是通過內部的_proto_鏈來實現的。
例如:
A.prototype.say=function(){alert("haha");}
那所有的A的對象都具有了say方法,這個原型對象的say方法是唯一的副本給大家共享的,而不是每一個對象都有關于say方法的一個副本。
二、原型與繼承由于js不像java那樣是真正面向對象的語言,js是基于對象的,它沒有類的概念。所以,要想實現繼承,可以通過構造函數和原型的方式模擬實現類的功能。
js里常用的如下兩種繼承方式:
原型鏈繼承(對象間的繼承) 類式繼承(構造函數間的繼承)
以下通過實例詳細介紹這兩種繼承方式的原理
1、類式繼承詳解類式繼承是在子類型構造函數的內部調用超類型的構造函數。
1 function A(x){ 2 this.x = x; 3 } 4 function B(x,y){ 5 this.tempObj = A; 6 this.tempObj(x); 7 delete this.tempObj; 8 this.y=y; 9 }
解釋:
? ? ? ?第5、6、7行:創建臨時屬性tmpObj引用構造函數A,然后在B內部執行(注意,這里執行函數的時候并沒有用new),執行完后刪除。當在B內部執行了this.x=x后(這里的this是B的對象),B當然就擁有了x屬性,當然B的x屬性和A的x屬性兩者是獨立,所以并不能算嚴格的繼承。第5、6、7行有更簡單的實現,就是通過call(apply)方法:A.call(this,x);
? ? ? ?這兩種方法都有將this傳遞到A的執行里,this指向的是B的對象,這就是為什么不直接A(x)的原因。這種繼承方式即是類繼承(js沒有類,這里只是指構造函數),雖然繼承了A構造對象的所有屬性方法,但是不能繼承A的原型對象的成員。而要實現這個目的,就是在此基礎上再添加原型繼承。
原型式繼承是借助已有的對象創建新的對象,將子類的原型指向父類,就相當于加入了父類這條原型鏈。
1 function A(x){ 2 this.x = x; 3 } 4 A.prototype.a = "a"; 5 function B(x,y){ 6 A.call(this,x); 7 this.y = y; 8 } 9 B.prototype.b1 = function(){ 10 alert("b1"); 11 } 12 B.prototype = new A(); 13 B.prototype.b2 = function(){ 14 alert("b2"); 15 } 16 B.prototype.constructor = B; 17 var obj = new B(1,3);
解釋:
? ? ? ?這個例子講的就是B繼承A。第7行類繼承:A.call(this.x);上面已講過。實現原型繼承的是第12行:B.prototype = new A();
? ? ? ?就是說把B的原型指向了A的1個實例對象,這個實例對象具有x屬性,為undefined,還具有a屬性,值為"a"。所以B原型也具有了這2個屬性(或者說,B和A建立了原型鏈,B是A的下級)。而因為方才的類繼承,B的實例對象也具有了x屬性,也就是說obj對象有2個同名的x屬性,此時原型屬性x要讓位于實例對象屬性x,所以obj.x是1,而非undefined。第13行又定義了原型方法b2,所以B原型也具有了b2。雖然第9~11行設置了原型方法b1,但是你會發現第12行執行后,B原型不再具有b1方法,也就是obj.b1是undefined。因為第12行使得B原型指向改變,原來具有b1的原型對象被拋棄,自然就沒有b1了。
? ? ? ?第12行執行完后,B原型(B.prototype)指向了A的實例對象,而A的實例對象的構造器是構造函數A,所以B.prototype.constructor就是構造對象A了(換句話說,A構造了B的原型)。alert(B.prototype.constructor)出來后就是"function A(x){...}" 。同樣地,obj.constructor也是A構造對象,alert(obj.constructor)出來后就是"function A(x){...}" ,也就是說B.prototype.constructor===obj.constructor(true),但是B.prototype===obj.constructor.prototype(false),因為前者是B的原型,具有成員:x,a,b2,后者是A的原型,具有成員:a。如何修正這個問題呢,就在第16行,將B原型的構造器重新指向了B構造函數,那么B.prototype===obj.constructor.prototype(true),都具有成員:x,a,b2。
? ? ? ?如果沒有第16行,那是不是obj = new B(1,3)會去調用A構造函數實例化呢?答案是否定的,你會發現obj.y=3,所以仍然是調用的B構造函數實例化的。雖然obj.constructor===A(true),但是對于new B()的行為來說,執行了上面所說的通過構造函數創建實例對象的3個步驟,第一步,創建空對象;第二步,obj.__proto__ === B.prototype,B.prototype是具有x,a,b2成員的,obj.constructor指向了B.prototype.constructor,即構造函數A;第三步,調用的構造函數B去設置和初始化成員,具有了屬性x,y。雖然不加16行不影響obj的屬性,但如上一段說,卻影響obj.constructor和obj.constructor.prototype。所以在使用了原型繼承后,要進行修正的操作。
? ? ? ?關于第12、16行,總言之,第12行使得B原型繼承了A的原型對象的所有成員,但是也使得B的實例對象的構造器的原型指向了A原型,所以要通過第16行修正這個缺陷。
為了讓子類繼承父類的屬性(也包括方法),首先需要定義一個構造函數。然后,將父類的新實例賦值給構造函數的原型。
function Parent(){ this.name = "mike"; } function Child(){ this.age = 12; } Child.prototype = new Parent();//Child繼承Parent,通過原型,形成鏈條 var test = new Child(); alert(test.age); alert(test.name);//得到被繼承的屬性 //繼續原型鏈繼承 function Brother(){ //brother構造 this.weight = 60; } Brother.prototype = new Child();//繼續原型鏈繼承 var brother = new Brother(); alert(brother.name);//繼承了Parent和Child,彈出mike alert(brother.age);//彈出12
以上原型鏈繼承還缺少一環,那就是Object,所有的構造函數都繼承自Object。而繼承Object是自動完成的,并不需要我們自己手動繼承,那么他們的從屬關系可以使用操作符instanceof和函數isPrototypeOf()判斷,如下:
alert(test instanceof Parent);//true alert(test instanceof Child);//true alert(brother instanceof Parent);//true alert(brother instanceof Child);//true
alert(Parent.prototype.isPrototypeOf(test));//true alert(Child.prototype.isPrototypeOf(test));//true alert(Parent.prototype.isPrototypeOf(brother));//true alert(Child.prototype.isPrototypeof(brother));//true
問題字面量重寫原型會中斷關系,使用引用類型的原型,并且子類型還無法給超類型傳遞參數。
偽類解決引用共享和超類型無法傳參的問題,我們可以采用“借用構造函數”技術
2、借用構造函數繼承(類式繼承,這種方式可以實現多繼承)示例1
function Parent(firstname) { this.fname=firstname; this.age=40; this.sayAge=function() { console.log(this.age); } } function Child(firstname) { this.parent=Parent; this.parent(firstname); delete this.parent;//以上三行也可以用call和apply函數改寫 this.saySomeThing=function() { console.log(this.fname); this.sayAge(); } } var mychild=new Child("李"); mychild.saySomeThing();
示例2:用call函數實現
function Parent(firstname) { this.fname=firstname; this.age=40; this.sayAge=function() { console.log(this.age); } } function Child(firstname) { this.saySomeThing=function() { console.log(this.fname); this.sayAge(); } this.getName=function() { return firstname; } } var child=new Child("張"); Parent.call(child,child.getName()); child.saySomeThing();
示例3:用apply函數實現
function Parent(firstname) { this.fname=firstname; this.age=40; this.sayAge=function() { console.log(this.age); } } function Child(firstname) { this.saySomeThing=function() { console.log(this.fname); this.sayAge(); } this.getName=function() { return firstname; } } var child=new Child("張"); Parent.apply(child,[child.getName()]); child.saySomeThing();
問題:類式繼承沒有原型,子類型只是繼承了父類型構造對象的屬性和方法,沒有繼承父類型原型對象的成員。
舉例說明:
function Father(x){ this.x=x; } Father.prototype.say = function(){ alert(this.x); } function Child(x,y){ this.y=y; this.tempObj=Father; this.tempObj(x); delete this.tempObj; } var obj = new Child(1,2); alert(obj.x);//1 alert(obj.y);//2 alert(obj.say);//undefined3、組合繼承
組合繼承利用原型鏈+借用構造函數的模式解決了原型鏈繼承和類式繼承的問題。
示例
function Parent(age){ this.name = ["mike","jack","smith"]; this.age = age; } Parent.prototype.say = function(){ return this.name + "are both" + this.age; } function Child(age){ Parent.call(this,age); } Child.prototype = new Parent(); var test = new Child(1); alert(test.say());
組合式繼承是比較常用的一種繼承方法,其背后的思路是 使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。這樣,既通過在原型上定義方法實現了函數復用,又保證每個實例都有它自己的屬性。
問題:組合繼承的父類型在使用過程中會被調用兩次;一次是創建子類型的時候,另一次是在子類型構造函數的內部。
舉例說明如下:
function Parent(name){ this.name = name; this.arr = ["哥哥","妹妹","父母"]; } Parent.prototype.run = function () { return this.name; }; function Child(name,age){ Parent.call(this,age);//第二次調用 this.age = age; } Child.prototype = new Parent();//第一次調用
這兩次調用一次是通過類式繼承實現的,另一次是通過原型鏈繼承實現的,在上面原型鏈繼承詳解中有介紹??梢杂孟旅娼榻B的寄生組合繼承解決該問題。
4、原型式繼承這種繼承借助原型并基于已有的對象創建新對象。
1 function obj(o){ 2 function F(){} 3 F.prototype = o; 4 return new F(); 5 } 6 var person = { 7 name : "ss", 8 arr : ["hh","kk","ll"] 9 } 10 var b1 = obj(person); 11 alert(b1.name);//ss 12 b1.name = "join"; 13 alert(b1.name);//join 14 alert(b1.arr);//hh,kk,ll 15 b1.arr.push("gg"); 16 alert(b1.arr);//hh,kk,ll,gg 17 var b2 = obj(person); 18 alert(b2.name);//ss 19 alert(b2.arr);//hh,kk,ll,gg
疑問:(求解答)
這里的b1和b2會共享原型對象person的屬性,那么在12行通過b1.name="join"修改了原型對象person的name屬性后,為什么并沒有影響到b2.name,在18行仍然輸出ss???
而在15行通過b1.arr.push("gg")修改了原型對象person的arr屬性后,卻影響到了b2.arr,在19行的輸出多了gg
這種繼承方式是把原型式+工廠模式結合起來,目的是為了封裝創建的過程。
function obj(o){ function F(){} F.prototype = o; return new F() } function create(o){ var f = obj(o); f.say = function() { return this.arr } return f }6、寄生組合繼承
寄生組合繼承解決了組合繼承中父類型兩次調用問題
function obj(o){ function F(){} F.prototype = o; return new F() } function create(parent,child){//通過這個函數,實現了原型鏈繼承,但child只含有parent原型鏈中的屬性 var f = obj(parent.prototype); f.constructor = child; child.prototype = f; } function Parent(name){ this.name = name; this.arr = ["heheh","guagua","jiji"]; } Parent.prototype.say = function(){ return this.name } function Child(name,age){ Parent.call(this,name);//類式繼承,這里繼承parent構造函數中定義的屬性 this.age = age; } create(Parent,Child); var test = new Child("trigkit4",21); test.arr.push("nephew"); alert(test.arr);// alert(test.run());//只共享了方法 var test2 = new Child("jack",22); alert(test2.arr);//引用問題解決參考
1.js實現繼承的5中方式
2.ES6 Class
3.js繼承方式詳解
4.前段開發必須知道的js(一)原型和繼承
5.細說 Javascript 對象篇(二) : 原型對象
6.javascript原型概念(一)
7.Javascript基于 ‘__proto__’ 的原型鏈
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/87761.html
摘要:可以通過構造函數和原型的方式模擬實現類的功能。原型式繼承與類式繼承類式繼承是在子類型構造函數的內部調用超類型的構造函數。寄生式繼承這種繼承方式是把原型式工廠模式結合起來,目的是為了封裝創建的過程。 js繼承的概念 js里常用的如下兩種繼承方式: 原型鏈繼承(對象間的繼承) 類式繼承(構造函數間的繼承) 由于js不像java那樣是真正面向對象的語言,js是基于對象的,它沒有類的概念。...
摘要:此用來定義通過構造器構造出來的對象的原型,構造器內部的代碼用來給對象初始化。 對象繼承 VS 類繼承 在 class-based 的面向對象的世界里,要出現對象,必須先有類。類之間可以繼承,類再使用 new 操作創建出實體,父子對象之間的繼承體現在父類和子類上。你不能說 對象 a 繼承了對象 b,只能說 class A 繼承了 class B,然后他們各自有一個實例a、b。 JS中實現...
摘要:接下來我們來聊一下的原型鏈繼承和類。組合繼承為了復用方法,我們使用組合繼承的方式,即利用構造函數繼承屬性,利用原型鏈繼承方法,融合它們的優點,避免缺陷,成為中最常用的繼承。 JavaScript是一門面向對象的設計語言,在JS里除了null和undefined,其余一切皆為對象。其中Array/Function/Date/RegExp是Object對象的特殊實例實現,Boolean/N...
摘要:除此之外,在超類型的原型中定義的方法,對子類型而言也是不可兼得,結果所有類型都只能用構造函數模式。創建對象增強對象指定對象繼承屬性這個例子的高效率體現在它只調用了一次構造函數。 1、原型鏈 原型鏈的基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。構造函數、原型和實例的關系:每個構造函數都有一個原型對象;原型對象都包含著一個指向構造函數的指針;實例都包含一個指向原型對象的...
閱讀 3316·2023-04-25 19:42
閱讀 1334·2021-11-23 10:11
閱讀 2271·2021-11-16 11:51
閱讀 1596·2019-08-30 15:54
閱讀 2040·2019-08-29 18:44
閱讀 1619·2019-08-23 18:24
閱讀 496·2019-08-23 17:52
閱讀 1770·2019-08-23 15:33