zoukankan      html  css  js  c++  java
  • Javascript: 你真的了解this的含义吗

    原文链接:传送门

    Javascript 的 this是许多笑话的笑点所在,那是因为它真的很复杂。然而,我见过许多开发者做了许多更加复杂的和特定域的事情来避免与this打交道。如果你对this还有点不太确定,我希望这篇文章可以对你有所帮助。

    这篇文章是我的关于this对一个指导。

    我将以最明确的场景开始,然后以最不明确的场景结束。这篇文章有点像一个大的if (…) … else if () … else if (…)结构,因此你可以直接导航到匹配你正在看的代码的那个部分。

    如果函数被定义为一个箭头函数

    const arrowFunction = () => {
      console.log(this);
    };

    在这个情况下,this的值总是和父作用域中this的值一样。

    const outerThis = this;
    
    const arrowFunction = () => {
      // Always logs `true`:
      console.log(this === outerThis);
    };

    箭头函数是强大的,这是因为其内部的this值不会被改变,其总是与外部的this值保持一致,让我们继续看看其他示例。

    在箭头函数中,this的值不能以bind来改变:

    // Logs `true` - bound `this` value is ignored:
    arrowFunction.bind({foo: 'bar'})();

    在箭头函数中,this的值不能以call或者appy来改变。

    // Logs `true` - called `this` value is ignored:
    arrowFunction.call({foo: 'bar'});
    // Logs `true` - applied `this` value is ignored:
    arrowFunction.apply({foo: 'bar'});

    在箭头函数中,this的值不能以作为另一个对象的成员的方式被调用而改变。

    const obj = {arrowFunction};
    // Logs `true` - parent object is ignored:
    obj.arrowFunction();

    在箭头函数中,this的值不能通过以构造函数的方式来调用而改变。

    // TypeError: arrowFunction is not a constructor
    new arrowFunction();

    “绑定”实例方法

    在实例方法中,如果你想确保this总是指向类的实例,最好的方式就是使用箭头函数和类字段。

    class Whatever {
      someMethod = () => {
        // Always the instance of Whatever:
        console.log(this);
      };
    }

    当使用实例方法作为组件中的事件监听器时(比如React组件,web组件),这种模式真的非常有用。

    上面的内容或许感觉像打破了“this是与父作用域的this保持一致”这个规则。但是如果您认为类字段是在构造函数中设置内容的语法糖,那么其便开始变得有道理了。

    class Whatever {
      someMethod = (() => {
        const outerThis = this;
        return () => {
          // Always logs `true`:
          console.log(this === outerThis);
        };
      })();
    }
    
    // …is roughly equivalent to:
    
    class Whatever {
      constructor() {
        const outerThis = this;
        this.someMethod = () => {
          // Always logs `true`:
          console.log(this === outerThis);
        };
      }
    }

    可替代的模式涉及在构造函数中绑定一个已存在的函数,或者在构造函数中分配一个函数,如果你因为一些原因不能使用类字段,在构造函数中分配函数是一个理想的替代方案。

    class Whatever {
      constructor() {
        this.someMethod = () => {
          //
        };
      }
    }

    如果函数/类用new关键字调用

    new Whatever();

    如上的代码会调用Whatever(或者如果它是一个类的话,便会调用其构造函数),并将this设置为Object.create(Whatever.prototype)的结果。

    class MyClass {
      constructor() {
        console.log(
          this.constructor === Object.create(MyClass.prototype).constructor,
        );
      }
    }
    
    // Logs `true`:
    new MyClass();

    对于老式的构造函数来说,同样也是true。

    function MyClass() {
      console.log(
        this.constructor === Object.create(MyClass.prototype).constructor,
      );
    }
    
    // Logs `true`:
    new MyClass();

    其他示例。

    当以new的方式被调用时,this的值不能以bind来改变。

    const BoundMyClass = MyClass.bind({foo: 'bar'});
    // Logs `true` - bound `this` value is ignored:
    new BoundMyClass();

    当以new的方式被调用时,当作为另一个对象的成员的方式来调用时,this的值也不能被改变。

    const obj = {MyClass};
    // Logs `true` - parent object is ignored:
    new obj.MyClass();

    如果函数有一个“绑定的”this值

    function someFunction() {
      return this;
    }
    
    const boundObject = {hello: 'world'};
    const boundFunction = someFunction.bind(boundObject);

    当调用boundFunction时,它的this值将会是传递给bind的对象(boundObject)。

    // Logs `false`:
    console.log(someFunction() === boundObject);
    // Logs `true`:
    console.log(boundFunction() === boundObject);

    ⚠️避免使用bind来绑定一个函数到它的外部this,相应的,我们应该使用箭头函数,因为从它的函数声明中,其使得this的意义比较明确,而不是晚一点在代码中发生的那些事。不要使用bind来设置this到一些与父对象不想关的值。它通常不是我们所其期望的而且这也是为什么this获得了这么坏的一个名声的原因。相应的我们应该考虑传递一个值作为参数。它更加的明确,并与箭头函数工作良好。

    其他示例

    当调用一个bound函数时,this不能以call或者apply来改变。

    // Logs `true` - called `this` value is ignored:
    console.log(boundFunction.call({foo: 'bar'}) === boundObject);
    // Logs `true` - applied `this` value is ignored:
    console.log(boundFunction.apply({foo: 'bar'}) === boundObject);

    当调用一个bound函数时,this的值不能通过以另一个对象的成员的方式调用而改变。

     const obj = {boundFunction};
    // Logs `true` - parent object is ignored:
    console.log(obj.boundFunction() === boundObject);

    如果this在调用时被设置

    function someFunction() {
      return this;
    }
    
    const someObject = {hello: 'world'};
    
    // Logs `true`:
    console.log(someFunction.call(someObject) === someObject);
    // Logs `true`:
    console.log(someFunction.apply(someObject) === someObject);

    this的值将是传递给call/apply的对象。

    ⚠️不要使用call/apply来将this的值设置为与父对象无关的值。这通常不是我们所期望的结果,而且这也是为什么this获得了如此坏的名声的原因。考虑传递一个值作为参数来替代。其更加明确并且与箭头函数工作良好。

    不幸的是,this常常被诸如DOM事件监听器那样的东西设置为其他的值,并且使用它会导致难以理解的代码。

    不要这样使用:

    element.addEventListener('click', function (event) {
      // Logs `element`, since the DOM spec sets `this` to
      // the element the handler is attached to.
      console.log(this);
    });

    我会尽量避免在如上的场景中使用this,而会用如下方式来代替:

    element.addEventListener('click', (event) => {
      // Ideally, grab it from a parent scope:
      console.log(element);
      // But if you can't do that, get it from the event object:
      console.log(event.currentTarget);
    });

    如果函数通过一个父对象被调用(parent.func()

    const obj = {
      someMethod() {
        return this;
      },
    };
    
    // Logs `true`:
    console.log(obj.someMethod() === obj);

    在这种情况下函数被作为obj对象的一个成员来调用,因此this将会是obj。这个发生在调用时,因此如果这个函数没有被其父对象所调用,或者以一个不同的父对象所调用,那个连接便会被打破。

    const {someMethod} = obj;
    // Logs `false`:
    console.log(someMethod() === obj);
    
    const anotherObj = {someMethod};
    // Logs `false`:
    console.log(anotherObj.someMethod() === obj);
    // Logs `true`:
    console.log(anotherObj.someMethod() === anotherObj);

    someMethod() === obj将会返回false,因为someMethod并没有作为obj的成员被调用。当尝试诸如此类的事情时,你或许已经遇到了这个问题:

    const $ = document.querySelector;
    // TypeError: Illegal invocation
    const el = $('.some-element');

    这打破了规则。这是因为querySelector的实现会监视它自己的this值并期望它成为排序的DOM节点,然而如上的代码破坏了这个连接。要正确的实现如上的功能:

    const $ = document.querySelector.bind(document);
    // Or:
    const $ = (...args) => document.querySelector(...args);

    一个有趣的事实:并不是所有的APIs会在内部使用this。像console.log这样的控制台方法被更改为避免使用this引用,因此log便没有必要被绑定到console。

    ⚠️不要仅通过将this设置为与父对象不相关的值来将一个函数嫁接到一个对象上。它通常不是我们所期望的并会给this带来坏的名声。考虑作为参数传递一个值来代替。它更加显式,并与箭头函数工作良好。

    如果函数或者父作用域处于一个strict模式下

    function someFunction() {
      'use strict';
      return this;
    }
    
    // Logs `true`:
    console.log(someFunction() === undefined);

    在这种情况下,this的值会是underfined。如果父作用域已经是strict模式了,那么在函数中“use strict”便不是必须的了。

    ⚠️不要依赖这个,我的意思是,有更简单的方式来获取underfined值。

    其他场景

    function someFunction() {
      return this;
    }
    
    // Logs `true`:
    console.log(someFunction() === globalThis);

    在这个场景下,this的值是与globalThis一样的。

    ⚠️避免使用this来引用全局对象。相反,请显式的使用globalThis。

    【完结】

  • 相关阅读:
    java基础-代理模式
    java基础-反射(细节)
    java基础-反射
    设计模式之单例
    23种设计模式汇总整理
    dialog--not attached to window manager
    java之设计模式
    android-sdk和api版本
    studio之mac快捷键
    控件之ReleLayout属性
  • 原文地址:https://www.cnblogs.com/qianxingmu/p/14827771.html
Copyright © 2011-2022 走看看