16種JavaScript設計模式(上)

簡介

設計模式的定義是:在面向對象軟件設計過程當中針對特定問題的優雅而簡潔的解決方案。javascript

就比如在足球比賽中咱們把「邊後衛快速突破後向球門方向傳出高球,中路隊員接應頭球攻門」這種戰術稱做「下底傳中」同樣是針對一些常見問題設計的解決方案。html

相信你們在程序開發中都會碰到相似的狀況:感受本身的代碼寫的不優雅,但不知道從如何下手去優化,雖然暫時解決了問題,可是卻留下了很大的風險,一旦趕上了一些詭異的bug或者新的需求變更,就會發現本身都已經理不清代碼邏輯了根本無從下手。經過學習設計模式能夠幫助咱們開拓思路,用更好的結構調整代碼。java

在學習設計模式以前有幾個基礎概念須要瞭解:設計模式

  1. this指向
  2. 閉包
  3. 高階函數
  4. 原型,原型鏈

(關於這幾個知識點網上已近有不少教程說起了。如下是我我的的一些總結但願用簡短的語言幫助你更好的理解他們)數組

基礎知識

  1. this指向

總結:「函數中的this基本都指向函數的調用者」bash

// 示例一
function demo1() {
    console.log(this)
}

// 示例二
var demo2 = {
    log() {
        console.log(this)
    }
}

// 示例三
var demo3= {
    log() {
        setTimeout(function print(){ // 給一個函數名方便理解
            console.log(this)
        },1000)
    }
}

demo1() // window;
demo2.log() // {log: f};
demo3.log() // window;
複製代碼
  1. demo1(): 能夠理解成window.demo1() 因此調用者是window, this =window
  2. demo2.log(): log方法的調用者是demo2 因此this=demo2
  3. demo3.log(): 本示例中print方法做爲參數傳給了setTimeout方法, 因此在調用demo3.log()時並未執行該方法,而是在1s的延時以後執行了print這是調用者又變成了window 因此this=window

既然有通常狀況,那確定也存在幾種特殊的狀況:閉包

// 第一種:new (做爲構造函數調用)
function Demo() {
    this.a = 1;
    console.log(this)
}
new Demo() // {a: 1}

// 第二種:apply、call
function log() {
    console.log(this, arguments)
}
var target = {a: 1}

log.apply(target, [1,2,3]) // {a:1} [1,2,3]
log.call(target, 1, 2, 3) // {a:1} [1,2,3]

// 第三種:bind、箭頭函數
log.bind(target)
log(1, 2, 3) // {a:1} [1,2,3]

// 使用以前的demo3作示例
var demo3= {
    log() {
        setTimeout((function print() {
            console.log(this)
        }).bind(this), 1000)
        // 等同於 setTimeout(() => console.log(this), 1000)
    }
}
demo3.log() // {log: f}
複製代碼
  1. 使用new關鍵字調用函數時會先聲明一個新的對象而後將函數中的this指這個新的對象,因此例子中能夠理解成先執行了this ={}而後再執行以後的內容app

  2. apply、call是兩個經常使用的改變函數this指向的方法,他們的第一個參數是替換this的對象能夠理解成先執行了this =target,區別在於第二個參數。apply的第二個參數是一個數組這個數組會拆一個個參數傳入函數([1,2,3]會變成log(1,2,3)),而call會將第二個及以後的參數做爲函數的參數傳入函數ide

  3. bind和箭頭函數實際上是一種方式(箭頭函數實際上是一種語法糖簡化了bind寫法), 因爲如今箭頭函數用的比較多因此放在一塊兒說下。 bind方法的做用是綁定函數的做用域可是不會當即執行這個函數,這也更符咱們的使用場景。咱們改寫了demo3的實現,在聲明print方法的同時使用bin方法綁定了this。當咱們調用demo3.log時,這時this指向調用者demo3,所bind中的this就是demo3,即便延時了1s執行this指向也不會由於調用者的改而改變函數

  4. 閉包

總結:「函數的內部變量被其內部函數暴露給外部對象使用」,在本質上,包就是將函數內部和函數外部鏈接起來的一座橋樑

做用:1. 持久保存變量 2. 隔離做用域

// 示例一
function home() {
    var computer = 'pc'
    console.log(computer, 'at home')
    return function online() {
        console.log(computer, 'online')
        return computer
    }
}
var online = home() // pc at home
var computer = online() // pc online
console.log(computer) // pc

// 示例二
var funs = []
var i=0 // 這樣寫便於理解i是聲明在window下的
for(; i<5; i++) {
    funs.push(function() {
        console.log(i)
    })
}
funs[0]() // 5
funs[1]() // 5
funs[2]() // 5
複製代碼
  1. 經過示例一咱們來舉個🌰:我家有一臺電腦,一般狀況下我都在家裏用電,但有時候我不在家也想用電腦怎麼辦。我能夠在第一次使用時把電腦連上網記下ip,之後出門在外也能夠遠程訪問到這臺電腦。 這個例子中computer變量是home函數的內部變量,它被內部函數online引用當執行home函數時返回了online函數,繼續調用online函數咱們就在外部獲得了home的內部變量computer。
  2. 示例二展現了一個咱們工做中常見的場景,咱們預期的值應該是0,1,2..實際結果都輸出了最後一次循環的值。緣由是這裏的i咱們是在window下聲明能夠,當咱們調用funs數組中的方法時,循環已經結束i的值已經變成了5。們能夠用閉包來解決這個問題:
var funs = []
var i=0
for(; i<5; i++) {
    (function parent(i){
        funs.push(function child() {
            console.log(i)
        })
    })(i)
}
funs[0]() // 0
funs[1]() // 1
funs[2]() // 2
複製代碼

咱們聲明瞭一個parent函數並當即調用了它,把i當作參數傳入做爲parent的部變量,並在child函數中調用i造成了一個閉包。

更詳細的內容能夠參考阮一峯老師寫的這篇文章

  1. 高階函數

總結:「將函數做爲參數或者將函數做爲返回值的函數」

高階函數的概念比較好理解,就是一類函數的代稱, 來看個簡單的例子

function delay (cb, time) {
    return function() {
        setTimeout(cb, time)
    }
}
複製代碼
  1. 原型,原型鏈

關於原型,原型鏈的知識點比較多若是是第一次接觸能夠先參考[這篇文章]www.cnblogs.com/onepixel/p/…) 總結:「每一個對象都有一個隱藏屬性__proto__指向他的構造函數(constructr)的原型對象(prototype),這種鏈式的關係稱做原型鏈」

function Person(){}
console.log(Person.prototype); 
// Object {constructor:function Person(),__proto__:Object}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";   Person.prototype.sayName = function(){
    console.log(this.name);
};

var person = new Person();
person.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
console.log(person1.sayName == person2.sayName); //true
console.log(person1.__proto__); //{name,age,job,sayname}
複製代碼

首先咱們先來了解下上文提到的三個單詞

prototype:

當建立一個函數時,會根據一組特定的規則爲該函數建立一個名爲prototype的原型對象,這個原型對象包含一個constructor屬性。

constructor:

原型對象都默認會有一個constructor(構造函數)屬性,這個屬性包含一個向 prototype 屬性所在函數的指針。就拿前面的例子來講, Person.prototype.constructor 指向 Person 。

__proto__:

js中全部對象都默認包含一個指針[[Prototype]] (內部屬性),指向構造函數的原型對象。雖然在js中沒有標準的方式訪問 [[Prototype]],但 Firefox、Safari 和 Chrome在每一個對象上都支持一個屬性__proto__。在上例中person1.__proto__指向構造函數的原型即Person.prototype,而Person.prototype.__proto__又指向了Object(原型對象是經過new Object建立的),這樣的鏈式結構稱之爲原型鏈。

咱們能夠看一張圖來幫助理解

每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具備給定字的屬性。搜索首先從對象實例自己開始。若是在實例中找到了具備給定名的屬性,則返回該屬性的值;若是沒有找到,則繼續順着原型鏈往上找。

如在原型對象中找到了這個屬性,則返回該屬性的值。也就是說,在咱們調用person1.sayName() 的時候,會前後執行兩次搜索。首先,解析器會問:「實例 person1 有 sayName 屬性嗎?」答: 「沒有。」而後,它繼續搜索,再問: 「 person1 的原型有 sayName 屬性嗎?」答:「有。 」因而,它就讀取那個保存在原型對象中的函數。當咱們調用person2.sayName()時,將會重現相同的搜索過程,獲得相同的結果。而這正是多個對象實例共原型所保存的屬性和方法的基本原理。

原型模式

簡介:原型模式是一種用於建立對象的模式,不一樣於用類建立,原型模式使用克隆對象的方式來建立一個新的對象。JavaScript自己就是一門基於原型的面嚮對象語言,它的對象系統就是使用原型模式搭建的。

原型模式的實現關鍵是語言本省是否提供了clone方法,ES5中提供了Object.create這個方法來clone對象

例:

var AM = function() {
    this.HP = 1000
    this.ATK = 100
    this.DEF = 100
}

var am = new AM()
var amClone = Object.create(am)
console.log(amClone.HP) // 1000
console.log(amClone.ATK) // 100
console.log(amClone.DEF) // 100
複製代碼

上述🌰中,咱們經過Object.create方法來clone了一個「如出一轍」的對象,接下來咱們經過本身實現一個create函數來理解js中是怎麼實現clone的

(結合以前提到的原型和原型鏈你們能夠嘗試本身先實現一下這個create方法)

-------------    先思考,勿偷看   ---------------
-------------    Think first    ---------------

function create(obj) {
    var F = function() {} // 聲明一個空方法
    F.prototype = obj // 將該方法的原型對象設置爲須要克隆的對象
    return new F() // 返回這個對象的實例
}
var am = new AM()
var amClone = create(am)
console.log(amClone.HP) // 1000
console.log(amClone.ATK) // 100
console.log(amClone.DEF) // 100
複製代碼

最後補充一句,JavaScript中除了undefined以外,一切都應該是對象(null是個特例感興趣的同窗能夠看看這裏)。Object.prototype是全部對象的根對象,它是一個空對象,全部其餘對象都是從他克隆來的。

結語

本系列文章主要是我對《javascript設計模式與開發實踐》一書的學習總結,計劃分爲上中下三章介紹書中說起的16種設計模式,但願幫助你們提高本身的代碼質量的同時也能幫咱們更好的和同事溝通(裝x)。

系列連接

  1. 16種JavaScript設計模式(上)
  2. 16種JavaScript設計模式(中)
  3. 16種JavaScript設計模式(下)還在計劃中。

本文主要參考了《javascript設計模式與開發實踐》一書

相關文章
相關標籤/搜索