JavaScript學習之路 — 函數、閉包與原型鏈

今天這個話題是由於這幾天看了《JavaScript忍者祕籍》,感受這本書把這幾個內容講的蠻透徹了,特撰本文,以便往後翻閱。(應該都會以知識點的形式給出吧。)javascript

函數

1.【基本類型】

JavaScript中函數爲first-class object,typeof的結果是object,沒有function這個基本類型,但有能夠調用的Function構造器。說到這個就列舉一下JavaScript中的幾個基本類型:java

* null                       (空值)
            * undefined           (未定義)
            * boolean              (布爾)
            * number              (數字)
            * string                  (字符串)
            * object                 (對象)
            * symbol               (符號)

除了null的typeof結果是object之外,其餘類型的typeof都是本身的基本類型名稱。這幾個類型還能有一堆能夠講的地方、以後再寫~正則表達式

2.【函數聲明】

函數的字面量聲明有四個部分組成,但有些能夠省略,列舉以下:數組

* function 關鍵字
    * 可選名稱 ,能夠匿名,但若是有必須爲有效的JavaScript 標識符
    * 括號內部一個以逗號分隔的參數列表
    * 大括號括起來的函數體

3.【name屬性】

有名稱的函數name屬性永遠爲本身的名稱。關於匿名函數賦予變量後的name屬性是什麼。。。我這裏試出來ES5和ES6都是賦予後的變量名。瀏覽器

4.【函數調用與this的綁定】

在這本《忍者祕籍》裏給出了一個理解this綁定的方法:將this理解爲運行上下文,this指的就是調用函數時的運行上下文。this的綁定實在運行時候肯定的,而不是編譯時。(在《你不知道的JavaScript》中詳細講了四種this綁定,之後補~)緩存

這裏講了四種函數調用的方式:閉包

  • 做爲一個函數進行調用app

    就是做爲通常的函數,直接在全局上下文中調用。
  • 做爲一個方法進行調用
    將函數做爲一個對象中的方法進行調用,那麼這時候this就會綁定在這個對象的上下文中。同時若是在全局中定義一個函數,賦值到對象的屬性中,其能夠對不一樣對象進行操做,互不影響。函數

//全局的函數
function all () {
    console.log(this.a);
}
//第1個對象
let o1 = {
    a: "In1",
    func1: function () {
        return this.a
    },
    func2: all
 }
//第2個對象
let o2 = {
    a: "In2",
    func: all
}

let a = "Out" 

console.log(o1.func1())   //method in object    In1
o1.func2()      //this is o1      In1
o2.func()       //this is o2      In2
  • 做爲構造器進行調用測試

    也就是利用new運算符進行調用。構造器調用的時候會進行如下步驟:

1.建立一個新的對象
      2.傳遞給構造器的參數的對象是this參數,從而成爲構造器的函數上下文
      3.若是沒有顯示的返回值,建立的對象則做爲構造器的返回值返回
      (即便有return值,做爲3構造器調用的時候,
      也返回新建立的對象,若是return的object,那就返回這個object)
    
    Ex:
function Ninja () {
    this.ninjaName = function () {}
    return 3
}

let ninja1 = new Ninja()
let ninja2 = new Ninja()

 //利用構造器獲得兩個不一樣的對象
console.log(ninja1 === ninja2)    //false
//做爲構造器調用時,返回的是對象而不是return值
console.log(ninja1)        // Ninja { ninjaName: [Function] }
//做爲通常函數調用時,返回的是返回值
console.log(Ninja())      //3
  • 經過apply()或call()方法進行調用

    能夠隨意改變函數的調用上下文,apply與call的區別是除第一個指定函數執行時
    this綁定參數以外的參數。apply傳入參數數組,而call傳入所有參數。

5.【函數參數】

函數實際傳入的參數和聲明時候的參數列表能夠不一樣。
若是傳入的比聲明的少,那麼沒有傳入數據的聲明將會是undefined,若多,則多出來的的傳入數據將沒法經過變量名的方式訪問到。
參數能夠經過函數的argument屬性訪問到,這是一個類數組,沒法使用大多的數組的自帶方法。

6.【匿名函數】

對象內的匿名函數若是被賦值給了另外一個對象,會產生引用丟失的問題。

7.【函數的屬性】

函數做爲對象,能夠存儲一些參數值,以方便作一些特殊的處理。主要用途有函數存儲和自記憶函數。
函數存儲能夠用來存儲要調用的函數,自記憶函數能夠用來緩存函數已經運行過的結果,減小重複計算(自記憶函數能夠經過閉包的方式進行再優化)

8.【函數的變長參數】

能夠經過apply方法給出變長參數的數組。(ES6中可使用...解包)
如:

let list = [7,9,1,2,0,10]
Math.max(list[0],list[1]....) //此處省略,過於麻煩,要列出全部位置
Math.max.apply(Math, list)    //利用apply能夠直接傳入數組
Math.max(...list)            //ES6中能夠經過解包符直接傳入

經過閉包的特性,一樣能夠實現經過參數個數不一樣的判斷,進行函數的重載。

閉包

1.【閉包的定義】

閉包是一個函數在建立時容許自身訪問並操做該函數以外的變量時所建立的做用域。->聲明的函數何時均可以調用,即便是在做用域消失以後。
典型的閉包:

let outer = 'ninja'
let later

function outerFunction () {
    let inner = 'samurai'
    
    function innerFunction (paramValue) {
        console.log(inner)
        console.log(paramValue)
        console.log(toolate)
    }

    later = innerFunction
}
    console.log(toolate)    //undefined (用let會報錯)
    var toolate = 'ronin'
    outerFunction()   //建立閉包
    later('wakizashi')    //ninja wakizashi ronin 都輸出了

閉包建立了一個氣泡,保護了函數聲明那一時間點的做用域裏的全部函數和變量,得到了執行操做所須要的全部東西。
有三個有趣的結論:

  • 內部函數的參數是包含在閉包中的

  • 做用域以外的全部變量,即便是函數聲明以後,可是在函數被調用以前的那些聲明也都包含在閉包中

  • 相同的做用域內,還沒有聲明的變量不能使用(let聲明)、值爲undefined(var聲明)

2.【閉包的用處】

  • 建立私有變量:利用function的特性,能夠建立一個變量沒法在外部直接訪問,須要用getter和setter,這兩個函數就是閉包的做用

  • 回調和計時器:回調函數中能夠經過閉包來訪問外部的變量

3.【綁定函數上下文(bind)】bind函數的用法與apply和call不一樣,

簡化版的bind:

function bind (context, name) {
    return function () {
        return context[name].apply(context, arguments)
    }
}
//經過閉包的特性,來獲得要綁定的函數
//系統bind的使用:
functionName.bind(newThis)()

4.【使用閉包實現的函數緩存記憶】

使用到的技巧:每一個函數都有本身的上下文,因此函數歷來都不是閉包的一部分。可是能夠經過建立一個變量引用到這個上下文中(let fn = this,後面的function中能夠利用fn調用到以前的this指向的上下文),從而將上下文也經過閉包保存起來。

具體的實現能夠參見《忍者祕籍》書P103

5.【函數的即時調用】

一般用於匿名函數,主要做用是能夠保護做用域和變量名不污染全局。
而且能夠解決迭代問題(一樣能夠用來解決變量名稱過長的問題,將長名稱傳入即時調用的函數中,在函數中利用參數名進行操做):

for(let i = 0;i < div.length; i++) {
    (function(n) {
        div[n].addEventListener("click", function(){
            alert("div #" + n + "was clicked")
        }, false)
    })(i)
}
//經過當即執行函數,直接把i和對應的div綁定,若是不這樣作,最後按全部的按鈕都會是最後一個i值(由於沒有實時綁定)

閉包確定不止這麼多內容,之後還會補充《你不知道的JavaScript》的內容

原型鏈

JavaScript經過原型鏈實現繼承。

1.【prototype與new操做的共同使用】

只有經過new操做產生的對象,可使用構造器函數原型鏈上的內容,不然對象只能使用本身原型鏈上的內容。利用這個能夠得出,利用構造器函數能夠將JavaScript產生相似於類的概念。

2.【prototype的使用】

prototype在對象建立以後若是有改動,所作的改動一樣會影響到已經建立的對象上去。
對於對象中的引用,先檢查是否在本身的聲明中存在,若是存在則用本身聲明的,若是不存在則循着原型鏈向上找,找到object根元素,若是仍然不存在,則返回undefined。
prototype實際上是object隱藏屬性constructor的一個屬性,因此能夠利用這個進行原型鏈,原型是實時附加在對象上的。

3.【保持原型鏈】

用一個對象的實例做爲另外一個對象的原型,調用方式以下

SubClass.prototype = new SuperClass()

這樣SubClass的實例不只擁有原型,更有SuperClass中的全部屬性。而且instanceOf SuperClass也會判斷正確。

注意: 永遠不要使用SubClass.prototype = SuperClass.prototype,若是這樣作的話全部SubClass上的prototype修改都會影響到SuperClass上,會產生反作用。

4.【hasOwnProperty()方法】

利用這個方法能夠檢測屬性是不是原生就有的,而不是經過檢測原型鏈獲得的。

5.【一些須要避免的場景】

  • 擴展Object的prototype,這樣會影響到全部的對象

  • 擴展Number的prototype

  • 產生原生對象的子類(儘可能採用另外寫類,但同名方法經過prototype來調用原生對象方法的方法)

  • 經過構造器建立對象不加new操做符(這樣作會有可能產生錯誤得不到對象而且污染全局變量)

這些就是讀完《忍者祕籍》的一部分感想和知識點,這本書最精華的部分應該就是這三塊的精煉描述了,這本書還講到了JavaScript的測試方法、正則表達式、定時器、運行時求值、事件與DOM操做以及一些跨瀏覽器的實踐方法。是一本好書(常常參加促銷的好書~),能夠借來或者買來一讀~。

以上。

相關文章
相關標籤/搜索