前言
我们这里将从最底层的显隐式this绑定等类型去阐述,让你对this有一个清晰的认识。采用逆向思维去讲述。
一句话解释this绑定
常见说法:谁调用它,this就指向谁。
严格来说:this 的指向,是在调用函数时根据执行上下文所动态确定的。
啥意思呢,咱们来解释下,看如下demo
function f1 () {
console.log(this)
}
function f2 () {
'use strict'
console.log(this)
}
f1() // window
f2() // undefined
此时我们调用的f1在window上
,所以指向window
。严格模式就是undefined
(ps:react,vue等就是严格模式)。
this优先级
new(构造函数) > call/apply/bind > 上下文对象中 > 普通函数
(这里公理,可以通过绑定不同情况this验证。)
通过优先级进行this的判断
- 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。
var bar = new foo()
- 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是 指定的对象。
var bar = foo.call(obj2)
- 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上 下文对象。
var bar = obj1.foo()
- 如果都不是的话,使用(默认绑定)。如果在严格模式下,就绑定到undefined,否则绑定到 全局对象。
var bar = foo()
- 箭头函数 => 箭头函数会继承外层函数调用的 this 绑定,self = this 机制一样。
前面记住,面试够用了,想了解更深入的请看逐一分析,说实话平时写代码,直接箭头函数和bind就完美解决这些尴尬问题了,但是如果你想写更底层的东西,做更高级,甚至专家级别,以下也是必备的。
逐一分析:
ps: 我们只在非严格模式讨论,严格模式默认绑定指向underfined。
1. 默认绑定
function foo() {
console.log(this.a)
}
var a = 1;
foo(); // 1
分析:我们看 foo()
在最外层的window
上面,根据所以根据谁调用它,this就指向谁的原则
,所以window
上调用了foo
,window.a
就是我们在window
上设置的var a = 1
,则this.a = 1
;
2. 隐式绑定
2.1正常情况
function foo() {
console.log(this.a);
}
var obj = {
a: 1,
foo: foo
}
obj.foo(); // 1
分析:我们看 foo()
在obj
上面,根据所以根据谁调用它,this就指向谁的原则
,所以obj
上调用了foo
,那么this
指向obj
,所以this.a
就是obj.a
;
2.2 隐式绑定丢失(主动赋值)
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
var a = 1;
var bar = obj.foo; // 只是赋值 没有调用执行
bar(); // 1
分析:我们看 foo()
在obj
上面,根据所以根据谁调用它,this就指向谁的原则
,所以obj
上调用了foo
,那么this
指向obj
,所以this.a
就是obj.a
;
看似是丢失,其实原因在于调用的位置,不信咱稍微改一下这句话var bar = obj.foo
:
function foo() {
console.log(this.a);
}
var obj = {
a: 2,
foo: foo
};
var a = 1;
var bar = obj.foo() ; // 2
我们此时就直接 var bar = obj.foo()
,咋答案就是2
了呢?
分析:我们对比下第一种方法是用var bar = obj.foo
;吧obj.foo
赋值给bar
,然后我们调用了bar()
,调用的时候已经在window
上了。而第二种我们是直接obj.foo()
,其实还是在obj
上调用的(就是普通的隐式绑定)。所以原理就是在哪调用这个函数,this就指向谁
。
2.3 隐式绑定丢失(函数传参)
function foo() {
console.log('a', this.a);
}
var baz = {
a: 2,
foo: foo
};
var a = 1;
setTimeout(baz.foo, 100);
分析:一句话,函数传参
就是隐式赋值
,跟主动赋值
是一个道理。
3. 显式绑定(call、apply、bind)
3.1普通显式绑定call、apply
function foo() {
console.log(this.a);
}
var obj = { a: 2 };
var obj2 = {a: 3}
foo.call(obj); // 2
foo.call(obj2); // 3
分析:call,apply
的参数是谁,this
就指向谁。
此时你会发现之前说的谁调用它,this就指向谁
,就看不出来了,所以严格来说this 的指向,是在调用函数时根据执行上下文所动态确定的
。
这里顺带说一下call,apply区别,亮代码:
function foo(b, c) {
console.log(this.a, b, c, arguments);
}
var obj = { a: 2 };
foo.call(obj, 3, 4); // 2,3,4
foo.apply(obj, [5, 6]); // 2,5,6
不同的:传参不同,call是单个传参依次传递,apply是传递数组,传递的就是foo规定好的参数。
(我们也可以通过arguments获取传递来的参数。)
3.2 硬绑定与Bind
大家看上面普通显式绑定,每一次call不同的对象,指向就发生变化,那么我们想让this一直不变呢,那就使用硬绑定:
function foo() {
console.log(this.a);
}
var obj = { a: 2 };
var obj2 = { a: 3 };
var bar = function() {
foo.call(obj);
};
bar(); // 2
bar.call(obj); // 2
bar.call(obj2); // 3
分析:我们创建了函数 bar()
,并在它的内部手动调用了foo.call(obj)
,因此强制把foo 的 this
绑定到了 obj
。无论之后如何调用函数 bar
,它 总会手动在 obj
上调用foo
。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。
Bind是es5内置的硬绑定:
function foo() {
console.log(this.a);
}
var obj = { a: 2 };
var obj2 = { a: 3 };
var bar = foo.bind(obj);
bar(); // 2
bar.call(obj); // 2
bar.call(obj2); // 3
源码分析,请看这篇文章,说得很好:从一道面试题,到“我可能看了假源码”
3.3 硬绑定:softBind
这里不介绍了,用的很少,有兴趣同学可以研究下。
4. 构造函数new 和 this
首先介绍一个常见面试题:new做了什么?
- 创建一个新的对象;
- 将构造函数的 this 指向这个新对象;
- 为这个对象添加属性、方法等;
- 最终返回新对象。
代码解释:
var obj = {}
obj.__proto__ = Foo.prototype
Foo.call(obj)
如下是介绍this指向
- 普通版
function Foo() {
this.a = "a"
}
const f = new Foo()
console.log(f.a) // 'a'
- return 非对象版
function Foo() {
this.a = "a"
return 1
}
const f = new Foo()
console.log(f.a) // 'a'
- retrun 对象版
function Foo() {
this.a = "a"
return {a: "真假美猴王a"}
}
const f = new Foo()
console.log(f.a) // '真假美猴王a'
结论:如果构造函数
中显式返回的是一个对象
,那么 this
就指向这个返回的对象
;如果返回的不是一个对象
,那么 this
仍然指向实例
。
4. 箭头函数中的 this 指向
- 普通 function 版
const foo = {
a: '1',
fn: function() {
setTimeout(function() {
console.log(this);
});
}
};
foo.fn(); // window
- 箭头函数 => 版
const foo = {
a: '1',
fn: function() {
setTimeout(() => {
console.log(this);
});
}
};
foo.fn(); // {a: "1", fn: ƒ}
结论:箭头函数会继承外层函数调用的 this 绑定
,和self = this 机制一样。
更多内容可以看我的集录: 全面攻陷js:更新中...