摘要:因為操作符創(chuàng)建的對象都繼承自構(gòu)造函數(shù)的屬性。繼承的實現(xiàn)中常用的繼承方式是組合繼承,也就是通過構(gòu)造函數(shù)和原型鏈繼承同時來模擬繼承的實現(xiàn)。
原文發(fā)布在我的博客
我們都知道 JavaScript 是一門基于原型的語言。當(dāng)我們調(diào)用一個對象本身沒有的屬性時,JavaScript 就會從對象的原型對象上去找該屬性,如果原型上也沒有該屬性,那就去找原型的原型,一直找原型鏈的末端也就是 Object.prototype 的原型 null。這種屬性查找的方式我們稱之為原型鏈。
類的實現(xiàn)由于 JavaScript 本身是沒有的類的感念的。所以我們?nèi)绻獙崿F(xiàn)一個類,一般是通過構(gòu)造函數(shù)來模擬類的實現(xiàn):
function Person(name,age){ //實現(xiàn)一個類 this.name = name; this.age = age; } var you = new Person("you",23); //通過 new 來新建實例
首先新建一個 Person 的構(gòu)造函數(shù),為了和一般的函數(shù)區(qū)別,我們會使用 CamelCase 方式來命名構(gòu)造函數(shù)。
然后通過 new 操作符來創(chuàng)建實例,new 操作符其實干了這么幾件事:
創(chuàng)建一個繼承自 Person.prototype 的新對象
構(gòu)造函數(shù) Person 執(zhí)行時,相應(yīng)的參數(shù)傳入,同時上下文被指定為這個新建的對象。
如果構(gòu)造函數(shù)返回了一個對象,那么這個對象會取代 new 的結(jié)果。如果構(gòu)造函數(shù)返回的不是對象,則會忽略這個返回值。
返回值不是對象 function Person(name){ this.name = name; return "person" } var you = new Person("you"); // you 的值: Person {name: "you"} 返回值是對象 function Person(name){ this.name = name; return [1,2,3] } var you = new Person("you"); // you的值: [1,2,3]
如果類的實例需要共享類的方法,那么就需要給構(gòu)造函數(shù)的 prototype 屬性添加方法了。因為 new 操作符創(chuàng)建的對象都繼承自構(gòu)造函數(shù)的 prototype 屬性。他們可以共享定義在類 prototype 上的方法和屬性。
function Person(name,age){ this.name = name; this.age = age; } Person.prototype = { sayName: function(){ console.log("My name is",this.name); } } var you = new Person("you",23); var me = new Person("me",23); you.sayName() // My name is you. me.sayName() // My name is me.繼承的實現(xiàn)
JavaScript 中常用的繼承方式是組合繼承,也就是通過構(gòu)造函數(shù)和原型鏈繼承同時來模擬繼承的實現(xiàn)。
//Person 構(gòu)造函數(shù)如上 function Student(name,age,clas){ Person.call(this,name,age) this.clas = clas; } Student.prototype = Object.create(Person.prototype); // Mark 1 Student.constructor = Student; //如果不指明,則 Student 會找不到 constructor Student.prototype.study = function(){ console.log("I study in class",this.clas) }; var liming = new Student("liming",23,7); liming instanceof Person //true liming instanceof Student //true liming.sayName(); // My name is liming liming.study(); // I study in class 7
代碼中 Mark 1 用到了 Object.create 方法。這個是 ES5 中新增的方法,用來創(chuàng)建一個擁有指定原型的對象。如果環(huán)境不兼容,可以用下面這個 Polyfill 來實現(xiàn)(僅實現(xiàn)第一個參數(shù))。
if(!Object.create){ Object.create = function(obj){ function F(){}; F.prototype = obj; return new F(); } }
其實就是把 obj 賦值給臨時函數(shù) F ,然后返回一個 F 的實例。這樣通過代碼 Mark 1 Student 就得到了 Person.prototype 上的所有屬性。有人會問了,那么為什么不干脆把 Person.prototype 直接賦值給 Student.prototype 呢?
是的,直接賦值是可以達到子類共享父類 prototype 的目的,但是它破壞了原型鏈。即:子類和父類共用了同一個 prototype,這樣當(dāng)某一個子類修改 prototype 的時候,其實同時也修改了父類的 prototype,那么就會影響到所有基于這個父類創(chuàng)建的子類,這并不是我們想要的結(jié)果。看例子:
//Person 同上 //Student 同上 Student.prototype = Person.prototype; Student.prototype.sayName = function(){ console.log("My name is",this.name,"my class is",this.clas) } var liming = new Student("liming",23,7) liming.sayName() //My name is liming,my class is 7; //另一個子類 function Employee(name,age,salary){ Person.call(name,age); this.salary = salary; } Employee.prototype = Person.prototype; var emp = new Employee("emp",23,10000); emp.sayName() //Mark 2
你們猜 Mark 2 會輸出什么?
我們期望的 Mark 2 應(yīng)該會輸出 "My name is emp". 但實際上報錯,為什么呢?因為我們改寫 Student.prototype 的時候,也同時修改了 Person.prototype,最終導(dǎo)致 emp 繼承的 prototype 是我們所不期望的,它的 sayName 方法是 My name is",this.name,"my class is",this.clas,這樣自然是會報錯的。
ES6 的繼承隨著 ECMAScript 6 的發(fā)布,我們有了新的方法來實現(xiàn)繼承。也就是通過 class 關(guān)鍵字。
類的實現(xiàn)class Person { constructor(name,age){ this.name = name; this.age = age; } sayHello(){ console.log(`My name is ${this.name},i"m ${this.age} years old`) } } var you = new Person("you",23); you.sayHello() //My name is you,i"m 23 years old.繼承
ES6 里面的繼承也很方便,通過 extends 關(guān)鍵字來實現(xiàn)。
class Student extends Person{ constructor(name,age,cla){ super(name,age); this.class = cla; } study(){ console.log(`I"m study in class ${this.class}`) } } var liming = new Student("liming",23,7) liming.study() // I"m study in class 7.
這個繼承相比上面的 ES5 里面實現(xiàn)的繼承要方便了很多,但其實原理是一樣的,提供的這些關(guān)鍵字方法只是語法糖而已,并沒有改變 Js 是基于原型這么一個事實。不過 extends 這樣實現(xiàn)的繼承有一個限制,就是不能定義屬性,只能定義方法。要新添屬性,還是得通過修改 prototype 來達到目的。
Student.prototype.teacher = "Mr.Li" var liming = new Student("liming",23,7) var hanmeimei = new Student("hanmeimei",23,7) liming.teacher //Mr.Li hanmeimei.teacher //Mr.Li靜態(tài)方法
ES6 還提供了 static 關(guān)鍵字,來實現(xiàn)靜態(tài)方法。靜態(tài)方法可以繼承,但只能由類本身調(diào)用,不能被實例調(diào)用。
class Person{ constructor(name,age){ this.name = name; this.age = age; } static say(){ console.log("Static") } } class Student extends Person{} Person.say() // Static Student.say() // Static var you = new Person("you",23); you.say() // TypeError: liming.say is not a function
可以看到,在實例上調(diào)用的時候會直接報錯。
Super關(guān)鍵字在子類中可以通過 super 來調(diào)用父類,根據(jù)調(diào)用位置的不同,行為也不同。在 constructor 中調(diào)用,相當(dāng)于調(diào)用父類的 constructor 方法,而在普通方法里面調(diào)用則相當(dāng)與調(diào)用父類本身。
class Person { constructor(name,age){ this.name = name; this.age = age; } sayHello(){ console.log(`My name is ${this.name},i"m ${this.age} years old`) } } class Student extends Person{ constructor(name,age,cla){ super(name,age); // 必須在子類調(diào)用 this 前執(zhí)行,調(diào)用了父類的 constructor this.class = cla; } sayHello(){ super.sayHello; // 調(diào)用父類方法 console.log("Student say") } } var liming = new Student("liming",23,7); liming.say() // My name is liming,i"m 23 years old. Student say.總結(jié)
至此,我們可以看到:在 ES6 發(fā)布以后,JavaScript 中實現(xiàn)繼承有了一個標(biāo)準(zhǔn)的方法。雖然它們只是語法糖,背后的本質(zhì)還是通過原型鏈以及構(gòu)造函數(shù)實現(xiàn)的,不過在寫法上更易于我們理解而且也更加清晰。
參考:
JavaScript繼承方式詳解
JavaScript 原型系統(tǒng)的變遷,以及 ES6 class
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/79034.html
摘要:屬性每個函數(shù)默認(rèn)有屬性方法返回的函數(shù)除外,其值為構(gòu)造函數(shù)創(chuàng)建對象繼承的對象。其思路使用原型鏈實現(xiàn)原型屬性和方法的繼承通過借用構(gòu)造函數(shù)實現(xiàn)實例屬性繼承。 1 類和模塊 每個獨立的JavaScript對象都是一個屬性的集合,獨立對象間沒有任何關(guān)系 ES5中的類是基于原型繼承實現(xiàn)的:如果兩個對象從同一個原型對象繼承屬性,稱兩個對象為同一個類的實例。r instanceof Range.pr...
摘要:使用新的易用的類定義,歸根結(jié)底也是要創(chuàng)建構(gòu)造函數(shù)和修改原型。首先,它把構(gòu)造函數(shù)當(dāng)成單獨的函數(shù)且包含類屬性集。該節(jié)點還儲存了指向父類的指針引用,該父類也并儲存了構(gòu)造函數(shù),屬性集和及父類引用,依次類推。 原文請查閱這里,略有刪減,本文采用知識共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第...
摘要:使用新的易用的類定義,歸根結(jié)底也是要創(chuàng)建構(gòu)造函數(shù)和修改原型。首先,它把構(gòu)造函數(shù)當(dāng)成單獨的函數(shù)且包含類屬性集。該節(jié)點還儲存了指向父類的指針引用,該父類也并儲存了構(gòu)造函數(shù),屬性集和及父類引用,依次類推。 原文請查閱這里,略有刪減,本文采用知識共享署名 4.0 國際許可協(xié)議共享,BY Troland。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第...
摘要:下面是用實現(xiàn)轉(zhuǎn)成抽象語法樹如下還支持繼承以下是轉(zhuǎn)換結(jié)果最終的結(jié)果還是代碼,其中包含庫中的一些函數(shù)??梢允褂眯碌囊子谑褂玫念惗x,但是它仍然會創(chuàng)建構(gòu)造函數(shù)和分配原型。 這是專門探索 JavaScript 及其所構(gòu)建的組件的系列文章的第 15 篇。 想閱讀更多優(yōu)質(zhì)文章請猛戳GitHub博客,一年百來篇優(yōu)質(zhì)文章等著你! 如果你錯過了前面的章節(jié),可以在這里找到它們: JavaScript 是...
閱讀 2187·2023-04-25 17:23
閱讀 3002·2021-11-17 09:33
閱讀 2594·2021-08-21 14:09
閱讀 3752·2019-08-30 15:56
閱讀 2661·2019-08-30 15:54
閱讀 1678·2019-08-30 15:53
閱讀 2197·2019-08-29 13:53
閱讀 1209·2019-08-29 12:31