1.this到底是什么
this是在运行时绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件**,this的绑定和函数声明的位置没有关系,只取决于函数的调用方式。**当一个函数被调用时,会创建一个活动记录(上下文)。这个记录会包含函数在哪里被调用 函数的调用方式 传入的参数等信息,this就是这个记录的一个属性,会在函数执行过程中用到。
2.调用位置
通常来说,寻找调用位置就是寻找“函数被调用的位置”,最重要的是要分析调用栈,调用位置就在当前正在执行函数的前一个调用中。
**
3.绑定规则
函数执行过程中调用位置决定this的绑定对象,具体有以下四条规则。
3.1 默认绑定
独立的函数调用,函数内部的this在非严格模式下会指向全局的window,实际上独立函数调用上层上下文是window,示例如下:
function bar() {
console.log(this.a)
}
var a = 12
bar() // 12
3.2 隐式绑定
函数被某个对象所调用,那么该函数被调用时隐式绑定规则会把函数调用中的this绑定到这个上下文对象,对象属性引用链中只有上一层或者最后一层在调用位置中起作用,示例如下:
function foo() {
console.log(this.a)
}
var obj2 = {
a: 42,
foo: foo
}
var obj1 = {
a: 2,
obj2: obj2
}
obj1.obj2.foo() //42
注意:被隐式绑定的函数在某些情况下会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否在严格模式。1:函数赋值。2:函数作为参数传递(通常是回调函数)或者函数别名,示例如下:
function foo() {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
var bar = obj.foo
var a = 'oops,global'
bar() // oops,global 虽然bar是obj.foo的一个引用,但实际上,它引用的是foo函数本身,因此此时的bar其实是一个不带任何修饰的函数调用
function foo() {
console.log(this.a)
}
var obj = {
a: 2,
foo: foo
}
var a = "oops,global"
setTimeout(obj.foo,100) // oops,global 参数传递实际上是一种隐式赋值,因此我们传入函数时也会被隐式赋值。
3.3 显式绑定
Function.prototype.call Function.prototype.apply Function.prototype.bind 它们的第一个参数接受一个对象,在调用时将this绑定到该对象。如果传入的参数是一个基本类型(字符串,布尔,数字),这个原始值会被转换成它的对象形式(装箱).
第三方库的许多函数及js语言和宿主环境的许多内置函数都提供了一个可选的参数,通常被称为上下文(context),其作用和bind一样,确保你的回调函数使用指定的this。这些函数实际上是通过call或allpy实现了显示绑定。
3.4 new 绑定
使用new来调用构造函数,或者说发生构造函数调用时,会执行以下动作:
- 创建一个全新对象
- 这个对象会被执行[[Prototype]]连接
- 这个新对象被绑定到函数调用的this
- 如果函数没有返回其他各对象,那么new 表达式中的函数调用会自动返回这个新对象
使用new调用函数时,我们会构造一个新对象兵把它绑定到函数调用的this上。
**
4.优先级
new绑定 > call apply bind > 隐式绑定 > 默认绑定
5.特殊情况
5.1 被忽略的this
如果把null或者undefined作为this的绑定对象传入call apply或bind,这些值在调用时会被忽略,实际上应用的是默认绑定
function foo() {
console.log(this.a)
}
var a = 2
foo.call(null) // 2
null或者undefined传入call apply 或bind时,通常有两种用途:
1.apply展开数组
2.bind函数柯里化
function foo(a,b) {
console.log( "a":+a+" ,b: "+b )
}
//把数组展开成参数
foo.apply(null,[2,3]) //a: 2, b: 3
//使用bind进行柯里化
var bar = foo.bind(null,2)
bar(3) // a: 2, b: 3
但是传入null或undefined会将this绑定到全局变量,某些情况下会造成全局污染,因此更安全的做法是传入一个特殊的对象,把this绑定到这个对象不会对程序产生任何副作用,示例如下:
function foo(a,b) {
console.log("a":+a+" ,b: "+b)
}
var dmz = Object.create(null)
foo.apply(dmz,[2,3])
var bar = foo.bind(dmz,2)
bar(3)
5.2 间接引用
另一个需要注意的是,可能会创建一个函数的间接引用(函数赋值,函数作为参数传递),在这种情况下,调用这个函数会应用默认绑定规则。
5.3软绑定
与硬绑定(bind)类似,强制将this绑定到传入的第一个参数,同时提高硬绑定的灵活性,首先检查调用时的this,如果this绑定到全局对象或者undefined,那就把指定的默认对象绑定到this,否则不会修改this
if(!Function.prototype.softBind) {
Function.prototype.softBind = function(context) {
let fn = this
let arg = [].slice.call(arguments,1)
let bound = function() {
return fn.apply(
!this || this === (window || global)?
context : this,
arg.concat([...arguments])
)
}
bound.prototype = Object.create(fn.prototype)
return bound
}
}
6.箭头函数
箭头函数不使用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) //2