成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

JavaScript 的繼承方式及優(yōu)缺點(diǎn)

nanchen2251 / 3130人閱讀

摘要:繼承簡(jiǎn)介在的中的面向?qū)ο缶幊?,繼承是給構(gòu)造函數(shù)之間建立關(guān)系非常重要的方式,根據(jù)原型鏈的特點(diǎn),其實(shí)繼承就是更改原本默認(rèn)的原型鏈,形成新的原型鏈的過程。


閱讀原文


前言

JavaScript 原本不是純粹的 “OOP” 語言,因?yàn)樵?ES5 規(guī)范中沒有類的概念,在 ES6 中才正式加入了 class 的編程方式,在 ES6 之前,也都是使用面向?qū)ο蟮木幊谭绞剑?dāng)然是 JavaScript 獨(dú)有的面向?qū)ο缶幊?,而且這種編程方式是建立在 JavaScript 獨(dú)特的原型鏈的基礎(chǔ)之上的,我們本篇就將對(duì)原型鏈以及面向?qū)ο缶幊套畛S玫降睦^承進(jìn)行刨析。


繼承簡(jiǎn)介

在 JavaScript 的中的面向?qū)ο缶幊?,繼承是給構(gòu)造函數(shù)之間建立關(guān)系非常重要的方式,根據(jù) JavaScript 原型鏈的特點(diǎn),其實(shí)繼承就是更改原本默認(rèn)的原型鏈,形成新的原型鏈的過程。


復(fù)制的方式進(jìn)行繼承

復(fù)制的方式進(jìn)行繼承指定是對(duì)象與對(duì)象間的淺復(fù)制和深復(fù)制,這種方式到底算不算繼承的一種備受爭(zhēng)議,我們也把它放在我們的內(nèi)容中,當(dāng)作一個(gè) “不正經(jīng)” 的繼承。

1、淺復(fù)制

創(chuàng)建一個(gè)淺復(fù)制的函數(shù),第一個(gè)參數(shù)為復(fù)制的源對(duì)象,第二個(gè)參數(shù)為目標(biāo)對(duì)象。

// 淺復(fù)制方法
function extend(p, c = {}) {
    for (let k in p) {
        c[k] = p[k];
    }
    return c;
}

// 源對(duì)象
let parent = {
    a: 1,
    b: function() {
        console.log(1);
    }
};

// 目標(biāo)對(duì)象
let child = {
    c: 2
};

// 執(zhí)行
extend(parent, child);
console.log(child); // { c: 2, a: 1, b: ? }

上面的 extend 方法在 ES6 標(biāo)準(zhǔn)中可以直接使用 Object.assign 方法所替代。

2、深復(fù)制

可以組合使用 JSON.stringifyJSON.parse 來實(shí)現(xiàn),但是有局限性,不能處理函數(shù)和正則類型,所以我們自己實(shí)現(xiàn)一個(gè)方法,參數(shù)與淺復(fù)制相同。

// 深復(fù)制方法
function extendDeeply(p, c = {}) {
    for (let k in p) {
        if (typeof p[k] === "object" && typeof p[k] !== null) {
            c[k] = p[k] instanceof Array ? [] : {};
            extendDeeply(p[k], c[k]);
        } else {
            c[k] = p[k];
        }
    }
    return c;
}

// 源對(duì)象
let parent = {
    a: {
        b: 1
    },
    b: [1, 2, 3],
    c: 1,
    d: function() {
        console.log(1);
    }
};

// 執(zhí)行
let child = extendDeeply(parent);

console.log(child); // { a: {b: 1}, b: [1, 2, 3], c: 1, d: ? }
console.log(child.a === parent.a); // false
console.log(child.b === parent.b); // false
console.log(child.d === parent.d); // true

在上面可以看出復(fù)制后的新對(duì)象 childa 屬性和 b 的引用是獨(dú)立的,與 parentab 毫無關(guān)系,實(shí)現(xiàn)了深復(fù)制,但是 extendDeeply 函數(shù)并沒有對(duì)函數(shù)類型做處理,因?yàn)楹瘮?shù)內(nèi)部執(zhí)行相同的邏輯指向不同引用是浪費(fèi)內(nèi)存的。


原型替換

原型替換是繼承當(dāng)中最簡(jiǎn)單也是最直接的方式,即直接讓父類和子類共用同一個(gè)原型對(duì)象,一般有兩種實(shí)現(xiàn)方式。

// 原型替換
// 父類
function Parent() {}

// 子類
function Child() {}

// 簡(jiǎn)單粗暴的寫法
Child.prototype = Parent.prototype;

// 另一種種實(shí)現(xiàn)方式
Object.setPrototypeOf(Child.prototype, Parent.prototype);

上面這種方式 Child 的原型被替換掉,Child 的實(shí)例可以直接調(diào)用 Parent 原型上的方法,實(shí)現(xiàn)了對(duì)父類原型方法的繼承。

上面第二種方式使用了 Object.setPrototypeOf 方法,該方法是將傳入第一個(gè)參數(shù)對(duì)象的原型設(shè)置為第二個(gè)參數(shù)傳入的對(duì)象,所以我們第一個(gè)參數(shù)傳入的是 Child 的原型,將 Child 原型的原型設(shè)置成了 Parent 的原型,使父、子類原型鏈產(chǎn)生關(guān)聯(lián),Child 的實(shí)例繼承了 Parent 原型上的方法,在 NodeJS 中的內(nèi)置模塊 util 中用來實(shí)現(xiàn)繼承的方法 inherits,底層就是使用這種方式實(shí)現(xiàn)的。

缺點(diǎn):父類的實(shí)例也同樣可以調(diào)用子類的原型方法,我們希望繼承是單向的,否則無法區(qū)分父、子類關(guān)系,這種方式一般是不可取的。


原型鏈繼承

原型鏈繼承的思路是子類的原型的原型是父類的原型,形成了一條原型鏈,建立子類與父類原型的關(guān)系。

// 原型鏈繼承
// 父類
function Parent(name) {
    this.name = name;
    this.hobby = ["basketball", "football"];
}

// 子類
function Child() {}

// 繼承
Child.prototype = new Parent();

上面用 Parent 的實(shí)例替換了 Child 自己的原型,由于父類的實(shí)例原型直接指向 Parent.prototype,所以也使父、子類原型鏈產(chǎn)生關(guān)聯(lián),子類實(shí)例繼承了父類原型的方法。

缺點(diǎn) 1:只能繼承父類原型上的方法,卻無法繼承父類上的屬性。
缺點(diǎn) 2:由于原型對(duì)象被替換,原本原型的 constructor 屬性丟失。
缺點(diǎn) 3:如果父類的構(gòu)造函數(shù)中有屬性,則創(chuàng)建的父類的實(shí)例也會(huì)有這個(gè)屬性,用這個(gè)實(shí)例的作為子類的原型,這個(gè)屬性就變成了所有子類實(shí)例所共有的,這個(gè)屬性可能是多余的,并不是我們想要的,也可能我們希望它不是共有的,而是每個(gè)實(shí)例自己的。


構(gòu)造函數(shù)繼承

構(gòu)造函數(shù)繼承又被國(guó)內(nèi)的開發(fā)者叫做 “經(jīng)典繼承”。

// 構(gòu)造函數(shù)繼承
// 父類
function Parent(name) {
    this.name = name;
}

// 子類
function Child() {
    Parent.apply(this, arguments);
}

let c = new Child("Panda");
console.log(c); // { name: "Panda" }

構(gòu)造函數(shù)繼承的原理就是在創(chuàng)建 Child 實(shí)例的時(shí)候執(zhí)行了 Child 構(gòu)造函數(shù),并借用 callapply 在內(nèi)部執(zhí)行了父類 Parent,并把父類的屬性創(chuàng)建給了 this,即子類的實(shí)例,解決了原型鏈繼承不能繼承父類屬性的缺點(diǎn)。

缺點(diǎn):子類的實(shí)例只能繼承父類的屬性,卻不能繼承父類的原型的方法。


構(gòu)造函數(shù)原型鏈組合繼承

為了使子類既能繼承父類原型的方法,又能繼承父類的屬性到自己的實(shí)例上,就有了這種組合使用的方式。

// 構(gòu)造函數(shù)原型鏈組合繼承
// 父類
function Parent(name) {
    this.name = name;
}

Parent.prototype.sayName = function() {
    console.log(this.name);
};

// 子類
function Child() {
    Parent.apply(this, arguments);
}

// 繼承
Child.prototype = new Parent();

let c = new Child("Panda");
console.log(c); // { name: "Panda" }
c.sayName(); // Panda

這種繼承看似完美,但是之前 constructor 丟失和子類原型上多余共有屬性的問題還是沒有解決,在這基礎(chǔ)上又產(chǎn)生了新的問題。

缺點(diǎn):父類被執(zhí)行了兩次,在使用 callapply 繼承屬性時(shí)執(zhí)行一次,在創(chuàng)建實(shí)例替換子類原型時(shí)又被執(zhí)行了一次。


原型式繼承

原型式繼承主要用來解決用父類的實(shí)例替換子類的原型時(shí)共有屬性的問題,以及父類構(gòu)造函數(shù)執(zhí)行兩次的問題,也就是說通過原型式繼承能保證子類的原型是 “干凈的”,而保證只在繼承父類的屬性時(shí)執(zhí)行一次父類。

// 原型式繼承
// 父類
function Parent(name) {
    this.name = name;
}

// 子類
function Child() {
    Parent.apply(this, arguments);
}

// 繼承函數(shù)
function create(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}

// 繼承
Child.prototype = create(Parent.prototype);

let c = new Child("Panda");
console.log(c); // { name: "Panda" }

原型式繼承其實(shí)是借助了一個(gè)中間的構(gòu)造函數(shù),將中間構(gòu)造函數(shù) Fprototype 替換成了父類的原型,并創(chuàng)建了一個(gè) F 的實(shí)例返回,這個(gè)實(shí)例是不具備任何屬性的(干凈的),用這個(gè)實(shí)例替換子類的原型,因?yàn)檫@個(gè)實(shí)例的原型指向 F 的原型,F 的原型同時(shí)又是父類的原型對(duì)象,所以子類實(shí)例繼承了父類原型的方法,父類只在創(chuàng)建子類實(shí)例的時(shí)候執(zhí)行了一次,省去了創(chuàng)建父類實(shí)例的過程。

原型式繼承在 ES5 標(biāo)準(zhǔn)中被封裝成了一個(gè)專門的方法 Object.create,該方法的第一個(gè)參數(shù)與上面 create 函數(shù)的參數(shù)相同,即要作為原型的對(duì)象,第二個(gè)參數(shù)則可以傳遞一個(gè)對(duì)象,會(huì)把對(duì)象上的屬性添加到這個(gè)原型上,一般第二個(gè)參數(shù)用來彌補(bǔ) constructor 的丟失問題,這個(gè)方法不兼容 IE 低版本瀏覽器。


寄生式繼承

寄生式繼承就是用來解決子統(tǒng)一為原型式繼承中返回的對(duì)象統(tǒng)一添加方法的問題,只是在原型式繼承的基礎(chǔ)上做了小小的修改。

// 寄生式繼承
// 父類
function Parent(name) {
    this.name = name;
}

// 子類
function Child() {
    Parent.apply(this, arguments);
}

// 繼承函數(shù)
function create(obj) {
    function F() {}
    F.prototype = obj;
    return new F();
}

// 將子類方法私有化函數(shù)
function creatFunction(obj) {
    // 調(diào)用繼承函數(shù)
    let clone = create(obj);
    // 子類原型方法(多個(gè))
    clone.sayName = function() {};
    clone.sayHello = function() {};

    return clone;
}

// 繼承
Child.prototype = creatFunction(Parent.prototype);

缺點(diǎn):因?yàn)榧纳嚼^承最后返回的是一個(gè)對(duì)象,如果用一個(gè)變量直接來接收它,那相當(dāng)于添加的所有方法都變成這個(gè)對(duì)象自身的了,如果創(chuàng)建了多個(gè)這樣的對(duì)象,無法實(shí)現(xiàn)相同方法的復(fù)用。


寄生組合式繼承
// 寄生組合式繼承
// 父類
function P(name, age) {
    this.name = name;
    this.age = age;
}

P.prototype.headCount = 1;
P.prototype.eat = function() {
    console.log("eating...");
};

// 子類
function C(name, age) {
    P.apply(this, arguments);
}

// 寄生組合式繼承方法
function myCreate(Child, Parent) {
    function F() {}
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
    // 讓 Child 子類的靜態(tài)屬性 super 和 base 指向父類的原型
    Child.super = Child.base = Parent.prototype;
}

// 調(diào)用方法實(shí)現(xiàn)繼承
myCreate(C, P);

// 向子類原型添加屬性方法,因?yàn)樽宇悩?gòu)造函數(shù)的原型被替換,所以屬性方法仍然在替換之后
C.prototype.language = "javascript";
C.prototype.work = function() {
    console.log("writing code use " + this.language);
};
C.work = function() {
    this.super.eat();
};

// 驗(yàn)證繼承是否成功
let f = new C("nihao", 16);
f.work();
C.work();

// writing code use javascript
// eating...

寄生組合式繼承基本規(guī)避了其他繼承的大部分缺點(diǎn),應(yīng)該比較強(qiáng)大了,也是平時(shí)使用最多的一種繼承,其中 Child.super 方法的作用是為了在調(diào)用子類靜態(tài)屬性的時(shí)候可以調(diào)用父類的原型方法。

缺點(diǎn):子類沒有繼承父類的靜態(tài)方法。


class...extends... 繼承

在 ES6 規(guī)范中有了類的概念,使繼承變得容易,在規(guī)避上面缺點(diǎn)的完成繼承的同時(shí),又在繼承時(shí)繼承了父類的靜態(tài)屬性。

// class...extends... 繼承
// 父類
class P {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    sayName() {
        console.log(this.name);
    }
    static sayHi() {
        console.log("Hello");
    }
}

// 子類繼承父類
class C extends P {
    constructor(name, age) {
        supper(name, age); // 繼承父類的屬性
    }
    sayHello() {
        P.sayHi();
    }
    static sayHello() {
        super.sayHi();
    }
}

let c = new C("jack", 18);

c.sayName(); // jack
c.sayHello(); // Hello
C.sayHi(); // Hello
C.sayHello(); // Hello

在子類的 constructor 中調(diào)用 supper 可以實(shí)現(xiàn)對(duì)父類屬性的繼承,父類的原型方法和靜態(tài)方法直接會(huì)被子類繼承,在子類的原型方法中使用父類的原型方法只需使用 thissupper 調(diào)用即可,此時(shí) this 指向子類的實(shí)例,如果在子類的靜態(tài)方法中使用 thissupper 調(diào)用父類的靜態(tài)方法,此時(shí) this 指向子類本身。


文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/98287.html

相關(guān)文章

  • 復(fù)習(xí)Javascript專題(三):面向?qū)ο螅▽?duì)象創(chuàng)建與繼承,原型原型鏈)

    摘要:在創(chuàng)建子類實(shí)例時(shí),不能向超類型的構(gòu)造函數(shù)中傳遞參數(shù)。構(gòu)造函數(shù)繼承子類傳進(jìn)的值是基本思想是在子類構(gòu)造函數(shù)的內(nèi)部調(diào)用超類或父類型構(gòu)造函數(shù)。繼承保證構(gòu)造函數(shù)指針指向如果想同時(shí)繼承多個(gè),還可使用添加屬性的方式類繼承, OOP:Object Oriented Programming 面向?qū)ο缶幊獭?題外話:面向?qū)ο蟮姆秶鷮?shí)在太大,先把這些大的東西理解理解。 1.什么是對(duì)象? 根據(jù)高程和權(quán)威指南上...

    testHs 評(píng)論0 收藏0
  • 7個(gè) Javascript 面試題回答策略

    摘要:使用異步編程,有一個(gè)事件循環(huán)。它作為面向?qū)ο缶幊痰奶娲桨?,其中?yīng)用狀態(tài)通常與對(duì)象中的方法搭配并共享。在用面向?qū)ο缶幊虝r(shí)遇到不同的組件競(jìng)爭(zhēng)相同的資源的時(shí)候,更是如此。 翻譯:瘋狂的技術(shù)宅原文:https://www.indeed.com/hire/i... 本文首發(fā)微信公眾號(hào):jingchengyideng歡迎關(guān)注,每天都給你推送新鮮的前端技術(shù)文章 不管你是面試官還是求職者,里面...

    李義 評(píng)論0 收藏0
  • 深入理解JavaScript

    摘要:深入之繼承的多種方式和優(yōu)缺點(diǎn)深入系列第十五篇,講解各種繼承方式和優(yōu)缺點(diǎn)。對(duì)于解釋型語言例如來說,通過詞法分析語法分析語法樹,就可以開始解釋執(zhí)行了。 JavaScript深入之繼承的多種方式和優(yōu)缺點(diǎn) JavaScript深入系列第十五篇,講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 寫在前面 本文講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 但是注意: 這篇文章更像是筆記,哎,再讓我...

    myeveryheart 評(píng)論0 收藏0
  • JavaScript之深入各種繼承

    摘要:通常有這兩種繼承方式接口繼承和實(shí)現(xiàn)繼承。理解繼承的工作是通過調(diào)用函數(shù)實(shí)現(xiàn)的,所以是寄生,將繼承工作寄托給別人做,自己只是做增強(qiáng)工作。適用基于某個(gè)對(duì)象或某些信息來創(chuàng)建對(duì)象,而不考慮自定義類型和構(gòu)造函數(shù)。 一、繼承的概念 繼承,是面向?qū)ο笳Z言的一個(gè)重要概念。通常有這兩種繼承方式:接口繼承和實(shí)現(xiàn)繼承。接口繼承只繼承方法簽名,而實(shí)現(xiàn)繼承則繼承實(shí)際的方法。 《JS高程》里提到:由于函數(shù)沒有簽名,...

    tomlingtm 評(píng)論0 收藏0
  • js中對(duì)數(shù)據(jù)類型總結(jié)判斷數(shù)據(jù)類型各種方法優(yōu)缺點(diǎn)

    摘要:最常見的判斷方法它的官方解釋操作符返回一個(gè)字符串,表示未經(jīng)計(jì)算的操作數(shù)的類型。另外,是判斷對(duì)象是否屬于某一類型,而不是獲取的對(duì)象的類型。多個(gè)窗口意味著多個(gè)全局環(huán)境,不同的全局環(huán)境擁有不同的全局對(duì)象,從而擁有不同的內(nèi)置類型構(gòu)造函數(shù)。 js中的數(shù)據(jù)類型 js中只有六種原始數(shù)據(jù)類型和一個(gè)Object: Boolean Null Undefined Number String Symbol ...

    voyagelab 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<