前端面试:一篇文章搞懂this

119 阅读4分钟

一、this

1.1 为什么要使用this

this提供了一种更优雅的方式来隐式“传递”一个对象引用,因此可以将API设计得更加简洁并且易于复用。

1.2 对this认识的误区

1. this指向自身 ×

  • 如果要从函数对象内部引用它自身,那只使用this是不够的。通常还需要一个指向函数对象的词法标识符(变量)来引用它。
  • 对于一个具名函数foo(),在它的内部可以使用foo来引用自身(例如:foo.count)
  • 对于匿名函数,无法从函数内部引用自身(曾经可以使用arguments.callee来引用,但目前已经被弃用)
  • 可以强制this指向foo函数对象,如下:
function foo(num) {
    console.log("foo: " + num);
    this.count++; // 使用this关键词
}

foo.count = 0for(let i = 0; i < 10; i++) {
    if(i>5) {
        // 使用call(...)可以确保this指向函数对象foo本身
        foo.call(foo, i);
    }
}

console.log(foo.count); // 4

2. this指向函数的作用域 ×

  • this在任何情况下都不指向函数的词法作用域,使用this不可能在词法作用域中查到什么

1.3 this是什么

当一个函数被调用时,会创建一个活动记录(也称执行上下文)。这个记录会包含:函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

1.4 this绑定规则

优先级:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

1. 默认绑定(无法应用其它规则时的默认规则)

function foo() {
    console.log(this.a);
}

var a = 2;

foo(); // 2

这里,foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其它规则。

另外,如果使用严格模式(strict mode),不能将全局对象用于默认绑定,因此this会绑定到undefined

2. 隐式绑定

考虑调用位置是否有上下文对象,或者说是被某个对象拥有或者包含。隐式绑定规则会把函数调用中的this绑定到这个上下文对象。

然而,对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。

隐式绑定最常见的问题是隐式丢失,即,被隐式绑定的函数会丢失绑定对象,它会应用默认绑定,从而把this绑定到全局对象或者undefined上。参数传递其实就是一种隐式赋值。

3. 显式绑定

如果不想在对象内部包含函数引用,而在某个对象上强制调用函数,这个时候就需要用到显示绑定了。

可以使用call(...)apply(...)方法。

例如:foo.call(obj)就可以在调用foo时强制把它的this绑定到obj上。如果传入了一个基本数据类型的值,会被转换成它的对象形式(“装箱”)。

面对丢失绑定问题,可以使用以下两种方法:

(1)硬绑定

在内部手动调用foo.call(obj)

(2)API调用的“上下文”

function foo(el) {
    console.log(el, this.id);
}

var obj = {
    id: "awesome"
}

// 调用foo(...)时把this绑定到obj
[1,2,3].forEach(foo, obj);
// 1 awesome 2 awesome 3 awesome

4. new绑定

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

  • 创建(构造)一个全新的对象
  • 这个新对象会被执行[[Prototype]]连接
  • 这个新对象会绑定到函数调用的this
  • 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
function foo(a) {
    this.a = a;
}

var bar = new foo(2);
console.log(bar.a); // 2

使用new来调用foo(...)时,我们会构造一个新对象并把它绑定到foo(...)调用中的this上。new是最后一种可以影响函数调用时this绑定行为的方法,称之为new绑定。

1.5 普通函数中的this指向

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

var bar = new foo()

  1. 如果函数通过call apply(显示绑定)或者硬绑定调用,this绑定的是指定的对象。

var bar = foo.call(obj2)

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

var bar = obj1.foo()

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

var bar = foo()

1.6 箭头函数中的this指向

this取值取上级作用域的值。在箭头函数中,根据当前的词法作用域来决定this

欢迎评论区补充讨论!