返回

JavaScript中的this 谜团:飘忽不定的this与call/apply/bind/new

前端

JavaScript,一门以其怪异而闻名于世的语言,其中最引人注目的就是this。它是一个飘忽不定的家伙,时而出现在函数作用域中,时而在对象方法中,甚至在箭头函数和闭包中都有它的身影。更让人费解的是,它的值还会受到call、apply、bind和new操作符的影响。这不禁让我们发出疑问:this到底是什么?它在JavaScript中扮演着怎样的角色?

JavaScript中的this

在JavaScript中,this是一个特殊变量,它指向当前正在执行代码的上下文对象。这个上下文对象可能是全局对象、函数对象、对象实例或其他对象。要理解this的含义,首先需要了解JavaScript的执行环境。

JavaScript代码在执行时,会创建一个执行环境(Execution Context),其中包含了局部变量、函数参数、this对象等信息。执行环境在函数调用时创建,并在函数调用结束后销毁。函数调用时,会将当前执行环境作为参数传递给新创建的执行环境。因此,this的值也随着函数调用的层级而发生变化。

函数作用域中的this

在函数作用域中,this的值由函数的调用方式决定。如果函数作为对象的方法被调用,则this指向该对象。如果函数作为独立函数被调用,则this指向全局对象。

function Person(name) {
  this.name = name;
  this.greet = function() {
    console.log(`Hello, my name is ${this.name}.`);
  };
}

const person = new Person('John');
person.greet(); // Output: Hello, my name is John.

// 作为独立函数调用
greet(); // Output: Hello, my name is undefined.

在上面的例子中,Person构造函数中的this指向新创建的Person对象,因此我们可以通过person对象访问和调用greet方法。当greet方法作为独立函数被调用时,this指向全局对象,因此this.name的值是undefined。

对象方法中的this

在对象方法中,this指向该方法所属的对象。这使得我们可以通过对象的方法来访问和操作对象本身的数据和方法。

const person = {
  name: 'John',
  greet: function() {
    console.log(`Hello, my name is ${this.name}.`);
  }
};

person.greet(); // Output: Hello, my name is John.

在上面的例子中,person对象中的greet方法中的this指向person对象本身,因此我们可以通过this.name来访问person对象中的name属性。

箭头函数中的this

箭头函数是一个特殊的函数类型,它没有自己的this值,而是继承外层函数的this值。这意味着箭头函数中的this值与它所在作用域的this值相同。

const person = {
  name: 'John',
  greet: () => {
    console.log(`Hello, my name is ${this.name}.`);
  }
};

person.greet(); // Output: Hello, my name is John.

在上面的例子中,person对象中的greet方法是一个箭头函数,它的this值继承自person对象,因此我们可以通过this.name来访问person对象中的name属性。

闭包中的this

闭包是指可以在其定义作用域之外访问自由变量的函数。当一个函数内部嵌套了另一个函数时,内部函数就可以访问外部函数的作用域,包括其局部变量和this值。

function outerFunction() {
  const name = 'John';

  function innerFunction() {
    console.log(`Hello, my name is ${this.name}.`);
  }

  return innerFunction;
}

const innerFunction = outerFunction();
innerFunction(); // Output: Hello, my name is John.

在上面的例子中,innerFunction函数是一个闭包,它可以访问outerFunction函数的作用域,包括其局部变量name和this值。因此,当我们调用innerFunction函数时,this.name的值为'John'。

原型链中的this

JavaScript中的对象都有一个原型对象,原型对象也是一个对象,它包含了该对象的所有共有属性和方法。当对象访问一个不存在的属性或方法时,JavaScript引擎会沿着原型链向上查找,直到找到该属性或方法为止。

function Person(name) {
  this.name = name;
}

Person.prototype.greet = function() {
  console.log(`Hello, my name is ${this.name}.`);
};

const person = new Person('John');
person.greet(); // Output: Hello, my name is John.

在上面的例子中,Person函数的prototype属性是一个对象,它包含了greet方法。当person对象调用greet方法时,JavaScript引擎会沿着原型链向上查找,直到找到greet方法为止,然后执行该方法。因此,当我们调用person对象的greet方法时,this.name的值为'John'。

call、apply和bind方法

call、apply和bind方法都是Function对象的内置方法,它们都可以改变函数的this值。

  • call方法:call方法接收两个参数,第一个参数是this值,第二个参数是函数的参数列表。当调用call方法时,函数将使用指定的this值和参数列表执行。
function greet(name) {
  console.log(`Hello, ${name}!`);
}

greet.call(null, 'John'); // Output: Hello, John!
  • apply方法:apply方法与call方法类似,但它接收一个参数数组而不是参数列表。
function greet(name) {
  console.log(`Hello, ${name}!`);
}

greet.apply(null, ['John']); // Output: Hello, John!
  • bind方法:bind方法创建一个新的函数,该函数的this值被绑定为指定的值。当调用bind方法创建的新函数时,该函数将使用指定的this值执行。
function greet(name) {
  console.log(`Hello, ${name}!`);
}

const boundGreet = greet.bind(null, 'John');
boundGreet(); // Output: Hello, John!

new操作符

new操作符用于创建对象实例。当使用new操作符调用函数时,函数将创建一个新的对象,并将该对象作为this值执行。

function Person(name) {
  this.name = name;
}

const person = new Person('John');
console.log(person.name); // Output: John

在上面的例子中,当我们使用new操作符调用Person函数时,会创建一个新的Person对象,并将该对象作为this值执行。因此,我们可以通过person对象访问和调用name属性。