前端全棧工程化開發專題 — JS中回撥函式的深入解讀

我是家碧發表於2018-12-12

1、回撥函式核心原理分析

js中的定時器及動畫

完整版動畫庫封裝

  • 回撥函式初步講解
  • 擴充套件更多的運動方式(非勻速)
  • options物件引數的應用
  • ...

什麼是回撥函式?

把一個函式當做實參值傳遞給函式的形參變數(或者傳遞給函式,通過函式arguments獲取),在另外一個函式中把傳遞的函式執行,這種機制就是回撥函式機制

凡是在某一個函式的某一個階段需要完成某一件事情(而這件事情是不確定的),都可以利用回撥函式機制,把需要處理的事情當做值傳遞進來

function fn(num,callBack){
    // callBack就是傳遞進來的回撥函式
    typeof callBack === 'function' ? callBack() : null;
    // callBack && callBack();//這種方式預設就是,要不然不傳遞引數,傳遞的話引數值肯定是函式
}
fn(10);
fn(20,function(){
    // 此處的匿名函式就是給callBack傳遞的值
});
複製程式碼

既然我們已經把函式作為值傳遞給fn了,此時在fn中我們可以盡情的操作傳遞的函式

1、我們可以在fn中把回撥函式執行0~n次

2、我們還可以給回撥函式傳遞引數值

3、我們還可以把回撥函式中的this進行修改

4、我們還可以接收回撥函式執行返回的值

...typeof

// 需求:執行fn可以實現任意數求和,把求出的和傳遞給回撥函式
function fn(callBack){
    // 把arguments中的除第一項以外的引數值獲取到,並且轉變為陣列(並且給陣列求和)
    var argNumAry = Array.prototype.slice.call(arguments,1),
        total = eval(argNumAry.join('+'));
    // 執行回撥函式,把求出的和當做實參傳遞給回撥函式,並且改變回撥函式中的this指向
    typeof callBack === 'function' ? callBack.call(fn,total) : null;
}
fn(function(result){
    console.log(result,this);// 100 fn
},10,20,30,40);
複製程式碼

之前寫的知識點中,很多方法都是依託於回撥函式來完成的

var ary = [12,23,34];
ary.sort(function(a,b){
    // a:當前項
    // b:後一項
    return a-b;//返回一個大於零的值,a和b的位置進行交換
})

ary.forEach(function(item,index,input){//不相容ie
    // item:當前遍歷的這一項
    // index:當前遍歷這一項的索引
    // input:原始遍歷的陣列

    // forEach每當迴圈遍歷到陣列中的某一項,都會把傳遞的回撥函式執行一次(不僅執行
    // 還把遍歷的這一項值傳遞給回撥函式)
})

// map遍歷陣列中的每一項,原有陣列不變,返回的結果是修改後的新陣列(map相當於forEach來說
// ,增加了對原有項的修改)
var newAry = ary.map(function(item,index,input){//不相容ie
    return item*10;//回撥函式中返回的是什麼,相當於把當前遍歷這一項修改為什麼(回撥函式中不寫return,預設返回的是undefined)
})


// forEach和map都不相容  陣列上比如 find方法也支援回撥函式  定時器也是回撥函式機制


var str = 'shujiab123ilihai'
// 拿第一個引數正則和str匹配,第一次捕獲的到結果是2017,把這個結果傳遞給回撥函式,這個回撥函式把原始str當前捕獲物件2017替換成@並且返回新的str
str=str.replace(/\d+/g,function(){
    return '@';
})
複製程式碼

2、回撥函式THIS指向問題

回撥函式中的this一般都是window(或者在嚴格模式下是undefined),原因:

我們一般在執行回撥函式的時候,都是直接的吧它執行了,沒有特意指定執行主體或者使用call改變this,所以預設一般都是window

function fn(){

}

setTimeout(fn,1000);//fn中的this是window

// 為什麼這裡的this是window? 原理如下:

function aa(callBack) {
    callBack && callBack();
}
aa(function(){
    console.log(this);//window
})
複製程式碼

有關定時器回撥函式中this的處理

var obj = {name:'哈哈'};

setTimeout(function(){
    console.log(this)//非嚴格模式或者嚴格模式下都是window(因為setTimeout做了處理)
},1000);


setTimeout(function(){
    console.log(this)//還是window  傳遞第三個引數也沒有用
},1000,obj);

// =====================================================================

var obj = {name:'哈哈',fn:fn};
function fn() {
    console.log(this);
}
setTimeout(fn,100);//非嚴格模式或者嚴格模式下都是window(因為setTimeout做了處理)
setTimeout(fn.call(obj),100);//設定定時器的時候就把fn執行了,把fn的返回結果賦值給定時器
// (fn沒有寫return,所以fn的返回結果是undefined,所以1s中後執行的是undefined),所以
// 雖然剛開始已經把fn執行了並把this改成obj了,但是1s中後執行的是undefined,所以不行

// 我們想讓1s中後執行的是fn才行,所以如下:bind預處理this,call立即執行,但是bind不相容 
setTimeout(fn.bind(obj),100);//fn中的this都是obj
setTimeout(function(){//1s後先執行匿名函式,再執行匿名函式的時候去手動改變fn的this執行並立即執行
    fn.call(obj);//fn中的this都是obj
},1000);

setTimeout(obj.fn,1000);//這裡的obj.fn並不是obj.fn()這樣執行,
//1s中之後找到obj.fn對應的這個值=>函式所對應的堆記憶體地址,把它執行,所以this還是window

複製程式碼

陣列中方法回撥函式中this指向問題

"use strict";

var obj = {name: '珠峰培訓'};
var ary = [12, 23, 34, 45];
ary.sort(function () {
    console.log(this);//=>WINDOW(嚴格模式下是UNDEFINED,定時器嚴格模式下還是window)
});
ary.sort(function () {
    console.log(this);//=>WINDOW(嚴格模式下是UNDEFINED)
},obj);


ary.forEach(function () {
    console.log(this);//=>WINDOW(嚴格模式下是UNDEFINED)
});
ary.forEach(function () {
    console.log(this);//=>OBJ
}, obj);//=>FOR-EACH 和 MAP 這兩個內建方法,除了第一個引數是回撥函式以外,第二個引數是改變回撥函式中的THIS指向的 
// (SOME、FILTER、FIND、EVERY... 這些方法的第二個引數都是改變回撥函式中THIS的)

var newAry = ary.filter(function(item,index){
    // console.log(item,index);
    console.log(this);
    return item > 20;
},obj)
console.log(newAry);

ary.some(function(item,index){
    console.log(item,index,this);
},obj)

ary.find(function(item,index){
    console.log(item,index,this);
},obj)

ary.reduce(function(item,index){
    console.log(item,index,this);
},obj)

ary.every(function(item,index){
    console.log(item,index,this);
},obj)

//字串中的有些方法也執行回撥函式,可以在回撥函式中輸出this看看字串中this指向問題
複製程式碼

3、完成EACH方法的封裝

用回撥函式機制自己封裝個each方法,既可以遍歷陣列,也可以遍歷類陣列和物件,而且支援類似於forEach的回撥函式模式,也支援回撥函式有返回值,還可以把原有陣列變成一個新的陣列

需求:

相容所有的瀏覽器

類似於jq中的each方法,我們需要支援對陣列、類陣列、純粹物件的遍歷任務

在遍歷的過程中,通過回撥函式返回值,來結束當前正在遍歷的操作(回撥函式中返回false,我們應該立即結束對陣列的遍歷操作)=> jq支援

~function () {
    function each(value, callBack, context) {//傳遞進來的vaule值由三種情況:陣列、類陣列、物件  物件只能for in迴圈,其他的當陣列用for迴圈
        context = context || window;//處理this  讓this指向context  不傳遞context,this就是window
        var valueType = Object.prototype.toString.call(value);

        //->如果傳遞的VALUE是一個純粹的物件,我們使用FOR IN遍歷
        if (valueType === '[object Object]') {
            for (var key in value) {
                if (value.hasOwnProperty(key)) {
                    if (typeof callBack === 'function') {
                        var result = callBack.call(context, value[key], key);
                        if (result === false) {
                            break;
                        }
                    }
                }
            }
            return;
        }

        //->如果當前傳遞的VALUE有LENGTH屬性,並且屬性值是純數字,我們就可以使用FOR迴圈遍歷了
        // if (value.hasOwnProperty('length') && !isNaN(value.length)) { // 不能用hasOwnProperty  
        // 因為當第一個引數傳遞的是document.getElementsByClassName("*")類陣列  console.log(value,value.hasOwnProperty('length')=>false)
        if (('length' in value) && !isNaN(value.length)) {
            for (var i = 0; i < value.length; i++) {
                if (typeof callBack === 'function') {
                    result = callBack.call(context, value[i], i);
                    if (result === false) {
                        break;
                    }
                }
            }
            return;
        }


        //->傳遞的引數有錯誤的  丟擲型別錯誤
        throw new TypeError('The value of the parameter you pass is not legal!');
    }

    window.$each = each;
}();

$each([12,23,34,45], function (item, index) {
    if (index > 1) {
        return false;//想要的是結束當前迴圈
    }
    console.log(item, index);
});

$each({name: '哈哈哈', age: 12, 0: 13}, function (item, index) {
    console.log(item, index,this);//this變成12
    if (index === 'name') {
        return false;//想要的是結束當前迴圈
    }
},12);

$each(document.getElementsByClassName("*"), function (item, index) {
    console.log(item, index);
});
複製程式碼

附加思考:

需要支援對原有陣列的修改(回撥函式中的返回值,可以修改原來陣列中的某一項值)

相關文章