zoukankan      html  css  js  c++  java
  • apply, bind, call方法剖析

    Function.prototype.call(),Function.prototype.apply(),Function.prototype.bind()

    是三种改变函数内部this指向(即函数执行时所在作用域)的方法。

    1.Function.prototype.call(thisValue, param1, param2,....)

    // 模拟源码
        /**
         * 1. 让函数立即执行
         * 2. 改变函数内部的this指向
        */
       Function.prototype.call = function call(context) {
        if (this.name === 'call' && typeof context !== 'function') {
          // 多个call函数连续调用
          throw new TypeError('the first param should be a function');
        }
        context = context ? Object(context) : window;
        context.fn = this; // 改变函数内部的this指向
        const args = [];
        for(let i=1; i<arguments.length; i++) {
          args.push('arguments[' + i + ']');
        } 
        // 立即执行;利用了数组的toString()属性
        const r = eval('context.fn('+ args + ')'); 
        delete context.fn;
        return r;   
      }

    1)第一个参数

    第一个参数必须是对象,

    • 如果是 空,undefine, null 都默认绑定到window;
    • 如果是基础类型(如字符型,数值型,布尔型)会自动将基础类型转为包装类型,如: Number(5)
    • 如果是this, 表示绑定的是当前作用域
    var n = 123;
    var obj = {
        n: 456
    }
    function a() {
        return this;
    }
    // this === window
    a.call(); // window
    a.call(null); // window
    a.call(undefine); //window
    a.call(window); // window
    // this === obj
    a.call(obj); // obj
    // this === 包装对象
    a.call(5); // Number(5) {[[PrimitiveValue]]: 5}

    2) 其余的参数

    其余的参数作为函数调用时传入的参数

    function add(a,b) {
      return a + b;
    }
    //this表示固定当前this的指向; this === window
    add.call(this, 1, 2); // 3 

    3)应用

    // 调用对象的原生方法
    var obj = {
      a: 5
    }
    console.log(obj.hasOwnProperty('toString')); // fasle
    obj.hasOwnProperty = function() { // 覆盖obj对象继承自原型链上的方法
      return true; // 改变的是obj本身的方法,原型链上的方法不变
    }
    console.log(obj.hasOwnProperty('toString')); // true
    // obj可以使用原型链上的方法,表示在obj的作用域使用hasOwnProperty的方法
    console.log(Object.prototype.hasOwnProperty.call(obj)); // false

    2. Function.prototype.apply(thisValue, [param1, ....])

    // 模拟源码
    /**
     * 1. apply方法让函数立即执行
     * 2. apply方法绑定函数内部的this指向
     * 3. 参数以数组形式传递
     */
    Function.prototype.apply = function apply(context, args) {
      if (this.name === 'apply' && typeof context !== 'function') {
        // 多个apply函数连续调用;apply作为函数原型链上的方法,只能被函数调用
        throw new TypeError('the first param should be a function');
      }
      context = context ? Object(context) : window;
      context.fn = this;
      if(!args) {// 如果不传参
        return context.fn();    
      }
      const r = eval('context.fn('+ args +')');
      delete context.fn;
      return r;
    }

    1)第一个参数和call方法规则相同

    2)第二个参数

    第二个参数是数组, 将数组中的参数依次作为调用函数的参数,如果函数中参数个数少于apply传参个数,只取前面的。

            function add(a,b) {
                console.log(a,b); // 1 2
                return a + b;
            }
            add.apply(null, [1, 2, 3, 4, 5]);

     第二个参数还可以是类数组,如arguments

            function add(a,b) {
                console.log(a,b); // 3,4
                return a + b;
            }
            function newAdd() {
                return add.apply(null, arguments);
            }
            var result = newAdd(3,4,5,5);
            console.log(result); // 7

    3)应用

    //1) 查找数组的最大值
    const arr = [1,3,5];
    Math.max.apply(null, arr); // 5
    // 还可以
    Math.max(...arr); // 5
    
    // 2) 将数组的空项转为undefined;undefine可以被遍历,空会被遍历函数忽略
    const arr = [1,,4];
    Array.apply(null, arr); // [1,undefined,4] Array是数组的构造函数 

    3. Function.prototype.bind(thisValue, params, param2...)

    // 模拟源码
    /**
     * 1. bind可以绑定this的指向;bind还可以绑定参数;
     *    最后的参数=bind时传入的参数+新函数的参数
     * 2. bind绑定后返回一个新的函数
     * 3. 如果返回的函数被使用了new命令,this指向实例对象
     * 4. new命令生成的实例可以找到原有类的原型对象
     */
    Function.prototype.bind = function bind(context) {
      context = context ? Object(context) : window;
      // 获取bind时传入的参数
      const boundArgs = Array.prototype.slice.call(arguments, 1); 
      const that = this; //原函数
      let boundFn = function() {
        const args = Array.prototype.slice.call(arguments, 1);
        if (this instanceof boundFn) {// 说明使用了new命令
          context = this; //new绑定 > 显示绑定
        }
        return that.apply(context, boundArgs.concat(args));
      };
      // 新生成的函数继承bind之前的原型对象
      function Fn() {}
      Fn.prototype = this.prototype;
      boundFn.prototype = new Fn();
      // 返回新函数
      return boundFn;
    }

    1)第一个参数

    同call,apply方法

    2)剩余的参数

    当bind(thisValue, ...)后面参数的个数小于原函数的个数时,绑定部分参数;

    // 绑定部分参数;相当于参数复用,只需要处理剩余的参数
    function fn(a,b) {
        return a + b
    }
    var newFn = fn.bind(null, 5)
    /*
        newFn = function(b) {
            return 5 + b
        }
    */
    console.log(newFn(6)); // 11

    3)应用

    • bind方法每次运行都返回一个新的函数;在监听事件时需要注意
    document.addEventListener('click', obj.fn.bind(this))
    // 下面取消绑定无效;因为是不同的函数
    document.removeEventListener('click',obj.fn.bind(this))

    正确的写法写法应该是

    var listener = obj.fn.bind(this);
    document.addEventListener('click', listener);
    document.removeEventListener('click', listener);
    • 结合回调函数使用
    obj.print = function () {
      this.times.forEach(function (n) {
        console.log(this.name);
      }.bind(this));
    };
    
    obj.print()
    // 张三
    // 张三
    // 张三
    • 和call方法结合使用;改变传参格式

    [1, 2, 3].slice(0, 1) // [1]
    // 等同于
    Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]
    // 上面代码的意思是在Array.prototype.slice对象上调用call方法
    //
    var mySlice = Function.prototype.call.bind(Array.prototype.slice);
    console.log(mySlice([1,2,3], 0, 1)) // 能调用slice的对象是第一个参数
    //同理
    var myPush = Function.prototype.call.bind(Array.prototype.push);
    // bind方法传参也可以被改变
    function f() {
        console.log(this.a);
    }
    var obj = {a: 1}
    var myBind = Function.prototype.call.bind(Function.prototype.bind); // 能调用bind方法只能是function
    myBind(f, obj)(); // 1

    4. apply.call,bind之间的区别

    1)call和apply方法调用后,相当于绑定this后立即执行

    2)bind方法是返回一个新的函数,并不立即执行

    应用:

    1)将类数组转为数组

    // 1)将类数组转为数组
    // apply,call方法立即执行
    Array.prototype.slice.apply({ 0: 1, length: 1,});
    Array.prototype.slice.call({ 0: 1, length: 1,});
    // bind方法生成一个新的函数,需要手动执行,后面加()
    Array.prototype.slice.bind({ 0: 1, length: 1,})();

    2)给回调函数绑定对象

    // 2)绑定回调函数的对象;
    // 未进行绑定前,回调函数中的this一般都是window
    var name = 'Hello World';
    var obj = {
      name: 'Lyra',
      times: [1,2,4],
      print: function() {
        console.log(this === obj); // true
        this.times.forEach(function() {
          console.log(this === window); // true 
          console.log(this.name);  // Hello World
        })
      }
    }
    obj.print(); 
    
    // 使用bind方法绑定回调函数中的this
    var name = 'Hello World';
    var obj = {
      name: 'Lyra',
      times: [1,2,4],
      print: function() {
        this.times.forEach((function() {
          console.log(this); // obj --3次
          console.log(this.name);  // Lyra --3次
        }.bind(this))) // 不能用call,apply替换,因为会立即执行,就不再是函数了,会返回函数的默认返回值undefined
      }
    }
    obj.print(); 
    
    // 使用call, apply方法绑定回调函数中的this;
    var name = 'Hello World';
    var obj = {
      name: 'Lyra',
      times: [1,2,4],
      print: function() {
        const that = this;
        this.times.forEach((function() {// 因为apply,call会立即执行,所以要嵌套一层函数
          (function IIFE() {
            console.log(this); // obj --3次
            console.log(this.name);  // Lyra --3次
          }).call(that); // 可以替换成apply。IIFE需要用括号扩起来变为函数表达式。否则函数声明不能调用call方法。
        }))
      }
    }
    obj.print();

     

  • 相关阅读:
    Secure your iPhone with 6 digit passcode by upgrading to iOS9
    Threatening letter in Naver Line App
    Android Malware Analysis
    OGG目录清理数据
    RAC配置2个私网网卡使用HAIP服务
    sql调优脚本
    匿名内部类
    权限修饰符
    Android源码目录结构
    luffy项目:基于vue与drf前后台分离项目(1)
  • 原文地址:https://www.cnblogs.com/lyraLee/p/11441879.html
Copyright © 2011-2022 走看看