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);
});
複製程式碼
附加思考:
需要支援對原有陣列的修改(回撥函式中的返回值,可以修改原來陣列中的某一項值)