Skip to content

this关键字和上下文

this关键字是JavaScript中的一个特殊变量,它指向当前执行代码的上下文对象。理解this的指向规则是掌握JavaScript的关键之一,因为它在不同的执行环境中会有不同的指向。本文将详细介绍JavaScript中this关键字的概念、指向规则以及如何改变this的指向。

1. this的概念

this是一个在函数执行时自动生成的变量,它指向当前函数的执行上下文。执行上下文是指函数执行时的环境,包括函数的调用者、调用方式、传入的参数等。

特点

  • this的值在函数定义时是不确定的,只有在函数执行时才能确定。
  • this的指向与函数的调用方式有关,与函数的定义位置无关。
  • 在严格模式和非严格模式下,this的指向可能会有所不同。

2. this的指向规则

在JavaScript中,this的指向主要取决于函数的调用方式,以下是几种常见的情况:

2.1 全局上下文

在全局上下文中,this指向全局对象(在浏览器中是window,在Node.js中是global)。

使用方式

javascript
// 全局上下文
console.log(this); // window(浏览器中)

// 全局函数中的this
function globalFunction() {
  console.log(this); // window(浏览器中)
}

globalFunction();

// 严格模式下的全局函数
'use strict';
function strictGlobalFunction() {
  console.log(this); // undefined
}

strictGlobalFunction();

2.2 函数作为对象方法调用

当函数作为对象的方法调用时,this指向调用该方法的对象。

使用方式

javascript
const obj = {
  name: 'Object',
  sayHello: function() {
    console.log(`Hello, ${this.name}!`);
    console.log(this); // obj
  }
};

obj.sayHello(); // Hello, Object!

// 方法赋值给变量后调用
const sayHello = obj.sayHello;
sayHello(); // Hello, undefined!(this指向全局对象)

2.3 构造函数调用

当函数通过new关键字作为构造函数调用时,this指向新创建的实例对象。

使用方式

javascript
function Person(name, age) {
  this.name = name;
  this.age = age;
  console.log(this); // Person { name: 'John', age: 30 }
}

const person = new Person('John', 30);
console.log(person.name); // John
console.log(person.age); // 30

// 不使用new关键字调用构造函数
const notPerson = Person('John', 30);
console.log(notPerson); // undefined(构造函数没有返回值)
console.log(name); // John(this指向全局对象,污染全局变量)

2.4 箭头函数

箭头函数没有自己的this,它会捕获定义时所在上下文的this值,作为自己的this值。

使用方式

javascript
const obj = {
  name: 'Object',
  regularFunction: function() {
    console.log(this); // obj
    
    const arrowFunction = () => {
      console.log(this); // obj(捕获外部函数的this)
    };
    
    arrowFunction();
  },
  arrowMethod: () => {
    console.log(this); // window(箭头函数没有自己的this,捕获全局上下文的this)
  }
};

obj.regularFunction();
obj.arrowMethod();

2.5 事件处理函数

在DOM事件处理函数中,this指向触发事件的元素。

使用方式

javascript
// HTML: <button id="btn">Click me</button>
document.getElementById('btn').addEventListener('click', function() {
  console.log(this); // <button id="btn">Click me</button>
});

// 箭头函数作为事件处理函数
document.getElementById('btn').addEventListener('click', () => {
  console.log(this); // window(箭头函数捕获全局上下文的this)
});

2.6 定时器函数

setTimeoutsetInterval的回调函数中,this指向全局对象(在浏览器中是window)。

使用方式

javascript
const obj = {
  name: 'Object',
  timeoutFunction: function() {
    setTimeout(function() {
      console.log(this); // window(定时器回调函数中的this指向全局对象)
      console.log(`Hello, ${this.name}!`); // Hello, undefined!
    }, 1000);
    
    // 使用箭头函数
    setTimeout(() => {
      console.log(this); // obj(箭头函数捕获外部函数的this)
      console.log(`Hello, ${this.name}!`); // Hello, Object!
    }, 1000);
  }
};

obj.timeoutFunction();

3. 改变this的指向

在JavaScript中,我们可以通过以下方法改变函数中this的指向:

3.1 call方法

call方法调用一个函数,并用指定的this值和单独的参数列表。

使用方式

javascript
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person1 = { name: 'John' };
const person2 = { name: 'Jane' };

// 使用call方法改变this指向
greet.call(person1, 'Hello', '!'); // Hello, John!
greet.call(person2, 'Hi', '.'); // Hi, Jane.

// 没有参数时
greet.call(person1); // undefined, Johnundefined

特点

  • call方法的第一个参数是this的指向对象。
  • 从第二个参数开始,是传递给函数的参数列表。
  • 函数会立即执行。

3.2 apply方法

apply方法调用一个函数,并用指定的this值和参数数组(或类数组对象)。

使用方式

javascript
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person1 = { name: 'John' };
const person2 = { name: 'Jane' };

// 使用apply方法改变this指向
greet.apply(person1, ['Hello', '!']); // Hello, John!
greet.apply(person2, ['Hi', '.']); // Hi, Jane.

// 没有参数时
greet.apply(person1); // undefined, Johnundefined

// 使用apply传递数组参数
function sum(a, b, c) {
  return a + b + c;
}

const numbers = [1, 2, 3];
console.log(sum.apply(null, numbers)); // 6(this指向全局对象)

特点

  • apply方法的第一个参数是this的指向对象。
  • 第二个参数是一个数组或类数组对象,包含传递给函数的参数。
  • 函数会立即执行。

3.3 bind方法

bind方法创建一个新函数,当调用时,将其this关键字设置为提供的值,并在调用新函数时,将给定的参数列表作为原函数的参数序列的前几个参数。

使用方式

javascript
function greet(greeting, punctuation) {
  console.log(`${greeting}, ${this.name}${punctuation}`);
}

const person1 = { name: 'John' };
const person2 = { name: 'Jane' };

// 使用bind方法创建新函数
const greetPerson1 = greet.bind(person1);
const greetPerson2 = greet.bind(person2, 'Hi'); // 预先设置第一个参数

// 调用新函数
greetPerson1('Hello', '!'); // Hello, John!
greetPerson2('.'); // Hi, Jane.

// 绑定this后原函数不受影响
greet('Hello', '!'); // Hello, undefined!(this指向全局对象)

特点

  • bind方法的第一个参数是this的指向对象。
  • 从第二个参数开始,是预先设置的参数,会在调用新函数时传递给原函数。
  • bind方法返回一个新函数,不会立即执行。

3.4 三种方法的比较

方法特点是否立即执行参数传递方式
call改变this指向单独的参数列表
apply改变this指向参数数组或类数组对象
bind改变this指向并创建新函数预先设置的参数列表

4. 箭头函数中的this

箭头函数是ES6引入的新特性,它与普通函数的一个重要区别是箭头函数没有自己的this值,而是捕获定义时所在上下文的this值。

4.1 箭头函数的this绑定

箭头函数的this绑定是词法的,它会捕获定义时所在上下文的this值,作为自己的this值,并且在箭头函数的整个生命周期中保持不变。

使用方式

javascript
const obj = {
  name: 'Object',
  regularFunction: function() {
    console.log(this); // obj
    
    // 普通函数
    const regularInnerFunction = function() {
      console.log(this); // window(普通函数的this指向调用者)
    };
    
    // 箭头函数
    const arrowInnerFunction = () => {
      console.log(this); // obj(捕获外部函数的this)
    };
    
    regularInnerFunction();
    arrowInnerFunction();
  },
  arrowMethod: () => {
    console.log(this); // window(箭头函数捕获全局上下文的this)
  }
};

obj.regularFunction();
obj.arrowMethod();

4.2 箭头函数的优点

  • 简化代码:箭头函数的语法更简洁,减少了代码量。
  • 避免this指向问题:箭头函数捕获外部上下文的this,避免了普通函数中this指向不明确的问题。
  • 适合回调函数:箭头函数特别适合作为回调函数,因为它可以保持外部上下文的this指向。

4.3 箭头函数的注意事项

  • 不能作为构造函数:箭头函数没有自己的this,不能使用new关键字调用。
  • 没有arguments对象:箭头函数没有自己的arguments对象,但可以访问外部函数的arguments对象。
  • 不能使用yield关键字:箭头函数不能作为生成器函数。
  • 不适合作为对象方法:箭头函数作为对象方法时,this不会指向该对象,而是捕获外部上下文的this

5. 严格模式下的this

在严格模式('use strict')下,this的指向与非严格模式有所不同:

使用方式

javascript
// 非严格模式
function nonStrictFunction() {
  console.log(this); // window
}

nonStrictFunction();

// 严格模式
'use strict';
function strictFunction() {
  console.log(this); // undefined
}

strictFunction();

// 严格模式下作为对象方法调用
const obj = {
  name: 'Object',
  strictMethod: function() {
    'use strict';
    console.log(this); // obj
  }
};

obj.strictMethod();

// 严格模式下使用call/apply/bind
function greet() {
  'use strict';
  console.log(this);
}

greet.call(null); // null
greet.call(undefined); // undefined
greet.call(123); // 123

特点

  • 在严格模式下,全局函数中的this指向undefined,而不是全局对象。
  • 在严格模式下,使用callapplybind方法时,this的指向会严格按照传入的值,即使传入的是nullundefined
  • 在严格模式下,作为对象方法调用的函数,this仍然指向调用该方法的对象。

6. this的优先级

当多种this绑定规则同时存在时,它们的优先级从高到低依次是:

  1. new绑定:使用new关键字调用构造函数时,this指向新创建的实例。
  2. 显式绑定:使用callapplybind方法改变this的指向。
  3. 隐式绑定:函数作为对象的方法调用时,this指向调用该方法的对象。
  4. 默认绑定:在全局上下文中,this指向全局对象;在严格模式下,this指向undefined

使用方式

javascript
// 1. new绑定优先级高于显式绑定
function Person(name) {
  this.name = name;
  console.log(this);
}

const obj = { name: 'Object' };
const boundPerson = Person.bind(obj);

const person = new boundPerson('John'); // Person { name: 'John' }(new绑定优先级更高)
console.log(obj.name); // Object(obj的name没有被改变)

// 2. 显式绑定优先级高于隐式绑定
const obj1 = {
  name: 'Obj1',
  greet: function() {
    console.log(this.name);
  }
};

const obj2 = { name: 'Obj2' };

obj1.greet(); // Obj1(隐式绑定)
obj1.greet.call(obj2); // Obj2(显式绑定优先级更高)

// 3. 隐式绑定优先级高于默认绑定
const obj3 = {
  name: 'Obj3',
  greet: function() {
    console.log(this.name);
  }
};

const greet = obj3.greet;
greet(); // undefined(默认绑定)
obj3.greet(); // Obj3(隐式绑定优先级更高)

面试常见问题

1. 什么是this?this的指向取决于什么?

this是JavaScript中的一个特殊变量,它指向当前执行代码的上下文对象。

this的指向取决于函数的调用方式,与函数的定义位置无关。具体来说:

  • 在全局上下文中,this指向全局对象。
  • 当函数作为对象的方法调用时,this指向调用该方法的对象。
  • 当函数通过new关键字作为构造函数调用时,this指向新创建的实例对象。
  • 箭头函数没有自己的this,它会捕获定义时所在上下文的this值。
  • 在DOM事件处理函数中,this指向触发事件的元素。
  • 在定时器函数中,this指向全局对象。

2. 如何改变this的指向?call、apply和bind有什么区别?

改变this指向的方法

  • call:调用函数,并用指定的this值和单独的参数列表。
  • apply:调用函数,并用指定的this值和参数数组(或类数组对象)。
  • bind:创建一个新函数,当调用时,将其this关键字设置为提供的值,并在调用新函数时,将给定的参数列表作为原函数的参数序列的前几个参数。

区别

  • 执行方式callapply会立即执行函数,bind会返回一个新函数,不会立即执行。
  • 参数传递call接收单独的参数列表,apply接收参数数组或类数组对象,bind接收预先设置的参数列表。

3. 箭头函数中的this与普通函数有什么不同?

箭头函数中的this

  • 箭头函数没有自己的this,它会捕获定义时所在上下文的this值,作为自己的this值。
  • 箭头函数的this绑定是词法的,在箭头函数的整个生命周期中保持不变。
  • 箭头函数不能作为构造函数使用,因为它没有自己的this

普通函数中的this

  • 普通函数的this值在函数执行时确定,取决于函数的调用方式。
  • 普通函数可以作为构造函数使用,使用new关键字调用时,this指向新创建的实例对象。

4. 下面的代码输出什么?为什么?

javascript
const obj = {
  name: 'Object',
  sayHello: function() {
    console.log(`Hello, ${this.name}!`);
  }
};

const sayHello = obj.sayHello;
sayHello();

输出

Hello, undefined!

原因:当obj.sayHello被赋值给变量sayHello后,函数的调用方式变成了全局调用,此时this指向全局对象(在浏览器中是window),而全局对象中没有name属性,所以输出Hello, undefined!

5. 如何修改上面的代码,使其输出Hello, Object!

方法1:使用bind方法

javascript
const obj = {
  name: 'Object',
  sayHello: function() {
    console.log(`Hello, ${this.name}!`);
  }
};

const sayHello = obj.sayHello.bind(obj);
sayHello(); // Hello, Object!

方法2:使用箭头函数

javascript
const obj = {
  name: 'Object',
  sayHello: function() {
    const innerSayHello = () => {
      console.log(`Hello, ${this.name}!`);
    };
    return innerSayHello;
  }
};

const sayHello = obj.sayHello();
sayHello(); // Hello, Object!

方法3:使用call/apply方法

javascript
const obj = {
  name: 'Object',
  sayHello: function() {
    console.log(`Hello, ${this.name}!`);
  }
};

const sayHello = obj.sayHello;
sayHello.call(obj); // Hello, Object!
sayHello.apply(obj); // Hello, Object!

6. 下面的代码输出什么?为什么?

javascript
function Person(name) {
  this.name = name;
  this.sayHello = function() {
    setTimeout(function() {
      console.log(`Hello, ${this.name}!`);
    }, 1000);
  };
}

const person = new Person('John');
person.sayHello();

输出

Hello, undefined!

原因setTimeout的回调函数是一个普通函数,它的this指向全局对象(在浏览器中是window),而全局对象中没有name属性,所以输出Hello, undefined!

7. 如何修改上面的代码,使其输出Hello, John!

方法1:使用箭头函数

javascript
function Person(name) {
  this.name = name;
  this.sayHello = function() {
    setTimeout(() => {
      console.log(`Hello, ${this.name}!`);
    }, 1000);
  };
}

const person = new Person('John');
person.sayHello(); // Hello, John!

方法2:使用bind方法

javascript
function Person(name) {
  this.name = name;
  this.sayHello = function() {
    setTimeout(function() {
      console.log(`Hello, ${this.name}!`);
    }.bind(this), 1000);
  };
}

const person = new Person('John');
person.sayHello(); // Hello, John!

方法3:使用变量保存this

javascript
function Person(name) {
  this.name = name;
  this.sayHello = function() {
    const self = this;
    setTimeout(function() {
      console.log(`Hello, ${self.name}!`);
    }, 1000);
  };
}

const person = new Person('John');
person.sayHello(); // Hello, John!

8. 什么是词法作用域?它与this的指向有什么关系?

词法作用域是指变量的作用域由其在代码中的定义位置决定,而不是由其执行位置决定。

与this的指向的关系

  • 普通函数的this指向由其执行方式决定,与词法作用域无关。
  • 箭头函数的this绑定是词法的,它会捕获定义时所在上下文的this值,作为自己的this值,与词法作用域类似。

9. 在严格模式下,this的指向有什么不同?

严格模式下的this

  • 在严格模式下,全局函数中的this指向undefined,而不是全局对象。
  • 在严格模式下,使用callapplybind方法时,this的指向会严格按照传入的值,即使传入的是nullundefined
  • 在严格模式下,作为对象方法调用的函数,this仍然指向调用该方法的对象。

10. 什么是this的优先级?请按从高到低的顺序排列。

this的优先级从高到低依次是:

  1. new绑定:使用new关键字调用构造函数时,this指向新创建的实例。
  2. 显式绑定:使用callapplybind方法改变this的指向。
  3. 隐式绑定:函数作为对象的方法调用时,this指向调用该方法的对象。
  4. 默认绑定:在全局上下文中,this指向全局对象;在严格模式下,this指向undefined

11. 箭头函数为什么不能作为构造函数?

箭头函数不能作为构造函数的原因:

  • 箭头函数没有自己的this值,它会捕获定义时所在上下文的this值。
  • 箭头函数没有prototype属性,而构造函数需要使用prototype来创建实例对象。
  • 使用new关键字调用函数时,会创建一个新对象,并将函数的this指向该新对象,但箭头函数没有自己的this,所以不能作为构造函数使用。

12. 如何在箭头函数中访问arguments对象?

箭头函数没有自己的arguments对象,但可以通过以下方法访问外部函数的arguments对象:

方法1:使用剩余参数

javascript
function outer(...args) {
  const inner = () => {
    console.log(args); // 访问外部函数的参数
  };
  inner();
}

outer(1, 2, 3); // [1, 2, 3]

方法2:在外部函数中保存arguments对象

javascript
function outer() {
  const args = arguments;
  const inner = () => {
    console.log(args); // 访问外部函数的arguments对象
  };
  inner();
}

outer(1, 2, 3); // Arguments(3) [1, 2, 3]

总结

this关键字是JavaScript中的一个特殊变量,它指向当前执行代码的上下文对象。理解this的指向规则对于掌握JavaScript至关重要。

  • this的指向规则

    • 全局上下文:指向全局对象。
    • 函数作为对象方法调用:指向调用该方法的对象。
    • 构造函数调用:指向新创建的实例对象。
    • 箭头函数:捕获定义时所在上下文的this值。
    • 事件处理函数:指向触发事件的元素。
    • 定时器函数:指向全局对象。
  • 改变this指向的方法

    • call:调用函数,并用指定的this值和单独的参数列表。
    • apply:调用函数,并用指定的this值和参数数组。
    • bind:创建一个新函数,将其this关键字设置为提供的值。
  • 箭头函数的this

    • 箭头函数没有自己的this,它会捕获定义时所在上下文的this值。
    • 箭头函数的this绑定是词法的,在整个生命周期中保持不变。
    • 箭头函数不能作为构造函数使用。
  • 严格模式下的this

    • 严格模式下,全局函数中的this指向undefined
    • 严格模式下,使用callapplybind方法时,this的指向会严格按照传入的值。
  • this的优先级: new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。

通过理解和掌握this关键字的指向规则,你可以编写更加清晰、可维护的JavaScript代码,避免常见的this指向问题。

好好学习,天天向上