返回

是谁发明了this关键字,以及它的作用是什么?

前端

在JavaScript的世界里,this 就像一个变色龙,它代表着函数执行时的上下文环境,但它具体指代什么,却常常让人捉摸不透。你也许写过这样的代码,满怀期待地运行,结果却发现 this 并没有指向你预期的对象,反而抛出一个 undefined 或者指向了全局对象 window,让你百思不得其解。

为什么会这样呢?

这主要是因为 JavaScript 函数的执行环境非常灵活。函数可以被赋值给不同的变量,也可以作为参数传递给其他函数,甚至可以被动态创建。这种灵活性也导致了函数的执行上下文难以确定,而 this 的指向就取决于函数的调用方式。

拨开迷雾,看清 this 的真面目

想要驾驭 this 这个变色龙,我们首先需要了解 JavaScript 中函数的四种主要调用方式:

  1. 作为对象的方法调用: 当函数作为对象的一个方法被调用时,this 指向该对象。

    const person = {
      name: 'Alice',
      greet: function() {
        console.log('Hello, my name is ' + this.name); 
      }
    };
    
    person.greet(); // 输出: Hello, my name is Alice
    

    在这个例子中,greet 函数是 person 对象的一个方法,当我们调用 person.greet() 时,this 就指向了 person 对象,因此 this.name 就能正确访问到 person 对象的 name 属性。

  2. 作为函数调用: 当函数直接被调用,不作为任何对象的属性时,在非严格模式下,this 指向全局对象 window(在浏览器环境中),在严格模式下,this 的值为 undefined

    function sayHello() {
      console.log('Hello, ' + this); 
    }
    
    sayHello(); // 非严格模式: Hello, [object Window] 
                // 严格模式: Hello, undefined
    
  3. 作为构造函数调用: 当使用 new 调用函数时,该函数会被当作构造函数,创建一个新的对象,并将 this 指向这个新创建的对象。

    function Person(name) {
      this.name = name;
    }
    
    const john = new Person('John');
    console.log(john.name); // 输出: John
    

    这里,Person 函数被当作构造函数,new Person('John') 创建了一个新的 Person 对象,并将 this 指向这个新对象,因此 this.name = name 就给新对象添加了一个 name 属性。

  4. 使用 callapplybind 方法调用: callapplybind 方法可以改变函数的 this 指向。

    function greet(greeting) {
      console.log(greeting + ', ' + this.name);
    }
    
    const person = { name: 'Bob' };
    
    greet.call(person, 'Hello'); // 输出: Hello, Bob
    greet.apply(person, ['Hi']); // 输出: Hi, Bob
    
    const greetBob = greet.bind(person);
    greetBob('Hey'); // 输出: Hey, Bob
    

    callapply 方法都可以立即调用函数,并指定 this 的值,区别在于传递参数的方式不同。bind 方法则会创建一个新的函数,并将 this 绑定到指定的值,但不立即执行。

this 陷阱及应对策略

理解了 this 的四种调用方式,我们就能更好地掌控它,避免一些常见的陷阱:

陷阱一:回调函数中的 this 丢失

当函数作为回调函数传递给其他函数时,this 的指向可能会发生改变,不再指向我们期望的对象。

const counter = {
  count: 0,
  increment: function() {
    setTimeout(function() {
      this.count++; // this 指向全局对象 window,而不是 counter
      console.log(this.count); 
    }, 1000);
  }
};

counter.increment(); // 输出: NaN

解决方法:

  • 使用箭头函数:箭头函数没有自己的 this,它会继承外层函数的 this

    const counter = {
      count: 0,
      increment: function() {
        setTimeout(() => {
          this.count++; // this 指向 counter
          console.log(this.count); 
        }, 1000);
      }
    };
    
    counter.increment(); // 输出: 1
    
  • 使用 bind 方法:在传递回调函数之前,使用 bind 方法将 this 绑定到期望的对象。

    const counter = {
      count: 0,
      increment: function() {
        setTimeout(function() {
          this.count++;
          console.log(this.count); 
        }.bind(this), 1000); 
      }
    };
    
    counter.increment(); // 输出: 1
    

陷阱二:事件处理函数中的 this

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

const button = document.getElementById('myButton');

button.addEventListener('click', function() {
  console.log(this); // this 指向 button 元素
});

需要特别注意的是,如果事件处理函数被绑定到其他对象上,this 的指向就会发生改变。

陷阱三:this 在严格模式下的变化

在严格模式下,作为函数调用时,this 的值为 undefined,而不是全局对象 window

结论

this 关键字是 JavaScript 中一个非常重要的概念,它连接着函数和它的执行环境。掌握 this 的指向规则,了解常见的 this 陷阱,才能写出更加健壮和易于维护的 JavaScript 代码。

常见问题解答

1. this 关键字和作用域有什么区别?

this 关键字指向函数的执行上下文,而作用域指的是变量的访问范围。this 的值取决于函数的调用方式,而作用域则由函数定义的位置决定。

2. 箭头函数中的 this 是如何工作的?

箭头函数没有自己的 this,它会继承外层函数的 this

3. 如何改变函数的 this 指向?

可以使用 callapplybind 方法改变函数的 this 指向。

4. 在严格模式下,this 的指向有什么变化?

在严格模式下,作为函数调用时,this 的值为 undefined,而不是全局对象 window

5. 为什么理解 this 对于编写 JavaScript 代码很重要?

理解 this 的指向规则,才能正确访问对象的属性和方法,避免出现 this 指向错误的问题,从而写出更加健壮和易于维护的 JavaScript 代码。