这是我参与11月更文挑战的第25天,活动详情查看:2021最后一次更文挑战
大家好,我是一碗周,一个不想被喝(内卷)的前端。如果写的文章有幸可以得到你的青睐,万分有幸~
写在前面
this关键字是JavaScript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。但是即使是非常有经验的JavaScript开发者也很难说清楚他到底指向什么。
实际上,JavaScript中this的机制并没有那么先进,但是开发者往往吧理解过程复杂化。this在实际开发中有着非常重要的作用。
this都有一个共同的点,它总是返回一个对象。简单的说,this就是属性或方法“当前”所在的对象。
this提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将API设计得更加简洁并且易于复用,随着以后开发的场景越来越复杂,显式传递上下文对象会使代码变得越来越复杂和混乱,使用this则不会这样。
想要了解this的绑定过程,首先要理解调用位置就是函数在代码中被调用的位置(而不是声明的位置)。通常的说,寻找调用位置就是寻找“函数被调用的位置”。最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数。)
绑定规则
默认绑定
在一个函数中使用this,当函数被独立调用,我们可以将这条规则看做是无法应用其他规则的而应用默认规则。示例代码如下所示:
var v = 100
function fun() {
console.log(this.v)
}
fun()
在浏览器环境下,这个函数的中的this指代window对象,所以会输入100;在Node环境下,得到的结果为undefined这是以为Node中的v并不是挂在全局对象上的。
还有就是在严格模式下这个this会指向undefiend。
隐式绑定
隐式绑定的规则需要考虑的是调用的位置是否有上下文对象,或者说是否被某个对象拥有或者包含。当然这种说法并不是很准确,只是为了帮助我们自己理解。
示例代码如下所示:
var v = 200
var obj = {
v: 100,
fun: function () {
console.log(this.v)
},
}
obj.fun() //100
隐式绑定最常见的问题就是绑定丢失,指的是被隐式绑定的函数会丢失绑定对象,也就是说他会应用默认绑定,从而把this绑定到全局对象。
示例代码如下所示:
var obj = {
v: 100,
fun: function () {
console.log(this.v)
},
}
var fun = obj.fun
fun() //undefined
我们将obj对象的fun方法赋值给全局对象的fun方法,将其引入到了全局作用域,使其应用了默认绑定。
除了上面这一种,绑定丢失也会发生在回调函数中,示例代码如下:
var person = {
name: '一碗周',
sayMe: function () {
console.log(this.name)
},
}
setTimeout(person.sayMe) // undefined
按照我们的思维,这里的this应该指向的是person对象,实际上这里应用的默认绑定规则,相当于将person.sayMe赋值给一个变量,然后执行。
显式绑定
所谓的显式绑定,就是明确在调用对象时,this所绑定的对象。JavaScript中的Function提供的apply()方法和call()方法实现。这两个方法的第一个参数接受的是一个对象,会将这个对象绑定到this,接着调用函数是指向这个this。
除了apply()方法和call()方法,Function提供bind()方法也可以实现显式绑定,不同的是,前者会执行应用的函数,而bind()只是创建一个绑定了新this指向的函数。
示例代码如下:
var obj = {
v: 100,
fun: function () {
console.log(this.v)
},
}
var fun = obj.fun
// 通过 call() 方法
fun.call(obj) // 100
// 通过 apply() 方法
fun.apply(obj) // 100
// 两者的区别只是接受参数的方式不同
// 使用bind
newFun = fun.bind(obj)
newFun() // 100
new绑定
在JavaScript中,构造函数只是使用new操作符是被调用的函数。包括内置对象函数在内的所有函数都可以用new来调用,这种函数调用被称为构造函数调用。
使用new来调用函数,会自动执行下面的操作:
- 创建一个全新的空对象,构造函数中的
this指向这个空对象 - 将对象的的原型进行连接
- 执行构造函数方法,属性和方法被添加到
this引用的对象中 - 如果函数没有返回其他对象那么
new表达式中的函数调用会自动返回这个新对象。
示例代码如下:
function Fun() {
this.v = 100
}
var fun = newFun()
console.log(fun.v) //100
绑定规则的例外
被忽略的this
如果吧null或者undefined作为this的绑定对象传入call、apply或者bind方法,这些值在调用是会被忽略,实际应用的是默认绑定规则。
示例代码如下所示:
var v = 200
var obj = {
v: 100,
fun: function () {
console.log(this.v)
},
}
obj.fun.call(null) //200
间接引用
我们在实际的开发中,有可能无意间创建了一个函数的“间接引用”,在这种情况下,调用这个函数会应用默认绑定规则。简洁引用最有可能发生在赋值时,示例代码如下所示:
var v = 100
function fun() {
console.log(this.v)
}
var obj = {
v: 200,
fun: fun,
}
var ob = {
v: 300,
}
;(ob.fun = obj.fun)() //100
最后一行的赋值操作等于以下这个自调函数
;(function () {
console.log(this.v)
})()
这也就导致了this指向了全局对象。
注意事项
避免多层this
多层函数或方法嵌套可能导致不同层次的this绑定对象不同,如下示例代码所指示
var obj = {
f1: function () {
console.log(this) //指向obj
var f2 = (function () {
console.log(this) //指向全局
})()
},
}
obj.f1()
这就有可能导致我们并不知道this的最终指向,从而导致一些异常。
避免数组方法中的this
在数组的map和forEach方法中,允许提供一个函数作为参数,这个函数内部不应该使用this
示例代码如下所示:
var arr = [1, 2, 3, 4, 5]
var v = 100
arr.forEach(function () {
console.log(this.v)
})
此时的this.v是指向全局对象的
避免回调函数中的this
回调函数中的this经常会改变绑定的对象,最好的解决方案就是避免这样使用this。
示例代码如下所示:
var v = 100
var obj = {
v: 200,
f: function () {
console.log(this.v)
},
}
function fn(f) {
f()
}
fn(obj.f) //100
上述代码中,fn()方法中的this其实是指向全局对象的。
写在最后
本篇文章介绍了this的概念、this在各种位置的一个指向以及使用this关键字的一些注意事项。
这是【从头学前端】系列文章的第五十五篇-《错误与异常》,如果你喜欢这个专栏,可以给我或者专栏一个关注~
本系列文章在掘金首发,编写不易转载请获得允许
往期推荐