在this All Makes Sense Now! [上]中我们介绍了什麽是call-site与4种绑定的规则,我们需要做的就是观察一个程序找到他的call-site并了解他适用於哪个规则,这样就可以找到this所指向的是什麽了。
透过找到call-site与适用的规则可以找到this所指向的地方,但是如果一个call-site符合多个规则那怎麽办?这些规则具有优先顺序
,接下来我们将介绍这些规则的顺序,首先要先了解default binding是所有规则中优先级最低的,所以先不讨论。
我们先讨论一下是implicit binding还是explicit binding?
function foo() {
console.log( this.a );
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo(); // 2
obj2.foo(); // 3
obj1.foo.call( obj2 ); // 3
obj2.foo.call( obj1 ); // 2
上面的程序码中可以看到当对function使用显性绑定後,显性绑定的规则会高於隐性绑定
,这意味着当你需要使用隐性绑定的时候需要检查funciton是否有被显性绑定住。
接下来要看看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的优先级是比隐性绑定高的,但是要如何确定new binding与显性绑定谁的优先极高呢?
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a ); // 2
var baz = new bar( 3 );
console.log( obj1.a ); // 2
console.log( baz.a ); // 3
bar强制绑定着obj1,之後使用new binding将bar中函数的this指向这个物件,但是结果却是用new创出来的baz没有改变到obj1.a的值,因为使用new binding後回传的会是一个新的物件并将函数的this指向这个新物件,所以他与bar中绑定的obj1是没关系的。
Currying(柯里化),又称为 parital application 或 partial evaluation,是个「将一个接受 n 个参数的 function,转变成 n 个只接受一个参数的 function」的过程。
bind(...)可以将输入的参数(需绑定物件後面的参数)默认的当作前函数的标准参数
function foo(p1,p2) {
this.val = p1 + p2;
}
var bar = foo.bind( null, "p1" ); // 将"p1"默认的当作每次呼叫foo的参数
var baz = new bar( "p2" );
var bax = new bar( "p3" );
baz.val; // p1p2
bax.val; // p1p3
了解了绑定的优先级,我们可以总结一下判断this的规则。
被创建出来的新物件
。var bar = new foo(); // this指向bar
var bar = foo.call(obj2); //this指向obj2
const = obj1 = {
foo: foo
}
obj1.foo();
凡事均有特例,对於this的判定也不例外。
如果传递null
或undefined
给call,apply或bind的参数,那麽这些值就会被忽略绑定规则会自动回到default binding。
function foo() {
console.log( this.a );
}
var a = 2;
foo.call( null ); // 2
既然传递非法的参数(null,undefined)会造成binding的忽略那麽为什麽会需要使用?
在ES6中我们可以使用扩展运算符(...)
来将阵列中的元素拆开,但是在ES6之前要达到同样的效果需要使用apply
来来达到。
// ES5
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// spreading out array as parameters
foo.apply( null, [2, 3] ); // a:2, b:3
// ES6
foo(...[2, 3]); // a:2, b:3
将null当作参数传递给bind(...)可以达到currying的功能。
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3
若是创建了函数的间接引用(indirect reference),这种情况下会失去原本的bind而导致变回适用default binding。
function foo() {
console.log( this.a );
}
var a = 2; // declaration in global
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
(p.foo = o.foo)(); // 2
对於p.foo = o.foo来说,虽然看起来像是将o物件中的foo(...)赋予给p物件,但实际上p所拿到的是foo(...)本体的reference,代表着他的位置与o物件中的foo是不一样的,所以只适用default binding。
对於普通函数来说我们可以遵守4条绑定规则中找到this所指向的位置,但是ES6引入了一个不适用於这些规则的函数箭头函数
。
箭头函数不采用4个标准的绑定规则而是从封闭的(函数或全域)范围采用this绑定。
function foo() {
// return an arrow function
return (a) => {
// `this` here is lexically adopted from `foo()`
console.log( this.a );
};
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3
};
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, not 3!
在foo(...)中创建一个箭头函数,这个箭头函数会自动绑定foo()被调用时的this,以上面的例子来说当foo(...)被呼叫并且this被绑定为obj1,那麽箭头函数中的this也会绑定obj1,而且箭头函数的绑定是不能被覆盖的
。
最常见的用法式将箭头函数应用在callback function中
function foo() {
setTimeout(() => {
// `this` here is lexically adopted from `foo()`
console.log( this.a );
},100);
}
var obj = {
a: 2
};
var a = 4; // declaration in global
foo.call( obj ); // 2
使用箭头函数当作callback function的参数传递给setTimeout并不会像我们在this All Makes Sense Now! [上]提到的,因为隐性赋值而产生binding遗失,箭头函数的this会在foo()被呼叫的时候就指定给foo()所绑定的物件。
其实这种绑定的方式在ES6的箭头函数出来之前就有类似的方式了
function foo() {
var self = this; // lexical capture of `this`
setTimeout( function(){
console.log( self.a );
}, 100 );
}
var obj = {
a: 2
};
var a = 4; // declaration in global
foo.call( obj ); // 2
透过在一进function後就先捕获foo中this所指向的物件,这样就不会因为隐性赋值的问题导致binding遗失。
当我们需要找到函数中this所指向的位置,我们需要找到这个函数的call-site
与看这个函数符合4种绑定规则的哪一种。
new
调用funciton则this绑定新创建的物件
。call
、apply
或bind
调用则this绑定指定的物件
。呼叫函数的环境物件
。undefined
否则this指向全域物件
。ES6提供的箭头函数不遵守上面的4个绑定规则,箭头函数的this绑定取决於他被创建的当下所绑定的物件。
参考文献:You Don't Know JavaScript
>>: 老师!我想知道!要怎麽让终端机变漂亮呢 - Mac 篇
引言 昨天学到 ssh 以及 「大括号的分配律」─ Brace Expansion 这边再补充一点...
Eureka 介绍 ...
前言 在整理实验结果之前,先来说说怎麽纪录实验~~ 你484常常听到以下对话 A: 哭啊,明天Mee...
本文内容 阅读有关 Angular 中有 ngFor 语法的笔记内容。 ngFor 在干嘛的? 它用...