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高级程序设计(第四版)
相关文章推荐
- js 深入理解this指向问题
- js中this的指向问题
- 从Ecma规范深入理解js中的this的指向
- js中的this指向问题及解决方案
- js基础梳理-关于this常见指向问题的分析
- JS匿名函数内部this指向问题详析
- js中函数this指向问题
- what's this? 浅谈js中this的指向问题
- JS中this指向的问题
- 详解JS中定时器setInterval和setTImeout的this指向问题
- js中this的应用及指向问题
- js中this的指向问题
- JS中的this 指向问题
- Js中this指向问题总结
- js中this指向问题
- js中this的应用及指向问题
- js中this的指向问题归纳总结
- js函数中this指向问题
- 小白也能彻底理解js中this的指向问题
- JS中各种this指向问题