20160124-You dont know js(Chapter 2)

Everything In Order

now we’ve uncovered the 4 rules for binding thisin function calls.
现在要做的就是找到调用并检查哪种规则试用,但是,如果有符合多个条件的规则呢?这些规则一定有一个优先级。下面我们将演示这些规则的顺序。
首先要说明 default binding 在四种方式中优先级最低。
那么是隐式绑定还是显示绑定优先级更高?

function foo(){
    console.log(this.a)
}
var obj1 = {
    a: 2,
    foo: foo
}
var obj2 = {
   a: 2,
   foo: foo
}
obj1.foo();  // 2
obj2.foo();  // 3

obj1.foo.call(obj2);  // 3
obj2.foo.call(obj1);  // 2

所以,显示绑定的优先级高于隐式绑定,这意味着你在调用显示绑定之前先要检查是否有隐式绑定。
现在,只需要找出 new binding 的优先级

function foo(something){
    this.a = something;
}
var obj1 = {
    foo: foo
}
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a );    // 2
obj1.foo.call( obj2, 3 );
console.log( obj2.a );    // 3

var bar = new obj1.foo( 4 );
console.log( obj1.a );    // 2
console.log( bar.a );     // 4

所以,new binding 的优先级比隐式绑定高
newcall/apply不能被用到一起,但是可以用hard binding来测试优先级。

function foo(something){
    this.a = something;
}
var obj1 = {};
// 将 foo 函数给 bar,this 绑定为 obj1
var bar = foo.bind(obj1);
bar(2);
console.log(obj1.a);   // 2

// 将 bar 函数给 baz,但因为 bar 中的 this 是显示绑定,所以bar中的 this 值不会变,而用 new binding 方式的 baz 中 this 值为 baz(可能有问题)
var baz = new bar(3);
console.log(obj1.a);   // 2
console.log(baz.a);    // 3

Determining this

现在,我们可以总结函数的调用和其优先顺序。检查它们的调用方式,每次问这些问题知道有一个问题适用为止
1. is the function called with new( new binding ), if so, this is thw newly constructed object.

var bar = new foo()

2.is the function called with call or apply( explicit binding ), even hidden inside a bind hard binding, if so, this is the explicitly specified object.

var bar = foo.call(obj2)

3.if function called with a context( implicit binding ), otherwise known as an owning or containing object, otherwise? if so, this is that context object.

 var bar = obj1.foo()
  1. otherwise, default the this( default binding ), If in strict mode, pick undefined, otherwise pick the global object.
var bar = foo();

That’s it. That’s all it takes to understand the rules of this binding for normal function calls. Well… almost.

Binding Exceptions

As usual, there are some exceptions to the “rules”.
可能绑定的行为会出现一些意外,你想用其他的绑定方式最后结果是全局绑定。(像前面提到的:赋给全局变量、传进参数用全局的函数调用,setTimeOut)

Ignored this

当你将 null 或者 undefined 作为参数传入 call 或者 apply 或者 bind 时,这些值被忽略,default binding 被用于这个调用。(可能有问题)

function foo(){
    console.log(this.a);
}
var a = 2;
// 这里 this 默认到 global object
foo.call(null);  // 2

为什么故意把参数赋为 null?
这在用 apply(..) 传参数是非常常见,相似的 bind() 可以预设值这将非常有用。

function foo(a,b) {
    console.log( "a:" + a + ", b:" + b );
}

// spreading out array as parameters
foo.apply( null, [2, 3] ); // a:2, b:3

// currying with `bind(..)`
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3

上面这两个函数需要传一个函数 this,如果向上面一样函数不关心传入的 this 值,但你需要一个占位符,用 null 似乎是一个合理的选择。

Note: ES6 中有 ... 操作符将会让数组作为函数的参数,在不用apply的情况下。像 foo(...[1,2]),相当于 foo(1,2) – 避免了用 this 如果是不必要的。不幸的是,不能用 ES6 句法代替局部调用,所以在使用bind(..)时仍然需要注意。

但是当你用 null 在不考虑 this 的值得时候可能会有危险,当你使用第三方库时,可能里面用到了 this,默认绑定意味着它可能在不经意间引用全局对象(在浏览器窗口)。

显然,这种缺陷会产生很多难以诊断 / 追踪的 bug。

Safer this

用更安全的方式,中间有一段没翻译…
Whatever you call it, the easiest way to set it up as totally empty is Object.create(null) (see Chapter 5). Object.create(null) is similar to { }, but without the delegation to Object.prototype, so it’s “more empty” than just { }.

function foo(a,b) {
    console.log( "a:" + a + ", b:" + b );
}

// our DMZ empty object
var ø = Object.create( null );

// spreading out array as parameters
foo.apply( ø, [2, 3] ); // a:2, b:3

// currying with `bind(..)`
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3

Not only functionally “safer”, there’s a sort of stylistic benefit to ø, in that it semantically conveys “I want the this to be empty” a little more clearly than null might. But again, name your DMZ object whatever you prefer.

Indirection

Another thing to be aware of is you can (intentionally or not!) create “indirect references” to functions, and in those cases, when that function reference is invoked, the default binding rule also applies.
你可以创建”间接引用”的函数,但是在调用这些函数时,默认绑定的规则应用在了上面。
最常见的方式之一,间接引用在一种任务中发生

function foo(){
    console.log(this.a);
}
var a = 2;
var o = {a: 3, foo: foo};
var p = {a: 4};

o.foo();             // 3
(p.foo = o.foo)();   // 2

在上面的例子中,有效的调用是 foo() ,不是别的。

The result value of the assignment expression p.foo = o.foo is a reference to just the underlying function object. As such, the effective call-site is just foo(), not p.foo() or o.foo() as you might expect. Per the rules above, the default binding rule applies.

它是表达式并没有执行函数,因为它们引用的都是 foo() ,所以执行了 foo()。

解释代码最后一句
one: the function isn’t getting called during the assignment. you are using the function as a variable. so after this operation p.foo and o.foo both reference the defined method foo() in the global scope. The assignment doesn’t actually call the method. Assignments return the value being assigned so you can do c = (a = b) and everything will equal c. Since the thing being assigned is the foo function, that is what gets called at the end.

最后一句话的作用只是让 p.foo 和 o.foo 都引用 foo() 在全局的作用域内,所以最后被调用的是 foo()

another: p.foo = o.foo : this is assigning the variable from right to left. Just like var a = “something”; so this means that now, p.foo equals o.foo, and since o.foo equals foo, we are now saying p.foo = foo, in other words this: (p.foo = o.foo)(); is the same as (foo)() which then runs the foo function.

Softening Binding

因为 hard-binding 降低了绑定的灵活性,防止手动覆盖隐式绑定,甚至后来的显示绑定。