阅读 511

前端全栈工程化开发专题 — JS中回调函数的深入解读

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);
});
复制代码

附加思考:

需要支持对原有数组的修改(回调函数中的返回值,可以修改原来数组中的某一项值)

关注下面的标签,发现更多相似文章
评论