JS中的this指向一直困扰着很多人,要弄清楚this的绑定过程,就要先找到函数被调用的位置,就是为了到达当前执行位置所调用的所有函数.针对不同的调用位置具体分析,有以下四种绑定规则:
1.默认绑定
独立函数调用,无法应用其他规则时的默认规则.
function foo(){
console.log(this.a)
}
var a = 2
foo() // 2
复制代码
非严格模式下,默认绑定this指向全局对象,严格模式下this绑定到undefined.
"use strict";
function foo(){
console.log(this.a)
}
var a = 2
foo() // TypeError: this is undefined
复制代码
2.隐式绑定
判断调用位置是否有上下文对象
function foo(){
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
var a = 3
obj.foo() // 2
复制代码
无论函数声明在对象内部还是独立声明,当函数调用有上下文对象时,隐式绑定会把this绑定到这个上下文对象.
隐式丢失
看了上面两条感觉很简单对不对,那么再看看下面这段代码:
function foo(){
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
var a = 3
var bar = obj.foo
bar(); // 3
复制代码
和上面极其相似的写法,为什么会产生不懂的结果?
原因就在于 bar 虽然是 obj.foo的引用,但本质上引用的是 foo函数本身,因此实际上bar()是一个无上下文的独立函数调用,应用默认绑定.
类似的情况同样发生在回调函数中:
var a = 123
var obj = {
a: 56,
foo: function(){
console.log(this.a)
}
}
setTimeout(obj.foo,1000) //123
复制代码
参数传递也是一种隐式赋值,原理与上面的例子相同,引用的是函数本身.
3.显式绑定
使用 call,apply方法强制在某个对象上调用函数,this指向该对象
function foo(){
console.log(this.a)
}
var obj = {
a: 2
}
var a = 3
foo.call(obj) // 2
复制代码
bind硬绑定
bind函数接收要绑定的对象,返回一个硬编码的新函数,并把指定的对象设置为this的上下文,并调用原始函数.
function foo(){
console.log(this.a)
}
var obj = {
a: 2
}
var a = 3
var bar = foo.bind(obj)
bar() // 2
复制代码
API 调用上下文
第三方库的一些函数和js宿主环境内置函数提供了一个可选的参数,称为'上下文',作用和bind函数一样,确保你的回调函数使用正确的this
function foo(el){
console.log(el, this.id)
}
var obj = {
id: 'cool'
}
[1,2,3].forEach(foo,obj) //1 cool 2 cool 3 cool
复制代码
4.new 绑定
在传统面向类语言中,如java,new操作符被用来实例化一个类,而在js中,实现的机制完全不同 obj = new foo()
, foo只是一个被new操作符调用的普通函数,使用new 操作符时会发生下面的操作
- 创建一个全新的对象
- 对这个新对象执行[[prototype]]连接
- 这个新对象指向函数调用的this
- 如果函数没有返回其他对象,那么函数执行完毕会自动返回这个新对象
以上四条规则的优先级是 new > 显示 > 隐式 > 默认
一些例外情况
箭头函数
箭头函数的this在函数定义时绑定,指向父执行上下文.
var x=11;
var obj={
x:22,
y:()=>{
console.log(this.x);
}
}
obj.y();
//输出的值为11
复制代码
这里箭头函数以键值对的方式定义,作为对象 obj 的一个属性,因此this指向定义obj的上下文 window
被忽略的this
向call,apply,bind函数传入 null 或者undefined,在调用时会被忽略,实际应用的时默认绑定规则.
function foo(){
console.log(this.a)
}
var a = 2
foo.apply(null) //2
复制代码