聊一聊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 中打开
无数据

猜你喜欢

转载自www.cnblogs.com/cuixiaohua/p/12716955.html