zoukankan      html  css  js  c++  java
  • 前端进击的巨人(六):知否知否,须知this

    前端进击的巨人(六):知否知否,须知this

    常见this的误解

    1. 指向函数自身(源于this英文意思的误解)
    2. 指向函数的词法作用域(部分情况)

    this的应用环境

    1. 全局环境

    无论是否在严格模式下,全局执行环境中(任何函数体外部)this都指向全局对象

    var name = '以乐之名';
    this.name;  // 以乐之名
    
    2. 函数(运行内)环境

    函数内部,this的值取决于函数被调用的方式(被谁调用)

    var name = '无名氏';
    function getName() {
     console.log(this.name);
    }
    getName();         // 无名氏 调用者是全局对象
    
    var myInfo = {
      name: '以乐之名',
      getName: getName
    };
    myInfo.getName();  // 以乐之名 调用者是myInfo对象
    

    this的正解

    "this的指向是在运行时进行绑定的,而不是代码书写(函数声明)时确定!!!"

    "看谁用",this的指向取决于调用者,这也是很多文章提到过的观点。"谁调用,this指向谁",只是这句话稍有偏颇,某些情况不见得都适用。

    生活栗子:你的钱并不一定是你的钱,只有当你使用消费了才是你的钱 。
    "看谁用"),借出去的钱就不是你的了。。。

    回到正文,我们先通过栈,来理解什么是调用位置?

    JavaScript中函数的调用是以栈的方式来存储,栈顶是正在运行的函数,函数调用时入栈,执行完成后出栈。

    function foo() {
      // 此时的栈:全局 -> foo,调用位置在foo
      bar();
    }
    
    function bar() {
      // 此时的栈:全局 -> foo -> bar,调用位置在bar
      baz();
    }
    
    function baz() {
      // 此时的栈:全局 -> foo -> bar -> baz,调用位置在baz
      // ...
    }
    
    foo();
    

    代码中虽然函数存在多层嵌套使用,但处于栈顶的只有正在执行的函数,也即调用者只有顶层的那一个(或最后一个),理清调用位置(调用者)有助于我们理解this

    this的绑定规则

    1. 默认绑定(函数单独调用)
    2. 隐式绑定(作为对象的属性方法调用,带有执行上下文)
    3. 显示绑定(call/apply/bind
    4. new绑定(new创建实例)
    5. 箭头函数绑定(ES6新增,基于词法作用域)

    默认绑定下(函数单独调用)区分严格模式

    • 非严格模式,this会指向全局对象(浏览器全局对象是window,NodeJS全局对象是global);
    • 严格模式,this指向undefined
    // 非严格模式
    function getName() {
      console.log(this.name);  // this指向全局对象
    }
    getName();  // "",并不会报错,如果外部有全局变量name,则会输出对应值
    
    // 严格模式
    function getName() {
      "use strict"
     console.log(this.name);   // this指向undefined
    }
    getName();  // TypeError: Cannot read property 'name' of undefined
    

    TIPS: 严格模式中,对函数中this的影响,只在函数内声明了严格模式才会存在,如果是调用时声明严格模式则不会影响。

    function getName() {
      console.log(this.name);
    }
    
    // 调用时声明严格模式
    "use strict";
    getName();  // ""
    

    隐式绑定

    隐式绑定中,函数一般作为对象的属性调用,带有调用者的执行上下文。因此this值取决于调用者的上下文环境。如果存在多层级属性引用,只有对象属性引用链中最顶层(最后一层)会影响调用位置,而this的值取决于调用位置。文章开头以栈来理解调用者的例子。

    function getName() {
      return this.name;
    }
    
    var myInfo = {
      name: '以乐之名',
      getName: getName
    };
    
    var leader = {
      name: '大神组长'
      man: myInfo
    };
    leader.man.getName();  // '以乐之名'
    // man 指向 myInfo,最顶层(最后一层)对象为 myInfo
    

    apply/call的区别

    apply/call方法两者类似,都可以显示绑定this,两者的区别是参数传递的方式不同。apply/call第一个参数都为要指定this的对象,不同的是apply第二个参数接受的是一个参数数组,而call从第二个参数开始接受的是参数列表。

    apply语法:func.apply(thisArg, [argsArray])

    call语法:func.call(thisArg, arg1, arg2, ...)

    var numbers = [5, 6, 2, 3, 7];
    
    // 求numbers的最大值
    
    // apply
    var max = Math.max.apply(null, numbers);
    
    // call
    var max = Math.max.call(null, ...numbers); // ...展开运算符
    

    TIPS: 如果thisArg为原始值(数字,字符串,布尔值),this会指向该原始值的自动包装对象,如Number, String, Boolean

    func.apply(1);
    // func中的this -> Number对象;
    

    bind的特别(柯里化的应用)

    bind是ES5新增的方法,跟apply/call功能一样,可以显示绑定this。

    bind语法:function.bind(thisArg[, arg1[, arg2[, ...]]])

    bind()方法创建一个新的函数,在调用时设置this关键字为提供的值,并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。

    -- 《Function.prototype.bind() | MDN》

    "bind与apply/call的区别:apply/call传入this并立即执行函数,而bind传入this则返回一个函数,并不会立即执行,只有调用返回的函数才会执行原始函数"

    bind方法是函数柯里化的一种应用,看过上篇《前端进击的巨人(五):学会函数柯里化(curry) 》的小伙伴,应该还记得"函数柯里化的特点:延迟执行,部分传参,返回一个可处理剩余参数的函数"

    bind相较apply/call的优点,可以通过部分传参提前对this进行一次"永久绑定",也就是说this只需绑定一次,省却每次执行都要进行this绑定的操作。

    function getName() {
      return this.name;
    }
    
    var myInfo = {
      name: '以乐之名',
      job: '前端工程师'
    };
    
    var getName = getName.bind(myInfo);
    getName();  // '以乐之名';
    getName(); //  '以乐之名';
    
    // 一次性绑定,之后调用无需再修改this
    

    TIPS: 函数柯里化可以用于参数预设,像一次性操作(判断/绑定)等。

    有关函数柯里化的详解,请回阅:《前端进击的巨人(五):学会函数柯里化(curry) 》

    构造函数中的this

    通过new操作符可以实现对函数的构造调用。JavaScript中本身并没有"构造函数",一个函数如果没有使用new操作符调用,那么它就是个普通函数,new Func()实际上是对函数Func的"构造调用"。

    在了解构造函数中的this前,有必要先了解下new实例化对象的过程。

    new实例过程

    1. 创建(构造)一个全新的空对象
    2. 这个新对象会被执行"原型"链接(新对象的__proto__会指向函数的prototype)
    3. 构造函数的this会指向这个新对象,并对this属性进行赋值
    4. 如果函数没有返回其他对象,则返回这个新对象(注意构造函数的return,一般不会有return)
    // 正常不带return的构造函数
    function People(name, sex) {
      this.name = name;
      this.sex = sex;
    }
    
    var man = new People('亚当', '男');
    var woman = new People('夏娃', '女');
    // 实例化对象成功
    
    // 构造函数带了return
    function People(name, sex) {
      return 1;  // 返回的是Number对象
    }
    function People(name, sex) {
      return 'hello world';  // 返回的是String对象
    }
    function People(name, sex) {
      return function() {}
    }
    function People(name, sex) {
      return {};
    }
    // 以上并未正确实例化对象
    

    构造函数自定义return,会造成new无法完成正确的实例化操作。如果返回值为基本类型,则返回其包装对象Number/String/Bollean

    TIPS: 原型链中的this指向其实例化的对象

    People.prototype.say = function() {
      console.log(`我的名字:${this.name}`);
    };
    
    var man = new People('亚当', '男');
    man.say();  // 我的名字:亚当
    

    this绑定规则的优先级

    显示绑定 / new绑定 > 隐式绑定 > 默认绑定

    TIPS: new无法跟apply/call同时使用

    this判定步骤

    1. 函数被new操作符使用(new绑定)? YES --> this绑定的是new创建的新对象
    2. 函数通过call/apply/bind(显示绑定)? YES --> this绑定的是指定的对象
    3. 函数在某个上下文对象中调用(隐式绑定)? YES --> this绑定的是那个上下文对象
    4. 默认绑定,严格模式指向undefined,否则指向全局对象

    ES6的箭头函数(词法作用域的this机制,规则之外)

    箭头函数的this机制不同于传统的this机制,它采取的是另外一种机制,词法作用域的this判定规则。

    // 例子一
    var name = '无名氏';
    var myInfo = {
      name: '以乐之名',
      getName: () => {
        console.log(this.name);
      }
    };
    var getName = myInfo.getName;
    window.getName();     // 无名氏
    myInfo.getName();     // 无名氏
    // myInfo是在全局环境定义的,因此根据词法作用域,this指向全局对象
    
    // 例子二
    var name = '无名氏';
    var myInfo = {
      name: '以乐之名',
      say: () => {
        setTimeout(() => {
          console.log(this.name);
        })
      }
    };
    myInfo.say();  // 无名氏
    // 箭头函数通过作用域链来逐层查找this,最终找到全局变量myInfo,this指向全局对象
    
    // 例子三
    var name = '无名氏';
    var myInfo = {
      name: '以乐之名',
      say: function() {
        setTimeout(() => {
          console.log(this.name);
        })
      }
    };
    myInfo.say(); // 以乐之名
    // 箭头函数找到say: function(){},因此this的作用域来自myInfo
    

    TIPS: setTimeout/setInterval/alert的调用者都是全局对象

    "箭头函数的this始终指向函数定义时的this,而非执行(调用)时的this。箭头函数中的this必须通过作用域链一层一层向外查找,来确定this指向。"

    扩展:箭头函数的书写规则

    1. 箭头函数只能用函数表达式,不能用函数声明式写法(不包括匿名函数)
    // 函数表达式
    const getName = (name) => { return 'myName: ' + name };
    
    // 匿名函数
    setTimeout((name) => {
      console.log(name);
    }, 1000)
    
    2. 如果参数只有一个,可不加括号();如果没有参数或多个参数需加括号()
    // 只有一个参数
    const getName = name => {
      return `myName: ${name}`;
    }
    
    // 无参数
    const getName = () => {
      return 'myName: "以乐之名"';
    }
    
    // 多参数
    const getName = (firstName, lastName) => {
      return `myName: ${firstName} ${lastName}`;
    }
    
    3. 函数体只有一个可不加花括号{}
    const getName = name => return `myName: ${name}`;
    
    4. 函数体没有花括号{},可不写return,会自动返回
    const getName = name => `myName: ${name}`;
    

    参考文档:

    本文首发Github,期待Star!
    https://github.com/ZengLingYong/blog

    作者:以乐之名
    本文原创,有不当的地方欢迎指出。转载请指明出处。

  • 相关阅读:
    dotnet 新项目格式与对应框架预定义的宏
    dotnet 线程静态字段
    dotnet 线程静态字段
    dotnet 通过 WMI 拿到显卡信息
    dotnet 通过 WMI 拿到显卡信息
    dotnet 通过 WMI 获取指定进程的输入命令行
    dotnet 通过 WMI 获取指定进程的输入命令行
    dotnet 通过 WMI 获取系统信息
    dotnet 通过 WMI 获取系统信息
    PHP show_source() 函数
  • 原文地址:https://www.cnblogs.com/kenz520/p/10335231.html
Copyright © 2011-2022 走看看