多態(tài)
同一個(gè)操作作用于不同的對(duì)象积暖,得到不同的結(jié)果。
重點(diǎn)分離:做什么 和 誰去做怪与。
鴨子辯型:如果它走起路來像鴨子夺刑,叫起來也是鴨子,那么它就是鴨子分别。
// 做什么
function sayName( people ) {
people.sayName()
}
// 誰去做
const xiaoming = {
name: '小明',
sayName() {
console.log( this.name )
}
}
const lihong = {
name: '李紅',
sayName() {
console.log( this.name )
}
}
// 同一個(gè)操作作用在不同的對(duì)象
sayName( xiaoming ) // 小明
sayName( lihong ) // 李紅
封裝
只讓別人知道你想讓別人知道的遍愿,不讓別人知道你不想讓別人知道的。
封裝的目的是將信息隱藏耘斩,數(shù)據(jù)沼填、類型、實(shí)現(xiàn)括授、變化都能封裝倾哺。
- 封裝數(shù)據(jù)
Javascript 中要達(dá)到封裝的目的轧邪,只有通過創(chuàng)建作用域來達(dá)到隱藏的目的。
const xiaoming = ( function() {
// 封裝私有數(shù)據(jù)羞海,變量命名前綴加_下劃線是潛規(guī)則,表示是私有的
const _name = '小明',
_age = 22
// 向外界提供公共方法
return {
getName() { return _name },
getAge() { return _age }
}
})()
xiaoming.getName() // '小明'
xiaoming.getAge() // 22
xiaoming._name // undefined
xiaoming._age // undefined
- 封裝類型
Javascript是弱類型語(yǔ)言曲管,封裝類型沒多大意義却邓。(完)
- 封裝實(shí)現(xiàn)
對(duì)象只需要對(duì)外提供一個(gè)固定的接口,而內(nèi)部如何實(shí)現(xiàn)這個(gè)接口院水,對(duì)外界來說應(yīng)該是透明的腊徙,外界不用關(guān)心這個(gè)對(duì)象內(nèi)部如何去實(shí)現(xiàn)這個(gè)接口,外界只需要調(diào)用這個(gè)接口就行了檬某。只要這個(gè)接口不變撬腾,內(nèi)部的實(shí)現(xiàn)如何去改變都不會(huì)影響外界。只要你打一個(gè)電話給我恢恼,你都會(huì)得到我送給你的一顆同品牌同口味同款式的棒棒糖民傻,你不用管我怎么得到這顆棒棒糖,你想吃的時(shí)候只需要打一個(gè)電話給我就好了嘿嘿嘿场斑。
/**
* 有一個(gè) obj對(duì)象漓踢,這個(gè)對(duì)象有一個(gè)接口
* 外界只要調(diào)用這個(gè)接口,就會(huì)返回一個(gè)數(shù)字 666
* 接口的實(shí)現(xiàn)對(duì)于外界來說是透明的漏隐,外界只需調(diào)用接口
*/
const obj = ( function() {
let _num = 666
return {
generate666() { return _num }
}
})()
// 外界只需要調(diào)用這個(gè)對(duì)象的generate666接口就行了
obj.generate666() // 666
/**
* 某天這個(gè)對(duì)象的內(nèi)部實(shí)現(xiàn)改了喧半,但是接口沒有改
* 依舊是對(duì)外返回一個(gè)數(shù)字 666
*/
const obj = ( function() {
let _num = 600 + 60 + 6
return {
generate666() { return _num }
}
})()
// 外界依舊只是調(diào)用這個(gè)對(duì)象的generate666接口就行了
obj.generate666() // 666
- 封裝變化:
考慮你的設(shè)計(jì)中哪些地方可能變化,這種方式與關(guān)注會(huì)導(dǎo)致重新設(shè)計(jì)的原因相反青责。它不是考慮什么時(shí)候會(huì)迫使你的設(shè)計(jì)改變挺据,而是考慮你怎樣才能夠在不重新設(shè)計(jì)的情況下進(jìn)行改變。這里的關(guān)鍵在于封裝發(fā)生變化的概念脖隶,這是許多設(shè)計(jì)模式的主題扁耐。
----《設(shè)計(jì)模式》
找到變化的東西,封裝起來浩村,和不變的分開做葵。這樣就能夠容易地把變化的東西替換掉。
繼承
Javascript 是基于原型的語(yǔ)言心墅,沒有類的概念酿矢。但是你可以用類的概念去理解,實(shí)際上在 Javascript 中怎燥,“類”也是一個(gè)對(duì)象瘫筐。
原型模式是一種設(shè)計(jì)模式,也是一種編程泛型铐姚。
在諸如 JAVA 或者 Python 這種以類為中心的面向?qū)ο蟮恼Z(yǔ)言來說策肝,類和對(duì)象的關(guān)系就像是設(shè)計(jì)圖和成品的關(guān)系肛捍。要?jiǎng)?chuàng)造成品(對(duì)象),就先設(shè)計(jì)圖紙(類)之众,然后按照?qǐng)D紙(類)制造出(new)成品(對(duì)象)拙毫。
而在 Javascript 中,雖然有 new 這個(gè)關(guān)鍵字棺禾,但是他做的事情和 JAVA 或者 Python 做的事情不一樣缀蹄。
Javascript 中 new 做的事情是通過找到一個(gè)對(duì)象的原型進(jìn)行克隆,獲得新對(duì)象膘婶。
Javascript 中絕大部分?jǐn)?shù)據(jù)都是對(duì)象缺前,根對(duì)象是 Object.prototype
,所有對(duì)象都克隆這個(gè)根對(duì)象悬襟。
// 所有對(duì)象都克隆 Object.prototype
const o1 = {}
const o2 = new Object()
Object.getPrototypeOf( o1 ) === Object.prototype // true
Object.getPrototypeOf( o2 ) === Object.prototype // true
// 如果在 Object.prototype 上增加一個(gè)屬性衅码,那么所有對(duì)象都會(huì)增加這個(gè)屬性
Object.prototype.six = 666
const o3 = {}
const o4 = new Object()
const o5 = new Date()
const o6 = new Number( 66 )
o3.six // 666
o4.six // 666
o5.six // 666
o6.six // 666
要?jiǎng)?chuàng)建一個(gè)新對(duì)象,不是通過實(shí)例化類脊岳,而是找到一個(gè)原型對(duì)象進(jìn)行克隆逝段,得到新對(duì)象。
而在現(xiàn)代瀏覽器中逸绎,都會(huì)向外暴露一個(gè) __proto__
屬性(在ECMA規(guī)范中的描述是 [[ Prototype ]]
)惹恃,這個(gè)屬性指向這個(gè)對(duì)象的原型對(duì)象。原型對(duì)象會(huì)有一個(gè) constructor
屬性來存儲(chǔ)它的構(gòu)造函數(shù)名棺牧。
這樣在訪問一個(gè)對(duì)象的時(shí)候旨巷,如果本身有屬性截歉,就返回本身的屬性肪跋,如果沒有腊脱,對(duì)象會(huì)去它的原型對(duì)象上找這個(gè)屬性,如果原型對(duì)象上還沒有乏悄,又會(huì)去原型對(duì)象的原型對(duì)象找浙值,一層一層找,如果你愿意一層一層一層地?fù)荛_原型對(duì)象的屬性檩小,直到盡頭 null
开呐。如果找不到了,你會(huì)發(fā)現(xiàn)规求,你會(huì)流淚筐付,就返回 undefined
。這不就是原型鏈的原理嗎嘿嘿嘿阻肿。
這個(gè)克隆的過程是引擎幫忙實(shí)現(xiàn)的瓦戚。但是我們也可以實(shí)現(xiàn)一下 new
的過程。
function A() {
this.ownNum = 666
this.ownFn = function() { console.log(123) }
}
A.prototype.protoNum = 888
A.prototype.protoFn = function() { console.log(456) }
A.prototype.constructor = A // 隱含屬性丛塌,無需設(shè)置
const a = new A()
a.constructor === A // true
a.__proto__ === A.prototype // true
/** new 的過程 **/
// 克隆 Object.prototype
const o1 = new Object()
// 讓 o1 的 [[ Prototype ]] 指針指向一個(gè)原型對(duì)象
o1.__proto__ = A.prototype
// A.call( o1 ):給 o1 設(shè)置新的屬性较解,o1可能被改變(取決于 A構(gòu)造函數(shù) 中是否使用了 this)
// o2 = A.call( o1 ):A 構(gòu)造函數(shù)可能直接 return 一個(gè)新對(duì)象畜疾,而不是默認(rèn) return 構(gòu)造出來的 o1 對(duì)象
// 比如 function A() { ... return {b: 9} },那么在 new A() 之后返回的是 { b: 9 }印衔,而不是默認(rèn)的 o1
const o2 = A.call( o1 )
// 總是返回一個(gè)這個(gè)新建的對(duì)象
// 無論是 o1 還是 { b: 9 }啡捶,如果有 { b: 9 },那么這個(gè)的優(yōu)先級(jí)是最高的
return typeof o2 === 'object' ? o2 : o1
而這上述所有過程涉及到的繼承是這樣的:(每一個(gè)函數(shù)都有 prototype
屬性)
/*-------------------< 代碼的層面 >---------------------*/
// 引擎實(shí)現(xiàn)的代碼 [native code]
function Object() {}
Object.prototype.xx = xx
// 引擎實(shí)現(xiàn)的代碼 [native code]
function Function() {}
Function.prototype.yy = yy
// 用戶編寫的代碼
function A() {}
A.prototype.zz = zz
// new一個(gè)對(duì)象
const a = new A()
/*-------------------< 代碼的層面 />---------------------*/
/*-------------------< 背后的邏輯 >---------------------*/
a.__proto__ => A.prototype
A.__proto__ => Function.prototype // typeof A === 'function'
A.prototype.constructor => A
A.prototype.__proto__ => Object.prototype // typeof A.prototype === 'object'
Function.__proto__ => Object.prototype
Function.prototype.constructor => Function
Function.prototype.__proto__ => Object.prototype // typeof Function.prototype === 'function'
Object.__proto__ => Function.prototype // typeof Object === 'function'
Object.prototype.constructor => Object
Object.prototype.__proto__ => null
/*-------------------< 背后的邏輯 />---------------------*/
在訪問 a
的時(shí)候奸焙,如果本身有想要的屬性届慈,就會(huì)返回本身的屬性,此時(shí)用 a.hasOwnProperty( 'a自有屬性' )
進(jìn)行屬性檢測(cè)是返回 true
忿偷。如果 a
本身的自有屬性沒有,此時(shí)就展現(xiàn)原型鏈的作用了臊泌。a
會(huì)通過 a.__proto__
訪問到 A.prototype
鲤桥,在 A.prototype
找到了就返回,如果還沒找到渠概,就繼續(xù)一直往下找茶凳,直到終點(diǎn) null
,還沒找到播揪,就返回 undefined
贮喧。
可以說,最初的時(shí)候猪狈,每一個(gè)對(duì)象都是一樣的箱沦,都是從 Object.prototype
上克隆而來,都是空白的雇庙。但是 Javascript 的世界是繽紛多彩的谓形,大家都一樣就沒什么意思了。
于是有些對(duì)象被人為(引擎)增加了 length
長(zhǎng)度的數(shù)據(jù)屬性疆前,有了 push
寒跳、shift
等方法屬性,再用語(yǔ)法層面的 []
符號(hào)表示竹椒,就成為了定義中的數(shù)組童太,即Array
function Array() {
this.length = xxx
}
Array.prototype.push = function() {}
Array.prototype.shift = function() {}
var arr = []
// 等價(jià)
var arr = new Array()
是不是和 function A() {}
的套路很像?是的胸完。
同樣道理书释,Date
對(duì)象也是如此,因?yàn)檫@個(gè)對(duì)象有了 GMT
時(shí)間的數(shù)據(jù)屬性等舶吗,又有了 getDay
征冷、getMonth
等方法,就成了定義中的日期對(duì)象誓琼。
Javascript 的繼承的另一種理解检激,更像是“借用”肴捉。類似 Object.prototype.toString.call( ... )
。
繼承的本質(zhì)是改變這個(gè)對(duì)象的 __proto__
指向叔收。
一個(gè)對(duì)象有兩種屬性齿穗,自有屬性和繼承屬性。自有屬性就是本身自己身上有的東西饺律,訪問之后直接就給你返回的窃页,繼承屬性其實(shí)算是借用的屬性,是通過訪問自身的 __proto__
的指向?qū)ο髮傩愿幢簦@時(shí)候已經(jīng)是在訪問其他對(duì)象了脖卖,不是自己的屬性。
就好比如兩個(gè)對(duì)象其實(shí)是不認(rèn)識(shí)巧颈,不相關(guān)的畦木,但是你把 A對(duì)象 的 proto 屬性改為 B對(duì)象,這兩個(gè)對(duì)象就產(chǎn)生了關(guān)系砸泛,這時(shí)候你也可以稱為繼承十籍。
這種理解和其他基于 CLASS 類與對(duì)象的繼承有很大的區(qū)別。
此外唇礁,繼承也是代碼重用的手段勾栗,Javascript 中的繼承不是通過嚴(yán)格意義上的拓展父類來實(shí)現(xiàn)的,而是通過原型實(shí)現(xiàn)的盏筐。
形如 Photoshop围俘,一般都不會(huì)直接操作原始圖層,都會(huì)復(fù)制(克禄稀)一份圖層楷拳,在復(fù)制的圖層上進(jìn)行編輯。