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);
});
复制代码
附加思考:
需要支持对原有数组的修改(回调函数中的返回值,可以修改原来数组中的某一项值)