JavaScript中有哪些繼承的實現(xiàn)方法

JavaScript中有哪些繼承的實現(xiàn)方法,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

創(chuàng)新互聯(lián)建站專業(yè)為企業(yè)提供安吉網站建設、安吉做網站、安吉網站設計、安吉網站制作等企業(yè)網站建設、網頁設計與制作、安吉企業(yè)網站模板建站服務,十年安吉做網站經驗,不只是建網站,更提供有價值的思路和整體網絡服務。

JavaScript繼承的實現(xiàn)方法:1、利用構造原型模式;2、使用動態(tài)原型,根據(jù)面向對象的設計原則,類型的所有成員應該都被封裝在類結構體內;3、使用工廠模式;4、使用類繼承,即在子類中調用父類構造函數(shù)。

構造原型

直接使用 prototype 原型設計類的繼承存在兩個問題。

由于構造函數(shù)事先聲明,而原型屬性在類結構聲明之后才被定義,因此無法通過構造函數(shù)向原型動態(tài)傳遞參數(shù)。這樣實例化對象都是一個模樣,沒有個性。要改變原型屬性值,則所有實例都會受到干擾。

當原型屬性的值為引用類型數(shù)據(jù)時,如果在一個對象實例中修改該屬性值,將會影響所有的實例。

示例1

簡單定義 Book 類型,然后實例化。

function Book () {};  //聲明構造函數(shù)
Book.prototype.o = {x : 1, y : 2};  //構造函數(shù)的原型屬性o是一個對象
var book1 = new Book ();  //實例化對象book1
var book2 = new Book ();  //實例化對象book2
console.log(book1.o.x);  //返回1
console.log(book2.o.x);  //返回1
book2.o.x = 3;  //修改實例化對象book2中的屬性x的值
console.log(book1.o.x);  //返回3
console.log(book2.o.x);  //返回3

由于原型屬性 o 為一個引用型的值,所以所有實例的屬性 o 的值都是同一個對象的引用,一旦 o 的值發(fā)生變化,將會影響所有實例。

構造原型正是為了解決原型模式而誕生的一種混合設計模式,它把構造函數(shù)模式與原型模式混合使用,從而避免了上述問題的發(fā)生。

實現(xiàn)方法:對于可能會相互影響的原型屬性,并且希望動態(tài)傳遞參數(shù)的屬性,可以把它們獨立出來使用構造函數(shù)模式進行設計。對于不需要個性設計、具有共性的方法或屬性,則可以使用原型模式來設計。

示例2

遵循上述設計原則,把其中兩個屬性設計為構造函數(shù)模式,設計方法為原型模式。

function Book (title, pages) {  //構造函數(shù)模式設計
    this.title = title;
    this.pages = pages;
}
Book.prototype.what = function () {  //原型模式設計
    console.log(this.title + this.pages);
};
var book1 = new Book("JavaScript 程序設計", 160);
var book2 = new Book("C語言程序設計", 240);
console.log(book1.title);
console.log(book2.title);

構造原型模式是 ECMAScript 定義類的推薦標準。一般建議使用構造函數(shù)模式定義所有屬性,使用原型模式定義所有方法。這樣所有方法都只創(chuàng)建一次,而每個實例都能夠根據(jù)需要設置屬性值。這也是使用最廣的一種設計模式。

動態(tài)原型

根據(jù)面向對象的設計原則,類型的所有成員應該都被封裝在類結構體內。例如:

function Book (title, pages) {  //構造函數(shù)模式設計
    this.title = title;
    this.pages = pages;
    Book.prototype.what = function () {  //原型模式設計,位于類的內部
        console.log(this.title + this.pages);
    };
}

但當每次實例化時,類 Book 中包含的原型方法就會被重復創(chuàng)建,生成大量的原型方法,浪費系統(tǒng)資源??梢允褂?if 判斷原型方法是否存在,如果存在就不再創(chuàng)建該方法,否則就創(chuàng)建方法。

function Book (title, pages) {
    this.title = title;
    this.pages = pages;
    if (typeof Book.isLock == "undefined") {  //創(chuàng)建原型方法的鎖,如果不存在則創(chuàng)建
        Book.prototype.what = function () {
            console.log(this.title + this.pages);
        };
        Book.isLock = true;  //創(chuàng)建原型方法后,把鎖鎖上,避免重復創(chuàng)建
    }
}
var book1 = new Book("JavaScript 程序設計", 160);
var book2 = new Book("C語言程序設計", 240);
console.log(book1.title);
console.log(book2.title);

typeof Book.isLock 表達式能夠檢測該屬性值的類型,如果返回為 undefined 字符串,則不存在該屬性值,說明沒有創(chuàng)建原型方法,并允許創(chuàng)建原型方法,設置該屬性的值為 true,這樣就不用重復創(chuàng)建原型方法。這里使用類名 Book,而沒有使用 this,這是因為原型是屬于類本身的,而不是對象實例的。

動態(tài)原型模式與構造原型模式在性能上是等價的,用戶可以自由選擇,不過構造原型模式應用比較廣泛。

工廠模式

工廠模式是定義類型的最基本方法,也是 JavaScript 最常用的一種開發(fā)模式。它把對象實例化簡單封裝在一個函數(shù)中,然后通過調用函數(shù),實現(xiàn)快速、批量生產實例對象。

示例1

下面示例設計一個 Car 類型:包含汽車顏色、驅動輪數(shù)、百公里油耗 3 個屬性,同時定義一個方法,用來顯示汽車顏色。

function Car (color, drive, oil) {  //汽車類
    var _car =  new Object();  //臨時對象
        _car.color = color;  //初始化顏色
        _car.drive = drive;  //初始化驅動輪數(shù)
        _car.oil = oil;  //初始化百公里油耗
        _car.showColor = function () {  //方法,提示汽車顏色
            console.log(this.color);
        };
        return _car;  //返回實例
}
var car1 = Car("red", 4, 8);
var car2 = Car("blue", 2, 6);
car1.showColor();  //輸出“red”
car2.showColor();  //輸出“blue”

上面代碼是一個簡單的工廠模式類型,使用 Car 類可以快速創(chuàng)建多個汽車實例,它們的結構相同,但是屬性不同,可以初始化不同的顏色、驅動輪數(shù)和百公里油耗。

示例2

在類型中,方法就是一種行為或操作,它能夠根據(jù)初始化參數(shù)完成特定任務,具有共性。因此,可以考慮把方法置于 Car() 函數(shù)外面,避免每次實例化時都要創(chuàng)建一次函數(shù),讓每個實例共享同一個函數(shù)。

function showColor () {  //公共方法,提示汽車顏色
    console.log(this.color);
};
function Car (color, drive, oil) {  //汽車類
    var _car = new Object();  //臨時對象
        _car.color = color;  //初始化顏色
        _car.drive = drive;  //初始化驅動輪數(shù)
        _car.oil = oil;  //初始化百公里油耗
        _car.showColor = showColor;  //引用外部函數(shù)
    return _car;  //返回實例
}

在上面這段重寫的代碼中,在函數(shù) Car() 之前定義了函數(shù) showColor()。在 Car() 內部,通過引用外部 showColor() 函數(shù),避免了每次實例化時都要創(chuàng)建一個新的函數(shù)。從功能上講,這樣解決了重復創(chuàng)建函數(shù)的問題;但是從語義上講,該函數(shù)不太像是對象的方法。

類繼承

類繼承的設計方法:在子類中調用父類構造函數(shù)。

在 JavaScript 中實現(xiàn)類繼承,需要注意以下 3 個技術問題。

在子類中,使用 apply 調用父類,把子類構造函數(shù)的參數(shù)傳遞給父類父類構造函數(shù)。讓子類繼承父類的私有屬性,即 Parent.apply(this, arguments); 代碼行。

在父類和子類之間建立原型鏈,即 Sub.prototype = new Parent(); 代碼行。通過這種方式保證父類和子類是原型鏈上的上下級關系,即子類的 prototype 指向父類的一個實例。

恢復子類的原型對象的構造函數(shù),即 Sub.prototype.constructor=Sub;語句行。當改動 prototype 原型時,就會破壞原來的 constructor 指針,所以必須重置 constructor。

示例1

下面示例演示了一個三重繼承的案例,包括基類、父類和子類,它們逐級繼承。

//基類Base
function Base (x) {  //構造函數(shù)Base
    this.get = function () {  //私有方法,獲取參數(shù)值
        return x;
    }
}
Base.prototype.has = function () {  //原型方法,判斷get()方法返回值是否為0
    return ! (this.get() == 0);
}
//父類Parent
function Parent () {  //構造函數(shù)Parent
    var a = [];  //私有數(shù)組a
    a = Array.apply(a, arguments);  //把參數(shù)轉換為數(shù)組
    Base.call(this, a.length);  //調用Base類,并把參數(shù)數(shù)組長度傳遞給它
    this.add = function () {  //私有方法,把參數(shù)數(shù)組補加到數(shù)組a中并返回
        return a.push.apply(a, arguments)
    }
    this.geta = function () {  //私有方法,返回數(shù)組a
        return a;
    }
}
Parent.prototype = new Base();  //設置Parent原型為Base的實例,建立原型鏈
Parent.prototype.constructor = Parent;  //恢復Parent類原型對象的構造器
Parent.prototype.str = function (){  //原型方法,把數(shù)組轉換為字符串并返回
    return this.geta().toString();
}
//子類Sub
function Sub () {  //構造函數(shù)
    Parent.apply(this, arguments);  //調用Parent類,并把參數(shù)數(shù)組長度傳遞給它
    this.sort = function () {  //私有方法,以字符順序對數(shù)組進行排序
        var a = this.geta();  //獲取數(shù)組的值
        a.sort.apply(a, arguments);  //調用數(shù)組排序方法 sort()對數(shù)組進行排序
    }
}
Sub.prototype = new Parent();  //設置Sub原型為Parent實例,建立原型鏈
Sub.prototype.constructor = Sub;  //恢復Sub類原型對象的構造器
//父類Parent的實例繼承類Base的成員
var parent = new Parent (1, 2, 3, 4);  //實例化Parent類
console.log(parent.get());  //返回4,調用Base類的方法get()
console.log(parent.has());  //返回true,調用Base類的方法has()
//子類Sub的實例繼承類Parent和類Base的成員
var sub = new Sub (30, 10, 20, 40);  //實例化Sub類
sub.add(6, 5);  //調用Parent類方法add(),補加數(shù)組
console.log(sub.geta());  //返回數(shù)組30,10,20,40,6,5
sub.sort();  //排序數(shù)組
console.log(sub.geta());  //返回數(shù)組10,20,30,40,5,6
console.log(sub.get());  //返回4,調用Base類的方法get()
console.log(sub.has());  //返回true,調用Base類的方法has()
console.log(sub.str());  //返回10,20,30,40,5,6

【設計思路】

設計子類 Sub 繼承父類 Parent,而父類 Parent 又繼承基類 Base。Base、Parent、Sub 三個類之間的繼承關系是通過在子類中調用的構造函數(shù)來維護的。

例如,在 Sub 類中,Parent.apply(this, arguments); 能夠在子類中調用父類,并把子類的參數(shù)傳遞給父類,從而使子類擁有父類的所有屬性。

同理,在父類中,Base.call(this, a.length); 把父類的參數(shù)長度作為值傳遞給基類,并進行調用,從而實現(xiàn)父類擁有基類的所有成員。

從繼承關系上看,父類繼承了基類的私有方法 get(),為了確保能夠繼承基類的原型方法,還需要為它們建立原型鏈,從而實現(xiàn)原型對象的繼承關系,方法是添加語句行 Parent.prototype=new Base();。

同理,在子類中添加語句 Sub.prototype=new Parent();,這樣通過原型鏈就可以把基類、父類和子類串連在一起,從而實現(xiàn)子類能夠繼承父類屬性,還可以繼承基類的屬性。

示例2

下面嘗試把類繼承模式封裝起來,以便規(guī)范代碼應用。

function extend (Sub, Sup) {  //類繼承封裝函數(shù)
    var F = function () {};  //定義一個空函數(shù)
    F.prototype = Sup.prototype;  //設置空函數(shù)的原型為父類的原型
    Sub.prototype = new F ();  //實例化空函數(shù),并把父類原型引用傳給給子類
    Sub.prototype.constructor = Sub;  //恢復子類原型的構造器為子類自身
    Sub.sup = Sup.prototype;  //在子類定義一個私有屬性存儲父類原型
    //檢測父類原型構造器是否為自身
    if (Sup.prototype.constructor == Object.prototype.constructor) {
        Sup.prototype.constructor = Sup;  //類繼承封裝函數(shù)
    }
}

【操作步驟】

1) 定義一個封裝函數(shù)。設計入口為子類和父類對象,函數(shù)功能是子類能夠繼承父類的所有原型成員,不涉及出口。

function extend (Sub, Sup) {  //類繼承封裝函數(shù)
    //其中參數(shù)Sub表示子類,Sup表示父類
}

2) 在函數(shù)體內,首先定義一個空函數(shù) F,用來實現(xiàn)功能中轉。設計它的原型為父類的原型,然后把空函數(shù)的實例傳遞給子類的原型,這樣就避免了直接實例化父類可能帶來的系統(tǒng)負荷。因為在實際開發(fā)中,父類的規(guī)??赡軙艽?,如果實例化,會占用大量內存。

3) 恢復子類原型的構造器為子類自己。同時,檢測父類原型構造器是否與 Object 的原型構造器發(fā)生耦合。如果是,則恢復它的構造器為父類自身。

下面定義兩個類,嘗試把它們綁定為繼承關系。

function A (x) {  //構造函數(shù)A
    this.x = x;  //私有屬性x
    this.get = function () {  //私有方法get()
        return this.x;
    }
}
A.prototype.add = function () {  //原型方法add()
    return this.x + this.x;
}
A.prototype.mul = function () {  //原型方法mul()
    return this.x * this.x;
}
function B (x) {  //構造函數(shù)B
    A.call (this.x);  //在函數(shù)體內調用構造函數(shù)A,實現(xiàn)內部數(shù)據(jù)綁定
}
extend (B, A);  //調用封裝函數(shù),把A和B的原型捆綁在一起
var f = new B (5);  //實例化類B
console.log(f.get());  //繼承類A的方法get(),返回5
console.log(f.add());  //繼承類A的方法add(),返回10
console.log(f.mul());  //繼承類A的方法mul(),返回25

在函數(shù)類封裝函數(shù)中,有這么一句 Sub.sup=Sup.prototype;,在上面代碼中沒有被利用,那么它有什么作用呢?為了解答這個問題,先看下面的代碼。

extend (B, A);
B.prototype.add = function () {  //為B類定義一個原型方法
    return this.x + "" + this.x;
}

上面的代碼是在調用封裝函數(shù)之后,再為 B 類定義了一個原型方法,該方法名與基類中原型方法 add() 同名,但是功能不同。如果此時測試程序,會發(fā)現(xiàn)子類 B 定義的原型方法 add() 將會覆蓋父類 A 的原型方法 add()。

console.log(f.add());  //返回字符串55,而不是數(shù)值10

如果在 B 類的原型方法 add() 中調用父類的原型方法 add(),避免代碼耦合現(xiàn)象發(fā)生。

B.prototype.add = function () {  //定義子類B的原型方法add()
    return B.sup.add.call(this);  //在函數(shù)內部調用父類方法add()
}

看完上述內容,你們掌握JavaScript中有哪些繼承的實現(xiàn)方法的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!

網頁題目:JavaScript中有哪些繼承的實現(xiàn)方法
本文URL:http://muchs.cn/article20/jojdco.html

成都網站建設公司_創(chuàng)新互聯(lián),為您提供Google、網站設計、網站設計公司、網頁設計公司、域名注冊、網站建設

廣告

聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

外貿網站建設