• 使用另一种方式实现js中Function的调用(call/apply/bind)


    在JavaScript中函数的调用可以有多种方式,但更经典的莫过于call和apply。call跟apply都绑定在函数上,他们两个的第一个参数意义相同,传入一个对象,他作为函数的执行环境(实质上是为了改变函数的Execution Context执行上下文),也就是this的指向;而第二个参数两者只是类型的不同,call传的是arguments,而apply传的是array。废话不多说,先上一个最基础的例子:

    function add(c,d){
      return this.a + this.b + c + d;
    }
    var o = {
      a: 1,
      b: 2        
    }
    add.call(o,3,4)          // 10
    add.apply(o,[3,4])    // 10

    再比如我之前看过的Twitter上的关于call和apply的一个面试题:定义一个函数log,传入任意数量参数,让它模拟console.log的方法,形如log('hello','world');

    function log(){
      console.log.apply(console,arguments);
    }
    

      

    当执行这个log函数时,该函数的执行上下文为console对象,arguments为传入的实参。

    然后此题又有需求,如果要给每个log信息加一个(app)前缀,比如 '(app) hello world';

    这时我们应该想到一点,那就是我们传入的实参,也就是arguments并不是一个数组,它实质上只是一个类数组的对象,我们在这里可以用 instanceof 来判断自定义对象。

    instanceof的意思就是看左边对象的原型链上是否有右边构造器的prototype属性。由上图可以看出arguments是一个类数组的对象,并且可以看出arguments没有array的方法。

    所以回到刚才那个题,我们必须让传入的实参变为Array类型才可以调用数组的unshift方法(在它的前面加上app)。

    function log(){
      var args = Array.prototype.slice.call(arguments);
      args.unshift('(app)');  
      console.log.apply(console,args);        
    }
    
    log('hello','world');  // (app) hello world

    在OOP面向对象中此方法用的更加多一些,当我们不想为另一个对象创建方法的时候,可以用call调用foo的方法。

    function foo () {}
    foo.prototype.name = 'james';
    foo.prototype.sayHello = function(){
        console.log(this.name);
    }
    var obj1 = new foo();
    obj1.sayHello();   //james
    var obj2 = {
        name: 'bond'
    }
    obj1.sayHello.call(obj2);  //bond

    再比如如果要想调用一些不能直接调用的方法,比如Object.prototype.toString(),我们也可以用call,它的本质在于将内部的变量改为包装对象。

     这个对象原型的方法指向window,也就是this,但是如果把log函数里call指向的对象改为window的话,就会输出global。因为在执行上下文中的全局对象为Global Context,

    比方说全局的Math,String,window都存在于[[global]]变量对象中。

    window对象依附于Global全局变量对象,虽然权威上来说在NodeJS里全局是Global对象,但是在全局执行上下文中window指向global,请看下图。

    function log(){
    console.log(this===window); // true
    return Object.prototype.toString.call(this); } log.call(5) // [Object Number] log.call(true) // [Object Boolean]

    对于js中的bind方法,他跟apply和call基本一样,里面传递的参数也是改变this指向的,比方下面一个关于执行上下文的代码,通过bind改变this的指向。

    var User = {
      count: 1,
      getCount: function(){
        return this.count;
      }
    }
    var func = User.getCount;
    console.log(func());  //undefined
    var func1 = User.getCount.bind(User) 
    console.log(func1());  // 1
     

    上面的答案是undefined,因为func是在全局作用域window中,window里面没有count属性,所以我们为了让this指向User对象,我们便想到了使用bind。但是bind是es5才有的方法,不兼容老版本浏览器,那如何解决这个问题呢?下面是我从火狐的MDN上拿下来的bind模拟。

    if(!Function.prototype.bind){
        Function.prototype.bind = function(oThis){
            if(typeof this !== 'function'){
                throw new TypeError('What is trying to be bound is not callable');
            }
            var args = Array.prototype.slice.call(arguments,1),
                fTobind = this,  //指向调用bind的函数
                fNOP = function(){},  //创建一个空函数,为了下面的继承
                fBound = function(){
                    return fTobind.apply(this instanceof fNOP ? this : oThis,  //改变this的指向
                        args.concat(Array.prototype.slice.call(arguments)));  //将通过bind传递的参数与调用时传的参数合并
                };
                fNOP.prototype = this.prototype;  //将目标函数的原型传递到新函数中
                fBound.prototype = new fNOP;  //这两条相当于Object.create的作用
                return fBound;
        }
    }

    我们通过下面这个例子来分析它吧:

    function foo(){
      this.b = 100;
      return this.a;
    };
    var func = foo.bind({a:1});
    func() // 1
    new func()  // {b:100}

    fBound指向新函数func,因为foo调用的bind方法,所以fToBind指向目标函数foo,在func()里面this指向传进去的对象,也就是bind模拟里面的oThis,通过this instanceof fNOP来判断this的指向,因为func()是window调用下的,所以this指向window,所以this instanceof fNOP 返回false,所以oThis也就是传入的{a:1}为当前的执行上下文,所以弹出1。但如果是对象调用(  new Func()  )的话,this instanceof fNOP中的this会指向一个空对象,空对象的原型会指向构造器的prototype属性,即func的prototype属性,这里注意一点,因为foo 中 return的不是对象,所以忽略return。

  • 相关阅读:
    Django----路由控制
    Django-ORM的使用
    Django-ORM框架
    Django对数据库表的操作
    Python操作mysql
    [mysql]linux mysql 基础命令操作
    [mysql]linux mysql 读写分离
    [mysql]linux mysql 主从复制
    [mysql 1]linux mysal 安装
    [mysql]linux mysal 安装
  • 原文地址:https://www.cnblogs.com/ssh-007/p/5223483.html
Copyright © 2020-2023  润新知