JavaScript重识bind、call、apply

前言——this的一些误解

  1. 是指向自身
  2. this 在任何状况下都指向函数的词法做用域

思考:javascript

function foo() {
    var a = 2;
    this.bar(); 
}
function bar() { 
    console.log( this.a );
}
foo(); // ReferenceError: a is not defined ?
复制代码

this 其实是在函数被调用时发生的绑定,它指向什么彻底取决于函数在哪里被调用。java

当一个函数被调用时,会建立一个活动记录(有时候也称为执行上下文)。这个记录会包 含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的 其中一个属性,会在函数执行的过程当中用到。数组

这里要说的call,apply,bind都是来改变this的指向的浏览器

一、call、apply、bind对比

call,apply能够屡次改变绑定对象;只是apply接受数组格式参数;缓存

  • 一、都是用来改变函数的this对象的指向的。
  • 二、第一个参数都是this要指向的对象。
  • 三、均可以利用后续参数传参。

bind()方法会建立一个新函数,称为绑定函数,当调用这个绑定函数时,绑定函数会以建立它时传入 bind()方法的第一个参数做为 this,传入 bind() 方法的第二个以及之后的参数加上绑定函数运行时自己的参数按照顺序做为原函数的参数来调用原函数。bash

bind 是返回对应函数,便于稍后调用;apply 、call 则是当即调用 。app

bind 这些改变上下文的 API 了,对于这些函数来讲,this 取决于第一个参数,若是第一个参数为空,那么就是 window。函数

二、关于绑定

  1. 默认的 this 绑定, 就是说 在一个函数中使用了 this, 可是没有为 this 绑定对象. 这种状况下, 非严格默认, this 就是全局变量 Node 环境中的 global, 浏览器环境中的 window.
  2. 隐式绑定: 使用 obj.foo() 这样的语法来调用函数的时候, 函数 foo 中的 this 绑定到 obj 对象.
  3. 显示绑定: foo.call(obj, ...), foo.apply(obj,[...]), foo.bind(obj,...)
  4. 构造绑定: new foo() , 这种状况, 不管 foo 是否作了绑定, 都要建立一个新的对象, 而后 foo 中的 this 引用这个对象.

优先级: 构造绑定>显示绑定>隐式绑定>默认的 this 绑定 post

三、硬绑定

bind只能被绑定一次;以第一次为准;优化

function foo() {
    console.log("name: " + this.name);
}
var obj = { name: "obj" }, obj2 = { name: "obj2" }, obj3 = { name: "obj3" };
foo.bind(obj).call(obj2)  // name: obj
foo.bind(obj).bind(obj2)()  // name: obj
复制代码

bind 内部就是 包了一个apply;等到调用的时候再执行这个包含apply的函; 实际上,ES5 中内置的 Function.prototype.bind(..) 更加复杂。下面是 MDN 提供的一种bind(..) 实现:

详细请看 为何JavaScript屡次绑定只有一次生效?

if (!Function.prototype.bind) {
    Function.prototype.bind = function(oThis) {
        //一个函数去调用,也就是说bind,call,apply的this是个函数;
        //而后再去改变这个函数里面的this;
        if (typeof this !== "function") {
         // 与 ECMAScript 5 最接近的
         // 内部 IsCallable 函数 
         throw new TypeError(
          "Function.prototype.bind - what is trying " +
         "to be bound is not callable"
         ); 
        }
        //这里将初始化的参数缓存起来;
        var aArgs = Array.prototype.slice.call( arguments, 1 ),
        // ftoBind 指向要bind的函数;
        fToBind = this,
        // 返回一个新函数
        fNOP = function(){}, 
        fBound = function(){
        //fToBind.apply 改变绑定this;
        // 执行的时候判断,当前this等于fNOP而且传入oThis,就设置成当前this,否则就改变成初始化传入的oThis;
           return fToBind.apply( 
            (this instanceof fNOP && oThis ? this : oThis ),
            aArgs.concat(Array.prototype.slice.call( arguments ) )
            ); 
        };
        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();
        return fBound;
    };
}

复制代码

解释(this instanceof fNOP && oThis ? this : oThis ) 这段代码请看 javascript 深刻解剖bind内部机制

四、软绑定

硬绑定这种方式能够把 this 强制绑定到指定的对象(除了使用 new 时),防止函数调用应用默认绑定规则。问题在于,硬绑定会大大下降函数的灵活性,使用硬绑定以后就没法使用隐式绑定或者显式绑定来修改 this。

if (!Function.prototype.softBind) {
    Function.prototype.softBind = function(obj) { 
        var fn = this; // 捕获全部 curried 参数
        var curried = [].slice.call( arguments, 1 );
        var bound = function() {
            return fn.apply((!this || this === (window || global)) ?
            obj : this,curried.concat.apply( curried, arguments ) ); 
        }; 
        bound.prototype = Object.create( fn.prototype );
        return bound;
    }; 
} 
复制代码

它会对指定的函 数进行封装,首先检查调用时的 this,若是 this 绑定到全局对象或者 undefined,那就把 指定的默认对象 obj 绑定到 this,不然不会修改 this。此外,这段代码还支持可选的柯里化;

function foo() {
    console.log("name: " + this.name);
}
var obj = { name: "obj" }, obj2 = { name: "obj2" }, obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ(); // name: obj
obj2.foo = foo.softBind(obj); 
obj2.foo(); // name: obj2 <---- 看!!!
fooOBJ.call( obj3 ); // name: obj3 <---- 看! 
setTimeout( obj2.foo, 10 );// name: obj <---- 应用了软绑定
复制代码

注意

若是你把 null 或者 undefined 做为 this 的绑定对象传入 call、apply 或者 bind,这些值 在调用时会被忽略,实际应用的是 默认绑定规则:

function foo(a,b) {
    console.log( "a:" + a + ", b:" + b );
}
// 把数组“展开”成参数
foo.apply( null, [2, 3] ); // a:2, b:3

// 使用 bind(..) 进行柯里化
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3
复制代码

用 null 来忽略 this 绑定可能会有反作用。若是某个函数确实使用了 this(好比第三方库中的一个函数),那默认绑定规则会把 this 绑定到全局对象(在浏览 器中这个对象是 window),这将致使不可预计的后果(好比修改全局对象)。

优化:用Object.create(null)替代 null 或者 undefined


  • 参考:《你不知道的javascript》
相关文章
相关标签/搜索