前端全棧工程化開發專題 — JS中回撥函式的深入解讀
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); }); 複製程式碼
附加思考:
需要支援對原有陣列的修改(回撥函式中的返回值,可以修改原來陣列中的某一項值)