在某些场景下 this 绑定会让人很吃惊,比如在你试图实施一种绑定,然而最终得到的却是 默认绑定 规则的绑定行为(见前面的内容)。

    如果你传递 nullundefined 作为 callapplybindthis 绑定参数,那么这些值会被忽略掉,取而代之的是 默认绑定 规则将适用于这个调用。

    为什么你会向 this 绑定故意传递像 null 这样的值?

    一个很常见的做法是,使用 apply(..) 来将一个数组散开,从而作为函数调用的参数。相似地,bind(..) 可以柯里化参数(预设值),也可能非常有用。

    1. function foo(a,b) {
    2. console.log( "a:" + a + ", b:" + b );
    3. }
    4. // 将数组散开作为参数
    5. foo.apply( null, [2, 3] ); // a:2, b:3
    6. // 用 `bind(..)` 进行柯里化
    7. var bar = foo.bind( null, 2 );

    这两种工具都要求第一个参数是 this 绑定。如果目标函数不关心 this,你就需要一个占位值,而且正如这个代码段中展示的,null 看起来是一个合理的选择。

    注意: 虽然我们在这本书中没有涵盖,但是 ES6 中有一个扩散操作符:...,它让你无需使用 apply(..) 而在语法上将一个数组“散开”作为参数,比如 foo(...[1,2]) 表示 foo(1,2) —— 如果 this 绑定没有必要,可以在语法上回避它。不幸的是,柯里化在 ES6 中没有语法上的替代品,所以 bind(..) 调用的 this 参数依然需要注意。

    可是,在你不关心 this 绑定而一直使用 null 的时候,有些潜在的“危险”。如果你这样处理一些函数调用(比如,不归你管控的第三方包),而且那些函数确实使用了 this 引用,那么 默认绑定 规则意味着它可能会不经意间引用(或者改变,更糟糕!)global 对象(在浏览器中是 )。

    更安全的 this

    也许某些“更安全”的做法是:为了 this 而传递一个特殊创建好的对象,这个对象保证不会对你的程序产生副作用。从网络学(或军事)上借用一个词,我们可以建立一个“DMZ”(非军事区)对象 —— 只不过是一个完全为空,没有委托(见第五,六章)的对象。

    如果我们为了忽略自己认为不用关心的 this 绑定,而总是传递一个 DMZ 对象,那么我们就可以确定任何对 this 的隐藏或意外的使用将会被限制在这个空对象中,也就是说这个对象将 global 对象和副作用隔离开来。

    因为这个对象是完全为空的,我个人喜欢给它一个变量名为 ø(空集合的数学符号的小写)。在许多键盘上(比如 Mac 的美式键盘),这个符号可以很容易地用 +o(option+o)打出来。有些系统还允许你为某个特殊符号设置快捷键。如果你不喜欢 ø 符号,或者你的键盘没那么好打,你当然可以叫它任意你希望的名字。

    无论你叫它什么,创建 完全为空的对象 的最简单方法就是 Object.create(null)(见第五章)。Object.create(null){} 很相似,但是没有指向 Object.prototype 的委托,所以它比 {} “空得更彻底”。

    不仅在功能上更“安全”,ø 还会在代码风格上产生些好处,它在语义上可能会比 null 更清晰的表达“我想让 this 为空”。当然,你可以随自己喜欢来称呼你的 DMZ 对象。

    另外一个要注意的是,你可以(有意或无意地!)创建对函数的“间接引用(indirect reference)”,在那样的情况下,当那个函数引用被调用时,默认绑定 规则也会适用。

    一个最常见的 间接引用 产生方式是通过赋值:

    1. function foo() {
    2. console.log( this.a );
    3. }
    4. var a = 2;
    5. var o = { a: 3, foo: foo };
    6. var p = { a: 4 };
    7. o.foo(); // 3
    8. (p.foo = o.foo)(); // 2

    提醒: 无论你如何得到适用 默认绑定 的函数调用,被调用函数的 内容strict mode 状态 —— 而非函数的调用点 —— 决定了 this 引用的值:不是 global 对象(在非 strict mode 下),就是 undefined(在 下)。

    我们之前看到 硬绑定 是一种通过将函数强制绑定到特定的 this 上,来防止函数调用在不经意间退回到 默认绑定 的策略(除非你用 new 去覆盖它!)。问题是,硬绑定 极大地降低了函数的灵活性,阻止我们手动使用 隐含绑定 或后续的 明确绑定 来覆盖 this

    如果有这样的办法就好了:为 默认绑定 提供不同的默认值(不是 globalundefined),同时保持函数可以通过 隐含绑定明确绑定 技术来手动绑定 this

    我们可以构建一个所谓的 软绑定 工具来模拟我们期望的行为。

    这里提供的 softBind(..) 工具的工作方式和 ES5 内建的 bind(..) 工具很相似,除了我们的 软绑定 行为。它用一种逻辑将指定的函数包装起来,这个逻辑在函数调用时检查 this,如果它是 globalundefined,就使用预先指定的 默认值obj),否则保持 this 不变。它也提供了可选的柯里化行为(见先前的 bind(..) 讨论)。

    我们来看看它的用法:

    1. function foo() {
    2. console.log("name: " + this.name);
    3. }
    4. var obj = { name: "obj" },
    5. obj2 = { name: "obj2" },
    6. obj3 = { name: "obj3" };
    7. var fooOBJ = foo.softBind( obj );
    8. fooOBJ(); // name: obj
    9. obj2.foo = foo.softBind(obj);
    10. obj2.foo(); // name: obj2 <---- 看!!!
    11. fooOBJ.call( obj3 ); // name: obj3 <---- 看!

    软绑定版本的 foo() 函数可以如展示的那样被手动 this 绑定到 obj2obj3,如果 默认绑定 适用时会退到 obj