JavaScript重识bind、call、apply

前言——this的一些误解

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

思考:

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

this 实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。

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

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

1、call、apply、bind对比

call,apply可以多次改变绑定对象;只是apply接受数组格式参数;

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

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

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

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

2、关于绑定

  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 绑定


image

3、硬绑定

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内部机制

4、软绑定

硬绑定这种方式可以把 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》
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342