每日小知识(JavaScript篇)-- 实现call()、apply()、bind()

前言

 使用自己写的call()、apply()函数实现防抖:在input输入框中输入内容后,隔一段时间在进行操作(比如在控制台打印出input的内容),而不是每次输入都触发操作
请添加图片描述

<body>
    <input type="text" name="" id="">
   <script>
        const input = document.querySelector('input');

        // 实现call()函数
        Function.prototype.myCall = function(){
        }

        function debounce(fn, delay){
            var timer = null;
            return function(){
                if(timer) clearTimeout(timer);
                var _this = this;
                var _arguments = arguments;
                timer = setTimeout(() => {
                    fn.myCall(_this, _arguments);
                }, delay);
            }
        }

        function searchChange(){
            console.log(this.value);
        }
        input.oninput = debounce(searchChange, 2000);

   </script>
</body>

分析(以call()为例):

  • 使用call()的时候,call需要实现:1. 改变函数fn的this指向,2. 将bendoun()的参数传递过来的参数传递给绑定call的函数fn
一、call、apply概述
call()
  • call()方法接收两个参数
    • 第一个参数代表调用call的函数体内的 this 指向
    • 与apply不同,call的第二个参数是依次传入函数的,不是带有下标的集合
function sum(num1, num2){
    return num1 + num2;
}

function callSum2(num1, num2){
    return sum.call(this, num1, num2);
}
console.log(callSum2(10, 10));	// 20
apply()
  • apply()方法接收两个参数

    • 第一个参数代表调用apply的函数体内的 this 指向
    • 第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数
    function sum(num1, num2){
        return num1 + num2;
    }
    
    function callSum2(num1, num2){
        return sum.call(this, [num1, num2]);
        // return sum.call(this, arguments);
    }
    console.log(callSum2(10, 10));	// 20
    
apply和call的区别
  • 它们的作用一模一样,区别仅在于传入参数的形式的不同
  • 它们的第一个参数是一样的,关键在于第二个参数
    • apply 接受两个参数,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数。
    • call 传入的参数数量不固定,从第二个参数开始往后,每个参数被依次传入函数。
关于apply和call改变函数this指向的问题(重点)
  • apply和call的强大之处在于控制函数调用上下文即函数体内this值的能力,可以将任意对象设置为任意函数的作用域

  • 通过下面的例子来理解(以call为例,call和apply都是一样的):

    window.color = 'red';
    let obj = {
        color: 'blue'
    };
    function setColor(){
        console.log(this.color);
    }
    
    setColor();	// red
    setColor.call(this); // red
    setColor.call(window); // red
    setColor.call(obj); // blue
    
    • 在全局作用域下调用setColor()时,setColor()的this是指向window的,所以调用setColor();得到的颜色为red

    • 当使用call在全局作用域下调用setColor.call(this);或者setColor.call(window);,得到的也是red,这里就涉及到:

      • 使用call/apply来改变函数的this值时,如果传入的第一个参数不是一个对象(比如上面的obj),而是this/window,参数为this的时候,this的指向跟当前调用函数的上下文有关,因为setColor是在全局环境中调用的,所以this值就是window。如果是一个对象,那么他就指向这个对象

      • 如果setColor是在另外一个函数b体内被调用了,那么this的指向就跟函数b的调用环境有关,例如:

        • 在全局环境下调用fn,setColor的this就是window
        function setColor(color){
            console.log(this); // window
            console.log(color); // red
        }
        
        // 1. fn是一个全局函数时
        function fn(color){
            return setColor.call(this, color);
        }
        // 在全局环境下调用fn
        fn('red');
        
        • obj对象调用fn,setColor的this就指向obj对象
        function setColor(color){
            console.log(this);	// obj
            console.log(color); // blue
        }
        
        // 2. fn是obj对象的一个属性时
        let obj = {
            color: blue,
            fn: function(){
                return setColor.call(this, this.color);
            }
        }
        // obj调用fn
        obj.fn(); // blue
        
    • 使用call将setColor函数的this切换为obj对象,所以颜色为blue

二、bind

bind语法和call一模一样,区别在于立即执行还是等待执行,bind不兼容IE6~8

fn.call(obj, 1, 2); // 改变fn中的this,并且把fn立即执行
fn.bind(obj, 1, 2); // 改变fn中的this,fn并不执行

bind会把fn中的this预处理为obj,此时fn没有执行,当点击的时候才会把fn执行

document.onclick = fn.bind(obj);
  • 使用bind可以在setColor上调用bind传入对象obj,创建一个新的函数fn, 但是call和apply都不行(会报错)

    function setColor(color){
        console.log(this);	// obj
        console.log(color); // blue
    }
    
    let obj = {
        color: 'blue'
    }
    
    let a = setColor.bind(obj, obj.color);
    a();
    
    • let a = setColor.call(obj, obj.color);
      在这里插入图片描述
三、实现call、apply、bind
实现call
  • 第一种写法
Function.prototype.myCall = function(){
    if (typeof this !== "function") {
    	console.error("type error");
    }

    const args = [...arguments];
    // 得到的t是myCall所在上下文环境的this
    const t = args.shift();

    // 这里this的指向是:谁调用了myCall,this就指向谁
    // 按照测试,是sum()调用了mycall(),所以this就指向sum()
    const _this = this;
    console.log(_this);     // f sum(num1, num2){}
    // 1. 让t改为指向sum(), 即调用了myCall的函数
    t.fn = _this;
    // 2. 经过上面一步,t.fn就相当于sum()
    // 2. 现在要实现把参数通过myCall传给sum
    // 2. 直接把删除掉第一个参数args传给t.fn
    const res = t.fn(...args);
    delete t.fn;
    return res;
}
  • 第二种写法

    • 判断调用对象是否为函数,即使是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
    • 判断传入上下文对象是否存在,如果不存在,则设置为 window 。
    • 处理传入的参数,截取第一个参数后的所有参数。
    • 将函数作为上下文对象的一个属性。
    • 使用上下文对象来调用这个方法,并保存返回结果。
    • 删除刚才新增的属性。
    • 返回结果。
    Function.prototype.myCall = function(context) {
        // context代表的就是传给myCall()的第一个参数
        console.log(context);   // this指向的对象
        // 判断调用对象
        if (typeof this !== "function") {
            console.error("type error");
        }
        // 获取参数
        // slice()返回数组中被选中的元素。
        let args = [...arguments].slice(1),
            result = null;
        //console.log(...args); // 20,30
        // 判断 context 是否传入,如果未传入则设置为 window
        context = context || window;
        // 将调用函数设为对象的方法
        context.fn = this;
        // 调用函数
        result = context.fn(...args);
        // 在需要返回值的情况下,如果调用myCall()的函数有返回值,那么context.fn()就是那个返回值
        console.log(context.fn());  // hello
        console.log(result);    // hello
        // 将属性删除
        delete context.fn;
        return result;
    };
    
    // 测试
    function fn(a,b){
        console.log('a: ',a); 
        console.log('b: ',b);
        console.log('this: ',this);
        return 'hello';
    }
    
    const res = fn.myCall(this, 20, 30);
    
实现apply
  • 判断调用对象是否为函数,即使是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。

  • 判断传入上下文对象是否存在,如果不存在,则设置为 window 。

  • 将函数作为上下文对象的一个属性。

  • 判断参数值是否传入

  • 使用上下文对象来调用这个方法,并保存返回结果。

  • 删除刚才新增的属性

  • 返回结果

Function.prototype.myApply = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  let result = null;
  // 判断 context 是否存在,如果未传入则为 window
  context = context || window;
  // 将函数设为对象的方法
  context.fn = this;
  // 调用方法
  if (arguments[1]) {
    result = context.fn(...arguments[1]);
  } else {
    result = context.fn();
  }
  // 将属性删除
  delete context.fn;
  return result;
};
实现bind
  • 判断调用对象是否为函数,即使是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。
  • 保存当前函数的引用,获取其余传入参数值。
  • 创建一个函数返回
  • 函数内部使用 apply 来绑定函数调用,需要判断函数作为构造函数的情况,这个时候需要传入当前函数的 this 给 apply 调用,其余情况都传入指定的上下文对象。
Function.prototype.myBind = function(context) {
  // 判断调用对象是否为函数
  if (typeof this !== "function") {
    throw new TypeError("Error");
  }
  // 获取参数
  var args = [...arguments].slice(1),
    fn = this;
  return function Fn() {
    // 根据调用方式,传入不同绑定值
    return fn.apply(
      this instanceof Fn ? this : context,
      args.concat(...arguments)
    );
  };
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值