• call、apply与bind在理解


    call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。

    fun.call(thisArg[, arg1[, arg2[, ...]]])
    
    

    apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数

    fun.apply(thisArg, [argsArray])
    

    bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数

    fun.bind(thisArg[, arg1[, arg2[, ...]]])
    

    当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。

    特点:
    返回一个函数
    可以传入参数

    1、理解

    call方法原理

    模拟Function中内置的call方法,写一个myCall方法,探讨call方法的执行原理
    
    function sum(){
        console.log(this);
    }
    function fn(){
        console.log(this);
    }
    var obj = {name:'iceman'};
    Function.prototype.myCall = function (context) {
        // myCall方法中的this就是当前我要操作和改变其this关键字的那个函数名
    
        // 1、让fn中的this关键字变为context的值->obj
        // 让this这个函数中的"this关键字"变为context
        // eval(this.toString().replace("this","obj"));
    
        // 2、让fn方法在执行
        // this();
    };
    fn.myCall(obj);// myCall方法中原来的this是fn
    sum.myCall(obj);// myCall方法中原来的this是sum
    
    

    call方法经典例子

    function fn1() {
        console.log(1);
    }
    function fn2() {
        console.log(2);
    }
    
    fn1.call(fn2); // 1
    

    首先fn1通过原型链查找机制找到Function.prototype上的call方法,并且让call方法执行,此时call这个方法中的this就是要操作的fn1。在call方法代码执行的过程过程中,首先让fn1中的“this关键字”变为fn2,然后再让fn1这个方法执行。

    fn1.call.call(fn2); // 2
    

    2、区别

    三个函数存在的区别, 用一句话来说的话就是: bind是返回对应函数, 便于稍后调用; apply, call则是立即调用,apply是call的一层封装,可以传数组。所以call比较快。 除此外, 在 ES6 的箭头函数下, call 和 apply 的失效, 对于箭头函数来说:

    • 函数体内的 this 对象, 就是定义时所在的对象, 而不是使用时所在的对象;
    • 不可以当作构造函数, 也就是说不可以使用 new 命令, 否则会抛出一个错误;
    • 不可以使用 arguments 对象, 该对象在函数体内不存在. 如果要用, 可以用 Rest 参数代替;
    • 不可以使用 yield 命令, 因此箭头函数不能用作 Generator 函数;

    call 方法比 apply 快的原因是 call 方法的参数格式正是内部方法所需要的格式

    3、模拟

    思路
    • 将函数设为对象的属性
    • 执行该函数
    • 删除该函数

    call

    Function.prototype.call2 = function (context) {
        var context = context || window;//null等传入指向window
        context.fn = this;//将函数设为对象的属性
    
        var args = [];
        for(var i = 1, len = arguments.length; i < len; i++) {
            args.push('arguments[' + i + ']');
        }
    
        var result = eval('context.fn(' + args +')');//执行该函数
    
        delete context.fn; // 删除函数
        return result;
    }
    

    apply

    Function.prototype.apply = function (context, arr) {
        var context = Object(context) || window;
        context.fn = this;
    
        var result;
        if (!arr) {
            result = context.fn();
        }
        else {
            var args = [];
            for (var i = 0, len = arr.length; i < len; i++) {
                args.push('arr[' + i + ']');
            }
            result = eval('context.fn(' + args + ')')
        }
    
        delete context.fn
        return result;
    }
    

    bind

    一个绑定函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

    Bound.prototype = this.prototype,我们直接修改 Bound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转

    Function.prototype.bind = Function.prototype.bind || function (context) {
        if (typeof this !== "function") {
              throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
        }
    
        var self = this;
        var args = Array.prototype.slice.call(arguments, 1);//先取到bind时传参,bar.bind(foo, 'daisy');
    
        var F = function () {};//继承用空函数来转
    
        var Bound = function () {
            var bindArgs = Array.prototype.slice.call(arguments);//在取到返回函数,bindFoo('18');
            //如果是new的this指向F,this指向构造函数,如果不是指向要改变的this
            return self.apply(this instanceof F ? this : context, args.concat(bindArgs)//合并参数);
        }
    
        F.prototype = this.prototype;
        Bound.prototype = new F();
        return Bound;
    };
    

    4、案例

    1、柯里化

    var currying = function( fn ){
        var args = [];
        return function(){
            if ( arguments.length === 0 ){
                return fn.apply( this, args );
            }else{
                [].push.apply( args, arguments );
                return arguments.callee;
            }
        }
    };
    var cost = (function(){
        var money = 0;
        return function(){
            for ( var i = 0, l = arguments.length; i < l; i++ ){
                money += arguments[ i ];
            }
            return money;
        }
    })();
    var cost = currying( cost ); // 转化成 currying 函数
    cost( 100 ); // 未真正求值
    cost( 200 ); // 未真正求值
    cost( 300 ); // 未真正求值
    alert ( cost() ); // 求值并输出: 
    
    
    var overtime = (function() {
      var args = [];
    
      return function() {
        if(arguments.length === 0) {
          var time = 0;
          for (var i = 0, l = args.length; i < l; i++) {
            time += args[i];
          }
          return time;
        }else {
          [].push.apply(args, arguments);
        }
      }
    })();
    
    overtime(3.5);    // 第一天
    overtime(4.5);    // 第二天
    overtime(2.1);    // 第三天
    //...
    
    console.log( overtime() );    // 10.1
    
    
    

    2、debounce 函数去抖

    var debounce = function(idle, action){
      var last
      return function(){
        var ctx = this, args = arguments
        clearTimeout(last)
        last = setTimeout(function(){
            action.apply(ctx, args)
        }, idle)
      }
    }
    
    
    var timer = null;
    window.onscroll = function(){
        if (timer) {
          // 清除未执行的逻辑,重新执行下一次逻辑,不论上一次是否执行完毕
          clearTimeout(timer); 
        }
        timer = setTimeout(function(){
            //执行逻辑
        }, 300);
    };
    
    
    

    3、throttle 函数节流

    var throttle = function ( fn, interval ) {
        var __self = fn, // 保存需要被延迟执行的函数引用
                timer, // 定时器
                firstTime = true; // 是否是第一次调用
        return function () {
            var args = arguments,
                    __me = this;
            if ( firstTime ) { // 如果是第一次调用,不需延迟执行
                __self.apply(__me, args);
                return firstTime = false;
            }
            if ( timer ) { // 如果定时器还在,说明前一次延迟执行还没有完成
                return false;
            }
            timer = setTimeout(function () { // 延迟一段时间执行
                clearTimeout(timer);
                timer = null;
                __self.apply(__me, args);
            }, interval || 5000 );
        };
    };
    window.onresize = throttle(function(){ console.log( 1 ); }, 5000 );
    
    var can = true;
    window.onscroll = function(){
      if(!can){
       //判断上次逻辑是否执行完毕,如果在执行中,则直接return
       return;
      }
      can = false;
      setTimeout(function(){
        //执行逻辑
        can = true;
      }, 100);
    };
    
    

    4、反柯里化(uncurring)

    Function.prototype.uncurring = function() {
      var self = this;  //self此时是Array.prototype.push
    
      return function() {
        var obj = Array.prototype.shift.call(arguments);
        //obj 是{
        //  "length": 1,
        //  "0": 1
        //}
        //arguments的第一个对象被截去(也就是调用push方法的对象),剩下[2]
    
        return self.apply(obj, arguments);
        //相当于Array.prototype.push.apply(obj, 2);
      };
    };
    
    //测试一下
    var push = Array.prototype.push.uncurring();
    var obj = {
      "length": 1,
      "0" : 1
    };
    
    push(obj, 2);
    console.log( obj ); //{0: 1,1: 2, length: 2 }
    
    

    5、取数组最大值

    Math.max(1,2,3,4);
    利用apply可以把传数组的方法

    var max = Math.max.apply(null, ary); 
    
    Math.max(1,2,3,4);
    
    var max = eval("Math.max(" + ary.toString() + ")");
    
    

    在非严格模式下,给apply的第一个参数为null的时候,会让max/min中的this指向window,然后将ary的参数一个个传给max/min

    6、将类数组转换数组

    slice在不穿参的情况下是复制数组

    Array.prototype.slice = function() {
        var result = [];
        for(var i = 0; i < this.length; i++){ 
            result[i] = this[i] }; 
            return result;  
        }
    
    [1,2,3,4].slice()
    => [1, 2, 3, 4]
    var obj = {length:'2', '0': 'aa', '1': 'bb'}
    [].slice.call(obj)
    => ["aa", "bb"]
    
    
    function listToArray(likeAry) {
        var ary = [];
        try {
            ary = Array.prototype.slice.call(likeAry);
        } catch (e) {
            for (var i = 0; i < likeAry.length; i++) {
                ary[ary.length] = likeAry[i];
            }
        }
        return ary;
    }
    

    7、push的理解

    Array.prototype.push = function(str){ return this.concat(str)  }
    [1,2,3].push(1)
    =>[1, 2, 3, 1]
    [].push.call([1,2,3],3)
    => [1, 2, 3, 3]
    

    5、caller与callee

    caller

    返回一个对函数的引用,该函数调用了当前函数。

    对于函数来说,caller 属性只有在函数执行时才有定义。
    如果函数是由顶层调用的,那么 caller 包含的就是 null 。
    如果在字符串上下文中使用 caller 属性,那么结果和 functionName.toString 一样,也就是说,显示的是函数的反编译文本。

    // caller demo {
    function callerDemo() {
         if (callerDemo.caller) {
             var a= callerDemo.caller.toString();
             alert(a);
         } else {
             alert("this is a top function");
         }
    }
    function handleCaller() {
         callerDemo();              //"function handleCaller() { callerDemo();}"
    }
    
    

    callee

    返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文。
    ES5 提示: 在严格模式下,arguments.callee 会报错 TypeError,因为它已经被废除了

    callee 属性是 arguments 对象的一个成员,它表示对函数对象本身的引用,这有利于匿名函数的递归或者保证函数的封装性

    arguments.length是实参长度,arguments.callee.length是形参长度

    在递归中有很好的应该

  • 相关阅读:
    Count on a tree
    图论1 1009
    DP2 1008
    DP1 1008
    NOIP 模拟 1006
    2019 CSP-S 初赛退役记
    9.13——TEST NOIP模拟测试
    [洛谷P2387][NOI2014]魔法森林
    [洛谷P2596][ZJOI2006]书架
    [BZOJ4241]历史研究
  • 原文地址:https://www.cnblogs.com/chenjinxinlove/p/8521325.html
Copyright © 2020-2023  润新知