js 中的 this 指向

151 阅读4分钟

this指向问题

es5 中this指向

参考资料: 嗨,你真的懂this吗?

  1. 默认绑定

a. 浏览器中

  • 非严格模式下 顶层代码、函数内部 this指向 window 或 self
  • 严格模式下 this指向undefined

b. nodejs 中

  • 顶层代码this指向CJS模块,
  • 函数内部this 非严格模式下指向global,严格模式下指向 undefined

c. 作为一个DOM事件处理函数 .addEventListener 方式 this指向 MouseEvent

d. class中的this

  • 在普通方法和构造函数中中this指向实例

  • 在静态方法中指向类

  • 继承后

    • 在父类普通方法和构造函数中指向子类实例
    • 父类静态方法中指向子类
    • 因私有属性不被继承,在父类方法中访问私有属性,拿到的是父类的私有属性

Tips: 普通函数做为参数传递的情况, 比如setTimeout, setInterval, 非严格模式下的this指向全局对象

var name = 'lubai';
var person = {
    name: 'hahahahahah',
    sayHi: sayHi
}
function sayHi(){
    console.log(this); // { name: hahahahhah, sayHi: Fn }
    setTimeout(function(){
        console.log('Hello,', this.name); // Hello, lubai
    })
}
person.sayHi();
  1. 隐式绑定

a. 即通常说说的方法调用,谁调用指向谁

b. 这里有个坑, 解构赋值会丢失隐式绑定

  • 有些隐式绑定比较坑 如 setTimeout(obj.fn, 0)

c. getter 与 setter  及 原型链中的this指向当前对象

Tips: 那如果有链式调用的情况呢? this会绑定到哪个对象上?

function sayHi(){
    console.log('Hello,', this.name);
}
var person2 = {
    name: 'lubai',
    sayHi: sayHi
}
var person1 = {
    name: 'hahhahaahh',
    friend: person2
}

person1.friend.sayHi(); // Hello, lubai
  1. 显示绑定

a. call apply bind 可以改变this指向

  • bind只有第一次绑定有效
// bind第一次绑定有效
var obj = {a:1}
function fn(a, ...args) {
  console.log(obj, ...args)
  this.b = 2
}
var nFn = fn.bind({}, 'a', 'b');
var bFn = nFn.bind(obj, 'c', 'd');
bFn() // {a:1} a b c d 
  1. new绑定 指向新创建的对象
  1. 优先级 new绑定 > 显示绑定 call/apply/bind > 隐式绑定 > 默认绑定
  2. 箭头函数中的this值是定义它所在环境中的this
var name = 'outer'
var obj = {
    name: 'inner',
    say() {
        console.log(this.name);
        return () => {
            console.log(this.name);
        }
    }
}

obj.say()(); // inner inner
var say = obj.say;
say()(); // outer outer

var bSay = say.bind({name: 'bind'});
bSay()(); // bind bind
  1. generator中的this
  • g()返回的是遍历器对象,不是this对象
  • 想用this的话g.call(g.prototype)

顶层对象

  • 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window
  • 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self
  • Node 里面,顶层对象是global,但其他环境都不支持。
  • 同一段代码为了能够在各种环境,都能取到顶层对象
  • 全局环境中,this会返回顶层对象。但是,Node.js 模块中this返回的是当前模块,ES6 模块中this返回的是undefined。
    • 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
    • 不管是严格模式,还是普通模式,new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。
// 综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。


// 方法一
(typeof window !== 'undefined'
   ? window
   : (typeof process === 'object' &&
      typeof require === 'function' &&
      typeof global === 'object')
     ? global
     : this);

// 方法二
var getGlobal = function () {
  if (typeof self !== 'undefined') { return self; }
  if (typeof window !== 'undefined') { return window; }
  if (typeof global !== 'undefined') { return global; }
  throw new Error('unable to locate global object');
};
  • globalThis

ES2020 在语言标准的层面,引入globalThis作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this。

垫片库global-this模拟了这个提案,可以在所有环境拿到globalThis。

super

  • 作为函数时,super()只能用在子类的构造函数之中
  • super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
  • 在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。
  • super.x= 3 修改的是子类实例x
  • 注意,使用super的时候,必须显式指定是作为函数、还是作为对象使用

练习

var name = '123';

var obj = {
	name: '456',
	print: function() {
		function a() {
			console.log(this.name);
		}
		a();
	}
}
obj.print(); // 123
function Foo(){
    Foo.a = function(){
        console.log(1);
    }
    this.a = function(){
        console.log(2)
    }
}

Foo.prototype.a = function(){
    console.log(3);
}

Foo.a = function(){
    console.log(4);
}

Foo.a(); // 4
let obj = new Foo();
obj.a(); // 2
Foo.a(); // 1
var length = 10;
function fn() {
    console.log(this.length);
}
 
var obj = {
  length: 5,
  method: function(fn) {
    fn(); // 10
    arguments[0](); // 2,  arguments: { 0: fn, 1: 1, length: 2 }
  }
};
 
obj.method(fn, 1);