js当中的this用法

180 阅读3分钟

引言

在 JavaScript 中,this 是一个关键且复杂的概念,它通常被称为“函数运行环境指针”。为了深入理解 this,我们需要先对内存管理、调用栈、执行上下文、作用域和作用域链有基本的了解。本文将深入探讨 this 的工作原理及其在不同函数调用方式下的表现。

内存管理与 this

JavaScript 中的内存分为栈内存和堆内存。栈内存用于存储基本类型(如数字、字符串、布尔值)和引用类型(如对象、数组)的引用。堆内存则用于存储对象本身。当我们理解这些内存管理机制时,可以更容易地理解 this 是如何指向特定对象的。

调用栈、执行上下文与作用域

JavaScript 引擎在执行代码时,会维护一个调用栈(Call Stack),用于跟踪函数的调用顺序。每当一个函数被调用时,一个新的执行上下文(Execution Context)就会被创建,并推入调用栈中。执行上下文包含变量对象(Variable Object,VO)、作用域链(Scope Chain)和 this 值。

作用域链(Scope Chain)是一个对象列表,用于在变量解析时查找变量。它基于函数的嵌套关系构建,从当前执行上下文开始,向上遍历父执行上下文,直到全局执行上下文。

然而,传统的执行上下文描述中缺少了一个关键元素:指向函数调用对象的指针,即 thisthis 在函数执行的一刹那被确定,并且其值依赖于函数的调用方式。

this 的不同调用方式

  1. 对象的方法被调用

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

    	const obj = {
    
    	    name: 'Alice',
    
    	    greet: function() {
    
    	        console.log(`Hello, ${this.name}!`);
    
    	    }
    
    	};
    
    	 
    
    	obj.greet(); // 输出: Hello, Alice!
    
  2. 普通函数被调用

    当函数作为普通函数(非方法)被调用时,在严格模式(strict mode)下,this 的值为 undefined;在非严格模式下,this 指向全局对象(在浏览器中是 window)。

    	function greet() {
    
    	    console.log(this);
    
    	}
    
    	 
    
    	greet(); // 在非严格模式下输出: window
    
    	 
    
    	'use strict';
    
    	greet(); // 在严格模式下输出: undefined
    
  3. 构造函数调用

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

    	function Person(name) {
    
    	    this.name = name;
    
    	}	 
    	const alice = new Person('Alice');
    
    	console.log(alice.name); // 输出: Alice
    
  4. 指定 this 的调用方式

    JavaScript 提供了几种方法来显式设置 this 的值,包括 callapply 和 bind 方法。

    • call 方法接受一个参数作为 this 的值,并依次接受其他参数作为函数参数。
    • apply 方法与 call 类似,但接受的是一个参数数组。
    • bind 方法返回一个新的函数,其 this 值被永久绑定到提供的值上。
    	function greet(greeting) {
    
    	    console.log(`${greeting}, ${this.name}!`);
    
    	}
    
    	 
    
    	const obj = { name: 'Bob' };
    
    	 
    
    	greet.call(obj, 'Hello'); // 输出: Hello, Bob!
    
    	greet.apply(obj, ['Hi']); // 输出: Hi, Bob!
    
    	 
    
    	const boundGreet = greet.bind(obj);
    
    	boundGreet('Howdy'); // 输出: Howdy, Bob!
    

牛刀小试

var name="刀郎";
// 定义一个对象 a
var a={
 // 对象 a 的属性 name,赋值为 "薛之谦"
 name:"薛之谦",
 // 定义对象 a 的方法 func1,用于打印对象 a 的 name 属性
 func1:function(){
     console.log(this.name);
 },
 // 定义对象 a 的方法 func2
 func2:function(){
    // 使用 setTimeout 函数,在 1000 毫秒后执行一个函数
    setTimeout(function(){
     // 调用对象 a 的 func1 方法
     this.func1()
    // 使用 call 方法将 this 指向对象 a
    }.call(a),1000)
 }
}

// 调用对象 a 的 func2 方法,1000 毫秒后会输出 "薛之谦"
a.func2();

这里使用了.call()方法显式绑定了this指向a对象所以调用a.func2()输出咱们的老薛,如果没有显式绑定会发生什么呢?万变不离其宗,a.func2()是普通函数调用,当 func2 被调用时,它会触发 setTimeout 函数,在 1000 毫秒后执行匿名函数。由于没有使用 .call(a) 来绑定 this,匿名函数中的 this 会指向 window 对象。而 window 对象通常没有 func1 方法,所以调用 this.func1() 会导致错误,因为 window.func1 是 undefined。所以当我们不明白this到底指向谁时,理清this的使用场景就豁然开朗了。

结论

this 在 JavaScript 中的行为是动态的,它的值取决于函数的调用方式。理解 this 的工作原理对于编写可预测和可维护的 JavaScript 代码至关重要。通过掌握对象的方法调用、普通函数调用、构造函数调用以及显式设置 this 的方法,我们可以更好地控制 this 的指向,从而编写出更加健壮和灵活的代码。