前面文章讲到深耕系列之执行上下文,提到在ES8中,执行上下文中的this值被归纳到词法环境,this指针也是我们经常使用的。
那么这篇文章会去深入理解this,理解this的指向。
先来看一个例子
var value = 1
var obj = {
value: 2,
getValue: function() {
return this.value
}
}
function fn() {
return this.value
}
console.log(fn())
console.log(obj.getValue())
console.log((obj.getValue)())
var test = obj.getValue
console.log(test())
粘贴到控制台上打印,发现相关结果如下:
console.log(fn()) // 1
console.log(obj.getValue()) // 2
console.log((obj.getValue)()) // 2
var test = obj.getValue
console.log(test()) // 1
注:以上是在非严格模式下的结果,严格模式下的this会保持为undefined,会报错。
可以看出,使用括号和使用赋值语句的打印结果是不相同的,那么该如何确定this的指向呢?
this指向
我们来分析一下不同执行上下文中的this指向。
全局上下文
无论是否在严格模式下,this都指向全局对象
console.log(this)
console.log(this === window) // true
可使用globalThis 获取全局对象,不管当前上下文是否是全局上下文。
函数上下文
在函数内部,可简单总结,谁调用就this就指向谁。
function fn() {
return this
}
fn() === window // true, fn()相当于window.fn(),为window调用,所以this指向window
但在严格模式下,如果没有设置this值,this会保持为undefined
function fn() {
"use strict";
return this
}
fn() === window // false
fn() === undefined // true
另外,最开始的那个例子中,使用括号并不会影响到this的指向,但使用赋值语句后,会改变this指向,this会指向全局对象
console.log((obj.getValue)()) // 2
var test = obj.getValue
console.log(test()) // 1
类上下文
因为类本质上也是函数,所以this指向是类似的。在类的构造函数中,会把所有非静态的方法添加到this的原型上。
class Example {
constructor() {
const proto = Object.getPrototypeOf(this);
console.log(Object.getOwnPropertyNames(proto));
}
first(){}
second(){}
static third(){}
}
new Example(); // ['constructor', 'first', 'second']
遇到需要更改this指向的场景,我们该怎么办?
更改this指向
call函数
var obj = { a: 1 }
var a = 2
function fn(param) {
return param + this.a
}
fn('test') // test2
fn.call(obj, 'test') // test1
通过call方法,把fn函数内的this设置为obj,所以fn.call(obj, 'test') 打印出来的是test1
为了更好的理解call,我们来手写代码实现call函数,如下:
Function.prototype.myCall = function(obj) {
obj = obj || window
obj.fn = this
const args = [...arguments].slice(1)
const result = obj.fn(...args)
delete obj.fn
return result
}
实现思路:
- 不传入第一个参数,那么上下文就默认为window
- 给对象obj创建一个属性fn,并将值设置为需要调用的函数
- 移除arguments的第一个元素来获取调用函数的参数
- 支持return并把ojb的fn属性删除
apply函数
var obj = { a: 1 }
var a = 2
function fn(param) {
return param + this.a
}
fn('test') // test2
fn.apply(obj, ['test']) // test1
apply方法和call方法类似,区别在于参数的处理,我也贴出apply的实现代码,如下:
Function.prototype.myApply = function(obj, arr) {
obj = obj || window
obj.fn = this
const result = obj.fn(...arr)
delete obj.fn
return result
}
bind函数
var obj = { a: 1 }
var a = 2
function fn(param = '') {
console.log(param + this.a)
}
fn() // 2
const newFn = fn.bind(obj, 'test')
newFn() // test1
可以看出,bind方法会创建一个新的函数, 在bind被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
实现代码如下:
Function.prototype.myBind = function(obj) {
const self = this
const args = [...arguments].slice(1)
return function F() {
// 这个时候的arguments是指bind返回的函数传入的参数
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new self(...args, ...arguments)
}
return self.apply(obj, args.concat(...arguments))
}
}
实现思路:
- bind方法会返回一个函数,该函数有两种调用方式,一种是直接调用,一种是通过new方式,都需支持。
- 针对直接调用,我们使用apply的方式实现。
- 因为bind会有类似这种的代码fn.bind(obj, 1)(2),所以需要将两边的参数通过concat拼接起来。
箭头函数
在箭头函数中,this与封闭词法环境的this保持一致。在全局代码中,它将被设置为全局对象:
var arrowFn = (() => this)
var fn = function() {
return this
}
var obj = { a: 1}
console.log(arrowFn() === window) // true
console.log(fn() === window) // true
console.log(arrowFn.call(obj) === window) // true
console.log(fn.call(obj) === window) // false
可以看出,如果将this传递给call、bind、apply来调用箭头函数,它将被忽略
总结
以上就是我对this指向的理解,后续文章会再总结new等内容。如果觉得对你有帮助,请帮忙点个赞+评论+收藏,关注不失联。