this
定义
this 是 js 中一个固有的关键字。为什么需要它呢?是因为它可以用来隐式“传递”一个对象引用,而不需要显式地传递上下文对象。不过它指向什么完全取决于函数在哪里被调用 ,而不是声明的位置。
function foo() {
var a = 2;
this.bar();
}
function bar() {
console.log(this.a);
}
foo(); // ReferenceError: a is not defined
那么这个this到底引用的是什么,或者说真正绑定了什么?
this绑定
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的其中一个属性,会在函数执行的过程中用到。
只有仔细分析调用位置才能回答这个问题,所以在分析前最好先弄清楚执行上下文与执行栈。
默认绑定
function foo() { console.log( this.a ); }
var a = 2; foo(); // 2 (严格模式下undefined)
foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。
调用位置是否有上下文对象
function foo() {
console.log( this.a );
}
var obj = { a: 2, foo: foo };
obj.foo(); // 2
当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。
存在的问题:绑定丢失
被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者 undefined 上,取决于是否是严格模式。
- 情景1
var a = 4
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo
bar()//非严格模式下为4
bar()其实是一个不带任何修饰的函数调用,因此应用了默认绑定。
- 情景2:回调函数
function foo() {
console.log( this.a );
}
function doFoo(fn) { // fn 其实引用的是foo
fn(); // <-- 调用位置!
}
var obj = { a: 2, foo: foo };
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "oops, global"
赋值也是一种隐式传递。
- 情景3:库函数
也是一样的结果
setTimeout( obj.foo, 100 ); // "oops, global"
作为一个构造函数
定义
this被绑定到正在构造的新对象。
步骤
通过构造函数创建一个对象执行如下:
- 创建新对象
- 将
this指向这个对象 - 给对象赋值(属性、方法)
- 返回
this
所以this就是指向创建的这个对象上。
显式绑定之软绑定---call,apply
定义
在某个对象上强制调用函数,但是无法解决绑定丢失的问题,
call 和 apply 的区别
-
相同点:第一个参数都是一个对象,它们会把这个对象绑定到
this,接着在调用函数时指定这个 this。因为你可以直接指定 this 的绑定对象,因此我 们称之为显式绑定。 -
不同点:
call()方法接受的是参数列表,而apply()方法接受的是一个参数数组
但是软绑定不能解决绑定丢失的问题.
function foo() {
setTimeout(function () {
console.log(this.a);
}, 100);
}
var obj = {
a: 2
};
foo.call(obj); // undefined
显式绑定的变种之硬绑定---bind
定义
强制把 foo的 this 绑定到了 obj。无论之后如何调用函数 bar,它 总会手动在 obj 上调用 foo。这种方式可以防止绑定丢失。
硬绑定可以解决绑定丢失的问题。
function foo() {
console.log(this.a);
}
var obj = {
a: 2
};
var obj1 = {
a: 3
};
//包裹函数
var bar = function () {
foo.call(obj);
};
bar(); // 2
setTimeout( bar, 100 ); // 2
bar.call(obj1); //2
// 硬绑定的 bar 不可能再修改它的 this
其原理就是利用闭包创建一个包裹函数,传入所有的参数并返回接收到的所有值,则形成一个硬绑定。
function foo(something) {
console.log(this.a, something);
return this.a + something;
}
var obj = {
a: 2
};
// 包裹函数
var bar = function () {
return foo.apply(obj, arguments);
};
console.log(bar(3))
es5中bind函数,用来表示这种绑定。
function foo(something) {
console.log(this.a, something);
return this.a + something;
}
var obj = {
a: 2
};
var obj1 = {
a: 3
};
var bar = foo.bind(obj);
bar = foo.bind(obj)
console.log(bar(8));
总结
this指向
如果要判断一个函数的this绑定,就需要找到这个函数的直接调用位置。然后可以顺序按照下面四条规则来判断this的绑定对象。
- 由
new调用:绑定到新创建的对象 - 由
call或apply、bind调用:绑定到指定的对象 - 由上下文对象调用:绑定到上下文对象
- 默认:全局对象