• 前端this指向详解-没写完的后续补充


    什么是this

    this 是 JavaScript 中的一个关键字。依赖于函数调用的上下文条件。

    函数的this指向谁,其实可以分为两种情况讨论:

    1. 普通函数的this:
      普通函数的this指向函数调用者,如果找不到调用者,默认指向window。普通函数的this指向可以通过call\apply\bind去改变
    2. 箭头函数的this
      箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)。箭头函数的this只和定义时的作用域this有关,和调用者无关,和调用环境无关,也永远不会改变。

    this在不同应用场景中的取值

    普通函数(非箭头函数)被调用时的this指向

    普通函数(非箭头函数)被调用时,this的指向是window
    示例:

    function fn () {
      console.log(this); // this指向window
    }
    
    fn();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    普通函数(非箭头函数)作为对象方法被调用时的this指向

    普通函数(非箭头函数)作为对象方法被调用时,this的指向是对象本身
    示例:

    const obj = {
      name: '张三',
      normal() {
        console.log(this); // this指向对象本身,即obj
      },
    }
    
    obj.normal();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    备注:如果对象方法中使用了计时器如setTimeout,setTimeout中回调函数的this会有不同的情况,在后面我们会介绍setTimeout的this指向

    普通函数(非箭头函数)作为class方法被调用时的this指向

    普通函数(非箭头函数)作为class方法被调用时,this的指向是class实例本身
    示例:

    class Person {
      constructor(name, age) {
        this.name = name; // this指向class实例本身
        this.age = age; // this指向class实例本身
      }
      logInfo() {
        console.log(this); // this指向class实例本身
      }
    }
    
    const zhangsan = new Person('张三', 20);
    zhangsan.logInfo();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    普通函数(非箭头函数)被call\apply\bind转化之后被调用时的this指向

    call\apply\bind可以改变普通函数(非箭头函数)的this指向,因此普通函数(非箭头函数)被call\apply\bind转化之后,this指向变成指定的对象
    示例:
    我们定义一个普通函数fn,
    当fn作为普通函数正常调用时,this指向window;
    当使用call\apply\bind将fn的this指向修改成一个对象obj时,此时this指向obj。

    function fn(params1, params2) {
      console.log(this, params1, params2);
    }
    
    const obj = {
      name: '张三',
      age: 20,
    }
    
    fn('参数1', '参数2'); // 当fn作为普通函数正常调用时,this指向window;
    fn.call(obj, '参数1', '参数2'); // 当使用call将fn的this指向修改成一个对象obj时,此时this指向obj。
    fn.apply(obj, ['参数1', '参数2']); // 当使用apply将fn的this指向修改成一个对象obj时,此时this指向obj。
    fn.bind(obj, '参数1', '参数2')(); // 当使用bind将fn的this指向修改成一个对象obj时,此时this指向obj。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    箭头函数中的this指向

    之前我们讲过,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)。箭头函数的this只和定义时的作用域this有关,和调用者无关,和调用环境无关,也永远不会改变。接下来我们用以下示例来说明:

    1. 普通函数fn1作为对象obj的方法,并return一个普通函数

      const obj = {
        name: '张三',
        age: 20,
        fn1() {
          console.log(this); // 打印obj,普通函数(非箭头函数)作为对象方法被调用时,this的指向是对象本身
          return function () {
            console.log(this); // 打印window,因为fn1 return出去的函数,调用者并不是obj,实际上是相当于普通函数(非箭头函数)的调用方式,所以此时this指向window
          }
        },
      }
      
      obj.fn1()();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      在这里插入图片描述

    2. 普通函数fn2作为对象obj的方法,并return一个箭头函数

      const obj = {
        name: '张三',
        age: 20,
        fn2() {
          console.log(this); // 打印obj,普通函数(非箭头函数)作为对象方法被调用时,this的指向是对象本身
          return () => {
            console.log(this); // 打印obj,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)
          }
        },
      }
      
      obj.fn2()();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      在这里插入图片描述

    3. 对象obj的对象字面量fn3赋值一个箭头函数,并return一个箭头函数

      const obj = {
        name: '张三',
        age: 20,
        fn3: () => {
          console.log(this); // 打印window
                             // 箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)
                             // 在字面量中直接定义的箭头函数无法继承该对象的this,而是往外再找一层,就找到了window,因为字面量对象无法形成自己的一层作用域,但是构造函数可以哦
          return () => {
            console.log(this); // 打印window,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)
          }
        },
      }
      
      obj.fn3()();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14

      在这里插入图片描述

    4. 对象obj的对象字面量fn4赋值一个普通函数,并return一个普通函数,再return一个箭头函数

      const obj = {
        name: '张三',
        age: 20,
        fn4: function () { 
          console.log(this) // 打印obj,普通函数(非箭头函数)作为对象方法被调用时,this的指向是对象本身
          
          return function () {
            console.log(this); // 打印window,因为fn4 return出去的函数,调用者并不是obj,实际上是相当于普通函数(非箭头函数)的调用方式,所以此时this指向window
            
            return () => {
              console.log(this); // 打印window,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)
            }
          }
        },
      }
      
      obj.fn4()()();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      在这里插入图片描述

    5. 普通函数fn,return一个箭头函数

      function fn() {
        console.log(this); // 打印window,普通函数(非箭头函数)被调用时,this的指向是window
        return () => {
          console.log(this); // 打印window,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)
        }
      }
      
      fn()();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      在这里插入图片描述

    6. 普通函数fn,return一个箭头函数,并且fn通过call修改this指向为obj

      const obj = {
        name: '张三',
        age: 20,
      }
      
      function fn() {
        console.log(this); // 打印obj,普通函数可以被call修改this指向
        return () => {
          console.log(this); // 打印obj,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)
        }
      }
      
      fn.call(obj)(); // 使用call修改fn的this指向
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      在这里插入图片描述

    7. 普通函数fn,return一个普通函数

      function fn() {
        console.log(this); // 打印window,普通函数(非箭头函数)被调用时,this的指向是window
      
        return function() {
          console.log(this); // 打印window,普通函数(非箭头函数)被调用时,this的指向是window
        }
      }
      
      fn()();
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      在这里插入图片描述

    8. 普通函数fn,return一个普通函数,并且fn通过call修改this指向为obj

      function fn() {
        console.log(this); // 打印obj,普通函数可以被call修改this指向
      
        return function() {
          console.log(this); // 打印window,因为fn return出去的普通函数,调用者并不是obj,实际上是相当于普通函数(非箭头函数)的调用方式,所以此时this指向window
        }
      }
      
      fn.call(obj)()
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      在这里插入图片描述

    回调函数的this指向

    计时器setTimeout\setInterval的回调函数,包括各种方法的回调函数,实际上都比较特殊,关于这类回调函数的this指向,首先我们要关注两个点,第一是回调函数的类型是属于普通函数还是箭头函数;第二是回调函数的调用者是谁,是window还是对象本身。

    const obj = {
      name: '张三',
      fn1() {
        console.log(this); // 打印obj,普通函数(非箭头函数)作为对象方法被调用时,this的指向是对象本身
      },
      fn2() {
        setTimeout(function() {
          console.log(this); // 打印window,因为fn2中的setTimeout的回调函数,调用者并不是obj
        })
      },
      fn3() {
        setTimeout(() => {
          console.log(this); // 打印obj,因为fn23中的setTimeout的回调函数是一个箭头函数,箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)
        })
      }
    }
    
    obj.fn1();
    obj.fn2();
    obj.fn3();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    在这里插入图片描述

    call\apply\bind

    call,apply,bind可以改变普通函数的this指向(不能改变箭头函数的this指向)

    call\apply\bind区别

    后续补充

    手写call\apply\bind

    后续补充
    https://blog.csdn.net/qinge_Crazy/article/details/120087645

    函数柯里化

    手动实现call/apply/bind函数的底层原理就是函数柯里化的应用,关于函数柯里化,可以参考我的文章函数柯里化详解

    箭头函数详解

    什么是箭头函数

    箭头函数是ES6的新特性,箭头函数本质上也是一个匿名函数。箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。
    创造箭头函数的初衷就是为了简化函数的定义,以及规避this指向带来的问题。

    1. 写法:
    x => x * x
    
    • 1

    相当于:

    function (x) {
        return x * x;
    }
    
    • 1
    • 2
    • 3
    1. 如果使用箭头函数,以前的那种hack写法就不再需要了。
    var that = this; // 不再需要这样的写法
    
    • 1

    箭头函数的原理

    实现原理还没找到,后续补充

    箭头函数的特点(和普通函数的区别)

    1. this指向:

      • 普通函数this指向:
        指向它的调用者,如果没有调用者则默认指向window
      • 箭头函数指向:
        箭头函数本身并无this,箭头函数的this由定义箭头函数时所处的作用域决定,即箭头函数的this永远指向定义箭头函数时所在的作用域的this(也可以说是上层作用域,强调的是作用域!)。箭头函数的this只和定义时的作用域this有关,和调用者无关,和调用环境无关,也永远不会改变(因此不能使用call\apply\bind改变箭头函数的this指向)。
        MDN的解释:箭头函数不会创建自己的this,所以它没有自己的this,它只会从自己的作用域链的上一层继承this。
    2. 箭头函数不能当构造函数,用的话会抛出一个错误(因此也不能使用new关键字)

      var Fun = (name, age) => { this.name = name; this.age = age; };
      var foo = new Foo('张三', 20); // TypeError: Foo is not a constructor
      
      • 1
      • 2

      我们先了解一下构造函数的new都做了些什么?简单来说,分为四步:

      ① JS内部首先会先生成一个对象;

      ② 再把函数中的this指向该对象;

      ③ 然后执行构造函数中的语句;

      ④ 最终返回该对象实例。

      但是!!因为箭头函数没有自己的this,它的this其实是继承了外层执行环境中的this,且this指向永远不会随在哪里调用、被谁调用而改变,所以箭头函数不能作为构造函数使用,或者说构造函数不能定义成箭头函数,否则用new调用时会报错!

    3. 箭头函数没有 prototype 属性

      var Foo = () => {};
      console.log(Foo.prototype); // undefined
      
      • 1
      • 2
    4. 箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是外层局部(函数)执行环境中的值。

      // 例子一
      let fun = (val) => {
          console.log(val);   // 111
          // 下面一行会报错
          // Uncaught ReferenceError: arguments is not defined
          // 因为外层全局环境没有arguments对象
          console.log(arguments); 
      };
      fun(111);
      
      // 例子二
      function outer(val1, val2) {
          let argOut = arguments;
          console.log(argOut);    // ①
          let fun = () => {
              let argIn = arguments;
              console.log(argIn);     // ②
              console.log(argOut === argIn);  // ③
          };
          fun();
      }
      outer(111, 222);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22

      依据上面的输出结果,
      很明显,普通函数outer内部的箭头函数fun中的arguments对象,其实是沿作用域链向上访问的外层outer函数的arguments对象。

      但是,我们可以在箭头函数中使用rest参数(也叫剩余参数)代替arguments对象,即通过(形式为: …rest)来访问箭头函数的参数列表!!

      function foo(...arg) { 
        return arg; 
      }
        foo(1, 2, 3, 4); // 1
      
      • 1
      • 2
      • 3
      • 4
      function foo(...numbers) { 
        numbers.forEach((number)=>{
      	console.log(number);
        })
      } 
      foo(1, 2, 3, 4);  // 1 2 3 4
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    5. 无法使用yield命令,yield 关键字通常不能在箭头函数中使用(除非是嵌套在允许使用的函数内),所以箭头函数无法用作Generator函数

    6. 因为没有自己的this,所以没法通过bind、call、apply来改变this指向

    7. 但是这不代表箭头函数的this指向是静态的,我们可以通过改变它外层代码库的this指向来控制

    8. 箭头函数的this从外层代码库继承,所以箭头函数的this是在定义的时候就绑定好了的,而普通函数是在调用的时候确定this指向

    9. 字面量对象中直接定义的箭头函数的this不绑定该对象,而是往外找一层,最简单的情况是绑定到window(因为对象字面量形成不了作用域)

    10. 只有一个参数的时候,参数可以不加小括号,没有参数或2个及以上参数的,必须加上小括号

    11. 返回语句只有一条的时候可以不写{}和return,会自动加上return的,返回多条语句时必须加上{}和return

    12. 箭头函数在返回对象的时候必须在对象外面加上小括号
      记住用params => {object:literal}这种简单的语法返回对象字面量是行不通的。

      var func = () => { foo: 1 };
      // Calling func() returns undefined!
      
      var func = () => { foo: function() {} };
      // SyntaxError: function statement requires a name
      
      • 1
      • 2
      • 3
      • 4
      • 5

      这是因为花括号({} )里面的代码被解析为一系列语句(即 foo 被认为是一个标签,而非对象字面量的组成部分)。

      所以,记得用圆括号把对象字面量包起来:

      var func = () => ({foo: 1});
      
      • 1
    13. 箭头函数位于构造函数内部,它的定义生效的时候,是在构造函数执行的时候。这时,箭头函数所在的运行环境,肯定是实例对象,所以this会总是指向实例对象。

    14. 箭头函数是匿名函数,普通函数可以是匿名函数也可以是具名函数

    总结

    1. 普通函数的this不看作用域,而是看调用者,即谁调用,this就指向谁(call\apply\bind改造之后除外)
    2. 箭头函数本身无this,其函数内部的this不看函数调用者,而是看定义箭头函数时所处的作用域,即箭头函数的this永远指向定义箭头函数时所处的作用域的this
    3. 对象字面量形成不了作用域,但是构造函数可以形成作用域
  • 相关阅读:
    【转录组】 GSEA基因富集分析.java
    数据集MNIST手写体识别 pyqt5+Pytorch/TensorFlow
    国家金融监督管理总局公布《固定资产贷款管理办法》
    Java面试题,面试题,CAS,CAS原理
    odoo 给列表视图添加按钮实现数据文件导入
    【K8S系列】第七讲:有状态服务 VS 无状态服务
    Redis-使用java代码操作Redis->java连接上redis,java操作redis的常见类型数据存储,redis中的项目应用
    obb包围盒数组的含义
    学生邮箱白嫖/免费安装JetBrains全家桶(IDEA/pycharm等) —— 保姆级教程
    asp.net core mvc之路由
  • 原文地址:https://blog.csdn.net/Boale_H/article/details/126299538