likes
comments
collection
share

[this] 面试官:一句话解释 JavaScript 的 this

作者站长头像
站长
· 阅读数 11

一句话解释 this

如何一句话解释 this:它就是一个变量,指向了某个对象保存在堆中的数据。

从原理角度理解 this

我们知道在 JS 中,对象的变量名保存在栈(Stack)中,对象的数据保存在堆(Heap)中,像这样:

[this] 面试官:一句话解释 JavaScript 的 this

一句话解释 this,它就是一个变量,引用了某个对象保存在堆中的数据,就这么简单。 比如在上图中,personAthis 引用了 {name: "LB", age:20}

this 有什么用?

既然 this 指向的堆中某个对象的数据,那我们可以通过 this 来灵活的操作对象在堆中的数据呀。

比如下面的代码,可以把一个sayHi函数放在两个对象身上使用,代码可复用性这不就上来了么:

const user = { name: "john" };
const admin = { name: "admin" };

function sayHi() {
  console.log(this.name); // this 可以指向 user,也可以指向 admin
}

user.f = sayHi;
admin.f = sayHi;

user.f();  // john
admin.f();  // admin

把 this 掌握在自己手里

只有掌握好 this 的指向规则,才能让我们使用 this 的时候可以指哪打哪。

如何操纵普通函数的 this ?

普通函数的 this 的指向可以通过下面几种规则来判断,优先级从高到低:

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

    var bar = new foo()
    
  2. 看是不是显示绑定。函数是否通过 call、apply、bind 调用? 如果是的话,this 绑定的是 指定的对象。

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

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

     var bar = foo()
    

普通函数的 this 绑定是动态的,也就是在运行时才知道 this 绑定的是谁的 context,再具体一点来说,this 所在的函数被谁调用了,this 就绑定了谁的上下文。如果这个谁也没有上下文,就再往上看上一层调用者,直到找到有上下文的函数 (最顶层就是拥有全局上下文的全局函数)。

从调用栈的角度来说,一个普通函数出栈后,就有一个新的栈顶函数,然后被调用,这个函数的 this 绑定的是新的栈顶函数的上下文。

如何操纵箭头函数的 this ?

箭头函数可以理解为轻量级的普通函数,它压根就没有 this,所以普通函数的 this 规则不适用于它。它的 this 在创建函数的时候,从外层的 this 继承而来,而且一旦继承了 this,就不会再变了。

在创建箭头函数的时候,this 继承自外部,而且 this 确定之后不会变:

function foo() {
  return (a) => {
    // this 继承自 foo()
    // this 继承之后不会变
    console.log(this.a);
  };
}
var obj1 = { a: 2 };
var obj2 = { a: 3 };
var bar = foo.call(obj1); // 调用 foo,foo 的 this 指向 obj1
bar.call(obj2); // 2

因为不用担心 this 的指向会被三方库修改,所以常用在创建回调函数:

function foo() {
  setTimeout(() => {
    // 这里的 this 继承自 foo,不用担心 setTimeout 会修改它
    console.log(this.a);
  }, 100);
}
var obj = { a: 2 };
foo.call(obj); // 2

下面的代码和上面的一样,把回调函数的声明抽出来了,更好理解:

function foo() {
  const cb = () => {
    // 这里的 this 继承自 foo
    console.log(this.a);
  };
  setTimeout(cb, 100);
}
var obj = { a: 2 };
foo.call(obj); // 2

代码实操

下面的例子,如果没有额外说明 use strict,都是没有 use strict 的。 推荐你配合着上面的 this 绑定规则一起看下面的代码。

默认绑定:

function makeUser() {
  return {
    name: "John",
    ref: this
  };
}

// this 的指向只和 function 在哪里 call 有关系
let user = makeUser();

console.log( user.ref.name ); // Error: Cannot read property 'name' of undefinedoo

隐式绑定:

"use strict";
function makeUser() {
  return {
    name: "John",
    ref() {
      return this;  // 拿的是 makeUser 的 this
    },
  };
}

let user = makeUser();

// user 是一个对象,user.ref() 相当于隐式绑定了 this
console.log(user.ref().name); // John

箭头函数的 this 继承自外层:

obj = {
  count: 0,
  cool: function coolFn() {
    if (this.count < 1) {
      setTimeout(() => {   // 箭头函数的 this 就是 coolFn() 的 this
        console.log("awesome");
      }, 100);
    }
  },
};

obj.cool(); // awesome

// 下面代码是一样的
obj = {
  count: 0,
  cool: function coolFn() {
    if (this.count < 1) {
      let foo = () => {
	    // 箭头函数的 this 就是 coolFn() 的 this

        console.log("awesome");
      };
      setTimeout(foo, 100);
    }
  },
};

obj.cool(); // awesome

Refereces

《你不知道的 JavaScript(上卷)》- this 相关章节 javascript.info/object-meth… javascript.info/arrow-funct… javascript.info/arrow-funct… developer.mozilla.org/en-US/docs/…