您的位置:首页 > Web前端 > JavaScript

js中this指向的问题与联系深入探究

2021-02-23 19:48 971 查看

前言

JavaScript 中最大的一个安全问题,也是最令人困惑的一个问题,就是在某些情况下

this
的值是如何确定的。有js基础的同学面对这个问题基本可以想到:
this
的指向和函数调用的方式相关。这当然是正确的,然而,这几种方式有什么联系吗?这是我接下来要说明的问题。

this
从哪里来

this
是js的一个关键字,和
arguments
类似,它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。这句话似乎与认知不同,我们在函数体外部即全局作用域下也能使用
this

// 直接在全局作用域下输出this
console.log(this);
// 输出window

但是不要忘记,即便是全局作用域,依旧是运行在

window
下的,我们写的代码都在
window
的某个函数中。而这也催生了一种理解
this
指向的方法:
this
永远指向调用者
(非箭头函数中)。

作为普通函数调用

函数作为普通函数直接调用(也称为自执行函数)的时候,无论函数在全局还是在另一个函数中,

this
都是指向
window

function fn() {
this.author = 'Wango';
}

fn();
console.log(author);
// Wango

这很好理解,但又不是很好理解,因为在代码中省略了

window
,补全后就好理解了:
this
指向的是调用者。

function fn() {
this.author = 'Wango';
}

window.fn();
console.log(window.author);
// Wango

而在内部函数中,自执行函数中的

this
依旧指向全局作用域,我们无法通过
window.foo()
调用函数,但并不妨碍我们先这样理解(具体参见本文最后一部分
this
的强制转型
)。

function fn() {
function foo() {
console.log(this);
}
foo();
// Window
window.foo();
// TypeError
}

fn();

作为构造函数调用

在构造函数中,

this
指向
new
生成的新对象,即构造函数是通过
new
调用的,构造函数内部的
this
当然就应该指向
new
出来的对象。

function Person(name, age) {
this.name = name;
this.age = age;
console.log(this);
// Person { name: 'Wango', age: 24 }
}

new Person('Wango', 24);

构造函数中的

this
与构造函数的返回值类型无关,下列代码中
p
指向了构造函数返回的对象,而不是
new
出来的对象。当然,这是构造函数的特性,与本主题关系不大。

function Person(name, age) {
console.log(this);
// Person {}
this.name = name;
this.age = age;
console.log(this);
// Person { name: 'Wango', age: 24 }

return {
name: 'Lily',
age: 25
}
}

Person.prototype.sayName = function() {
return this.name + ' ' + this.age
}

const p = new Person('Wango', 24);
console.log(p.sayName());
// TypeError: p.sayName is not a function

作为对象方法调用

通过对象方法调用时,

this
指向应该是最明晰的了。与其他面向对象语言的
this
行为相同,指向该方法的调用者。

function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype.sayName = fn;

function fn() {
return this.name + ' ' + this.age
}

const p = new Person('Wango', 24);
console.log(p);
// Person { name: 'Wango', age: 24 }
console.log(p.sayName());
// Wango 24

通过
[]
调用对象方法

通常,我们对于对象方法是通过

.
语法调用,但通过
[]
也可以调用对象方法,在这种情况下的
this
指向常常会被我们混淆、忽略。

function fn() {
console.log(t
56c
his);
}

const arr = [fn, 1];

arr[0]();
// [Function: fn, 1]

function fn2() {
arguments[0]();
}

fn2(fn, 1);
// [Arguments] { '0': [Function: fn], '1': 1 }

在上例中,无论是数组还是伪数组,其本质上都是对象,在通过

[]
获取函数元素并调用的时候,会改变函数中的
this
指向,
this
指向这个数组或伪数组,与对象调用函数的行为一致。

通过call、apply调用

function fn() {
console.log(this.name);
}

const author = {
name: 'Wango'
}

fn.call(author);
// Wango

这似乎与

this
永远指向调用者相违背,但一旦我们明白了call函数的实现机制就会明白,这不仅不是违背,反而是佐证。对
call
apply
bind
实现机制不熟悉的同学可以参考我另一篇文章,下面截取
call
简要说明。

// 保存一个全局变量作为默认值
const root = this;

Function.prototype.myCall = function(context, ...args) {
if (typeof context === 'object') {
// 如果参数是null,使用全局变量
context = context || r
ad8
oot;
} else {
// 参数不是对象的创建一个空对象
context = Object.create(null);
}
// 使用Symbol创建唯一值作为函数名
let fn = Symbol();
context[fn] = this;
context[fn](...args);
delete context[fn];
}

call
函数最核心的实现在于
context[fn] = this;
context[fn](...args);
这两行。实际上就是将没有函数调用者的普通函数挂载到指定的对象上,这时
this
指向与对象调用方法的一致。而
delete context[fn];
是在调用后立即解除对象与函数之间的关联。

严格模式下的不同表现

this
强制转型

使用函数的

apply()
call()
方法时,在非严格模式下
null
undefined
值会被强制转型为全局对象。在严格模式下,则始终以指定值作为函数
this
的值,无论指定的是什么值。这也是为何在严格模式下,自执行函数的
this
不再指向
window
,而是指向
undefined
的根本原因。

// 定义一个全局变量
color = "red";
function displayColor() {
console.log(this.color);
}
// 在非严格模式下使用call修改this指向,并指定null,或undefined,
displayColor.call(null);
displayColor.call();
// red
// 修改指向无效,传入null或undefined被转换为了window

实际上,我们也可以将自执行函数,如

fn()
,看作是
fn.call()
的语法糖,在普通模式下,第一个参数默认为
undefined
,但被强制转换为
window
。这也就解释了为何所有自执行函数中
this
都指向
window
但无法通过
window
调用的问题(函数在
call
函数中挂载到
window
对象上,执行后被立即删除,所以无法再次通过
window
访问)。

apply()
call()
方法在严格模式下传入简单数据类型作为第一个参数时,该简单数据类型会被转换为相应的包装类,而非严格模式不会如此转换。

function foo() {
console.log(this);
}

foo.call(); // Window {}
foo.call(2); // Number {2}

function foo() {
console.log(this);
}

foo.call(); // undefined
foo.call(2); // 2

箭头函数的
this
指向

在箭头函数中,

this
引用的是定义箭头函 56c 数的上下文。即箭头函数中的
this
不会随着函数调用方式的改变而改变。

function Person(name) {
this.name = name;

this.getName = () => console.log(this.name);
}

const p = new Person('Wango');

p.getName();
// Wango

const getName = p.getName;

getName();
// Wango
getName.call({name: 'Lily'});
// Wango

参考资料:

Javascript 的 this 用法

Javascript高级程序设计(第四版)

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: