一文读懂JS中的this指向
初识
在JS中this是一个非常重要的关键字,下面通过几个函数调用场景初步认识一下
function foo() {
console.log(this.a)
}
var a = 1
foo() // 1
const obj = {
a: 2,
foo: foo
}
obj.foo() // 2
const c = new foo() // undefined
- 首先我们需要知道在函数直接调用时,this指向全局对象。 所以当直接执行foo时this指向全局window,此时
this.a = 1 - 当函数作为一个对象的方法被调用时,此时this指向这个对象。 所以
obj.foo(),打印this.a = obj.a = 2 - 第三次调用是通过关键字new调用,我们知道通过new调用时,会返回一个新的对象,此时this指向的是这个新对象c,c中并没有a这个属性,因此
this.a 就是undefined
其实当我们理解上述函数调用就可以解决大部分this指向问题,下面我们来看一下箭头函数中的this指向
箭头函数中的this指向
下面通过一个🌰,认识一下箭头函数中的this
function a() {
return () => {
return () => {
console.log(this) // window
}
}
}
首先我们要知道因为箭头函数没有prototype,因此箭头函数本身是没有this的。箭头函数的this其实是继承于在定义的时候外层第一个普通函数的this。所以这个上述代码就不难看出这个this就是a的this指向window。
读到这里我们已经了解了JS基础的this指向,下面我们介绍一下bind,call,apply三个方法
bind,apply,call方法修改this指向
注意:这三个方法不能修改箭头函数的this指向
bind方法
bind()方法,会接受多个参数:
- 第一个参数为this绑定的对象;
- 后续的参数为执行函数时传入的参数。
在执行完fn.bind()后,会返回一个新的函数,新函数除了this指向为执行bind方法时绑定的this,其他没有变化。
function foo() {
console.log(this.a)
}
var a = 1
foo() // 1
const obj = {
a: 2,
}
const resFoo = foo.bind(obj)
resFoo() // 2
手写bind
手写之前,我们需要知道bind在执行的时候都做了哪些事情
- 通过链式调用,返回一个新参数
- 接受多个参数,第一个为新的this指向,后续为传入的参数
- 如果没有传参数,默认指向window
// 1. 第一步实现链式调用,将myBind定义在函数原型上
Function.prototype.myBind = function (context, ...args) {
// 2. 判断是否为函数调用
if (typeof this !== 'function') {
throw TypeError('Error')
}
// 3. 判断有没有传参,没传则默认为window
context = context || window
context.fn = this
// 4. 返回新的函数
return function Fn(...args2) {
const newArr = [...args, ...args2]
// 5. 将fn作为context的属性执行,使fn的this指向context
const result = context.fn(...newArrs)
// 6. fn作为暂存属性使用完立即删除,避免被访问
delete context.fn
return result
}
}
apply方法
经过
函数.apply()会立即执行
apply()方法,接受两个参数
- 第一个参数为this绑定的对象
- 第二个参数为参数数组
function foo(...args) {
console.log(this.a)
console.log(args)
}
var a = 1
foo() // 1, []
const obj = {
a: 2,
}
foo.apply(obj, [1, 2, 3]) // 2, [1, 2, 3]
手写apply
还是老步骤,在开始手写之前总结一下这个方法在调用过程中会做哪些事情
- apply会立即执行
- 接受两个参数,第一个参数为新的this指向,第二个参数为为函数接收的实参列表
Function.prototype.myApply = function (context, args) {
if (typeof this !== 'function') {
throw new TypeError("Error");
}
let res = null
context = context || window
context.fn = this
res = context.fn(...args)
delete context.fn
// 因为函数会立即执行将res接受函数执行的返回值并作为apply的return返回
return res
}
call方法
经过
函数.call()会立即执行
call()方法,接受多个参数
- 第一个参数为this绑定的对象
- 后面的参数为执行函数时传入的参数
function foo(...args) {
console.log(this.a)
console.log(args)
}
var a = 1
foo() // 1, []
const obj = {
a: 2,
}
foo.call(obj, 1, 2, 3) // 2, [1, 2, 3]
手写call方法
总结call方法的使用,发现和apply和bind有很多相似之处,话不多说直接上代码
Function.prototype.myCall = function (context, ...args) {
if (typeof this !== 'function') {
throw new TypeError("Error");
}
let res = null
context = context || window
context.fn = this
res = context.fn(...args)
delete context.fn
return res
}
总结一下
最后我们对本文知识点进行一下总结:
1. 如何判断this指向
- 函数调用: 当一个函数不是作为对象属性,而是作为函数来调用的时候,this指向全局对象
- 方法调用: 如果一个函数作为一个对象的方法来调用时,this 指向这个对象
- 构造器调用: 如果函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象
- apply,bind, call的调用: 三个方法第一个参数为this的指向,如果参数为空则默认指向window
2. this绑定的优先级
new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级
- 显示绑定:apply,bind,call这些函数
- 隐式绑定:对象将函数当作属性调用
- 默认绑定:函数调用
- 箭头函数的this一旦绑定,就不会被任何方式改变