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