多態(tài):同一操作作用于不同對象上面,可以產(chǎn)生不同的解釋和不同的執(zhí)行結(jié)果。給不同的對象發(fā)送同一個(gè)消息,這些對象會根據(jù)消息給出不同的反饋。
首先我們把不變的部分抽離出來,就是所有動物都會發(fā)出叫聲
let makeSound = (animal) => { animal.sound() }
var Duck = function () {}
Duck.prototype.sound = () => { console.log(ggg) }
class Duck {
sound () {
}
}
- 類型檢查和多態(tài)
現(xiàn)在使用java實(shí)現(xiàn)
public class Duck {
public void makeSound() {
// ggg
}
}
public class AnimalSound {
public void makeSound( Duck duck ) {
duck.makeSound();
}
}
此時(shí),我們只能讓鴨叫,為了解決這種問題,靜態(tài)類型的面向?qū)ο笳Z言可以設(shè)計(jì)為向上轉(zhuǎn)型;當(dāng)給一個(gè)類變量賦值時(shí),這個(gè)變量的類型既可以這個(gè)類本身,也可以是使用這個(gè)類的超類。
使用繼承得到多態(tài)效果,是讓對象表現(xiàn)出多態(tài)性的最常用手段。
我們首先創(chuàng)建一個(gè)Animal抽象類,再分別讓Duck和chicken都繼承自Animal抽象類
js的根對象是Object.prototype
- 對象沒有原型,只能說對象的構(gòu)造器有原型,對象把請求委托給構(gòu)造器的原型,
js給對象提供了一個(gè)proto的隱藏屬性,proto默認(rèn)會指向它的構(gòu)造器的原型對象,proto就是就是對象和構(gòu)造器原型對象聯(lián)系起來的紐帶。
當(dāng)對象a需要繼承對象b的能力時(shí),可以讓a的構(gòu)造器的原型指向?qū)ο骲,達(dá)到繼承的效果
var obj = {}
function A() {}
A.prototype = obj
當(dāng)我們期望得到一個(gè)類繼承另一個(gè)類的時(shí)候的時(shí)候
function A () {}
A.prototype = { name: 'hhh' }
function B() {}
B.prototype = new A()
實(shí)例在查找屬性時(shí),先查找實(shí)例本身,在查找實(shí)例的構(gòu)造函數(shù),在查找構(gòu)造函數(shù)的原型繼承的構(gòu)造函數(shù)的實(shí)例。。。。
原型鏈并不是無限長,終點(diǎn)是Object.prototype = undefined
通過Object.create(null)可以創(chuàng)建一個(gè)沒有原型對象的對象
this, call, apply
js的this總是指向一個(gè)對象,而具體指向哪個(gè)對象是在運(yùn)行時(shí)基于函數(shù)的執(zhí)行環(huán)境動態(tài)綁定的,而非函數(shù)被聲明時(shí)的環(huán)境。
func.apply(null, [1, 2, ,3])
func.call(null, 1, 2, 3) 改變函數(shù)里的this,后續(xù)的參數(shù)是func執(zhí)行時(shí)的參數(shù)
第一個(gè)參數(shù)代表this在func中的指向
如果是null,瀏覽器默認(rèn)指向null
document.getElementById = () => {}
func.prototype.bind = function(context) {
return () => {
this.call(context, ...arguments)
}
}
在bind函數(shù)內(nèi)部,先把func函數(shù)引用保存,然后返回一個(gè)新的函數(shù),執(zhí)行func的時(shí)候,實(shí)際上執(zhí)行的是新函數(shù)。在執(zhí)行的內(nèi)部才是執(zhí)行原來的func函數(shù),并且指定context對象為func函數(shù)體內(nèi)的this。
- 借用其他對象的方法
- 借用構(gòu)造函數(shù)
var A = function(name) {
this.name = name
}
var B = function() {
}
-
閉包
閉包和變量作用域,生存周期密切相關(guān)。函數(shù)執(zhí)行會形成作用域,變量的搜索會隨著執(zhí)行函數(shù)的作用域鏈逐層搜索.
var Tv = {
open: function() {}
} 高階函數(shù)
- 函數(shù)可以作為參數(shù)被傳遞
- 函數(shù)可以作為返回值輸出
判斷數(shù)據(jù)類型
var isString = (obj) => Object.prototype.toString.call(obj) == '[object string]'
var isArray = (obj) => Object.prototype.toString.call(obj) == '[Object array]'
var isNumber = (obj) => Object.prototype.toString.call(obj) == '[Object Number]'單例模式
var getString = function(fn) {
var ret;
return function() {
return ret || (ret = fn.apply(this,arguments))
}
}
- 高階函數(shù)實(shí)現(xiàn)aop
在js中實(shí)現(xiàn)aop,都是把一個(gè)函數(shù)“動態(tài)植入”到另一個(gè)函數(shù)中,我們使用原型擴(kuò)展
Function.prototypr.before = function(beforefn) {
var _self = this; // 這_sel里的this指向調(diào)用的對象
return function() {
beforeFn()
return _self.apply(this)
}
}
var func = function() {
console.log(2)
}
func = func.before(function() {
console.log(1)
}).after(function() { console.log(3) })
func()
調(diào)用before的是func這個(gè)函數(shù)
curry
let cost = (function(){
var args = []
return () => {
}
})()
var currying = function(fn) {
var args = []
return fucntion() {
// 根據(jù)參數(shù)判斷是求值還是保存值
}
}
Function.prototype.uncurry = function() {
var self = this;
return function() {
var obj = Array.prototype.shift.call(arguments)
return self.apply(obj)
}
}
- 函數(shù)節(jié)流
面臨的問題是函數(shù)被觸發(fā)的頻率太高。
throttle函數(shù)的原理是將即將被執(zhí)行的函數(shù)用settimeout延遲一段時(shí)間執(zhí)行。如果該次延遲執(zhí)行還沒有完成,則忽略接下來的函數(shù)請求。
var throttle = (fn, interval) => {
var self = fn;
timer, // 用一個(gè)標(biāo)識標(biāo)識定時(shí)器是否結(jié)束
firsttime
return (...args) => {
if (firsttime) {
self()
return firsttime = false
}
// 如果定時(shí)器存在,說明上一次的執(zhí)行還沒有結(jié)束
if (timer) {
return false
}
timer = setTimeout (() => {
// 每次定時(shí)器一執(zhí)行就清除當(dāng)前定時(shí)器
clearTimeout(timer)
timer = null;
}, interval || 500)
}
}
- 分時(shí)函數(shù)
函數(shù)被頻繁調(diào)用時(shí),可以使用節(jié)流,當(dāng)一個(gè)列表中有成千上百的好友時(shí),當(dāng)創(chuàng)建列表時(shí),要創(chuàng)建成千上百個(gè)的節(jié)點(diǎn)會讓瀏覽器卡死
所以我們需要一個(gè)timechunk函數(shù),它可以讓創(chuàng)建節(jié)點(diǎn)的工作分批進(jìn)行,比如1s創(chuàng)建一千個(gè),改成每隔200ms創(chuàng)建8個(gè)節(jié)點(diǎn)。
var timeChunk = (ary, fn, count) => {
var obj, t;
var len=ary.length
var start = () => { }
return () => {
t = serInterval(() => {
if (!ary.length) { claearInterval(t) }
})
}
}
- 單例模式
單例模式的定義是:一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局的訪問點(diǎn)
線程池,全局緩存,瀏覽器中的window對象唯一的登錄浮窗,用一個(gè)變量標(biāo)志當(dāng)前是否已經(jīng)為某個(gè)類創(chuàng)建過對象,下一次獲取類的實(shí)例時(shí),直接返回之前創(chuàng)建的對象
// 通過閉包保存單例標(biāo)識
function A (name) { this.name = name }
A.prototype.getIns = (() => { var ins = null;return (name) => {
if (!ins) { ins = new A(name) } return ins
}})()
- 透明的單例模式
var Create = (() => {
var instance;
// 構(gòu)造函數(shù)執(zhí)行的時(shí)候,如果存在實(shí)例,則直接返回實(shí)例
var Create = function (html) {
if (instance) return instance
this.html = html
this.init = ()
return instance = this
}
Create.prototype.init = () => { var div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
return Create
})()
var a = new Create('sc')
var b = new Create('sv')
- 將創(chuàng)建instance從構(gòu)造函數(shù)中抽離出來
var proxyCreate = (() => { var instance = null; return (html) => {
van instance;
if (!instance) instance = new Create(html)
return instance
} })()
策略模式
本質(zhì)是多種途徑達(dá)到目的地
策略模式的定義是:定義一系列算法,把它們一個(gè)個(gè)封裝起來,并且可以使它們相互替換。使用策略模式計(jì)算獎金
一個(gè)策略模式至少由兩部分組成。第一部分是策略類,策略類封裝了具體的算法,并負(fù)責(zé)具體的計(jì)算過程。第二部分是環(huán)境類context,context接受客戶的請求,隨后把請求委托給策略類。context中要維持對某個(gè)策略對象的引用。
首先我們封裝策略類,將具體的計(jì)算過程封裝在策略類中.
var performanceS = function() {}
performanceS.prototype.calculate = function(salary) {
return salary * 4;
}
var performanceA = function() {}
performanceS.prototype.calculate = function(salary) {
return salary * 3;
}
var performanceB = function() {}
performanceS.prototype.calculate = function(salary) {
return salary * 2;
}
vae Bonus = function () {
this.salary = null;
this.stratery = null;
}
Bonus.prototype.setSalary = function(salary) { this.salary = salary }
Bonus.prototype.setStratery = function(stratery) { this.stratery = stratery }
Bonus.prototype.getBonus = function() { return this.stratery.calculate(this.salary) }
通過使用策略模式重構(gòu)的代碼,我們消除了源程序中大片的條件分支語句。
實(shí)現(xiàn)動畫效果的原理
動畫片是把一些差距不大的原畫以較快的幀數(shù)播放表單校驗(yàn)
首先把校驗(yàn)封裝成對象
var strategies = {
isNoneEmpty: (value, errorMsg) => { if (!value) return errorMsg},
minLength: () =>
}
var validataFunc = () => {
var validator = new Validator()
validator.add(regis.userName, 'isNoneEmpty', 'userName不能為空')
var errorMsg = validator.start()
return errorMsg
}
當(dāng)我們往validator對象里添加完一系列的校驗(yàn)規(guī)則之后,通過start方法來啟動校驗(yàn),
var Validator = function(){ this.cache = [] }
Validator.prototype.add = (dom, rule, errorMsg) => {
var ary = rule
}
Validator.prototype.start = () => {
for (var i=0, validatorFunc; )
}
- 代理模式
代理模式是為一個(gè)對象提供一個(gè)代用品或者占位符
var Flower = function(){}
var xiaoming = {
sendFlower: function(target) {
var flower = new Flower()
target.receiveFlower(flower)
}
}
var B = {
receiveFlower: (flower) => {
A.listenGoodMood(() => {
A.receiveFlower(flower)
})
}
}
xiaoming.sendFlower(B)
保護(hù)代理和虛擬代理
代理B可以幫助代理A過濾掉一些請求,比如年齡太大,這種請求可以直接在代理B中被拒絕。這種代理叫保護(hù)代理。
另外,假設(shè)現(xiàn)實(shí)中的花價(jià)格不菲,new Flower也是一個(gè)代價(jià)昂貴的操作,可以放到A心情好的時(shí)候去創(chuàng)建。虛擬代理可以把一些開銷很大的操作,延遲到真正需要的時(shí)候去創(chuàng)建。使用虛擬代理實(shí)現(xiàn)圖片的預(yù)加載
代理和本體接口的一致性
如果有一天,我們不需要再預(yù)加載,那么就不再需要代理對象,可以直接選擇請求本體。其中關(guān)鍵是代理對象和本體都對外提供了setSrc方法,在客戶看來,代理對象和本體是一致的,代理接手請求的過程對于用戶透明,用戶不清楚代理和本體的區(qū)別,這樣有兩個(gè)好處:
- 用戶可以放心的使用代理,它只關(guān)心是否能得到想要的結(jié)果。
- 在任何使用本體的地方都可以替換成使用代理
在java等語言中,代理和本體都需要顯示的實(shí)現(xiàn)同一個(gè)接口,一方面接口保證了它們會使用同樣的方法,另一方面,通過接口向上轉(zhuǎn)型,避開編譯器的類型檢查,代理和本體將來可以被替換使用。
- 虛擬代理合并請求
- 虛擬代理在惰性加載中的應(yīng)用
未真正加載js之前的代碼
let cache = []
let minConsole = {
log: (...args) => {cache.push(() => { })}
}
當(dāng)用戶按下f2的時(shí)候