參考:阮一峰老師的博客
Javascript是一種基于對(duì)象(object-based)的語言,我們遇到的所有東西幾乎都是對(duì)象。但是,它又不是一種真正的面向?qū)ο缶幊蹋∣OP)語言,因?yàn)樗恼Z法中沒有class(類)的概念。那么,如果我們要把"屬性"(property)和"方法"(method),封裝成一個(gè)對(duì)象,甚至要從原型對(duì)象生成一個(gè)實(shí)例對(duì)象,我們應(yīng)該怎么做呢?
1. 工廠模式(改進(jìn)的原始模式)
這種模式抽象了創(chuàng)建具體對(duì)象的過程,用函數(shù)來封裝以特定接口創(chuàng)建對(duì)象的細(xì)節(jié):
const creatPerson = function(name, age) {
return {
name: name,
age: age,
sayName: function() {
console.log(this.name)
},
}
}
// 生成實(shí)例對(duì)象的過程其實(shí)就是調(diào)用這個(gè)函數(shù)
const xcmy = creatPerson('小明', 23)
const xchw = creatPerson('小花', 22)
xcmy.sayName() // 小明
console.log(xchw.age) // 22
弊端
工廠模式雖然解決了創(chuàng)建多個(gè)相似對(duì)象的問題,但卻沒有解決對(duì)象識(shí)別的問題(即怎樣知道一個(gè)對(duì)象的類型),也不能反映實(shí)例之間的聯(lián)系
構(gòu)造函數(shù)模式(構(gòu)造器模式)
為了解決從原型對(duì)象生成實(shí)例的問題,Javascript提供了一個(gè)構(gòu)造函數(shù)(Constructor)模式。所謂"構(gòu)造函數(shù)",其實(shí)就是一個(gè)普通函數(shù),但是內(nèi)部使用了this變量。對(duì)構(gòu)造函數(shù)使用new運(yùn)算符,就能生成實(shí)例,并且this變量會(huì)綁定在實(shí)例對(duì)象上。重寫 Person:
const Person = function(name, age) {
this.name = name
this.age = age
this.sayName = function() {
console.log(this.name)
}
}
// 生成實(shí)例就是 new Person,
// 現(xiàn)在的 Person 函數(shù)叫做構(gòu)造函數(shù)
const xcmy = new Person('小明', 23)
const xchw = new Person('小花', 22)
xcmy.sayName() // 小明
console.log(xchw.age) // 22
這時(shí)xcmy和xchw會(huì)自動(dòng)含有一個(gè)constructor屬性,指向它們的構(gòu)造函數(shù)。
console.log(xcmy.constructor == Person); //true
console.log(xchw.constructor == Person); //true
創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來可以將它的實(shí)例標(biāo)識(shí)為一種特定的類型,而這正是構(gòu)造函數(shù)模式勝過工廠模式的地方。
Javascript 還提供了一個(gè)instanceof運(yùn)算符,驗(yàn)證原型對(duì)象與實(shí)例對(duì)象之間的關(guān)系。
console.log(xcmy instanceof Person); //true
console.log(xchw instanceof Person); //true
使用 new 創(chuàng)建實(shí)例對(duì)象發(fā)生了什么?
要?jiǎng)?chuàng)建 Person 的新實(shí)例,必須使用 new 操作符。以這種方式調(diào)用構(gòu)造函數(shù)實(shí)際上會(huì)經(jīng)歷以下 4個(gè)步驟:
- 創(chuàng)建一個(gè)新對(duì)象;
- 將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此 this 就指向了這個(gè)新對(duì)象);
- 執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性);
- 返回新對(duì)象。
創(chuàng)建自定義的構(gòu)造函數(shù)意味著將來可以將它的實(shí)例標(biāo)識(shí)為一種特定的類型;而這正是構(gòu)造函數(shù)模式勝過工廠模式的地方。
弊端
每個(gè)用 new 創(chuàng)建出來的實(shí)例對(duì)象都擁有同樣的屬性和方法,但是方法(method) 對(duì)于每個(gè)實(shí)例都是一模一樣的內(nèi)容,每次生成一個(gè)實(shí)例,都必須重復(fù)同樣的內(nèi)容,多占用一些內(nèi)存。這樣既不環(huán)保,也缺乏效率
原型模式(Prototype模式)
Javascript規(guī)定,每一個(gè)構(gòu)造函數(shù)都有一個(gè)prototype屬性,指向另一個(gè)對(duì)象。這個(gè)對(duì)象的所有屬性和方法,都會(huì)被構(gòu)造函數(shù)的實(shí)例繼承。這意味著,我們可以把那些不變的屬性和方法,直接定義在prototype對(duì)象上。
const Person = function(name, age) {
this.name = name
this.age = age
}
Person.prototype.sayName = function() {
console.log(this.name)
}
// 生成實(shí)例就是 new Person,
// 現(xiàn)在的 Person 函數(shù)叫做構(gòu)造函數(shù)
const xcmy = new Person('小明', 23)
const xchw = new Person('小花', 22)
xcmy.sayName() // 小明
console.log(xchw.age) // 22
console.log(xcmy.sayName === xchw.sayName) // true
這時(shí)所有實(shí)例的sayName()方法,其實(shí)都是同一個(gè)內(nèi)存地址,指向 prototype 對(duì)象,因此就提高了運(yùn)行效率。
Prototype模式的驗(yàn)證方法
為了配合 prototype 屬性,Javascript 定義了一些輔助方法,幫助我們使用它。
isPrototypeOf()
這個(gè)方法用來判斷,某個(gè)proptotype對(duì)象和某個(gè)實(shí)例之間的關(guān)系。
console.log(Person.prototype.isPrototypeOf(xcmy)); //true
console.log(Person.prototype.isPrototypeOf(xchw)); //true
hasOwnProperty()
每個(gè)實(shí)例對(duì)象都有一個(gè)hasOwnProperty()方法,用來判斷某一個(gè)屬性到底是本地屬性,還是繼承自prototype對(duì)象的屬性。
console.log(xcmy.hasOwnProperty("name")) // true
console.log(xcmy.hasOwnProperty("sayName")) // false
in運(yùn)算符
in運(yùn)算符可以用來判斷,某個(gè)實(shí)例是否含有某個(gè)屬性,不管是不是本地屬性。
console.log("name" in xcmy); // true
console.log("sayName" in xcmy); // true
in運(yùn)算符還可以用來遍歷某個(gè)對(duì)象的所有屬性。
for(let prop in xcmy) {
console.log("xcmy[" + prop + "]=" + xcmy[prop])
}