2023.10.17谈谈js中的this是如何工作的

43 阅读4分钟

this在js中作为函数中的关键字,在运行期间被绑定且不可被赋值,在严格模式和非严格模式下也有一些差别

this的值:

是当前执行上下文(global、function 或 eval)的一个属性,在非严格模式下,总是指向一个对象,在严格模式下可以是任意值。

描述:

1.全局上下文:

无论是否在严格模式下,在全局执行环境中(在任何函数体外部)this 都指向全局对象。

2.函数上下文:

在函数内部,this的值取决于函数被调用的方式。

不在严格模式下,且 this 的值不是由该调用设置的,所以 this 的值默认指向全局对象,浏览器中就是 window,然而,在严格模式下,如果进入执行环境时没有设置 this 的值,this 会保持为 undefined

3.类上下文:

在类的构造函数中,this 是一个常规对象。类中所有非静态的方法都会被添加到 this 的原型中:

  class Example {
    constructor() {
      const proto = Object.getPrototypeOf(this);
      console.log(Object.getOwnPropertyNames(proto));
    }
    first(){}
    second(){}
    static third(){}
  }
  new Example(); // ['constructor', 'first', 'second']
  • 静态方法不是 this 的属性,它们只是类自身的属性。
4.派生类

不像基类的构造函数,派生类的构造函数没有初始的 this 绑定。在构造函数中调用 super() 会生成一个 this 绑定,并相当于执行如下代码,Base 为基类:

this = new Base();

派生类不能在调用 super() 之前返回,除非其构造函数返回的是一个对象,或者根本没有构造函数。

class Base {}
class Good extends Base {}
class AlsoGood extends Base {
  constructor() {
    return {a: 5};
  }
}
class Bad extends Base {
  constructor() {}
}

new Good();
new AlsoGood();
new Bad(); // ReferenceError

2.常见绑定规则:

1. 函数上下文中的 this
// 对象可以作为 bind 或 apply 的第一个参数传递,并且该参数将绑定到该对象。
var obj = { a: "Custom" };

// 声明一个变量,并将该变量作为全局对象 window 的属性。
var a = "Global";

function whatsThis() {
  return this.a; // this 的值取决于函数被调用的方式
}

whatsThis(); // 'Global' 因为在这个函数中 this 没有被设定,所以它默认为 全局/ window 对象
whatsThis.call(obj); // 'Custom' 因为函数中的 this 被设置为 obj
whatsThis.apply(obj); // 'Custom' 因为函数中的 this 被设置为 obj
2.this 和对象转换
function add(c, d) {
  return this.a + this.b + c + d;
}

var o = {a: 1, b: 3};

// 第一个参数是用作“this”的对象
// 其余参数用作函数的参数
add.call(o, 5, 7); // 16

// 第一个参数是用作“this”的对象
// 第二个参数是一个数组,数组中的两个成员用作函数参数
add.apply(o, [10, 20]); // 34

在非严格模式下使用 call 和 apply 时,如果用作 this 的值不是对象,则会被尝试转换为对象。null 和 undefined 被转换为全局对象。原始值如 7 或 'foo' 会使用相应构造函数转换为对象。因此 7 会被转换为 new Number(7) 生成的对象,字符串 'foo' 会转换为 new String('foo') 生成的对象。

function bar() {
  console.log(Object.prototype.toString.call(this));
}

bar.call(7);     // [object Number]
bar.call('foo'); // [object String]
bar.call(undefined); // [object global]
3.箭头函数

在箭头函数中,this与封闭词法环境的this保持一致(箭头函数的this指向它的外部)。在全局代码中,它将被设置为全局对象:

var globalObject = this;
var foo = () => this;
console.log(foo() === globalObject); // true

备注:  如果将this传递给callbind、或者apply来调用箭头函数,它将被忽略。不过你仍然可以为调用添加参数,不过第一个参数(thisArg)应该设置为null

// 接着上面的代码
// 作为对象的一个方法调用
var obj = { foo: foo };
console.log(obj.foo() === globalObject); // true

// 尝试使用 call 来设定 this
console.log(foo.call(obj) === globalObject); // true

// 尝试使用 bind 来设定 this
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
4.对象方法中的this
  • 当函数作为对象的方法调用时,this 将绑定到该对象。例如:
const obj = {
  name: 'John',
  greet: function() {
    console.log('Hello, ' + this.name);
  }
};

obj.greet(); // this 指向 obj,输出 "Hello, John"
5.构造函数中的this
  • 当使用 new 关键字创建对象实例时,构造函数中的 this 会指向新创建的对象。
function Person(name) {
  this.name = name;
}

const person1 = new Person('Alice');
console.log(person1.name); // 输出 "Alice"
6.事件处理函数中的 this
  • 在事件处理函数中,this 通常指向触发事件的元素。例如,当点击按钮时,按钮的事件处理函数中的 this 指向按钮元素。
7.显示绑定:

使用call(),apply(),bind()显示的绑定函数的this

.this丢失问题

  • 有时,this 可能会在函数传递、嵌套函数中被“丢失”。在这种情况下,可以通过保存 this 的引用(通常使用 self 或 that)来避免丢失。
const obj = {
  name: 'David',
  func: function() {
    const self = this; // 保存 this 的引用
    setTimeout(function() {
      console.log('Hello, ' + self.name); // 正确引用 this
    }, 1000);
  }
};

obj.func(); // 输出 "Hello, David"