JS中的類很難嗎?

魔爭發表於2018-02-05

什麼是Class 類?

MDN上說:類定義物件的特徵。它是物件的屬性和方法的模板定義。
簡單說,“類”是生產物件的模板,通過類這個模板,可以毫不費勁地生產出無數個一樣的物件,而不用通過一次次的定義去宣告物件。而這些物件,因為具有一樣的屬性、一樣的方法,所以將這些物件歸為一個“類”,就像將人類歸入人這一類一樣。

JavaScript 的類

在es 6 出現之前,ECMAScript 標準中都是沒有類的官方規範的,JavaScript 的類都是通過其他的方法來模擬定義。直到ES 6 標準的到來,JavaScript 才擁有官方的定義類的方法。

定義類的方法

1. 建構函式法

建構函式法使用建構函式來模擬“類”,使用 this 在建構函式內部指代例項物件。

function Person(){
    this.species = 'human'
}
複製程式碼

定義一個建構函式之後,使用 new 關鍵字來生成例項物件

let xxx = new Person
console.log(xxx)     // {species: 'human'}
複製程式碼

像上面定義的建構函式 Person,可以使用 new 關鍵字來生成無數個 擁有屬性 species = 'human' 的物件。
然而,使用這種方法構造物件,當構造物件的數量太多時,會極大地消耗記憶體,所以JavaScript提供了函式的prototype屬性來節約記憶體。

function Person(){

}

Person.prototype.species = 'human'

let xxx = new Person()
console.log(xxx)   // {}
複製程式碼

可以看到,使用 Person 的prototype屬性定義物件的公共屬性 species,依然可以生成一個物件。然而生成的卻是一個空物件。那麼species屬性去哪了?
species屬性跑到了例項物件 xxx 的原型上了:

console.log(xxx.species)      // human
console.log(xxx.__proto__)    // {species:human,constructor:function}
複製程式碼

通過建構函式的prototype屬性,可以將例項物件的公共屬性整合到一個原型物件上面,節約記憶體

建構函式法同時還可以實現物件的自有屬性和自有方法

function Person(){

}

Person.prototype.species = 'human'

let xxx = new Person()
xxx.abc = "abc"
console.log(xxx)   // {abc:abc}
複製程式碼

建構函式通過將值以引數的形式傳入函式內部,使構造出的例項物件具有不同的屬性值。

function Person(name,age){
    this.name = name
    this.age = age
}

Person.prototype.species = 'human'

let xxx = new Person(‘xiao’,18)
console.log(xxx)   // {name:xiao,age:18}
複製程式碼

那麼,使用建構函式法模擬類的流程是:

function 建構函式名(自有屬性值1,自有屬性值2,...){
    this.自有屬性1 = 自有屬性值1
    this.自有屬性2 = 自有屬性值2
}

建構函式名.prototype.xxx = xxx  // 設定建構函式的原型屬性
// 還可以直接往例項物件上新增自己的屬性
複製程式碼

2. Object.create() 實現類

Object.create()語法

Object.create(proto[, propertiesObject]) 引數:

  • proto:新建立物件的原型物件
  • propertiesObject:新建立物件的屬性配置。(如:是否可列舉、是否只寫等)
    返回值:
  • 返回新建立的物件。

使用Object.create()模擬類,是將一個物件直接作為新建立物件的原型,直接將原型植入新物件。
在這種方法中,“類”就是一個物件,而不是函式。

let Person = {
    species: 'human',
    walk: function(){},
    speak: function(){},
}

let xxx = Object.create(Person)
console.log(xxx)
複製程式碼

上面這段程式碼,以 Person 這個物件作為原型,生成一個新的空物件 xxx,xxx 的原型指向 Person。換言之,物件 Person 被當做了一個類,建立新的物件。

Object.create()模擬類的缺陷:

  • 例項物件的屬性全部在同一個”類“物件上面,只能例項物件名.屬性名 = 屬性值手動新增自有屬性和自有方法
  • 由於Object.create() 只是將建立的例項物件的原型繫結到一個”類“物件上面。一旦”類“物件發生改變,所有的例項物件的值都會改變。
  • 例項物件的共享資料全部繫結在”類“物件上面。

3. 極簡主義法

極簡主義法同樣使用一個物件作為”類“,在物件裡面,定義一個createNew方法來生成例項

let Person = {
    createNew: function(){},
}
複製程式碼

createNew方法裡面,定義一個例項物件作為返回值

let Person = {
    createNew: function(){
        let person = {}
        person.species = "human"
        person.walk = function(){}
        person.speak = function(){}
        return person
    },
}
複製程式碼

呼叫createNew方法,就可以得到一個新的物件

let xxx = Person.createNew()
console.log(xxx)  // {species: "human", walk: function, speak: function}
複製程式碼

極簡主義法的原理:使用一個物件作為原本,去複製完成另一個物件
事實上,極簡主義法的原理概念與Object.create()極為類似,兩個的唯一區別是:極簡主義法不會修改例項物件的原型,而Object.create()涉及到原型。兩者之間的公共屬性共享全部是通過操作“原本”來實現。

4. ES 6 的 class 宣告

ES 6 的 class 不是一個全新的類繼承模型,而是一個原有模型的語法糖。
ECMAScript2015 將 第一種:建構函式法 給官方化,定義一個 api 直接使用“類”。本質上, class 定義的“類”還是一個函式

class Person {
    constructor(name, age){
        this.name = name
        this.age = age
    }
    walk(){}
    speak(){}
}

let xxx = new Person('xiao',18)
typeof Person     // "function",Person 本質上還是一個函式
console.log(xxx)   // {name: "xiao", age: 18}
複製程式碼

用函式模擬一個類的過程(舉例)

假設現在在設計一款遊戲,需要生成許多小兵,就需要一個生成小兵的類。
使用函式來生成小兵

function createBing(id,hp){
    let bing = {}    //  建立一個空物件儲存小兵的屬性
    bing.id = id
    bing.hp = hp
    bing.attack = 5
    bing.walk= function(){console.log('walk')}
    return bing
}
複製程式碼

此時,呼叫函式 createBing 就能生成一個具有4個屬性的小兵物件。
此時,生成數量多的小兵時,會重複建立 hp 和 walk 這兩個屬性,浪費記憶體。JS 中有原型,可以將公共屬性繫結到原型上面。

// 首先需要一個原型物件,將公共屬性放到原型物件上面
bingPrototype = {
    attack: 5,
    walk: function(){console.log('walk')}
}

function creareBing(id, hp){
    let bing = {}

    bing.__proto__ = bingPrototype // 將原型屬性繫結到生成的物件上面

    bing.id = id
    bing.hp = hp

    return bing
}
複製程式碼

此時,呼叫 createBing 函式可以生成一個具有 id 和 hp 兩個屬性的小兵物件,attack 和 walk 被繫結到原型上面,所有小兵物件共享。

由於__proto__不是標準規範,所以使用另一個符合規範的方法,使用函式的 prototype 屬性和new關鍵字。
將例項物件的全部共有屬性繫結到生成例項物件的函式的prototype屬性上面,再用new關鍵字生成例項,可以直接將原型繫結到例項物件上。

function createBing(id, hp){
    this.id = id 
    this.hp = hp
}

createBing.prototype = {
    construcotr: createBing, // constrctor 是 prototype 的預設屬性,此寫法會覆蓋,所以要重新賦值
    attack: 5,
    walk: function(){console.log('walk')}
}
複製程式碼

至此,利用函式的prototypenew關鍵字,實現了用函式模擬類的目的。

相關文章