javascript
JavaScript大杂烩4 - 理解JavaScript对象的继承机制
JavaScript是單根的完全面向對象的語言
JavaScript是單根的面向對象語言,它只有單一的根Object,所有的其他對象都是直接或者間接的從Object對象繼承。而在JavaScript的眾多討論中,JavaScript的繼承機制也是最讓人津津樂道的,在了解它的機制之前,先讓我們溫習一下繼承的本質。
?
繼承的本質
繼承的本質是重用,就是這么簡單,重用是軟件編程技術向前發展最原始的動力。從語法上來講,繼承就是"D是B"的描述,其中B是基類,描述共性,D是子類,描述特性。例如"貓是動物",動物就是基類,貓是子類,動物的共性,比如會呼吸,貓也會,但是貓的特性,如打呼嚕,別的動物就不一定有了。
?
JavaScript的原型繼承機制
前面我們已經總結過了原型的作用,原型鏈繼承是JavaScript實現繼承的主要方式。這種繼承的典型實現方式如下:
function Base(name) {// 基對象構造函數實例成員this.name = name; }; // 基對象原型中的實例成員 Base.prototype.showName = function() {alert(this.name); }; // 子對象的構造函數 function Derived(name, age) {// 關鍵點1:獲取基對象構造函數中的成員Base.call(this, name);// 定義子對象自己的成員this.age = age; }; // 關鍵點2:獲取基對象原型上的成員 Derived.prototype = new Base(); // 關鍵點3:將子對象的原型的構造函數屬性設回正確的值 // 這樣可以防止new對象的掛接對象出錯 Derived.prototype.constructor = Derived; // 擴展子類自己的原型成員 Derived.prototype.showAge = function() {alert(this.age); };var d = new Derived('Frank', 10); d.showName(); d.showAge(); // 驗證繼承關系 alert(d instanceof Base);上面的實現關鍵點已經標了出來:
關鍵點1:使用call方式來調用基對象的構造函數,這樣子對象就能按照基對象構造函數的邏輯來構造一份基對象構造函數中的成員。
關鍵點2:使用new方式來創建一個新的Base對象,然后把它掛到Derived對象的原型上,這樣從"Derived對象->Derived原型->Base對象->Base原型->Object對象->Object原型"就形成了鏈條了。
關鍵點3:new操作符要求構造函數對象的原型的constructor屬性要指向構造函數,而在關鍵點2中把Derived對象的原型完全換成Base對象了,這樣有問題了。修復這個問題也很簡單,直接把這個屬性重新設一下就行了。
上面的實現近乎完美,只有一個問題:Base對象的構造函數被調用了2次,一次在構造函數中,一次在構造原型鏈中,這樣的效率并不高。而根據上面的幾點說明我們知道第一次調用是為了復制基對象構造函數中的成員,必不可少;而第二次的調用純粹是為了把原型鏈接上,構造函數中的成員并沒有使用上,于是這里就存在一個優化的契機:既然構造函數中的成員沒有使用到,那我就用一個空對象來輔助創建原型鏈不就就可以了,看下面的代碼:
// 利用空對象來掛接原型鏈 function extend(Child, Parent) {var F = function(){};F.prototype = Parent.prototype;Child.prototype = new F();Child.prototype.constructor = Child; }function Base(name) {this.name = name; };Base.prototype.showName = function() {alert(this.name); };function Derived(name, age) {Base.call(this, name);this.age = age; }; // 掛接原型鏈 extend(Derived, Base); Derived.prototype.showAge = function() {alert(this.age); };var d = new Derived('Frank', 10); d.showName(); d.showAge(); alert(d instanceof Base);這個版本的實現據說是YUI的實現繼承的方式,個人并沒參看其源代碼,有這個愛好的同學可以自行研究一下。
?
復制繼承
既然繼承的本質是復用,那么最直接的想法應該是復制基對象的所有成員,在JavaScript中確實可以這么做,看下面的代碼:
// 淺拷貝實現 function extendCopy(p) {var c = {};for (var i in p) { c[i] = p[i];}return c; } // 深拷貝實現,最為安全 function deepCopy(p, c) {var c = c || {};for (var i in p) {if (typeof p[i] === 'object') {c[i] = (p[i].constructor === Array) ? [] : {};deepCopy(p[i], c[i]);} else {c[i] = p[i];}}return c;} // 基對象 var Base = {name: 'Frank',showName: function() {alert(this.name);} } // 拷貝基對象的成員 var Derived = extendCopy(Base); // 擴展子對象自己的成員 Derived.age = 10; Derived.showAge = function() {alert(this.age); }; // 測試基對象的成員 Derived.showName();在上面的代碼中,我們需要注意兩個問題:
1. 我們從前面已經了解過,JavaScript有簡單的“值類型”和復雜的“引用類型”,這樣復制成員的時候就也有所謂的“淺拷貝”和“深拷貝”的說法。上面的代碼實現了兩種算法,深拷貝本質上就是為引用類型創建新的對象,這樣修改的時候就不會誤操作,修改了其他對象的成員。
2. 這種對象的擴展方式不能使用instanceof來檢查繼承關系,例如下面的代碼是無效的:
alert(Derived instanceof Base);運行這段代碼會返回異常:Uncaught TypeError: Expecting a function in instanceof check, but got #<Object> 。這是因為instanceof只能用于檢查函數實現的那種繼承關系。
?
JavaScript的多態性
談完了JavaScript的繼承機制,那就不能不說說與之密切相關的多態性。繼承與多態從來都是面向對象語言中不可分割的兩個概念。
由于JavaScript是腳本語言,動態語言,所以靜態的類型約束關系被壓縮到了極致。這一方面體現最為明顯的一點就是我們可以隨意的給對象添加和刪除成員,而另一個方面,很多語言都遵循“針對接口”的編程,這一點在動態語言中的表現也大為不同。在JavaScript這些動態語言中,我們不需要事先定義好一些接口,例如下面的例子:
var tank = {run : function () {alert('tank run');} };var person = {run : function () {alert('person run');} }; // 針對接口(run方法)的對象編程 function trigger(target) {target.run(); }trigger(tank); trigger(person);很多人對于這種使用方式不以為然,但是個人覺得這正是動態語言快捷編程的特點,很多時候還是很方便的。
轉載于:https://www.cnblogs.com/dxy1982/p/2688525.html
總結
以上是生活随笔為你收集整理的JavaScript大杂烩4 - 理解JavaScript对象的继承机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AssetManager asset的使
- 下一篇: gradle idea java ssm