前言

JavaScript 中最大的一个安全问题,也是最令人困惑的一个问题,就是在某些情况下this的值是如何确定的。有js基础的同学面对这个问题基本可以想到:this的指向和函数调用的方式相关。这当然是正确的,然而,这几种方式有什么联系吗?这是我接下来要说明的问题。

this从哪里来

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

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

但是不要忘记,即便是全局作用域,依旧是运行在window下的,我们写的代码都在window的某个函数中。而这也催生了一种理解this指向的方法:this永远指向调用者(非箭头函数)。

作为普通函数调用

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

  1. function fn() {
  2. this.author = 'Wango';
  3. }
  4. fn();
  5. console.log(author);
  6. // Wango

这很好理解,但又不是很好理解,因为在代码中省略了window,补全后就好理解了:this指向的是调用者。

  1. function fn() {
  2. this.author = 'Wango';
  3. }
  4. window.fn();
  5. console.log(window.author);
  6. // Wango

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

  1. function fn() {
  2. function foo() {
  3. console.log(this);
  4. }
  5. foo();
  6. // Window
  7. window.foo();
  8. // TypeError
  9. }
  10. fn();

作为构造函数调用

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

  1. function Person(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. console.log(this);
  5. // Person { name: 'Wango', age: 24 }
  6. }
  7. new Person('Wango', 24);

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

  1. function Person(name, age) {
  2. console.log(this);
  3. // Person {}
  4. this.name = name;
  5. this.age = age;
  6. console.log(this);
  7. // Person { name: 'Wango', age: 24 }
  8. return {
  9. name: 'Lily',
  10. age: 25
  11. }
  12. }
  13. Person.prototype.sayName = function() {
  14. return this.name + ' ' + this.age
  15. }
  16. const p = new Person('Wango', 24);
  17. console.log(p.sayName());
  18. // TypeError: p.sayName is not a function

通过对象方法调用

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

  1. function Person(name, age) {
  2. this.name = name;
  3. this.age = age;
  4. }
  5. Person.prototype.sayName = fn;
  6. function fn() {
  7. return this.name + ' ' + this.age
  8. }
  9. const p = new Person('Wango', 24);
  10. console.log(p);
  11. // Person { name: 'Wango', age: 24 }
  12. console.log(p.sayName());
  13. // Wango 24

通过[]调用对象方法

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

  1. function fn() {
  2. console.log(this);
  3. }
  4. const arr = [fn, 1];
  5. arr[0]();
  6. // [Function: fn, 1]
  7. function fn2() {
  8. arguments[0]();
  9. }
  10. fn2(fn, 1);
  11. // [Arguments] { '0': [Function: fn], '1': 1 }

在上例中,无论是数组还是伪数组,其本质上都是对象,在通过[]获取函数元素并调用的时候,会改变函数中的this指向,this指向这个数组或伪数组,与对象调用函数的行为一致。

通过call、apply调用函数

  1. function fn() {
  2. console.log(this.name);
  3. }
  4. const author = {
  5. name: 'Wango'
  6. }
  7. fn.call(author);
  8. // Wango

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

  1. // 保存一个全局变量作为默认值
  2. const root = this;
  3. Function.prototype.myCall = function(context, ...args) {
  4. if (typeof context === 'object') {
  5. // 如果参数是null,使用全局变量
  6. context = context || root;
  7. } else {
  8. // 参数不是对象的创建一个空对象
  9. context = Object.create(null);
  10. }
  11. // 使用Symbol创建唯一值作为函数名
  12. let fn = Symbol();
  13. context[fn] = this;
  14. context[fn](...args);
  15. delete context[fn];
  16. }
  17. let person = {
  18. name: 'Wango',
  19. fn: function() {
  20. console.log(this.name);
  21. }
  22. }
  23. function sayHi(age, sex) {
  24. console.log(this.name, age, sex);
  25. }
  26. sayHi.myCall(person, 24, 'male');
  27. // Wango 24 male
  28. sayHi.myCall(null, 24, 'male');
  29. // undefined 24 male
  30. sayHi.myCall(123, 24, 'male');
  31. // undefined 24 male
  32. // 原函数不受影响
  33. person.fn();
  34. // Wango

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

严格模式下的不同表现

this强制转型

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

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

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

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

  1. function foo() {
  2. console.log(this);
  3. }
  4. foo.call(); // Window {}
  5. foo.call(2); // Number {2}
  6. function foo() {
  7. console.log(this);
  8. }
  9. foo.call(); // undefined
  10. foo.call(2); // 2

箭头函数的this指向

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

  1. function Person(name) {
  2. this.name = name;
  3. this.getName = () => console.log(this.name);
  4. }
  5. const p = new Person('Wango');
  6. p.getName();
  7. // Wango
  8. const getName = p.getName;
  9. getName();
  10. // Wango
  11. getName.call({name: 'Lily'});
  12. // Wango

参考资料:

Javascript 的 this 用法

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

js中this指向的问题与联系的更多相关文章

  1. JavaScript面向对象(一)——JS OOP基础与JS 中This指向详解

      前  言 JRedu 学过程序语言的都知道,我们的程序语言进化是从"面向机器".到"面向过程".再到"面向对象"一步步的发展而来.类似于 ...

  2. 关于js中this指向的理解总结!

    关于js中this指向的理解! this是什么?定义:this是包含它的函数作为方法被调用时所属的对象. 首先,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁 ...

  3. 前端js中this指向及改变this指向的方法

    js中this指向是一个难点,花了很长时间来整理和学习相关的知识点. 一. this this是JS中的关键字, 它始终指向了一个对象, this是一个指针; 参考博文: JavaScript函数中的 ...

  4. js中this指向的三种情况

    js中this指向的几种情况一.全局作用域或者普通函数自执行中this指向全局对象window,普通函数的自执行会进行预编译,然后预编译this的指向是window //全局作用域 console.l ...

  5. JS中this指向的更改

    JS中this指向的更改 JavaScript 中 this 的指向问题 前面已经总结过,但在实际开中, 很多场景都需要改变 this 的指向. 现在我们讨论更改 this 指向的问题. call更改 ...

  6. 关于js中this指向的总结

    js中this指向问题一直是个坑,之前一直是懵懵懂懂的,大概知道一点,但一直不知道各种情况下指向有什么区别,今天亲自动手测试了下this的指向. 1.在对象中的this对象中的this指向我们创建的对 ...

  7. 如何理解JS中this指向的问题

    首先,用一句话解释this,就是:指向执行当前函数的对象. 当前执行,理解一下,也就是说this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定.this到底指向谁?this的最终指向的 ...

  8. 关于js中this指向的问题

    this的绑定规则有4种 默认绑定 隐性绑定 显性绑定 new绑定 this绑定优先级 new 绑定 > 显性绑定 > 隐性绑定 > 默认绑定 1.如果函数被new 修饰 this绑 ...

  9. js中this指向学习总结

      在面向对象的语言中(例如Java,C#等),this 含义是明确且具体的,即指向当前对象.一般在编译期绑定. 然而js中this 是在运行期进行绑定的,这是js中this 关键字具备多重含义的本质 ...

  10. JS中this指向问题和改变this指向

    首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象(这句话有些问题,后面会解释为什么会有问题,虽然 ...

随机推荐

  1. Spark练习之创建RDD(集合、本地文件),RDD持久化及RDD持久化策略

    Spark练习之创建RDD(集合.本地文件) 一.创建RDD 二.并行化集合创建RDD 2.1 Java并行创建RDD--计算1-10的累加和 2.2 Scala并行创建RDD--计算1-10的累加和 ...

  2. hbase远程api调用, 远程连接开发,环境构建相关问题总结

    平时在开发的过程中,都是将hbase的环境装在虚拟机或者双系统上的,开发是直接在unix或者linux上进行的,这样对于使用上,有些繁琐. 下面我将介绍我在使用windows远程开发过程中的一些总结: ...

  3. REST架构及其介绍

    概述     REST是英文Representational State Transfer的缩写,中文翻译:表述性状态转移.     他是由Roy Thomas Fielding博士在他的论文 < ...

  4. 闲聊CAP、BASE与XA

    CAP理论与BASE理论 首先要和大家说的就是大名鼎鼎的CAP理论与BASE理论了,这两个理论与解决分布式事务问题是密切相关的. 其实网上有很多关于CAP与BASE相关的文章,一写就写了一大堆,篇幅很 ...

  5. vue-cli-----vue实例中template: '<App/>',这样写是什么意思

    我刚开始学,看这个东西看了好久,官网文档的描述我不太理解,今天终于算明白了 官网的描述: 模板将会替换挂载的元素.挂载元素的内容都将被忽略 也就是说:template: '<App/>' ...

  6. Think in Java 第四 五 章

    Think in Java 第四章 控制执行流程 测试while public class whileTest { static boolean condition(){ boolean result ...

  7. 使用kubekey安装kubesphere

    下载 KubeKey KubeKey 是新一代 Kubernetes 和 KubeSphere 安装器,可帮助您以简单.快速.灵活的方式安装 Kubernetes 和 KubeSphere. expo ...

  8. HDOJ 3398

    这个题坑了太久太久啊!!!!!贡献了得有30+WA才发现 原来是因为在乘法中有溢出导致一直TLE啊.... 但是到最后也不知道有个问题怎么解决的. 就是在getp()中的num值的诡异的改变! #in ...

  9. Codeforces Round #652 (Div. 2) B. AccurateLee(思维)

    题意: 给你一个01字符串,现在你可以删除其中的一些子序列,要求如下:当遇到1 0的俩个连续子字符串后,可以删除其中的一个字符,现在要求把他删到尽量最短并且字典序最小,输出最后的字符串 题解: 刚开始 ...

  10. div 水平居中 内容居左

    <div style="margin:0 auto;width:500px;text-align:left"> </div> https://zhidao. ...