JavaScript篇:深度解析this指针
前端知识库 JavaScript 12

[转载内容: JavaScript篇:深度解析this指针](https://zhuanlan.zhihu.com/p/1931677503392546855)

this指针的定义与本质

JavaScript中的this指针是一个特殊的关键字,它代表函数执行时的上下文环境。与大多数面向对象语言不同,JavaScript中的this不是在函数定义时确定的,而是在函数调用时动态绑定的。这种动态绑定的特性使得this的行为变得复杂且容易混淆。

从本质上讲,this是一个指向当前执行上下文的引用。这个执行上下文可以是全局对象、某个对象实例、构造函数创建的实例,或者是通过call/apply/bind方法显式指定的对象。理解this的关键在于分析函数是如何被调用的,而不是函数是如何定义的。

// 示例1: 全局作用域中的this
console.log(this); // 在浏览器中输出window对象,在Node.js中输出global对象

// 示例2: 函数调用中的this
function showThis() {
  console.log(this);
}
showThis(); // 在非严格模式下输出全局对象,严格模式下输出undefined

this指针与执行上下文

执行上下文(Execution Context)是JavaScript中一个重要的概念,它定义了变量或函数有权访问的其他数据。每个执行上下文都与一个this值相关联。当JavaScript代码执行时,会创建一个新的执行上下文,这个上下文包含了this绑定、变量对象和作用域链等信息。

this指针的值是在函数被调用时确定的,而不是在函数定义时确定的。这意味着同一个函数在不同的调用方式下可能会有不同的this值。这种动态绑定的特性使得JavaScript的this行为既灵活又复杂。

// 示例3: 执行上下文与this的关系
const obj = {
  name: 'Example',
  showThis: function() {
    console.log(this);
  }
};

obj.showThis(); // this指向obj对象
const extractedFunc = obj.showThis;
extractedFunc(); // this指向全局对象(非严格模式)或undefined(严格模式)

this指针的绑定规则

默认绑定规则

默认绑定规则是指当函数作为独立函数调用时(不是作为对象的方法,也不是使用call/apply/bind等方法调用),this的绑定方式。在非严格模式下,默认绑定会将this绑定到全局对象;在严格模式下,默认绑定的this值为undefined。

// 示例4: 默认绑定(非严格模式)
function defaultBindExample() {
  console.log(this);
}
defaultBindExample(); // 浏览器中输出window对象

// 示例5: 默认绑定(严格模式)
'use strict';
function strictDefaultBindExample() {
  console.log(this);
}
strictDefaultBindExample(); // 输出undefined

隐式绑定规则

隐式绑定规则是指当函数作为对象的方法调用时,this会隐式地绑定到这个对象。这是JavaScript中最常见的this绑定方式之一。需要注意的是,如果函数被赋值给另一个变量或者作为回调函数传递,可能会发生隐式丢失的情况。

// 示例6: 隐式绑定
const person = {
  name: 'Alice',
  greet: function() {
    console.log(`Hello, my name is ${this.name}`);
  }
};

person.greet(); // this指向person对象

// 示例7: 隐式丢失
const greetFunc = person.greet;
greetFunc(); // this指向全局对象(非严格模式)或undefined(严格模式)

显式绑定规则

显式绑定规则是指通过call、apply或bind方法显式地指定函数执行时的this值。这三种方法都可以改变函数的this绑定,但它们在用法上有一些区别:

  1. call和apply方法会立即调用函数,而bind方法会返回一个新的函数。

  2. call和bind方法接受参数列表,而apply方法接受参数数组。

// 示例8: 显式绑定(call方法)
function showName() {
  console.log(this.name);
}

const obj1 = { name: 'Bob' };
const obj2 = { name: 'Charlie' };

showName.call(obj1); // 输出Bob
showName.call(obj2); // 输出Charlie

// 示例9: 显式绑定(apply方法)
function showAge(age) {
  console.log(`${this.name} is ${age} years old`);
}

showAge.apply(obj1, [30]); // 输出Bob is 30 years old

// 示例10: 显式绑定(bind方法)
const boundShowName = showName.bind(obj1);
boundShowName(); // 输出Bob

new绑定规则

当使用new操作符调用构造函数时,this会绑定到新创建的对象实例。这是JavaScript中面向对象编程的基础机制之一。new操作符会执行以下步骤:

  1. 创建一个新的空对象。

  2. 将这个新对象的[[Prototype]]链接到构造函数的prototype属性。

  3. 将构造函数的this绑定到这个新对象。

  4. 执行构造函数中的代码。

  5. 如果构造函数没有显式返回一个对象,则返回这个新对象。

// 示例11: new绑定
function Person(name) {
  this.name = name;
}

const alice = new Person('Alice');
console.log(alice.name); // 输出Alice

this指针的特殊情况

箭头函数中的this

箭头函数是ES6引入的一种新的函数语法,它没有自己的this绑定。箭头函数的this值继承自外层函数调用的this值,而且这个绑定在定义时就确定了,不会在调用时改变。这使得箭头函数在处理回调函数时特别有用,可以避免this绑定的常见问题。

// 示例12: 箭头函数中的this
const obj = {
  name: 'Dave',
  regularFunc: function() {
    console.log(this.name);
  },
  arrowFunc: () => {
    console.log(this.name); // this指向外层作用域的this
  }
};

obj.regularFunc(); // 输出Dave
obj.arrowFunc(); // 输出undefined(或全局对象的name属性)

// 示例13: 箭头函数在回调中的应用
const timerObj = {
  count: 0,
  startTimer: function() {
    setInterval(() => {
      this.count++;
      console.log(this.count);
    }, 1000);
  }
};

timerObj.startTimer(); // 每秒输出递增的数字

事件处理函数中的this

在DOM事件处理函数中,this通常指向触发事件的元素。这是浏览器提供的一种便利机制,使得事件处理函数可以直接访问事件目标元素。然而,如果使用箭头函数作为事件处理函数,this的行为会有所不同。

// 示例14: DOM事件处理函数中的this
document.getElementById('myButton').addEventListener('click', function() {
  console.log(this); // 指向触发事件的按钮元素
});

// 示例15: 箭头函数作为事件处理函数
document.getElementById('myButton').addEventListener('click', () => {
  console.log(this); // 指向外层作用域的this(通常是window对象)
});

定时器回调中的this

在使用setTimeout或setInterval等定时器函数时,回调函数中的this默认指向全局对象(非严格模式)或undefined(严格模式)。如果需要保持特定的this绑定,可以使用bind方法或者箭头函数。

// 示例16: 定时器回调中的this
const timerExample = {
  count: 0,
  start: function() {
    setTimeout(function() {
      console.log(this); // 指向全局对象
      this.count++; // 会报错,因为this不是timerExample
    }, 1000);

    setTimeout(() => {
      console.log(this); // 指向timerExample
      this.count++; // 正确递增
    }, 2000);
  }
};

timerExample.start();


JavaScript篇:深度解析this指针
http://localhost:8090/archives/yxLje3dw
作者
codevow
发布于
更新于
许可