首頁 > web前端 > js教程 > JavaScript繼承的實作方法有哪些

JavaScript繼承的實作方法有哪些

青灯夜游
發布: 2023-01-06 11:18:03
原創
2420 人瀏覽過

JavaScript繼承的實作方法:1、利用建構原型模式;2、使用動態原型,根據物件導向的設計原則,類型的所有成員應該都被封裝在類別結構體內;3、使用工廠模式;4、使用類別繼承,即在子類別中呼叫父類別建構子。

JavaScript繼承的實作方法有哪些

本教學操作環境:windows7系統、javascript1.8.5版、Dell G3電腦。

JavaScript 是以物件為基礎,以函數為模型,以原型為繼承的物件導向開發模式。本節將詳細介紹定義 JavaScript 類型的方法,以及實作繼承的常用模式。

建構原型

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

由於建構子事先聲明,而原型屬性在類別結構宣告之後才被定義,因此無法透過建構函式向原型動態傳遞參數。這樣實例化物件都是一個模樣,沒有個性。若要改變原型屬性值,則所有實例都會受到干擾。

當原型屬性的值為引用型別資料時,如果在一個物件實例中修改該屬性值,將會影響所有的實例。

範例1

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

function Book () {};  //声明构造函数
Book.prototype.o = {x : 1, y : 2};  //构造函数的原型属性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 的值發生變化,就會影響所有實例。

建構原型正是為了解決原型模式而誕生的混合設計模式,它把建構子模式與原型模式混合使用,從而避免了上述問題的發生。

實作方法:對於可能會互相影響的原型屬性,並且希望動態傳遞參數的屬性,可以把它們獨立出來使用建構函式模式來設計。對於不需要個性設計、具有共通性的方法或屬性,則可以使用原型模式來設計。

範例2

遵循上述設計原則,把其中兩個屬性設計為建構函式模式,設計方法為原型模式。

function Book (title, pages) {  //构造函数模式设计
    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 定義類別的建議標準。一般建議使用建構函式模式定義所有屬性,使用原型模式定義所有方法。這樣所有方法都只建立一次,而每個實例都能夠根據需要設定屬性值。這也是使用最廣的一種設計模式。

動態原型

根據物件導向的設計原則,類型的所有成員應該都會被封裝在類別結構體內。例如:

function Book (title, pages) {  //构造函数模式设计
    this.title = title;
    this.pages = pages;
    Book.prototype.what = function () {  //原型模式设计,位于类的内部
        console.log(this.title + this.pages);
    };
}
登入後複製

但當每次實例化時,類別 Book 中包含的原型方法就會重複創建,產生大量的原型方法,浪費系統資源。可以使用 if 判斷原型方法是否存在,如果存在就不再建立該方法,否則就建立方法。

function Book (title, pages) {
    this.title = title;
    this.pages = pages;
    if (typeof Book.isLock == "undefined") {  //创建原型方法的锁,如果不存在则创建
        Book.prototype.what = function () {
            console.log(this.title + this.pages);
        };
        Book.isLock = true;  //创建原型方法后,把锁锁上,避免重复创建
    }
}
var book1 = new Book("JavaScript 程序设计", 160);
var book2 = new Book("C语言程序设计", 240);
console.log(book1.title);
console.log(book2.title);
登入後複製

typeof Book.isLock 表達式能夠偵測該屬性值的類型,如果傳回為undefined 字串,則不存在該屬性值,說明沒有建立原型方法,並允許建立原型方法,設定該屬性的值為true,這樣就不用重複建立原型方法。這裡使用類別名稱 Book,而沒有使用 this,這是因為原型是屬於類別本身的,而不是物件實例的。

動態原型模式與建構原型模式在效能上是等價的,使用者可以自由選擇,不過建構原型模式應用比較廣泛。

工廠模式

工廠模式是定義型別的最基本方法,也是 JavaScript 最常用的開發模式。它把物件實例化簡單封裝在一個函數中,然後透過呼叫函數,實現快速、大量生產實例物件。

範例1

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

function Car (color, drive, oil) {  //汽车类
    var _car =  new Object();  //临时对象
        _car.color = color;  //初始化颜色
        _car.drive = drive;  //初始化驱动轮数
        _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 類別可以快速建立多個汽車實例,它們的結構相同,但是屬性不同,可以初始化不同的顏色、驅動輪數和百公里油耗。

範例2

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

function showColor () {  //公共方法,提示汽车颜色
    console.log(this.color);
};
function Car (color, drive, oil) {  //汽车类
    var _car = new Object();  //临时对象
        _car.color = color;  //初始化颜色
        _car.drive = drive;  //初始化驱动轮数
        _car.oil = oil;  //初始化百公里油耗
        _car.showColor = showColor;  //引用外部函数
    return _car;  //返回实例
}
登入後複製

在上面這段重寫的程式碼中,在函數 Car() 之前定義了函數 showColor()。在 Car() 內部,透過引用外部 showColor() 函數,避免了每次實例化時都要建立新的函數。從功能上講,這樣解決了重複創建函數的問題;但是從語義上講,該函數不太像是物件的方法。

類別繼承

類別繼承的設計方法:在子類別中呼叫父類別建構子。

在 JavaScript 中实现类继承,需要注意以下 3 个技术问题。

在子类中,使用 apply 调用父类,把子类构造函数的参数传递给父类父类构造函数。让子类继承父类的私有属性,即 Parent.apply(this, arguments); 代码行。

在父类和子类之间建立原型链,即 Sub.prototype = new Parent(); 代码行。通过这种方式保证父类和子类是原型链上的上下级关系,即子类的 prototype 指向父类的一个实例。

恢复子类的原型对象的构造函数,即 Sub.prototype.constructor=Sub;语句行。当改动 prototype 原型时,就会破坏原来的 constructor 指针,所以必须重置 constructor。

示例1

下面示例演示了一个三重继承的案例,包括基类、父类和子类,它们逐级继承。

//基类Base
function Base (x) {  //构造函数Base
    this.get = function () {  //私有方法,获取参数值
        return x;
    }
}
Base.prototype.has = function () {  //原型方法,判断get()方法返回值是否为0
    return ! (this.get() == 0);
}
//父类Parent
function Parent () {  //构造函数Parent
    var a = [];  //私有数组a
    a = Array.apply(a, arguments);  //把参数转换为数组
    Base.call(this, a.length);  //调用Base类,并把参数数组长度传递给它
    this.add = function () {  //私有方法,把参数数组补加到数组a中并返回
        return a.push.apply(a, arguments)
    }
    this.geta = function () {  //私有方法,返回数组a
        return a;
    }
}
Parent.prototype = new Base();  //设置Parent原型为Base的实例,建立原型链
Parent.prototype.constructor = Parent;  //恢复Parent类原型对象的构造器
Parent.prototype.str = function (){  //原型方法,把数组转换为字符串并返回
    return this.geta().toString();
}
//子类Sub
function Sub () {  //构造函数
    Parent.apply(this, arguments);  //调用Parent类,并把参数数组长度传递给它
    this.sort = function () {  //私有方法,以字符顺序对数组进行排序
        var a = this.geta();  //获取数组的值
        a.sort.apply(a, arguments);  //调用数组排序方法 sort()对数组进行排序
    }
}
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(),补加数组
console.log(sub.geta());  //返回数组30,10,20,40,6,5
sub.sort();  //排序数组
console.log(sub.geta());  //返回数组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 三个类之间的继承关系是通过在子类中调用的构造函数来维护的。

例如,在 Sub 类中,Parent.apply(this, arguments); 能够在子类中调用父类,并把子类的参数传递给父类,从而使子类拥有父类的所有属性。

同理,在父类中,Base.call(this, a.length); 把父类的参数长度作为值传递给基类,并进行调用,从而实现父类拥有基类的所有成员。

从继承关系上看,父类继承了基类的私有方法 get(),为了确保能够继承基类的原型方法,还需要为它们建立原型链,从而实现原型对象的继承关系,方法是添加语句行 Parent.prototype=new Base();。

同理,在子类中添加语句 Sub.prototype=new Parent();,这样通过原型链就可以把基类、父类和子类串连在一起,从而实现子类能够继承父类属性,还可以继承基类的属性。

示例2

下面尝试把类继承模式封装起来,以便规范代码应用。

function extend (Sub, Sup) {  //类继承封装函数
    var F = function () {};  //定义一个空函数
    F.prototype = Sup.prototype;  //设置空函数的原型为父类的原型
    Sub.prototype = new F ();  //实例化空函数,并把父类原型引用传给给子类
    Sub.prototype.constructor = Sub;  //恢复子类原型的构造器为子类自身
    Sub.sup = Sup.prototype;  //在子类定义一个私有属性存储父类原型
    //检测父类原型构造器是否为自身
    if (Sup.prototype.constructor == Object.prototype.constructor) {
        Sup.prototype.constructor = Sup;  //类继承封装函数
    }
}
登入後複製

【操作步骤】

1) 定义一个封装函数。设计入口为子类和父类对象,函数功能是子类能够继承父类的所有原型成员,不涉及出口。

function extend (Sub, Sup) {  //类继承封装函数
    //其中参数Sub表示子类,Sup表示父类
}
登入後複製

2) 在函数体内,首先定义一个空函数 F,用来实现功能中转。设计它的原型为父类的原型,然后把空函数的实例传递给子类的原型,这样就避免了直接实例化父类可能带来的系统负荷。因为在实际开发中,父类的规模可能会很大,如果实例化,会占用大量内存。

3) 恢复子类原型的构造器为子类自己。同时,检测父类原型构造器是否与 Object 的原型构造器发生耦合。如果是,则恢复它的构造器为父类自身。

下面定义两个类,尝试把它们绑定为继承关系。

function A (x) {  //构造函数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) {  //构造函数B
    A.call (this.x);  //在函数体内调用构造函数A,实现内部数据绑定
}
extend (B, A);  //调用封装函数,把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
登入後複製

在函数类封装函数中,有这么一句 Sub.sup=Sup.prototype;,在上面代码中没有被利用,那么它有什么作用呢?为了解答这个问题,先看下面的代码。

extend (B, A);
B.prototype.add = function () {  //为B类定义一个原型方法
    return this.x + "" + this.x;
}
登入後複製

上面的代码是在调用封装函数之后,再为 B 类定义了一个原型方法,该方法名与基类中原型方法 add() 同名,但是功能不同。如果此时测试程序,会发现子类 B 定义的原型方法 add() 将会覆盖父类 A 的原型方法 add()。

console.log(f.add());  //返回字符串55,而不是数值10
登入後複製

如果在 B 类的原型方法 add() 中调用父类的原型方法 add(),避免代码耦合现象发生。

B.prototype.add = function () {  //定义子类B的原型方法add()
    return B.sup.add.call(this);  //在函数内部调用父类方法add()
}
登入後複製

【相关推荐:javascript学习教程

以上是JavaScript繼承的實作方法有哪些的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板