zoukankan      html  css  js  c++  java
  • 聊一聊Javascript的this原理

    场景1:全局环境下的this

    这种情况相对简单直接,函数在浏览器全局环境中被简单调用,非严格模式下this指向window;在 use strict指明严格模式下就是undefined;

      function f1 (  ) {
        console.log (this);
      }
      function f2 (  ) {
        'use strict'
        console.log (this);
      }
      f1(); // window
      f2() // undefined

    这样的题目比较基础,请再看:

      const foo = {
        bar: 10,
        func: function (  ) {
          console.log (this); // window
          console.log (this.bar); // undefined
        }
      };
      let fn1 = foo.func;
      fn1();

    这里的this仍然指向的window。虽然func函数在foo对象中作为方法被引用,但是再赋值给fn1之后,调用fn1的是window,执行仍然是在window的全局环境中。因此输出仍然是window和undefined。

    如果调用改为:

      const foo = {
        bar: 10,
        func: function (  ) {
          console.log (this); // {bar: 10, func: ƒ}
          console.log (this.bar); // 10
        }
      };
      foo.func(); 

     因为这个时候this指向的是最后调用它的对象,此时的foo.func()语句中this指向foo对象。

    在执行函数时,如果执行函数中的this是被上一级的对象所调用(「foo.func()」),那么this指向的就是上一级的对象(这里是「foo」),否则指向全局对象「window」

     场景2:上下文对象调用中的this

      const person = {
        name: 'cuiHua',
        brother: {
          name: 'Mike',
          func: function (  ) {
            return this.name
          }
        }
      };
      console.log (person.brother.func()); // Mike
    
    

     在这种嵌套的关系中,「this」指向最后调用它的对象,因此输出的是 Mike

     我们在看一道更复杂的题目,请跟我一起做好“应试”的准备:

    const personA = {
        name: 'cuiHuaA',
        func: function (  ) {
          return this.name
        }
      };
      const personB = {
        name: 'cuiHuaB',
        func: function (  ) {
          return personA.func()
        }
      };
      const personC = {
        name: 'cuiHuaC',
        func: function (  ) {
          let fn = personA.func;
          return fn()
        }
      };
    console.log (personA.func());
    console.log (personB.func());
    console.log (personC.func());

     答案:cuiHuaA,cuiHuaA,undefined,你答对了吗?

    • 第一个 console 最简单,this指向 「 person1  」,所以输出 cuiHuaA
    • 第二个 console 的 personB.func(),最终调用的还是 personA.func(),所以this仍然指向「 person1  」,所以输出 cuiHuaA
    • 第三个 let fn = personA.func 赋值之后,是 「裸奔」调用( fn() )相当于 window.fn(),因此这里的 this 指向 「 window 」,输出 undefined

     如果我们需要输出 cuiHuaB ,该怎么做?

     可能你会想到使用bind/call/apply来对 this 指向进行干预,如果不能使用bind/call/apply,有别的办法吗?

      const personA = {
        name: 'cuiHuaA',
        func: function (  ) {
          return this.name
        }
      };
      const personB = {
        name: 'cuiHuaB',
        func: personA.func
      };
    console.log (personA.func());
    console.log (personB.func());

     还是应用那个重要的结论, this 指向最后调用它的对象, 在 func 执行时, 挂到 personB 对象上即可,我们提前进行了类赋值的操作。

     场景3:bind/call/apply改变this指向

     const foo = {
        name: 'me',
        func: function (  ) {
          console.log (this.name);
        }
      };
      const bar = {
        name: 'you',
      };
      console.log (foo.func.call(bar));

    将输出 you,这不难理解,但是对bind/call/apply的高级考察往往会结合构造函数以及组合式实现继承,实现继承的话题,我们会单独讲到。

    场景4:构造函数和this

     function Foo (  ) {
        this.bar = 'cuiHua'
      }
      const foo = new Foo();
      console.log (foo.bar);

    这样的场景往往伴随着下一个问题 new 操作符调用构造函数,具体做了什么?

    1. 创建一个新对象 
    2. 将构造函数的this指向这个新的对象
    3. 为这个对象添加属性、方法等
    4. 最终返回新对象

     以上过程,也可以用代码表述

    let obj = {};
    obj.__proto__ = Foo.prototype;
    Foo.call(obj);

    当然这里对new的模拟是一个简单的基本版的。

    需要指出的是,如果在构造函数中出现了显式 return 的情况,那么注意分为两种情况

     function  Foo(  ) {
        this.user = 'cuiHua'
        const obj = {};
        return obj
      }
      const foo = new Foo();
    console.log(foo); // {} console.log (foo.user);

    将会输出 undefined,此时 foo 返回的是空对象 obj

    function  Foo(  ) {
    this.user = 'cuiHua'
    return 1
    }
    const foo = new Foo();
    console.log (foo); // Foo {user: "cuiHua"}
    console.log (foo.user);

    将会输出 cuiHua,也就是说 foo 是返回的目标实例对象 this。

    结论:如果构造函数中显式返回一个值,且返回的是一个对象,那么this就指向这个返回的对象;如果返回的不是一个对象,那么this仍然指向实例。

     场景5:箭头函数中的this指向

     箭头函数使用this不适用以上标准规则,而是根据外层(嵌套或者全局)上下文作用域来决定。 

      const foo = {
        func: function ( ) {
          setTimeout(function ( ) {
            console.log (this);
          })
        }
      };
      console.log (foo.func());

     这段代码,this 出现在 setTimeout()中的匿名函数里,因此 this 指向 window对象。 如果需要 this 指向 foo 这个 object对象,可以巧用箭头函数解决:

     const foo = {
        func: function ( ) {
          setTimeout( ( )=>{
            console.log (this);
          })
        }
      };
      console.log (foo.func()); // {func: f}

     单纯箭头函数中的 this 非常简单, 但是综合所以情况, 结合 this 的优先级考察。这个时候 this 指向并不好确定, 请仔细阅读。

     场景6: this优先级相关

     我们常常把通过 call、appliy、bind、new 对 this 绑定的情况称为显式绑定; 根据调用关系确定的 this 指向称为隐式绑定。

     function foo (a) {
        console.log (this.a);
      }
      const obj1 = {
        a: 1,
        foo: foo
      };
      const obj2 = {
        a: 2,
        foo: foo
      }
      obj1.foo.call(obj2);
      obj2.foo.call(obj1);

    输出分别为2,1,也就是说call、apply的显式绑定一般来说优先级比隐式绑定更高。

      function foo (a) {
        this.a = a;
      }
      const obj1 = {};
      let bar = foo.bind(obj1);
      bar(2);
      console.log (obj1.a);

    上述代码通过 bind,将bar函数中的 this 指向 obj1 对象。现在的this是obj1, 执行 bar(2) 后, 「 this.a = a 」即 obj1.a = 2;即经过 bar(2) 执行后,obj1 对象为:{a: 2}

    当在使用 bar 作为构造函数时:

    let baz = new bar(3);
    console.log (baz.a);

    将会输出3. bar 函数本身是通过 bind 方法构造的函数, 其内部已经将 this 绑定为 obj1, 它再作为构造函数, 通过 new 调用时,返回的实例已经与 obj1 解绑。

    new 绑定修改了 bind 中的this, 因此 new 的优先级比显示 bind 绑定更高。

      function foo () {
        return a => {
          console.log (this.a);
        }
      }
      const obj1 = {
        a: 2
      };
      const obj2 = {
        a: 3
      };
      const bar = foo.call(obj1);
      console.log (bar.call(obj2));

    结果会输出2, 由于 foo() 的 this 绑定到 obj1, bar (引用箭头函数) 的 this 也会绑定 obj1, 箭头函数的绑定无法被修改。

    如果将 foo 完全写成箭头函数的形式:

     var a = 123;
     const foo = () => a => {
       console.log (this.a);
     };
     const  obj1 = {
       a: 2
     };
    const  obj2 = {
      a: 3
    };
    let bar = foo.call(obj1);
    console.log (bar.call(obj2));

    将会输出123;

    这里我用了 var 声明 a = 123;我们看一下仅仅将上述代码变量 a 的赋值改为:

     const a = 123;
     const foo = () => a => {
       console.log (this.a);
     };
     const  obj1 = {
       a: 2
     };
    const  obj2 = {
      a: 3
    };
    let bar = foo.call(obj1);
    console.log (bar.call(obj2));

     答案将会输出 undefined,原因是因为使用 const 声明的变量不会挂载到 window 全局对象当中。 因此 this 指向 window 时, 自然也就找不到 a 变量。

    在 PDFlux 中打开
    无数据
  • 相关阅读:
    iis 部署网站常见问题
    提高你开发效率的十五个Visual Studio 2010使用技巧
    SQL SERVER 错误代码 0x534
    SQL Server2005修改计算机名后不能发布订阅
    Fedora桌面系统Idea中的部分中文无法显示解决
    最近去滴滴面试的一道题:斐波拉契数列非递归实现
    二叉查找树之红黑树
    二叉查找树之AVL树
    二叉查找树
    再看SpringMVC通过一个DispatcherServlet处理Servlet
  • 原文地址:https://www.cnblogs.com/cuixiaohua/p/12716955.html
Copyright © 2011-2022 走看看