Javascript中的魔鬼

Zwe1發表於2018-03-30

寫作意圖

這篇文章用於總結一些javascript語言中常見的易混淆點。

call | apply | bind

在js中,最詭異莫測的莫過於this了,理解的不夠深入或是應用場景略微複雜,使用時就會出現各種意想不到的錯誤。所以,在很多時候,我們需要手動指定上下文環境,來修正this的指向。
最簡單判斷this所在環境的方法是,尋找this的實際呼叫者。

一個典型的錯誤

    var a = {
        name: 'ein',
        sayName: function () {
            console.log(this.name);
        }
    }

    var b = a.sayName;
    b();  //undefined
複製程式碼

我們本想將物件a的sayName賦值給變數b,通過呼叫來檢視a的name是什麼?結果卻輸出了undefined。發生這個異常的原因就是因為在呼叫函式b時,sayName中的this已經不再指向物件a而是指向了全域性物件window,由於window下並沒有name屬性,所以輸出undefined。

下面我們將通過以上三種方法來修正這個問題。

使用call

    var b = a.sayName;
    b.call(a);  //ein
複製程式碼

使用call方法,第一個引數為函式呼叫時的上下文環境,將其設定為物件a,這樣this就會指向物件a,如此便可以取到物件a中的name屬性,輸出想要的值。

使用apply

    var b = a.sayName;
    b.apply(a); //ein
複製程式碼

使用apply方法,傳入上下文環境,可以實現同樣的效果。

call和apply的區別

那麼call和apply的區別是什麼呢?他們的區別在於後續的引數。讓我們改造一下上面的程式碼,來觀察效果。

    var a = {
        name: 'ein',
        sayName: function (fistname, lastname) {
            console.log(`${fistname} ${this.name} ${lastname}`);
        }
    }

    var b = a.sayName;

    b.call(a,'nick','snow');  //nick ein snow
    b.apply(a,['nick','snow']);  //nick ein snow
複製程式碼

call和apply的區別在於後續引數,call依次傳入後續引數,將被函式所使用。apply需要將後續引數以陣列的形式傳入。

使用bind

bind同樣可以用來修正this的指向,它與以上二者的區別在於,bind繫結之後並不會立即執行函式。

    var b = a.sayName;
    b.bind(a);
複製程式碼

在為b繫結a的上下文環境之後,b並不會立即執行。

    b.bind(a,'nick','snow')();  //nick ein snow
    var c = b.bind(a);
    c('nick','snow');  //nick ein snow
複製程式碼

如此,便可以如願得到你想要的效果了。使用bind方法可以延緩函式的執行時間,在你想呼叫時再執行函式。
bind方法同樣可以傳入其它引數,和call方法的傳入方式相同。

splice | slice | split

看到這三個方法有沒有頭暈目眩的感覺?因為它們三個實在是太像三胞胎了,真的很難區分。首先,我們來學習或是回顧一下這三個單詞。

splice  拼接  
slice   片,切片  
split   分裂,裂開
複製程式碼

大多數api其實其名稱都與其用途有所關聯,這三個api便是很經典的案例。

  • splice意為拼接,它的用途,便是將一個陣列分割開,並且可以再以指定的方式重新拼接在一起。
  • slice意為切片,我們可以使用它來在一個陣列或是字串中切取我們想要的一段。
  • split意為分裂,它可以將一個字串分裂成一個陣列。

使用splice

    var a = [1,2,3,4,5];
    a.splice(1,2,4,4);
    console.log(a);
    //[1,4,4,4,5]
複製程式碼

splice(starts, count, item1, ..., itemx)方法接受多個引數,第一個引數為刪除的起始元素,第二個引數為刪除數量,後續為插入的內容。
注意: splice方法會修改原始陣列,返回被刪除的內容陣列。

使用slice

    var a = [1,2,3,4,5];
    var b = '12345';
    a.slice(1,3);  //[2,3]
    a.slice(1);  //[2,3,4,5]
    a.slice(2,-1)  //[3,4]
    b.slice(1,2);  //'2'
    console.log(a);  //[1,2,3,4,5]
    console.log(b);  //'12345'
複製程式碼
  • slice方法既可應用於陣列也可應用於字串。
  • slice(stats, beforeEnds)方法接受最多兩個引數,第一個引數代表的序列號必須小於第二個引數,第二個引數為切片的終止位置,第二個引數省略時預設擷取到資料末尾,引數為負數時,將反向查詢匹配項。
    注意: slice方法不會修改原始陣列,返回的是被切片節選的片段。

使用split

    var a = '123456';
    a.split('');  //['1','2','3','4','5','6']
    a.split('',3);  //['1','2','3']
    console.log(a);  //'123456'
複製程式碼

split(separator, count)方法可接受兩個引數,第一個引數為分割符,用於指定字串的分割規則,第二個引數為返回陣列的最大長度,返回的輸出長度不會大於這個引數。
注意: split不會修改原始字串,返回值為新陣列。

map | forEach | reduce | filter | every | some

這六個方法時常用的運算元組的api,均為Array.prototype的本地方法。所以一切陣列均可使用這些方法遍歷運算元組的每一項,下面將逐一介紹這些方法。

map

    var arr = [
        {'name': 'ein'},
        {'name': 'zwei'},
        {'name': 'drei'}
    ];

    let newarr = arr.map((item, index) => {
        return (
            {
                'name': item.name,
                'order': index
            }
        )
    });
    console.log(arr);
    // [{'name': 'ein'},{'name': 'zwei'},{'name': 'drei'}] 
    console.log(newarr);
    // [{'name': 'ein','order': 0},{'name': 'zwei','order': 1},{'name': 'drei','order': 2}]
複製程式碼

map(fn(item, index))方法接收一個函式作為引數,這個函式接收兩個引數,第一個引數為每一項的陣列內容,第二個引數為陣列下標。
注意: map方法返回一個新的陣列,如果在操作中沒有return返回值,預設返回一個值為undefined的陣列。
預設返回:[undefined, undefined, undefined]

forEach

    arr.forEach((item, index) => {
        item.old = true;
        delete item.name;
    })
    console.log(arr);
    //  [{'old': true},{'old': true},{'old': true}]
複製程式碼

forEach(fn(item, index))方法接收一個函式作為引數,這個函式接收兩個引數,第一個引數為每一項的陣列內容,第二個引數為陣列下標。
注意: forEach方法直接操作原始陣列,並且不返回任何內容。

reduce

簡單用例

    var arr1 = [1,2,3,4,5];
    var res = arr1.reduce((curr, next) => {
        return curr + next
    });
    console.log(res);  //15
    console.log(arr1);  //[1, 2, 3, 4, 5]
複製程式碼

複雜用例

    var res1 = arr1.reduce((curr, next,index,arr) => {
        console.log('content',curr,next,index,arr);
        return curr + next
    },10);
    console.log(res1);
複製程式碼
content 初始值(curr) 當前元素(next) 當前元素索引(index) 當前元素所屬陣列(arr) 函式初始值
content 10 1 0 [1, 2, 3, 4, 5] 10
content 11 2 1 [1, 2, 3, 4, 5] 10
content 13 3 2 [1, 2, 3, 4, 5] 10
content 16 4 3 [1, 2, 3, 4, 5] 10
content 20 5 4 [1, 2, 3, 4, 5] 10

reduce方法用於對陣列進行累積化操作,常用於陣列求和。接收兩個引數,第一個引數為操作函式,第二個引數為函式初始值。對於陣列的操作不會修改原始值。

filter

    var res = arr1.filter((item, index) => {
        console.log('data:',index,item);
        return item > 3
    });
    console.log(res);
    //  [4, 5]
複製程式碼

filter方法用於過濾陣列的每一項,刪選出符合條件的項,並組成一個新的陣列。

every

    var res = arr1.every((item, index) => {
        return item > 3
    });
    console.log(res);
    // false
複製程式碼

every方法用於檢查陣列的每一項是否符合條件,全部符合條件時返回true,否則返回false。

some

    var res = arr1.some((item, index) => {
        return item > 3
    });
    console.log(res);
    // true
複製程式碼

some方法用於檢查陣列中的是否存在符合條件的項,存在則返回true,否則返回false。

另外

一副生動有趣的圖解,everyday will be better!

pic

相關文章