• 《你不知道的JavaScript》整理(二)——this


    最近在读一本进阶的JavaScript的书《你不知道的JavaScript(上卷)》,这次研究了一下“this”。

    当一个函数被调用时,会创建一个活动记录(执行上下文)。

    这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。

    this就是记录的其中一个属性,会在函数执行的过程中用到。

    this既不指向函数自身也不指向函数的作用域

    this实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用

    一、调用位置

    调用位置就在当前正在执行的函数的前一个调用中,源码查看

    复制代码
    function baz() {
      // 当前调用栈是:baz
      // 因此,当前调用位置是全局作用域
      console.log("baz");
      bar(); // <-- bar 的调用位置
    }
    
    function bar() {
      // 当前调用栈是 baz -> bar
      // 因此,当前调用位置在 baz 中
      console.log("bar");
      foo(); // <-- foo 的调用位置
    }
    
    function foo() {
      // 当前调用栈是 baz -> bar -> foo
      // 因此,当前调用位置在 bar 中
      console.log("foo");
    }
    baz(); // <-- baz 的调用位置
    复制代码

    二、绑定规则

    你必须找到调用位置,然后判断需要应用下面四条规则中的哪一条。

    1)默认绑定

    最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则

    function foo() {
      console.log(this.a);
    }
    var a = 2;
    foo(); // 2

    2)隐式绑定

    隐式绑定的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含

    复制代码
    function foo() {
      console.log(this.a);
    }
    var obj = {
        a: 2,
        foo: foo
    };
    obj.foo(); // 2
    复制代码

    但有时候会出现隐式丢失

    复制代码
    function foo() {
      console.log(this.a);
    }
    var obj = {
        a: 2,
        foo: foo
    };
    var bar = obj.foo; // 函数  
    var a = "oops, global"; //
    bar(); // "oops, global"
    复制代码

    虽然bar是obj.foo的一个引用,但是实际上,它引用的是foo函数本身

    因此此时的bar()其实是一个不带任何修饰的函数调用,应用了默认绑定

    3)显式绑定

    使用函数的call(..)和apply(..)方法。

    复制代码
    function foo() {
      console.log(this.a);
    }
    var obj = {
        a: 2
    };
    foo.call(obj); // 2
    复制代码

    在很多库中经常能看到bind方法,这是一种硬绑定,一种显式的强制绑定,下面是一种bind实现。

    复制代码
    function foo(something) {
      console.log(this.a, something);
      return this.a + something;
    }
    // 简单的辅助绑定函数 
    function bind(fn, obj) {
      return function() {
        return fn.apply(obj, arguments);
      };
    }
    var obj = {
      a: 2
    };
    var bar = bind(foo, obj);
    var b = bar(3); // 2 3
    console.log(b); // 5
    复制代码

    4)new绑定

    使用new来调用函数,或者说发生构造函数调用时,会自动执行下面的操作:

    1. 创建(或者说构造)一个全新的对象。

    2. 这个新对象会被执行[[原型]]连接。

    3. 这个新对象会绑定到函数调用的this

    4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。

    function foo(a) {
      this.a = a;
    }
    var bar = new foo(2);
    console.log(bar.a); // 2

    三、优先级

    默认绑定的优先级是四条规则中最低的。

    1)显式绑定优先级比隐式绑定要更高

    复制代码
    function foo() {
      console.log(this.a);
    }
    var obj1 = {
        a: 2,
        foo: foo
    };
    var obj2 = {
        a: 3,
        foo: foo
    };
    
    obj1.foo(); // 2 
    obj2.foo(); // 3
    
    obj1.foo.call(obj2); // 3 
    obj2.foo.call(obj1); // 2
    复制代码

    2)new绑定比隐式绑定优先级高

    复制代码
    function foo(something) {
      this.a = something;
    }
    var obj1 = {
        foo: foo
    };
    
    obj1.foo(2);
    console.log(obj1.a); // 2
    
    var bar = new obj1.foo(4);
    console.log(obj1.a); // 2 
    console.log(bar.a); // 4
    复制代码

    3)new绑定会修改显示绑定中this

    复制代码
    function bind(fn, obj) {
      return function() {
        return fn.apply(obj, arguments);
      };
    }
    function foo(something) { 
      this.a = something;
    }
     
    var obj1 = {};
    var bar = foo.bind(obj1); //bar被硬绑定到obj1上
    bar(2);
    console.log( obj1.a ); // 2
     
    var baz = new bar(3); //
    console.log( obj1.a ); // 没有把obj1.a 修改为3,还是为2 
    console.log( baz.a ); // 3
    复制代码

    4)判断this

    1. 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。

    var bar = new foo()

    2. 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。

    var bar = foo.call(obj2)

    3. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。

    var bar = obj1.foo()

    4. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。

    var bar = foo()

    四、绑定例外

    1)被忽略的this

    如果你把null或者undefined作为this的绑定对象传入call、apply或者bind。

    这些值在调用时会被忽略,实际应用的是默认绑定规则。

    function foo() {
      console.log(this.a);
    }
    var a = 2;
    foo.call(null); // 2

    2)间接引用

    你有可能(有意或者无意地)创建一个函数的“间接引用”。

    在这种情况下,调用这个函数会应用默认绑定规则。

    复制代码
    function foo() {
      console.log(this.a);
    }
    var a = 2; 
    var o = { a: 3, foo: foo }; 
    var p = { a: 4 }; 
    
    o.foo(); // 3
    (p.foo = o.foo)(); // 2
    复制代码

    赋值表达式p.foo = o.foo的返回值是目标函数的引用,因此调用位置是foo()而不是p.foo()或者o.foo()。

    3)软绑定

    如果可以给默认绑定指定一个全局对象和undefined以外的值。

    那就可以实现和硬绑定相同的效果,同时保留隐式绑定或者显式绑定修改this的能力。

    可以通过一种被称为软绑定的方法来实现我们想要的效果。

    复制代码
    Function.prototype.softBind = function(obj) {
      var fn = this;
      // 捕获所有 curried 参数
      var curried = [].slice.call(arguments, 1);
      var bound = function() {
        return fn.apply((!this || this === (window || global)) ? obj : this, curried.concat.apply(curried, arguments));
      };
      bound.prototype = Object.create(fn.prototype);
      return bound;
    };
    复制代码
    复制代码
    function foo() {
      console.log("name: " + this.name);
    }
    var obj = { name: "obj" }, 
        obj2 = { name: "obj2" }, 
        obj3 = { name: "obj3" };
     
    var fooOBJ = foo.softBind( obj ); 
    
    fooOBJ(); // name: obj <---- 应用了软绑定
    
    obj2.foo = foo.softBind(obj); 
    obj2.foo(); // name: obj2 
    
    fooOBJ.call( obj3 ); // name: obj3
    
    setTimeout( obj2.foo, 10 );// name: obj   <---- 应用了软绑定
    复制代码

    软绑定版本的foo()可以手动将this绑定到obj2或者obj3上。

    但如果应用默认绑定,则会将this绑定到obj。

  • 相关阅读:
    php将字符串形式的数组转化为真数组
    Mysql8.0及以上 only_full_group_by以及其他关于sql_mode原因报错详细解决方案
    php使用base64_encode和base64_decode对数据进行编码和解码
    大数据基本概念
    sonarqube安装部署
    git-修改commit信息
    NLP-Commen Sense
    索引生命周期的管理
    kibana软件具体参数配置信息
    es机器监控x-pack导致的监控存储过大的问题
  • 原文地址:https://www.cnblogs.com/zhangxiaolei521/p/5848214.html
Copyright © 2020-2023  润新知