1. 程式人生 > >【JS】《JavaScript設計模式》 之 JS繼承

【JS】《JavaScript設計模式》 之 JS繼承

以前在準備面試的時候經常會看到面試題裡有js實現繼承的相關題,總覺得很少用的到,也沒有太理解為什麼要那麼寫,只是硬記下來了。最近在看《JavaScript設計模式》,裡面有一節很詳細的講到了js繼承,後面的模式裡也經常用到,在這裡記錄下。

1.子類的原型物件——類式繼承

// 宣告父類
function SuperClass() {
    this.superValue = true
}
// 為父類新增共有方法
SuperClass.prototype.getSuperValue = function () {
    return this.superValue
}
// 宣告子類
function SubClass() {
    this.subValue = false
}
// 繼承父類
SubClass.prototype = new SuperClass()
// 為子類新增共有方法
SubClass.prototype.getSubClass = function () {
    return this.subValue
}

類式繼承需要將第一個類的例項賦值給第二個類的原型。類的原型物件的作用就是為類的原型新增共有方法,但類不能直接訪問這些屬性和方法,必須通過原型prototype來訪問。而我們例項化一個父類時,新建立的物件複製了父類的建構函式內的屬性與方法,並將原型__proto__指向了父類的原型物件,這樣就擁有了父類的原型物件上的屬性與方法,並且這個新建立的物件可直接訪問到父類原型物件上的屬性與方法。

新建立的物件不僅僅可以訪問父類原型上的屬性和方法,同樣也可以訪問從父類建構函式中複製的屬性和方法。

使用方法:

var instance = new SubClass()
console.log(instance.getSuperValue())   // true
console.log(instance.getSubClass())     // false

我們還可以通過instanceof來檢測某個物件是否是某個類的例項。

console.log(instance instanceof SuperClass)   // true
console.log(instance instanceof SubClass)     // true
console.log(SubClass instanceof SuperClass)   // false

這裡可能大家會有疑問,為什麼subClass繼承了SuperClass,會得到false。

這裡我們要知道:instanceof是判斷前面的物件是否是後面類(物件)的例項,並不表示兩者的繼承。

這種類繼承模式有2個缺點:

  1. 由於子類通過其原型prototype對父類例項化,繼承了父類。所以說父類中的共有屬性要是引用型別,就會在子類中被所有例項共有,因此一個子類的例項更改子類原型從父類建構函式中繼承來的共有屬性就會直接影響到其他子類
  2. 由於子類實現的繼承是靠其原型prototype對父類的例項化實現的,因此在建立父類的時候,是無法向父類傳遞引數的,因此在例項化父類的時候也無法對父類建構函式內的屬性進行初始化

 

2.建立即繼承——建構函式繼承

// 宣告父類
function SuperClass(id) {
    this.books = ['JavaScript', 'html', 'css']
    this.id = id
}

// 父類宣告原型方法
SuperClass.prototype.showBooks = function () {
    console.log(this.books)
}

// 宣告子類
function SubClass(id) {
    // 繼承父類
    SuperClass.call(this, id)
}

var instance1 = new SubClass(10)
var instance2 = new SubClass(11)

instance1.books.push('設計模式')
console.log(instance1.books)  // ["JavaScript", "html", "css", "設計模式"]
console.log(instance1.id)     // 10
console.log(instance2.books)  // ["JavaScript", "html", "css"]
console.log(instance2.id)     // 11

SuperClass.call(this, id) 這條語句是建構函式式繼承的精華。call這個方法可以更改函式的作用環境,因此在子類中,對superClass呼叫這個方法就是將子類中的變數在父類中執行一遍,由於父類中是給this繫結屬性的,因此子類自然也就繼承了父類的共有屬性。

 

3.將優點為我所有——組合繼承

我們總結下上面兩種模式的特點,類式繼承是通過子類的原型prototype對父類例項化來實現的,建構函式式繼承是通過在子類的建構函式作用環境中執行一次父類的建構函式來實現的,在繼承中同時做到這兩點,即為組合繼承。

// 宣告父類
function SuperClass(name) {
    // 值型別共有屬性
    this.name = name
    // 引用型別共有屬性
    this.books = ['JavaScript', 'html', 'css']
}
// 父類原型共有方法
SuperClass.prototype.getName = function () {
    console.log(this.name)
}
// 宣告子類
function SubClass(name, time) {
    SuperClass.call(this, name)
    this.time = time
}
// 類式繼承 子類原型繼承父類
SubClass.prototype = new SuperClass()
// 子類原型方法
SubClass.prototype.getTime = function () {
    console.log(this.time)
}

var instance1 = new SubClass('js book', 2014)
instance1.books.push('設計模式')
console.log(instance1.books)  // ["JavaScript", "html", "css", "設計模式"]
instance1.getName()           // js book
instance1.getTime()           // 2014

var instance2 = new SubClass('js book', 2015)
console.log(instance2.books)  // ["JavaScript", "html", "css"]
instance2.getName()           // js book
instance2.getTime()           // 2015

這種繼承方法,子類的例項中更改父類繼承下來的引用型別屬性如books,不會影響到其他例項,並且子類例項化過程中又能將引數傳遞到父類的建構函式中,如name。

 

4.潔淨的繼承者——原型式繼承

function inheritObject(o) {
    function F() {}
    F.prototype = o
    return new F()
}

var book = {
    name: 'js book',
    alikeBook: ['css book, html book']
}

var newBook = inheritObject(book)
newBook.name = 'ajax book'
newBook.alikeBook.push('xml book')

var otherBook = inheritObject(book)
otherBook.name = 'flash book'
otherBook.alikeBook.push('as book')

console.log(book.name)            // js book
console.log(book.alikeBook)       // ["css book, html book", "xml book", "as book"]
console.log(newBook.name)         // ajax book
console.log(newBook.alikeBook)    // ["css book, html book", "xml book", "as book"]
console.log(otherBook.name)       // flash book
console.log(otherBook.alikeBook)  // ["css book, html book", "xml book", "as book"]

原型式繼承是對類式繼承的一個封裝,其實其中的過渡物件就相當於類式繼承中的子類,不過在原型式中作為一個過渡物件出現的,目的是為了建立要返回的新的例項化物件。

 

5.如虎添翼——寄生式繼承

var book = {
    name: 'js book',
    alikeBook: ['css book', 'html book']
}

function createBook(obj) {
    var o = new inheritObject(obj)
    o.getName = function () {
        console.log(name)
    }
    return o
}

寄生式繼承就是對原型繼承的第二次封裝,並且在這第二次封裝過程中對繼承的方法進行了拓展,這樣新建立的物件不僅僅有父類中的屬性和方法,而且還新增新的屬性和方法。

 

6.終極繼承者——寄生組合式繼承

function inheritObject(o) {
    function F() {}
    F.prototype = o
    return new F()
}
function inheritPrototype(subClass, superClass) {
    var p = inheritObject(superClass.prototype)
    p.constructor = subClass
    subClass.prototype = p
}
//定義父類
function SuperClass(name) {
    this.name = name
    this.colors = ['red', 'blue', 'green']
}
// 定義父類原型方法
SuperClass.prototype.getName = function () {
    console.log(this.name)
}
// 定義子類
function SubClass(name, time) {
    SuperClass.call(this, name)
    this.time = time
}
// 寄生式繼承父類原型
inheritPrototype(SubClass, SuperClass)
// 子類新增原型方法
SubClass.prototype.getTime = function () {
    console.log(this.time)
}
var instance1 = new SubClass('js book', 2014)
var instance2 = new SubClass('css book', 2013)

instance1.colors.push('black')
console.log(instance1.colors)   // ["red", "blue", "green", "black"]
console.log(instance2.colors)   // ["red", "blue", "green"]
instance2.getName()             // css book
instance2.getTime()             // 2013