图文结合深入理解 JS 中的 this 值

JS 中最常见的莫过于函数了,在函数(方法)中 this 的出现频率特别高,那么 this 到底是什么呢,今天就和大家一起学习总结一下 JS 中的 this

1. 初探this

thisJS 中是一个关键字,不是变量也不是属性名, JS 中不允许给this赋值。

它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。

this 指向的是函数运行时所在的环境,也就是说函数在哪个环境中运行,this 的值就指向哪个环境。

先看下面这段代码的输出结果:

  1. function f() {
  2. console.log(this.x);
  3. }
  4. var obj = {
  5. f: f,
  6. x: 1
  7. };
  8. var x = 2;
  9. f(); // 2
  10. obj.f(); // 1

有点奇怪,obj.ff 明明指向的是同一个函数为什么执行结果是不同的呢?

原因就在于这两个函数运行时所在的环境是不同的。

可以结合下面的两张图来理解

图一描述了上面这段代码的作用域链

图二描述了运行 obj.f() 时的部分执行过程



图一



图二

如图二所示,执行obj.f() 时,obj 对象需要先找到 f 属性,然后通过 f 属性中的 value 值获取到 f 函数的地址,通过这个地址再获取到 f 函数实际的代码开始运行,因此此时 f 函数运行时所在的环境是 obj 环境。因为 obj环境下 x 的值是 1 ,所以最终输出的值为 1

执行 f() 时,实际上是从全局对象 window 中找到 f 函数,然后再执行。此时 f 函数运行时所在的环境是全局环境,因为全局环境下的 x 的值为 2 ,因此最终输出的值为 2

下面是另外一个值得注意的地方:

this 值没有作用域的限制,嵌套函数不会从它的包含函数中继承 this很多人误以为调用嵌套函数时 this 值会指向它的外层函数的变量对象,其实并不是这样的。

如果想访问这个外层函数的 this 值,需要将 this 值保存在一个变量里,通常使用 self 来保存this

再看下面这段代码:

  1. let foo = function() {
  2. var self = this;
  3. console.log(this === obj); // true, this就是obj对象
  4. f(); // 嵌套函数f当做普通函数调用
  5. function f() {
  6. // 上面f()是被当做普通函数调用的,执行环境是全局作用域,因此f内部的this的值指向全局对象window
  7. console.log(this === obj) // false,this在这里指向全局对象
  8. // self保存的是外部方法中的this,指向对象obj
  9. console.log(self === obj) // true, self中保存的是外层函数中的this值
  10. }
  11. };
  12. var obj = {
  13. m: foo
  14. };
  15. obj.m();

下面这张图描述了执行 obj.m() 时内部运行的部分流程:



图三

执行obj.m() 时,obj对象需要先找到 m 属性,然后通过读取 m 属性中的 value 值来调用 foo 函数,所以此时 foo 函数运行时所在的环境是 obj 环境,所以 foo 内部的 this 指向 obj 环境,所以第一个 console.log 的输出结果为 true

foo 函数内部调用 f 时,直接写成了 f() 这种普通函数调用的方式,记住当被当做普通函数调用时,f 内部的 this 在是指向全局环境的。(严格模式下是 undefined 非严格模式下指向全局环境,一般情况下都是用的非严格模式 )。

因此,f 函数内部的 this 是全局对象 window 而不是obj,这也说明了内层函数不会继承外部函数的 this

所以,第二个 console.log 会输出 false,因为此时 f 内部的 this 指向全局对象 window 。第三个 console.log 会输出 true,因为 self 里存放的是外层函数的 this,外层函数的 this 指向 obj 环境。

看到这里可能有的小伙伴还是对于 this 的值到底是什么还是有一点疑惑,能不能再归纳一下呢?好,那接下来就根据不同的情况再做一下总结,其实这个总结是之前看的阮一峰老师归纳的,在这里加上一点自己的理解,拿过来借花献佛。

2. this指向总结

再重申一下,this 是在函数运行时,自动生成的一个对象,this 的指向不同,归根结底在于函数调用方式的不同,下面就以四种不同的函数调用方式来分析 this 的指向问题。

2.1 普通函数调用

如果一个函数被当做普通函数调用,在非严格模式下这个函数中的 this 值就指向全局对象 window,在严格模式下 this 值就是 undefined

下面结合代码和配图来说明一下:

  1. var x = 1;
  2. function foo() {
  3. console.log(this.x);
  4. }
  5. foo();



图四

运行foo()foo 是被当做普通函数调用,window 对象需要先找到 foo 属性,然后通过里面保存的地址找到 foo 函数的代码开始运行,因此 foo 函数的运行环境是 window 环境,此时 this 的值指向 window 环境。因为 window 环境中 x 属性的值为 1 ,因此最终的输出结果为 1

2.2 对象的方法调用

当某个函数被某个对象当做方法来调用时,this 就指向这个对象。

  1. function foo() {
  2. console.log(this.x);
  3. }
  4. var obj = {
  5. x : 1,
  6. foo : foo
  7. }
  8. obj.foo();



图五

运行 obj.foo()foo 函数被当做 obj 对象的方法来调用,此时 foo 函数的运行环境是 obj 环境,因此 this 指向 obj,因为 obj.x = 1, 所以最终输出 1

2.3 构造函数调用

使用 new 构造函数 的语法会创建一个新的对象,此时 this 就指向这个新的对象。

要想明白其中的原理,就要从 new 操作符说起, 使用 new 操作符时实际上 JS 引擎做了四件事:

  1. 创建一个新对象(创建 person1 对象)
  2. 将构造函数的环境赋给新对象(this 指向了 person1)
  3. 执行构造函数中的代码(为 person1 对象添加属性和方法,即 nameage 属性,eat 方法)
  4. 返回这个新对象(将新创建的对象的地址赋给 person1)

注:上面的1,2,3步中不应该出现 person1 ,因为最后一步才将新创建的对象的地址赋给 person1,上面那样写是为了理解方便。

  1. function eat() {
  2. console.log('I am eating');
  3. }
  4. function Person(name, age) {
  5. this.name = name;
  6. this.age = age;
  7. this.eat = eat;
  8. }
  9. let person1 = new Person('zhangsan', '18');
  10. console.log(person1.name); // 'zhangsan'
  11. console.log(person1.age); // 18
  12. person1.eat(); // 'I am eating'



图六

通过 new 操作符的第二步,我们就可以看出 Js 引擎将构造函数的环境赋给了新的对象(person1),因此 this 就指向了那个新创建的对象(person1)。

2.4 利用call,apply,bind方法调用函数

这几个都是函数的方法,它们可以改变函数运行时的环境, this 就指向它们的参数所指定的运行环境。

  1. var obj1 = {
  2. x : 1
  3. };
  4. var obj2 = {
  5. x : 2
  6. };
  7. var obj3 = {
  8. x : 3
  9. };
  10. var x = 4;
  11. function foo() {
  12. console.log(this.x);
  13. }
  14. var foo1 = foo.bind(obj1);
  15. foo1(); // 1
  16. foo.call(obj2); // 2
  17. foo.apply(obj3); // 3
  18. foo(); // 4



图 七

var foo1 = foo.bind(obj1); foo1(); 将函数运行的环境修改为 obj1this 指向 obj1,因此输出 1

foo.call(obj2); 将函数的运行环境修改为 obj2this 指向 obj2,因此输出为 2

foo.apply(obj3) 将函数的运行环境修改为obj3this 指向 obj3,因此输出为 3

foo() 纯粹的函数调用,运行环境为 全局对象window, this 指向 obj4,因此输出为 4

完,如有不恰当之处,欢迎指正哦.

图文结合深入理解 JS 中的 this 值的更多相关文章

  1. 图文结合深入理解JS中的this值

    文章目录 Js 中奇妙的this值 1. 初探this 2. this指向总结 2.1 普通函数调用 2.2 对象的方法调用 2.3 构造函数调用 2.4 利用call,apply,bind方法调用函 ...

  2. 怎么理解js中的事件委托

    怎么理解js中的事件委托 时间 2015-01-15 00:59:59  SegmentFault 原文  http://segmentfault.com/blog/sunchengli/119000 ...

  3. 如何更好的理解js中的this,分享2段有意思的代码

    关于js中this的浅析,大家可以点击[彻底理解js中this的指向,不必硬背]这篇博客了解. 今天遇到2段比较有意思的代码. ----------------第一段----------------- ...

  4. 深度理解js中var let const 区别

    首先要理解js中作用域的概念 作用域:指的是一个变量的作用范围 1.全局作用域 直接写在script中的js代码,在js中,万物皆对象,都在全局作用域,全局作用域在页面打开时创建,在全局作用域中有一个 ...

  5. 如何理解js中的this和实际应用中需要避开哪些坑

    this是什么 this就是函数内部的关键字 看下面例子理解js中的this // 例子1 function fnOne () { console.log(this) } 'use strict' f ...

  6. 深入理解JS中的对象(二):new 的工作原理

    目录 序言 不同返回值的构造函数 深入 new 调用函数原理 总结 参考 1.序言 在 深入理解JS中的对象(一):原型.原型链和构造函数 中,我们分析了JS中是否一切皆对象以及对象的原型.原型链和构 ...

  7. 深入理解JS中的对象(三):class 的工作原理

    目录 序言 class 是一个特殊的函数 class 的工作原理 class 继承的原型链关系 参考 1.序言 ECMAScript 2015(ES6) 中引入的 JavaScript 类实质上是 J ...

  8. 深入理解Js中的this

    深入理解Js中的this JavaScript作用域为静态作用域static scope,但是在Js中的this却是一个例外,this的指向问题就类似于动态作用域,其并不关心函数和作用域是如何声明以及 ...

  9. js中以键值对的形式当枚举

    js中以键值对的形式当枚举var Penum= { B: "姓名", C: "所属居委", D: "证件号", E: "性别&qu ...

随机推荐

  1. java基础之BigDecimal类

    由于在运算的时候,float类型和double很容易丢失精度,演示案例.所以,为了能精确的表示.计算浮点数,Java提供了BigDecimal BigDecimal类概述 不可变的.任意精度的有符号十 ...

  2. jmeter参数化之用户参数

    1.     用badboby进行录制,录制完成后保存,用JMeter格式进行保存,如:登陆.jmx 2.     在jmeter中打开保存的文件登陆.jmx. 3.在step1上右击-添加-前置处理 ...

  3. js (function(){}()),(function(){})(),$(function(){});之间的区别

    参考:https://blog.csdn.net/stpice/article/details/80586444 (function(){}()), (function(){})() 均为立即执行函数 ...

  4. 2018-11-26-win10-uwp-获取窗口的坐标和宽度高度

    title author date CreateTime categories win10 uwp 获取窗口的坐标和宽度高度 lindexi 2018-11-26 15:4:0 +0800 2018- ...

  5. [转]C#设计模式(4)-Simple Factory Pattern

    工厂模式专门负责将大量有共同接口的类实例化.工厂模式可以动态决定将哪一个类实例化,不必事先知道每次要实例化哪一个类.工厂模式有以下几种形态: 简单工厂(Simple Factory)模式 工厂方法(F ...

  6. Lucene 评分机制一

    1. 评分公式 1.1 公式介绍 这个公式是Lucene实际计算时使用的公式,是由原型公式推导而来 tf(t in d) 表示某个term的出现频率,定义了term t出现在当前document d的 ...

  7. csp-s模拟47 Emotional Flutter,Endless Fantasy题解

    题面:https://www.cnblogs.com/Juve/articles/11558523.html A:Emotional Flutter 如果起点确定,那么我们后面走的点都是固定的,及mo ...

  8. 计算机基础(day02)

    目录 什么是编程? 计算机的组成 CPU(大脑) 控制器 运算器 存储器 主存 外存 IO设备 input设备(输入设备) output设备(输出设备) 多核cpu 32位和64位 机械硬盘工作原理 ...

  9. 大牛就别进来了.npm --save-dev --save 的区别

    --save-dev 是你开发时候依赖的东西,--save 是你发布之后还依赖的东西. 比如,你写 ES6 代码,如果你想编译成 ES5 发布那么 babel 就是devDependencies.如果 ...

  10. typeof, offsetof, container_of宏

    container_of宏实现如下: #define container_of(ptr, type, member) ({ \ )->member ) *__mptr = (ptr); \ (t ...