1.1 为什么要用this
1.2 误解
1.2.1 指向自身
function foo(num) {
console.log( "foo: " + num );
this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {
if (i > 5) {
foo( i );
}
}
console.log( foo.count );
console.log(count);
foo函数调用了4次,但foo.count仍然为0, 实际上我们是在全局作用域下创建了count这个变量,但并未赋值,所以是NAN,即此时foo里面的this不是指向自身,而是指向全局window
1.2.2 指向它的作用域
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log( this.a );
}
foo();
报错,说明this不指向它的作用域,所以不能通过作用域来查找或者将this跟作用域联系起来使用
1.3 this到底指向什么
- this是在运行时才被绑定的
- this的绑定和函数声明的位置没有关系,只取决于函数的调用方式
- 当一个函数被调用时,会创建一个活动记录(执行上下文),this就是这个记录的一个属性,会在函数执行过程中用到
第二章 this全面解析
2.1 绑定规则
2.1.1 默认绑定
当函数是直接使用且不带任何修饰时(独立函数调用),会使用默认绑定规则
function foo() {
console.log( this.a );
}
var a = 2;
foo();
如果使用严格模式,则不能将全局对象用于默认绑定,因此this会绑定到undefined
function foo() {
"use strict";
console.log( this.a );
}
var a = 2;
foo();
这里有一个比较重要的细节,虽然this的绑定规则完全取决于调用位置,但是只有foo()运行在非严格模式下时,默认绑定才能绑定到全局对象,在严格模式下调用foo()则不影响默认绑定
function foo() {
console.log( this.a );
}
var a = 2;
(function(){
"use strict";
foo();
})()
注意:对于默认绑定来说,决定 this 绑定对象的并不是调用位置是否处于严格模式,而是函数体是否处于严格模式。如果函数体处于严格模式,this 会被绑定到 undefined,否则this 会被绑定到全局对象。
2.1.2 隐式绑定
这条规则则是需要考虑函数调用位置是否有上下文对象,
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo();
调用位置会是用obj的上下文来引用函数,当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象中
对象属性链中只有上一层或者最后一层在调用位置中起作用,如
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo();
隐式丢失
即被绑定的函数会丢失绑定对象,就会应用默认绑定
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo;
var a = "oops, global";
bar();
虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
一种更微妙、更常见并且更出乎意料的情况发生在传入回调函数时:
function foo() { console.log( this.a ); } function doFoo(fn) {
参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一
个例子一样
2.1.3 显示绑定
直接指定this的绑定对象,称为显示绑定,例如可以将函数用call或apply指定this的绑定对象
call和apply的使用
function foo() { console.log( this.a ); }
var obj = { a:2 };
foo.call( obj );
通过 foo.call(…),我们可以在调用 foo 时强制把它的 this 绑定到 obj 上。
如果你传入了一个原始值(字符串类型、布尔类型或者数字类型)来当作 this 的绑定对象,这个原始值会被转换成它的对象形式(也就是 new String(…)、new Boolean(…) 或者new Number(…))。这通常被称为“装箱”
硬绑定
硬绑定可以解决隐式丢失的问题,例子如下:
function foo() {
console.log( this.a );
}
var obj = {
a:2
};
var bar = function() {
foo.call( obj );
};
bar();
bar.call( window );
硬绑定的典型应用就是创建一个包裹函数,负责接受参数并返回值
function foo(something) {
console.log( this.a, something );
return this.a + something; }
var obj = { a:2 };
var bar = function() {
return foo.apply( obj, arguments ); };
var b = bar( 3 );
console.log( b );
另一种使用方法是创建一个可以重复使用的辅助函数:
function foo(something) {
console.log( this.a, something );
return this.a + something;
}
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments ); }; }
var obj = { a:2 };
var bar = bind( foo, obj );
var b = bar( 3 );
2.1.4 new绑定
使用new来调用函数时,会自动执行下面的操作:
- 创建或者说构造一个新对象
- 这个新对象会执行[[prototype]]连接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a );
使用 new 来调用 foo(…) 时,我们会构造一个新对象并把它绑定到 foo(…) 调用中的 this上。new 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定。
2.2 优先级
现在可以通过判断函数的调用位置来判断应用哪一条规则,但如果同时可以应用多条规则的话,就需要知道他们的优先级
隐式绑定和显式绑定比较
function foo() {
console.log( this.a );
}
var obj1 = {
a: 2,
foo: foo
};
var obj2 = {
a: 3,
foo: foo
};
obj1.foo();
obj2.foo();
obj1.foo.call( obj2 );
obj2.foo.call( obj1 );
可以看到,显式绑定优先级更高,也就是说在判断时应当先考虑是否可以存在显式绑定。
new绑定与隐式绑定的比较
function foo(something) {
this.a = something;
}
var obj1 = {
foo: foo
};
var obj2 = {};
obj1.foo( 2 );
console.log( obj1.a );
obj1.foo.call( obj2, 3 );
console.log( obj2.a );
var bar = new obj1.foo( 4 );
console.log( obj1.a );
console.log( bar.a );
可以看到 new 绑定比隐式绑定优先级高
new绑定与显示绑定的比较
new foo.call(obj1)
new 和 call/apply 无法一起使用,因此无法通过 new foo.call(obj1) 来直接进行测试。但是我们可以使用硬绑定来测试它俩的优先级。
function foo(something) {
this.a = something;
}
var obj1 = {};
var bar = foo.bind( obj1 );
bar( 2 );
console.log( obj1.a );
var baz = new bar(3);
console.log( obj1.a );
console.log( baz.a );
bar 被硬绑定到 obj1 上,但是 new bar(3) 并没有像我们预计的那样把 obj1.a 修改为 3。相反,new 修改了硬绑定(到 obj1 的)调用 bar(…) 中的 this。因为使用了new 绑定,我们得到了一个名字为 baz 的新对象,并且 baz.a 的值是 3。因此,new绑定的优先级高于显示绑定
总结
-
函数是否在 new 中调用(new 绑定)?如果是的话 this 绑定的是新创建的对象。var bar = new foo() -
函数是否通过 call、apply(显式绑定)或者硬绑定调用?如果是的话,this 绑定的是指定的对象。var bar = foo.call(obj2) -
函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。var bar = obj1.foo() -
如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到 undefined,否则绑定到全局对象。var bar = foo()
软绑定
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this;
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; }; }
2.3 箭头函数
箭头函数不使用This的四种标准规则,而是根据外层(函数或者全局)作用域来决定This
箭头函数的词法作用域:
function foo() {
return (a) => {
console.log( this.a );
};
}
var obj1 = { a:2 };
var obj2 = { a:3 };
var bar = foo.call( obj1 );
bar.call( obj2 );
|