設(shè)計(jì)模式與實(shí)踐

多態(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。

  • 借用其他對象的方法
  1. 借用構(gòu)造函數(shù)
var A = function(name) {
  this.name = name
}

var B = function() {
  
}
  • 閉包
    閉包和變量作用域,生存周期密切相關(guān)。函數(shù)執(zhí)行會形成作用域,變量的搜索會隨著執(zhí)行函數(shù)的作用域鏈逐層搜索.
    var Tv = {
    open: function() {

    }
    }

  • 高階函數(shù)

  1. 函數(shù)可以作為參數(shù)被傳遞
  2. 函數(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è)好處:

  1. 用戶可以放心的使用代理,它只關(guān)心是否能得到想要的結(jié)果。
  2. 在任何使用本體的地方都可以替換成使用代理

在java等語言中,代理和本體都需要顯示的實(shí)現(xiàn)同一個(gè)接口,一方面接口保證了它們會使用同樣的方法,另一方面,通過接口向上轉(zhuǎn)型,避開編譯器的類型檢查,代理和本體將來可以被替換使用。

  • 虛擬代理合并請求
  • 虛擬代理在惰性加載中的應(yīng)用
    未真正加載js之前的代碼
let cache = []
let minConsole = {
  log: (...args) => {cache.push(() => {  })}
}

當(dāng)用戶按下f2的時(shí)候

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容