javascript 總結(那些剪不斷理還亂的關係)

前言

整理 javascript 中一些類似的關鍵字、方法、概念。javascript

1. var、function、let、const 命令的區別

  • 使用var聲明的變量,其做用域爲該語句所在的函數內,且存在變量提高現象
  • 使用let聲明的變量,其做用域爲該語句所在的代碼塊內,不存在變量提高
  • 使用const聲明的是常量,在後面出現的代碼中不能再修改該常量的棧內存在的值和地址
  • 使用function聲明的函數,其做用域爲該語句所在的函數內,且存在函數提高現象
  • varjava

    //a. 變量提高
    console.log(a) // => undefined
    var a = 123
    
    //b. 做用域
    function f() {
        var a = 123
        console.log(a) // => 123
    }
    console.log(a) // => a is not defined
    
    for (var i = 0; i < 10; i ++) {}
    console.log(i) // => 10
  • let面試

    //a. 變量不提高
    console.log(a) // => a is not defined
    let a = 123
    
    //b. 做用域爲所在代碼塊內
    for (let i = 0; i < 10; i ++) {}
    console.log(i) // => i is not defined
  • constajax

    //a. 不能修改的是棧內存在的值和地址
    const a = 10
        a = 20 // => Assignment to constant variable 
    
    // 可是如下的賦值確是合法的
    const  a = {
        b: 20
    }
    a.b = 30
    console.log(a.b) // => 30
  • function編程

    //a. 函數提高
    fn() // => 123
    function fn() {
        return 123
    }
    
    //b. 做用域
    function fn() {
        function fn1 () {
            return 123456
        }
        fn1() // => 123456
    }
    fn1() // => fn1 is not defined
  • 經典面試題
  1. var a = 1
    function fn() {
        if (!a) {
            var a = 123
        }
        console.log(a)
    }
    fn() ?
  2. // 如何依次打印出0 - 9數組

    for (var i = 0; i < 10; i++) {
        setTimeout(function(){
            console.log(i)
        })
    }
  3. function Foo() {
        getName = function(){
            console.log("1");
        };
        return this;
    }
    Foo.getName = function() {
        console.log("2");
    };
    
    Foo.prototype.getName = function(){
        console.log("3");
    };
    
    var getName = function() {
        console.log("4");
    }
    function getName(){
        console.log("5");
    }
    
    Foo.getName(); ?
    getName(); ?
    Foo().getName(); ?  
    getName(); ?
    new Foo.getName(); ?
    new Foo().getName(); ?
  • 答案:
    第一題promise

    //咱們把它執行順序整理下
    var a = 1
    function fn() {
        var a = nudefined
        if (!a) {
            var a = 123
        }
        console.log(a)
    }
    //因此 答案很明顯 就是 123

    第2題服務器

    for (var i = 0; i < 10; i++) {
        print(i)
    }
    function print(i) { // 把每一個變量i值傳進來,變成只可當前做用域訪問的局部變量
        setTimeout(function(){
            console.log(i)
        })
    }
    
    // 或者自執行函數簡寫
    for (var i = 0; i < 10; i++) {
        (function(i){
            setTimeout(function(){
                console.log(i)
            })
        })(i)
    }

    第3題babel

    // 咱們整理下它的執行順序
    var getName = undefined
    function Foo() {
        getName = function(){
            console.log("1");
        };
        return this;
    }
    function getName(){
        console.log("5");
    }
    Foo.getName = function() {
        console.log("2");
    };
    
    Foo.prototype.getName = function(){
        console.log("3");
    };
    getName = function() {
        console.log("4");
    }
    
    Foo.getName(); // 2 
    /*
    函數也是對象, Foo.getName 至關於給 Foo這個對象添加了一個靜態方法 getName,咱們調用的實際上是這個靜態方法,並非調用的咱們實例化的 getName
     */
    
    getName(); // 4  
    /*
    按照上面的執行順序,其實這個就很好理解了,由於 `getName = function() { console.log("4"); }` 是最後一個賦值, 執行的應該是這個函數
     */
    
    Foo().getName(); // 1  
    /*
        這裏爲何是 1 而不是咱們想象的 3 呢?
        問題就是出在 調用的是 Foo(); 並無使用 new 這個關鍵字,因此那時候返回的 this 指向的並非 Foo, 而是 window;
        至於爲何不用 new 返回的 this 不指向 Foo, 這個隨便去哪查一下就好, 就不在這介紹了
     */
    
    getName(); // 1
    /*
        這裏爲何也是1 呢?  
        其實緣由就是 上面咱們調用了 `Foo().getName();` 這個方法引發的, 由於咱們執行了 Foo 函數, 觸發了
        getName = function(){
            console.log("1");
        }
        這段代碼, 並且並無在Foo裏面聲明  getName 變量, 因而就一直往上查找, 找到外部的 getName 變量 並賦值給它.
        因此這裏調用 getName() 方法時, 它的值已經變成
        getName = function(){
            console.log("1");
        } 了
     */
    
    new Foo.getName(); // 2
    /*這個時候仍是沒有實例化, 調用的仍是它的靜態方法*/
    
    new Foo().getName(); // 3
    /*由於實例化了,因此調的是原型上的方法*/

我記得看到過幾個經典的例子,找了半天沒找到, 暫時就這些吧.。併發

2. == 與 === 的區別

  • 相同點:
    它們兩個運算符都容許任意類型的的操做數,若是操做數相等,返回true,不然返回false
  • 不一樣點:
    ==:運算符稱做相等,用來檢測兩個操做數是否相等,這裏的相等定義的很是寬鬆,能夠容許進行類型轉換
    ===:用來檢測兩個操做數是否嚴格相等,不會進行類型轉換
  • == 轉換規則

    1. 首先看雙等號先後有沒有NaN,若是存在NaN,一概返回false。
    2. 再看雙等號先後有沒有布爾,有布爾就將布爾轉換爲數字。(false是0,true是1)
    3. 接着看雙等號先後有沒有字符串, 有三種狀況:
      a. 對方是對象,對象使用toString()或者valueOf()進行轉換;
      b. 對方是數字,字符串轉數字;
      c. 對方是字符串,直接比較;
      d. 其餘返回false
    4. 若是是數字,對方是對象,對象取valueOf()或者toString()進行比較, 其餘一概返回false
    5. null, undefined不會進行類型轉換, 但它們倆相等
// 不一樣類型,相同值
var a = 1
var b = '1'
console.log(a == b) // => true 
console.log(a === b) // => false 

// 對象和字符串
console.log([1,2,3] == '1,2,3') // => true  由於 [1,2,3]調用了 toString()方法進行轉換

// 對象和布爾
console.log([] == true)  // => false  []轉換爲字符串'',而後轉換爲數字0, true 轉換成1

// 對象和數字
console.log(['1'] == 1) // => true []轉換爲字符串'1'
console.log(2 == {valueOf: function(){return 2}}) // => true  調用了 valueOf()方法進行轉換

// null, undefined 不會進行類型轉換,  但它們倆相等
console.log(null == 1) // => false
console.log(null == 0) // => false
console.log(undefined == 1) // => false
console.log(undefined == 0) // => false
console.log(null == false) // => false
console.log(undefined == false) // => false
console.log(null == undefined) // => true 
console.log(null === undefined) // => false

// NaN 跟任何東西都不相等(包括本身)
console.log(NaN == NaN) // => false
console.log(NaN === NaN) // => false

下面幾張圖表示這些 == === 的關係

==
圖片描述

===
圖片描述

3. toSting 和 valueOf

全部對象繼承了這兩個轉換方法
toString: 返回一個反映這個對象的字符串
valueOf: 返回它相應的原始值

  • toString

    var arr = [1,2,3]
    var obj = {
        a: 1,
        b: 2
    }
    console.log(arr.toString()) // => 1,2,3
    console.log(obj.toString()) // => [object Object]
    // 那咱們修改一下它原型上的 toString 方法呢
    Array.prototype.toString = function(){ return 123 }
    Object.prototype.toString = function(){ return 456 }
    console.log(arr.toString()) // => 123
    console.log(obj.toString()) // => 456
    
    // 咱們看下其他類型轉換出來的結果, 基本都是轉換成了字符串
    console.log((new Date).toString()) // => Mon Feb 05 2018 17:45:47 GMT+0800 (中國標準時間)
    console.log(/\d+/g.toString()) // => "/\d+/g"
    console.log((new RegExp('asdad', 'ig')).toString()) // => "/asdad/gi"
    console.log(true.toString()) // => "true"
    console.log(false.toString()) // => "false"
    console.log(function(){console.log(1)}.toString()) // => "function (){console.log(1)}"
    console.log(Math.random().toString()) // => "0.2609205380591437"
  • valueOf

    var arr = [1,2,3]
    var obj = {
        a: 1,
        b: 2
    }
    console.log(arr.valueOf()) // => [1, 2, 3]
    console.log(obj.valueOf()) // => {a: 1, b: 2}
    // 證實valueOf返回的是自身的原始值
    // 一樣咱們修改下 valueOf 方法
    
    Array.prototype.valueOf = function(){ return 123 }
    Object.prototype.valueOf = function(){ return 456 }
    console.log(arr.valueOf()) // => 123
    console.log(obj.valueOf()) // => 456
    
    // valueOf轉化出來的基本都是原始值,複雜數據類型Object返回都是自己,除了Date 返回的是時間戳
    console.log((new Date).valueOf()) // => 1517824550394  //返回的並非字符串的世界時間了,而是時間戳
    console.log(/\d+/g.valueOf()) // => 456  當咱們不設置時valueOf時,正常返回的正則表式自己:/\d+/g,只是咱們設置了 Object.prototype.valueOf 因此返回的時:456
    console.log(Math.valueOf()) // => 456 同上
    console.log(function(){console.log(1)}.valueOf()) // => 456 同上
  • toString 和 valueOf 實例
  1. var a = {
        toString: function() {
            console.log('你調用了a的toString函數')
            return 8
        }
    }
    console.log( ++a) 
    // 你調用了a的toString函數 
    // 9  
    // 當你設置了 toString 方法, 沒有設置 valueOf 方法時,會調用toString方法,無視valueOf方法
  2. var a = {
        num: 10,
        toString: function() {
            console.log('你調用了a的toString函數')
            return 8
        },
        valueOf: function() {
            console.log('你調用了a的valueOf函數')
            return this.num
        }
    }
    console.log( ++a) 
    // 你調用了a的valueOf函數
    // 11
    // 而當你二者都設置了的時候,會優先取valueOf方法, 不會執行toString方法

4. || 和 && 的區別

  • 若是以 「||」 和 「&&」 作條件判斷的話

    • 「||」 只要其中有一個爲 true 那麼就知足條件
    • 「&&」 必需要全部條件都爲 true 才能知足條件

      var a = true,b = false, c = true, d = false
      var str = 'none'
      if (b || d || a) {
          str = '如今是 ||'
      }
      console.log(str) // => '如今是 ||'  ,由於其中a爲true全部知足條件
      
      var str = 'none'
      if (b || d ) {
          str = '如今是 ||'
      }
      console.log(str) // => 'none' ,由於b,d都是false, 不知足條件
      
      var str = 'none'
      if (a && c && d) {
          str = '如今是 &&'
      }
      console.log(str) // => 'none' ,由於d是false, 其中有一個false就不知足條件
      
      var str = 'none'
      if (a && c) {
          str = '如今是 &&'
      }
      console.log(str) // => '如今是 &&' ,由於b,d都是true, 知足條件
  • 短路原理:

    • ||(或):
      1.只要「||」前面是true,結果會返回「||」前面的值
      2.若是「||」前面是false,結果都會「||」返回後面的值

      var a = true,b = false, c = true, d = false
      var str = 'none'
      if (b || d || a) { str = '如今是 ||' }
      console.log(str) // => '如今是 ||'  ,由於其中a爲true全部知足條件
      
      var str = 'none'
      if (b || d ) { str = '如今是 ||' }
      console.log(str) // => 'none' ,由於b,d都是false, 不知足條件
      
      var str = 'none'
      if (a && c && d) { str = '如今是 &&' }
      console.log(str) // => 'none' ,由於d是false, 其中有一個false就不知足條件
      
      var str = 'none'
      if (a && c) { str = '如今是 &&' }
      console.log(str) // => '如今是 &&' ,由於b,d都是true, 知足條件
    • &&(與)
      1.只要「&&」前面是false,不管「&&」後面是true仍是false,結果都將返「&&」前面的值
      2.只要「&&」前面是true,不管「&&」後面是true仍是false,結果都將返「&&」後面的值

      var a = false, b = true
      console.log(a && b) // => false  只要「&&」前面是false,不管「&&」後面是true仍是false,結果都將返「&&」前面的值
      console.log(b && a) // => false  只要「&&」前面是true,不管「&&」後面是true仍是false,結果都將返「&&」後面的值

5. call/bind/apply 的區別

var name = '小剛'
var person = {
    name: '小明',
    fn: function() {
        console.log(this.name + '擼代碼')
    }
}
person.fn() // => 小明擼代碼
// 如何把它變成  「小剛擼代碼」  呢?

// 咱們能夠用 call/bind/apply 分別來實現
person.fn.call(window) // => 小剛擼代碼
person.fn.apply(window) // => 小剛擼代碼
person.fn.bind(window)() // => 小剛擼代碼

顯而易見,call 和 apply 更加相似,bind與二者形式不一樣
那 call 和 apply 的區別在哪呢?

obj.call(thisObj, arg1, arg2, ...)
obj.apply(thisObj, [arg1, arg2, ...])
// 經過上面的參數咱們能夠看出, 它們之間的區別是apply接受的是數組參數,call接受的是連續參數。
// 因而咱們修改上面的函數來驗證它們的區別

var person = {
    name: '小明',
    fn: function(a,b) {
        if ({}.toString.call(a).slice(8, -1) === 'Array') {
            console.log(this.name+','+a.toString()+'擼代碼')
        }else{
            console.log(this.name+','+a+','+b+'擼代碼')
        } 
    }
}

person.fn.call(this, '小紅', '小黑' ) // => 小剛,小紅,小黑擼代碼
person.fn.apply(this, ['小李', '小謝']) // => 小剛,小李,小謝擼代碼

那麼bind 與call,apply有什麼區別呢 ?
與call和apply不一樣的是,bind綁定後不會當即執行。它只會將該函數的 this 指向肯定好,而後返回該函數

var name = "小紅"
var obj = {
    name: '小明',
    fn: function(){
        console.log('我是'+this.name)
    }
}
setTimeout(obj.fn, 1000) // => 我是小紅
// 咱們能夠用bind方法打印出 "我是小明"
setTimeout(obj.fn.bind(obj), 1000) // => 我是小明
// 這個地方就不能用 call 或 apply 了, 否則咱們把函數剛一方去就執行了

// 注意: bind()函數是在 ECMA-262 第五版才被加入
// 因此 你想兼容低版本的話 ,得須要本身實現 bind 函數
Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
      throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1), 
        fToBind = this, 
        fNOP = function () {},
        fBound = function () {
          return fToBind.apply(
              this instanceof fNOP && oThis ? this : oThis || window,
              aArgs.concat(Array.prototype.slice.call(arguments))
          );
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    return fBound;
};

6. callback 、 promise 、 async/await

這三個東西牽涉到的可能就是咱們最多見到的 「同步」、「異步」、「任務隊列」、「事件循環」 這幾個概念了

    • 例:

      var data;
      $.ajax({
          ...
          success: function(data) {
              data = data
          }
      })
      console.log(data)

      當咱們從服務器獲取到數據的時候,爲何打印出來的是undefined ?
      解決這個問題以前咱們先來了解javascript的運行環境

      JavaScript是單線程語言,JS中全部的任務能夠分爲兩種:同步任務和異步任務。

    • 同步任務:
      意思是我必須作完第一件事,才能作第二件事,按照順序一件一件往下執行(在主線程上)
    • 異步任務:
      假如我第一件事須要花費 10s, 可是我第二件事急着要作, 因而咱們就把第一件事告訴主線程,而後主線程暫停先放到某個地方, 等把第二件事完成以後,再去那個地方執行第一件事,第一件事也就能夠理解爲異步任務
    • 任務隊列(task queue):
      任務隊列是幹嗎的呢; 上面咱們說了異步任務的狀況, 咱們把第一件放到某個地方, 那某個地方是什麼地方呢,就是 「任務隊列」 這個東西。裏面乘放的是全部異步任務。
    • Event Loop(事件循環)
      當主線程上面全部同步任務執行完以後,主線程就會向任務隊列中讀取異步任務(隊列方法:先進先出)
      並且是一直重複向任務隊列中,即便沒有任務。它也會一直去輪詢。
      只不過在任務列表裏面沒有任務的時候, 主線程只須要稍微過一遍就行, 一旦遇到任務隊列裏面有任務的時候,就會去執行它
      也就是說在咱們打開網頁的時候,JS引擎會一直執行事件循環,直到網頁關閉

      如圖所示
      圖片描述

      由此,上面爲何會產生 undefined的緣由了, 由於ajax 是異步任務,而咱們console.log(data)是同步任務,因此先執行的同步任務,纔會去執行 ajax

      說了這麼多,咱們來看下 爲何咱們很須要 從 callback => promise => async/await

      由於不少時候咱們須要把一個異步任務的返回值,傳遞給下一個函數,並且有時候是連續的n個

    1. callback

      // 只有一個callback的時候
      function fn(callback) {
          setTimeout(function(){
              callback && callback()
          }, 1000)
      }
      fn(function(){
          console.log(1)
      })
      
      // 一旦咱們多幾個呢?
      function fn(a){ // 傳入a  返回a1
          function fn1(a1){
              function fn2(a2){
                  function fn3(a3){
                      console.log(a3)
                      ....
                  }
              }
          }
      }
      // 當項目一複雜,這滋味。。。
    2. Promise

      • 什麼是promise?
        Promise是異步編程的一種解決方案,同時也是ES6的內置對象,它有三種狀態:

        1. pending: 進行中
        2. resolved: 已完成
        3. rejected:已失敗
      • Promise方法

        1. Promise.prototype.then() 接收兩個函數,一個是處理成功後的函數,一個是處理錯誤結果的函數。能夠進行鏈式調用
        2. Promise.prototype.catch() 捕獲異步操做時出現的異常, 通常咱們用來代替.then方法的第二個參數
        3. Promise.resolve() 接受一個參數值,能夠是普通的值, 會返回到對應的Promise的then方法上
        4. Promise.reject() 接受一個參數值,能夠是普通的值, 會返回到對應的Promise的catch方法上或着then方法的第二個參數上
        5. Promise.all() 接收一個參數,它必須是能夠迭代的,好比數組。一般用來處理一些併發的異步操做。成功調用後返回一個數組,數組的值是有序的,即按照傳入參數的數組的值操做後返回的結果
        6. Promise.race() 接收一個能夠迭代的參數,好比數組。可是隻要其中有一個執行了,就算執行完了,無論是成功仍是失敗。
      • 基本用法

        let promise = new Promise( (resolve, reject) => {
            setTimeout(function(){
                resolve(1)
            }, 1000)
        })
        promise.then( res => {
            console.log(res)// 一秒以後打印1
        })
      • 咱們把上面的回調地獄轉換下

        const fn = a => {
            return Promise.resolve(a)
        }
        const fn1 = a => {
            return Promise.resolve(a)
        }
        const fn2 = a => {
            // return Promise.resolve(a)
            return new Promise( (resolve, reject) => {
                setTimeout(function(){
                    resolve(a)
                },1000)
            })
        }
        const fn3 = a => {
            // return Promise.resolve(a)
            return new Promise( (resolve, reject) => {
                setTimeout(function(){
                    resolve(a)
                },1000)
            })
        }
        fn(123)
            .then(fn1)
            .then(fn2)
            .then(fn3)
            .then( res => {
                console.log(res) // => 123
            })

        這樣就簡單明瞭多了, 咱們就不須要一層一層嵌套callback了,能夠經過鏈式調用來解決callback的問題

        然而,僅僅這樣仍是以爲不夠好
        由於這種麪條式調用仍是讓人很不爽,並且 then 方法裏面雖然是按前後順序來的,可是其自己仍是異步的
        看下面這段代碼

        const promise = new Promise( (resolve, reject) => {
            setTimeout(function(){
                resolve(222)
            }, 1000)
        })
        console.log(111)
        promise.then( res => {
            console.log(res)
        })
        console.log(333)

        打印結果依然仍是 111 => 333 => 222, 並非咱們想象的 111 => 222 => 333
        依然不適合單線程的思惟模式。因此下一個解決方案 又出現了

    3. async/await
      這是ES7的語法,固然,在如今這種工程化的時代,基本babel編譯以後也都是能在項目中引用的

      • 基本用法跟規則
        async 表示這是一個async函數,
        await只能用在這個函數裏面。後面應該跟着是 Promise 對象, 不跟的話也不要緊, 可是await就不會在這裏等待了
        await 表示在這裏等待promise返回結果

        例:

        const fn = () => {
            return new Promise( (resolve, reject) => {
                setTimeout(function(){
                    resolve(222)
                }, 1000)
            })
        }
        (async function(){
            console.log(111)
            let data = await fn()
            console.log(data)
            console.log(333)
        })()
        // 是否是返回 111 => 222 => 333 了呢
        
        // 咱們來試下返回別的東西, 不返回 promise
        const fn = () => {
            setTimeout(function(){
                console.log(222)
            }, 1000)
        }
        (async function(){
            console.log(111)
            let data = await fn()
            console.log(data)
            console.log(333)
        })()
        // 打印結果: 111 => undefined => 333 => 222
        // 當咱們不是在await 關鍵字後面返回的不是 promise 對象時, 它就不會在原地等待 promise執行完再執行, 而是向正常的JS同樣執行,把異步任務跳過去
      • await 關鍵字必須包裹在 async 函數裏面,並且async 函數必須是它的父函數

        const fn = () => {
            let promise = new Promise( (resolve, reject) => {
                setTimeout(function(){
                    resolve(222)
                }, 1000)
            })
        }
        
        // 這樣是不行的,會報錯,由於的await關鍵字的父函數不是 async 函數
        const grand = async () => {
            return function parent() {
                let data = await fn()
            }
        }
        
        // 這樣才行,由於await 的父函數 是一個 async 函數
        const grand = () => {
            return async function parent() {
                let data = await fn()
            }
        }

    7. 柯里化 與 反柯里化

    • 柯里化
      函數柯里化就是對高階函數的降階處理。
      柯里化簡單的說,就是把 n 個參數的函數,變成只接受一個參數的 n 個函數
      function(arg1,arg2)變成function(arg1)(arg2)
      function(arg1,arg2,arg3)變成function(arg1)(arg2)(arg3)
      function(arg1,arg2,arg3,arg4)變成function(arg1)(arg2)(arg3)(arg4)

      • 柯里化有什麼做用

        1. 參數複用;
        2. 提早返回;
        3. 延遲計算/運行
      • 例:

        //求和
        function add (a, b, c) {
            return a + b + c
        }
        add(1,2,3)

        若是我只改變 c 的值,在求和
        add(1,2,4) 是否是得多出從新計算 a + b 的部分
        咱們是否是能夠提早返回a+b的值, 而後只傳入 c 的值進行計算就好了
        修改一下方法

        function add (a, b) {
            return function (c) {
                return a + b + c
            }
        }
        var sum = add(1, 2)
        sum(3)
        sum(4)

        在此基礎上咱們在作下修改

        function add (a) {
            return function (b) {
                return function (c) {
                    return a + b + c
                }
            }
        }

        這樣咱們是否是能夠隨時複用某個參數,而且控制在某個階段提早返回

        還有一個經典的例子

        var addEvent = function(el, type, fn, capture) {
            if (window.addEventListener) {
                el.addEventListener(type, function(e) {
                    fn.call(el, e);
                }, capture);
            } else if (window.attachEvent) {
                el.attachEvent("on" + type, function(e) {
                    fn.call(el, e);
                });
            } 
        };

        咱們每次調用事件時,都須要判斷兼容問題, 但咱們運用柯里化的方式就只要判斷一次就好了

        var addEvent = (function(){
            if (window.addEventListener) {
                return function(el, sType, fn, capture) {
                    el.addEventListener(sType, function(e) {
                        fn.call(el, e);
                    }, (capture));
                };
            } else if (window.attachEvent) {
                return function(el, sType, fn, capture) {
                    el.attachEvent("on" + sType, function(e) {
                        fn.call(el, e);
                    });
                };
            }
        })();

        還有一個做用就是延遲計算

        小明天天都會花一部分錢吃飯
        小明想知道它5天以後總共會花費多少錢

        var total = 0
        var fn = function(num) {
            total += num
        }
        fn(50)
        fn(70)
        fn(60)
        fn(100)
        fn(80)

        這樣咱們便能算出它總共花了都少錢

        可是小明又忽然想知道 若是他天天花費的的錢翻一倍 會產生多少錢
        因而咱們是否是得改下 上面的 函數

        var fn = function(num) {
            total += num*2
        }
        fn(50)
        fn(70)
        fn(60)
        fn(100)
        fn(80)

        那咱們是否是有什麼辦法,先把這些數 存起來,到最後在進行計算
        咱們接着來封裝

        var curry = function(fn) {
            var args = []
            return function() {
                if (arguments.length === 0) {
                    return fn.apply(null, args)
                }else{
                    args = args.concat([].slice.call(arguments))
                    return curry.call(null, fn, args)
                }
            }
        }
        
        var curryFn = function() {
            var args = [].slice.call(arguments),
                total = 0
            for (var i = 0; i < args.length; i++) {
                total += args[i]
            }
            return total
        }
        var fn = curry(curryFn)
        fn(50)
        fn(70)
        fn(60)
        fn(100)
        fn(80)
        
        fn() //不傳參數的時候進行計算

        這樣咱們只有最後的時候才進行計算。
        並且只須要修改 curryFn 裏面的計算方法就行

        咱們整理下上面的方法封裝完整的柯里化函數

        var curry = function (fn, length) {
            length = length || fn.length;
            var sub_curry = function (f) {
                var args = [].slice.call(arguments, 1);
                return function () {
                    return f.apply(null, args.concat([].slice.call(arguments)))
                }
            }
            return function () {
                var args = [].slice.call(arguments);
                if (length > args.length) {
                    var newArgs = [fn].concat(args);
                    return curry(sub_curry.apply(null,newArgs), length - args.length)
                }else{
                    fn.apply(null,arguments)
                }
            }
        }
        // 1.
        var fn  = curry( function(a,b,c){
            console.log(a, b, c)
        })
        fn('a')('b')('c')
        
        // 2.
        fn1 = curry(function(){
            console.log(arguments)
        }, 3)
        fn1('a')('b')('c')
    • 反柯里化
      反柯里化的做用在與擴大函數的適用性,使原本做爲特定對象所擁有的功能的函數能夠被任意對象所用.

      被任意對象使用? 是否是想到了用call, apply 設置this指向

      • 經過 call/apply 被任意對象所用

        var obj = {
            a: 1,
            fn: function (b) {
                return this.a + b
            }
        }
        obj.fn(2) // 3
        var obj1 = {a:4}
        obj.fn.call(obj1, 2) // 6
      • 反柯里化版本

        var uncurrying= function (fn) {
            return function () {
                var context=[].shift.call(arguments);
                return fn.apply(context,arguments);
            }
        }
        // const uncurrying = fn => (...args) => Function.prototype.call.apply(fn,args) // 簡潔版
        var f = function (b) {
            return this.a + b
        }
        var uncurry = uncurrying(f)
        var obj = {a:1},
            obj1 = {a:4}
        uncurry(obj, 2) // 3
        uncurry(obj1, 2) // 3

    相信你們已經看出區別了,這丫的就至關於一個外部的call方法

    總結

    上面不少只是本身的部分理解,不必定準確。若是有不一樣理解,謝謝指出。

    博客地址

    相關文章
    相關標籤/搜索