js中this指向的问题与联系
前言
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(this);
}
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 || root;
} else {
// 参数不是对象的创建一个空对象
context = Object.create(null);
}
// 使用Symbol创建唯一值作为函数名
let fn = Symbol();
context[fn] = this;
context[fn](...args);
delete context[fn];
}
let person = {
name: 'Wango',
fn: function() {
console.log(this.name);
}
}
function sayHi(age, sex) {
console.log(this.name, age, sex);
}
sayHi.myCall(person, 24, 'male');
// Wango 24 male
sayHi.myCall(null, 24, 'male');
// undefined 24 male
sayHi.myCall(123, 24, 'male');
// undefined 24 male
// 原函数不受影响
person.fn();
// Wango
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
引用的是定义箭头函数的上下文。即箭头函数中的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指向的问题与联系的更多相关文章
- JavaScript面向对象(一)——JS OOP基础与JS 中This指向详解
前 言 JRedu 学过程序语言的都知道,我们的程序语言进化是从"面向机器".到"面向过程".再到"面向对象"一步步的发展而来.类似于 ...
- 关于js中this指向的理解总结!
关于js中this指向的理解! this是什么?定义:this是包含它的函数作为方法被调用时所属的对象. 首先,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁 ...
- 前端js中this指向及改变this指向的方法
js中this指向是一个难点,花了很长时间来整理和学习相关的知识点. 一. this this是JS中的关键字, 它始终指向了一个对象, this是一个指针; 参考博文: JavaScript函数中的 ...
- js中this指向的三种情况
js中this指向的几种情况一.全局作用域或者普通函数自执行中this指向全局对象window,普通函数的自执行会进行预编译,然后预编译this的指向是window //全局作用域 console.l ...
- JS中this指向的更改
JS中this指向的更改 JavaScript 中 this 的指向问题 前面已经总结过,但在实际开中, 很多场景都需要改变 this 的指向. 现在我们讨论更改 this 指向的问题. call更改 ...
- 关于js中this指向的总结
js中this指向问题一直是个坑,之前一直是懵懵懂懂的,大概知道一点,但一直不知道各种情况下指向有什么区别,今天亲自动手测试了下this的指向. 1.在对象中的this对象中的this指向我们创建的对 ...
- 如何理解JS中this指向的问题
首先,用一句话解释this,就是:指向执行当前函数的对象. 当前执行,理解一下,也就是说this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定.this到底指向谁?this的最终指向的 ...
- 关于js中this指向的问题
this的绑定规则有4种 默认绑定 隐性绑定 显性绑定 new绑定 this绑定优先级 new 绑定 > 显性绑定 > 隐性绑定 > 默认绑定 1.如果函数被new 修饰 this绑 ...
- js中this指向学习总结
在面向对象的语言中(例如Java,C#等),this 含义是明确且具体的,即指向当前对象.一般在编译期绑定. 然而js中this 是在运行期进行绑定的,这是js中this 关键字具备多重含义的本质 ...
- JS中this指向问题和改变this指向
首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象(这句话有些问题,后面会解释为什么会有问题,虽然 ...
随机推荐
- CentOS7.X安装英伟达显卡采坑之路
1.系统信息 操作系统版本:CentOS7.X 显卡版本:英伟达 Tesla P100 其他软件包安装信息: CUDA 9.0 CUDNN 7.4.2.24 lightgbm 2.2.X Boost ...
- Flink-v1.12官方网站翻译-P023-The Broadcast State Pattern
广播状态模式 在本节中,您将了解如何在实践中使用广播状态.请参考状态流处理,了解状态流处理背后的概念. 提供的API 为了展示所提供的API,我们将在介绍它们的全部功能之前先举一个例子.作为我们的运行 ...
- ST在keil下开发时候文件options配置的一些小技巧
作者:良知犹存 转载授权以及围观:欢迎添加微信公众号:Conscience_Remains 总述 这是之前ST芯片载keil下开发时候总结的一些代码文件options配置小笔记,虽然不是很复杂 ...
- docker部署 springboot 多模块项目+vue
之前学习了docker,今天就来试试将这个项目打包成docker镜像并通过运行一个镜像来运行项目.这里使用的项目是el-admin.是一个开源的springboot后端管理框架(前端vue),有兴趣的 ...
- Codeforces Round #671 (Div. 2)
比赛链接:https://codeforces.com/contest/1419 A. Digit Game 题意 给出一个 $n$ 位数,游戏规则如下: 1-indexed Raze标记奇数位 Br ...
- Educational Codeforces Round 90 (Rated for Div. 2) A. Donut Shops(数学)
题目链接:https://codeforces.com/contest/1373/problem/A 题意 有两种包装的甜甜圈,第一种 $1$ 个 $a$ 元,第二种 $b$ 个 $c$ 元,问买多少 ...
- dsu on tree ——附带buff的暴力解法
这篇博客只是简单叙述思想(因为ML太弱了),具体例题请转其他博客. dsu on tree,许多OI将其归于启发式合并,当然如果你能理解更好,这只是一个理解方式罢了. 思想简述 顾名思义,这个算法是处 ...
- 【uva 11082】Matrix Decompressing(图论--网络流最大流 Dinic+拆点二分图匹配)
题意:有一个N行M列的正整数矩阵,输入N个前1~N行所有元素之和,以及M个前1~M列所有元素之和.要求找一个满足这些条件,并且矩阵中的元素都是1~20之间的正整数的矩阵.输入保证有解,而且1≤N,M≤ ...
- zjnuSAVEZ (字符串hash)
Description There are eight planets and one planetoid in the Solar system. It is not a well known fa ...
- 【uva 120】Stacks of Flapjacks(算法效率--构造法+选择排序思想)
题意:有N张正在锅里的一叠煎饼,每张都有一个数字,代表其大小.厨师每次可以选择一个数k,把从锅底开始数第k张上面的煎饼全部翻过来,即原来在上面的煎饼现在到了下面.要求设计一种方法使得所有煎饼按照从小到 ...