Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
天來講解javascript中實現繼承的幾種方式,有興趣的小伙伴們可以來看看,若有錯誤之處,歡迎指出!
一、構造函數模式
每個方法都要在每個實例上重新創建一遍。在前面的例子中,person1和person2都有一個名為sayName()的方法,但那兩個方法不是同一個Function的實例。
二、原型模式
無論什么時候,只要創建了一個新函數,就會根據一組特定的規則為該函數創建一個prototype屬性。在默認情況下,所有prototype屬性都會自動獲得一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數的指針。Person.prototype.constructor指向Person。創建了自定義的構造函數之后,其原型屬性默認只會取得constructor屬性;至于其他方法,則都是從object繼承而來的。當調用構造函數創建一個新實例后,該實例的內部將包含一個指針(內部屬性),指向構造函數的原型屬性。在很多實現中,這個內部屬性的名字是_proto_。不過,要明確的真正重要一點,就是這個鏈接存在于實例與構造函數的原型屬性之間,而不是存在于實例與構造函數之間。
三、原型鏈
原型鏈:ECMAScript中描述了原型鏈的概念,并將原型鏈作為實現繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。簡單回顧一下構造函數、原型、實例的關系:每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。那么,假如我們讓原型對象等于另一個類型的實例,結果會怎么樣呢?顯然,此時的原型對象將包含一個指向另一個原型的指針,相應地,另一個原型中也包含著一個指向另一個構造函數的指針。假如另一個原型又是另一個類型的實例,那么上述關系依然成立,如此層層遞進,就構成了實例與原型的鏈條。這就是所謂原型鏈的基本概念。
四、組合繼承
組合繼承,指的是將原型鏈和借用構造函數的技術組合到一塊,從而發揮二者之長的一種繼承模式。其背后的思路是使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。這樣,既通過在原型上定義方法實現了函數復用,又能夠保證每個實例都有它自己的屬性。
于有基于類的語言經驗 (如 Java 或 C++) 的開發人員來說,JavaScript 有點令人困惑,因為它是動態的,并且本身不提供一個class實現。(在 ES2015/ES6 中引入了class關鍵字,但只是語法糖,JavaScript 仍然是基于原型的)。
JavaScript 只有一種繼承結構:對象。每個實例對象(object )都有一個私有屬性(稱之為[[prototype]])指向它的原型對象(prototype)。該原型對象也有一個自己的原型對象 ,層層向上直到一個對象的原型對象為 null。根據定義,null 沒有原型,并作為這個原型鏈中的最后一個環節。
幾乎所有 JavaScript 中的對象都是位于原型鏈頂端的Object的實例。
遵循ECMAScript標準,someObject.[[Prototype]] 符號是用于指向 someObject的原型。從 ECMAScript 6 開始,[[Prototype]] 可以通過Object.getPrototypeOf()和Object.setPrototypeOf()訪問器來訪問。這個等同于 JavaScript 的非標準但許多瀏覽器實現的屬性 proto。
但它不應該與構造函數 func 的 prototype 屬性相混淆。被構造函數創建的實例對象的 [[prototype]] 指向 func 的 prototype 屬性。Object.prototype 屬性表示Object的原型對象。
// 讓我們從一個自身擁有屬性a和b的函數里創建一個對象o: let f=function () { this.a=1; this.b=2; } /* 你要這么寫也沒區別 function f(){ this.a=1; this.b=2; } */ let o=new f(); // {a: 1, b: 2} // 在f函數的原型上定義屬性 f.prototype.b=3; f.prototype.c=4; //不要在f函數的原型上直接定義 f.prototype={b:3,c:4};這樣會直接打破原型鏈 // o.[[Prototype]] 有屬性 b 和 c (其實就是o.__proto__或者o.constructor.prototype) // o.[[Prototype]].[[Prototype]] 是 Object.prototye. // 最后o.[[Prototype]].[[Prototype]].[[Prototype]]是null // 這就是原型鏈的末尾,即 null, // 根據定義,null 沒有[[Prototype]]. // 綜上,整個原型鏈如下: // {a:1, b:2} ---> {b:3, c:4} ---> Object.prototye---> null console.log(o.a); // 1 // a是o的自身屬性嗎?是的,該屬性的值為1 console.log(o.b); // 2 // b是o的自身屬性嗎?是的,該屬性的值為2 // 原型上也有一個'b'屬性,但是它不會被訪問到.這種情況稱為"屬性遮蔽 (property shadowing)" console.log(o.c); // 4 // c是o的自身屬性嗎?不是,那看看原型上有沒有 // c是o.[[Prototype]]的屬性嗎?是的,該屬性的值為4 console.log(o.d); // undefined // d是o的自身屬性嗎?不是,那看看原型上有沒有 // d是o.[[Prototype]]的屬性嗎?不是,那看看它的原型上有沒有 // o.[[Prototype]].[[Prototype]] 為 null,停止搜索 // 沒有d屬性,返回undefined
當繼承的函數被調用時,this 指向的是當前繼承的對象,而不是繼承的函數所在的原型對象
var o={ a: 2, m: function(){ return this.a + 1; } }; console.log(o.m()); // 3 // 當調用 o.m 時,'this'指向了o. var p=Object.create(o); // p是一個繼承自 o 的對象 p.a=4; // 創建 p 的自身屬性 a console.log(p.m()); // 5 // 調用 p.m 時, 'this'指向 p. // 又因為 p 繼承 o 的 m 函數 // 此時的'this.a' 即 p.a,即 p 的自身屬性 'a'
使用不同的方法來創建對象和生成原型鏈
var o={a: 1}; // o 這個對象繼承了Object.prototype上面的所有屬性 // o 自身沒有名為 hasOwnProperty 的屬性 // hasOwnProperty 是 Object.prototype 的屬性 // 因此 o 繼承了 Object.prototype 的 hasOwnProperty // Object.prototype 的原型為 null // 原型鏈如下: // o ---> Object.prototype ---> null var a=["yo", "whadup", "?"]; // 數組都繼承于 Array.prototype // (Array.prototype 中包含 indexOf, forEach等方法) // 原型鏈如下: // a ---> Array.prototype ---> Object.prototype ---> null function f(){ return 2; } // 函數都繼承于Function.prototype // (Function.prototype 中包含 call, bind等方法) // 原型鏈如下: // f ---> Function.prototype ---> Object.prototype ---> null
在 JavaScript 中,構造器其實就是一個普通的函數。當使用 new 操作符 來作用這個函數時,它就可以被稱為構造方法(構造函數)。
function Graph() { this.vertices=[]; this.edges=[]; } Graph.prototype={ addVertex: function(v){ this.vertices.push(v); } }; var g=new Graph(); // g是生成的對象,他的自身屬性有'vertices'和'edges'. // 在g被實例化時,g.[[Prototype]]指向了Graph.prototype.
ECMAScript 5 中引入了一個新方法:Object.create()。可以調用這個方法來創建一個新對象。新對象的原型就是調用 create 方法時傳入的第一個參數:
var a={a: 1}; // a ---> Object.prototype ---> null var b=Object.create(a); // b ---> a ---> Object.prototype ---> null console.log(b.a); // 1 (繼承而來) var c=Object.create(b); // c ---> b ---> a ---> Object.prototype ---> null var d=Object.create(null); // d ---> null console.log(d.hasOwnProperty); // undefined, 因為d沒有繼承Object.prototype
ECMAScript6 引入了一套新的關鍵字用來實現 class。使用基于類語言的開發人員會對這些結構感到熟悉,但它們是不同的。JavaScript 仍然基于原型。這些新的關鍵字包括 class, constructor,static,extends 和 super。
上面的章節中我們看到了JavaScript的對象模型是基于原型實現的,特點是簡單,缺點是理解起來比傳統的類-實例模型要困難,最大的缺點是繼承的實現需要編寫大量代碼,并且需要正確實現原型鏈。
有沒有更簡單的寫法?有!
新的關鍵字class從ES6開始正式被引入到JavaScript中。class的目的就是讓定義類更簡單。
我們先回顧用函數實現Student的方法:
function Student(name) { this.name=name; } Student.prototype.hello=function () { alert('Hello, ' + this.name + '!'); }
如果用新的class關鍵字來編寫Student,可以這樣寫:
class Student { constructor(name) { this.name=name; } hello() { alert('Hello, ' + this.name + '!'); } }
比較一下就可以發現,class的定義包含了構造函數constructor和定義在原型對象上的函數hello()(注意沒有function關鍵字),這樣就避免了Student.prototype.hello=function () {...}這樣分散的代碼。
最后,創建一個Student對象代碼和前面章節完全一樣:
var xiaoming=new Student('小明'); xiaoming.hello();
用class定義對象的另一個巨大的好處是繼承更方便了。想一想我們從Student派生一個PrimaryStudent需要編寫的代碼量。現在,原型繼承的中間對象,原型對象的構造函數等等都不需要考慮了,直接通過extends來實現:
class PrimaryStudent extends Student { constructor(name, grade) { super(name); // 記得用super調用父類的構造方法! this.grade=grade; } myGrade() { alert('I am at grade ' + this.grade); } }
注意PrimaryStudent的定義也是class關鍵字實現的,而extends則表示原型鏈對象來自Student。子類的構造函數可能會與父類不太相同,例如,PrimaryStudent需要name和grade兩個參數,并且需要通過super(name)來調用父類的構造函數,否則父類的name屬性無法正常初始化。
PrimaryStudent已經自動獲得了父類Student的hello方法,我們又在子類中定義了新的myGrade方法。
ES6引入的class和原有的JavaScript原型繼承有什么區別呢?實際上它們沒有任何區別,class的作用就是讓JavaScript引擎去實現原來需要我們自己編寫的原型鏈代碼。簡而言之,用class的好處就是極大地簡化了原型鏈代碼。
你一定會問,class這么好用,能不能現在就用上?
現在用還早了點,因為不是所有的主流瀏覽器都支持ES6的class。如果一定要現在就用上,就需要一個工具把class代碼轉換為傳統的prototype代碼,可以試試Babel這個工具。
需要瀏覽器支持ES6的class,如果遇到SyntaxError,則說明瀏覽器不支持class語法,請換一個最新的瀏覽器試試。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。