this到底是什么
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this就是记录的其中一个属性,会在函数执行的过程中用到。
this是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用
this的绑定四大规则
一、默认绑定:独立函数调用
-
默认绑定:无法应用其他规则时使用默认绑定。非严格模式下默认绑定全局对象(windows),严格模式下绑定undefined
-
独立函数调用:指在调用函数时,前面不带任何修饰符
<!--独立函数调用-->
function foo(){}
foo() //独立函数调用
<!--非独立函数调用-->
var obj = {
foo:foo
}
obj.foo() //前面带了修饰符,非独立函数调用
function foo(){
console.log(this.a) //2
}
var a = 2
foo()
//严格模式下
function foo(){
"use strict"
console.log(this.a) //TypeError
}
var a = 2
foo() //调用位置 windows
二、隐式绑定
必须在一个对象内部包含一个指向函数的属性,并通过这个属性间接引用函数,从而把this间接(隐式)绑定到这个对象上,也是通常所说的上下文对象
function foo(){
console.log(this.a)
}
var obj1 = {
a:42,
<!--直接定义foo-->
foo:function foo(){
console.log(this.a)
}
}
var obj2 = {
a:2,
<!--foo被当作引用属性添加-->
foo:foo,
obj1:obj1
}
var a = "global"
<!--隐式绑定-->
obj1.foo() // 42=>this=>obj1 foo调用位置 obj1
obj2.foo() // 2=>this=>obj2 foo调用位置 obj2
var bar = obj1.foo
var bar2 = obj2.foo
<!--不带任何修饰的函数调用,因此应用默认绑定-->
bar() //global=>this=>windows foo调用位置windows
bar2() //global=>this=>window foo调用位置windows
obj2.obj1.foo() // 42=>this=>obj1 foo调用位置obj1
1.无论foo()的声明方式是被当作引用属性添加到obj中,还是直接在obj中定义的,严格来说foo()函数都不属于obj对象 2.对象属性引用链中只有最顶层或者说最后一层会影响调用位置
思考题1:在上述的例子中,是否对于执行bar(),foo的this指向windows存在疑问,也就是我们常说的this丢失绑定问题,我们又该如何让this指向obj1呢?请继续往下看
三、显式绑定
所有函数都可以使用call(...)和apply(...)方法,他们的第一个参数是一个对象,他们会把这个对象绑定到this,接着在调用时指定这个this。因为我们可以直接指定this的绑定对象,因此称之为显式绑定
function foo(){
console.log(this.a)
}
var obj = {
a:2
}
foo.call(obj) // 2
显式绑定的变化方法
1.硬绑定
就是为了解决之前提出的丢失绑定问题
function foo(){
console.log(this.a)
}
var obj = {
a:2
}
// 硬绑定
var bar = function(){
foo.call(obj) //显式的将foo的this绑定为 obj
}
var a = "global"
bar() //2
setTimeout(bar,100) //2
//硬绑定的bar不可能再修改它的this
bar.call(windows) //2
2.API调用“上下文”
第三方库的许多函数,以及Javascript语言和宿主环境中许多内置函数,都提供可选的参数,通常被称为“上下文(context),其作用和bind(...)一样,确保回调函数使用指定的this”
function foo(el){
console.log(el,this.id)
}
var obj = {
id:'test'
}
[1,2,3].forEach(foo,obj)
//1 test 2 test 3 test
四、new绑定
使用new时,我们会构造一个新对象并把他绑定到构造函数调用中的this上
function foo(a){
this.a = a
}
var bar = new foo(2)
console.log(bar.a) //2
四种绑定的优先级
new 绑定>通过call、apply(显式绑定)>隐式绑定>默认绑定
1.比较隐式绑定与显式绑定
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
2.比较显示绑定与new绑定
function foo(a){
this.a = a
}
var obj1 = {}
<!--bar中this指向obj1-->
var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a) //2
<!--bar 中this指向baz-->
var baz = new bar(3)
console.log(obj1.a) //2
console.log(baz.a) //3
new操作修改了硬绑定(到obj1的)调用bar()中的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()
5.例外情况
- 将null或者undefined作为this的绑定对象传入call、apply或者bind,应用默认绑定规则
function foo(){
console.log(this.a)
}
var a = 2
foo.call(null) //2 this=>windows
- 函数的“间接引用”,调用这个函数应用默认绑定规则
function foo(){
console.log(this.a)
}
var a = 2
var o = {
a:3,
foo:foo
}
var p = {
a:4
}
o.foo() // 3
(p.foo = o.foo)() //2
特殊情况
ES6中的箭头函数无法使用上述的四种规则,而是根据外层(函数或者全局)作用域来决定this
function foo(){
return (a)=>{
//this继承自foo
console.log(this.a)
}
}
var obj1 = {
a:2
}
var obj2 = {
a:3
}
var bar = foo.call(obj1)
bar.call(obj2) //2
<!--由于 foo() 的 this 绑定到 obj1,bar(引用箭头函数)的 this 也会绑定到obj1,箭头函数的绑定无法被修改。(new 也不行!)-->
敲重点
运行中函数的this到底指向哪里?找到这个函数的直接调用位置
- 由new调用?指向新创建的对象。
- 由call或者apply(或者bind)调用?指向绑定的对象
- 由上下文对象调用?指向那个上下文对象
- 默认(独立函数调用)?在严格模式下指向undefined,非严格模式下指向全局对象
- 箭头函数:指向外层函数调用this的绑定
参考文献
我只是书本的搬运工,以上内容来自《你不知道的Javascript(上卷)》
感谢大家的阅读!!!