编程语言
首页 > 编程语言> > 深入研究 JavaScript:词法范围、闭包和“this”关键字

深入研究 JavaScript:词法范围、闭包和“this”关键字

作者:互联网




介绍


JavaScript 被广泛称为 Web 编程语言,被证明是创建交互式用户界面和支持后端功能的基本工具。当我们深入研究 JavaScript 世界时,我们会遇到一些有趣的概念,例如词法作用域、闭包和经常被误解的this关键字。在本文中,我的目标是阐明这三个概念,并演示词法作用域如何与闭包和关键字的动态解析交织在一起this。


理解词法范围


当我们谈论 JavaScript 中的词法作用域时,我们指的是如何根据变量和函数在代码物理结构(例如嵌套块或函数)中的位置来访问变量和函数。这个概念很重要,因为JavaScript 使用词法(静态)作用域在写入时解析变量名称。

词汇范围:

关心变量和函数的定义位置。

考虑块或函数的嵌套。

根据变量和函数在此嵌套结构中定义的位置确定范围,而不仅仅是代码在文件中实际存在的位置。

这导致了以下词法范围规则:

内部函数或块可以访问在其外部函数或块中声明的变量。

内部作用域可以在同一作用域或内部作用域内重新分配使用外部作用域声明的变量let或从外部作用域声明的变量。var

内部作用域不能通过重新分配整个变量引用来直接修改const外部作用域中声明的变量。但是,如果声明的对象和数组的属性是可变的,const则它们可以修改这些属性。

当引用变量时,JavaScript 从当前作用域开始查找变量或函数,然后逐步遍历外部作用域,直到到达全局作用域,从而允许对标识符进行分层解析。

让我们考虑以下示例:
function greet() {
  const message = 'Hello';

  function sayHello(name) {
    console.log(`${message}, ${name}!`);
  }

  sayHello('John'); // output: Hello John!
}

greet();

在此示例中,message被定义在greet函数的作用域内,允许sayHello函数根据词法作用域规则访问它。greet调用时,它sayHello使用参数“ John ”进行调用,导致输出“ Hello, John! ”。

现在,下一件怎么样?
function greet() {
  let greeting = 'Hello'; 

  const person = { name: 'Alice' }; 

  function updateGreeting(newGreeting) {
    greeting = newGreeting; 
    console.log(greeting); // Output: Updated greeting
  }

  function updateName(newName) {
    person.name = newName; // Modifying a property of an object declared with const
    console.log(person.name); // Output: Bob
  }

function updatePerson(newName) {
    // Attempting to reassign the entire object reference for a const variable (will result in an error)
    person = { name: newName }; // Error: Assignment to constant variable
  }

  updateGreeting('Updated greeting');
  updateName('Bob');
  updatePerson('Jane');
}

greet();
  
让我们分解一下三个内部函数的行为:

updateGreeting:此函数采用新的问候语作为参数,并更新greeting用 声明的变量let。由于let变量是可变的,我们可以在同一作用域或内部作用域内重新分配它们的值。因此,我们得到的输出是更新后的值“ Updatedgreeting ”。

updateName:该函数采用新名称作为参数,并修改用 声明的对象name的属性。如果属性本身是可变的,则关键字允许修改属性。因此,该函数成功更新并记录新名称“ Bob ”。personconstconst

updatePerson:该函数尝试将对象的整个对象引用重新分配person给具有不同名称的新对象。但是,由于person是用 声明的const,因此在初始化后不能重新分配其引用。尝试重新分配对象引用会导致错误(TypeError: Assignment to constant variable)。

现在我们对词法作用域规则有了深入的了解,让我们深入研究源于它的最强大的概念之一:闭包。


探索闭包


JavaScript 中的闭包就像由函数和创建它的环境形成的动态二重奏。它们与词法作用域密切相关,允许函数即使在外部函数完成执行后也可以保留对其周围变量的可访问性。

看看下面的代码片段:
function outerFunction() {
  const message = 'Hello';

  function innerFunction() {
    console.log(message); // Accessing message from outerFunction
  }

  return innerFunction;
}

const myClosure = outerFunction();
myClosure(); // Output: Hello

在此示例中,使用 中定义的变量innerFunction形成一个闭包。即使在完成执行后,由于关闭机制,仍保留对 的访问,导致输出“ Hello ”。messageouterFunctionouterFunctionmyClosuremessage

需要注意的是,闭包形成发生在内部函数的定义期间,而不是在运行时期间。

现在我们已经探索了闭包如何在词法作用域的上下文中工作,让我们将注意力转移到另一个关键方面:关键字的解析this。


解码this关键字


在 JavaScript 中,this关键字是一个特殊的标识符,它指的是调用函数的当前执行上下文。它在确定访问属性和方法的上下文方面起着至关重要的作用。

的分辨率this取决于所使用的函数类型,特别是常规函数和箭头函数之间。

考虑以下示例:
const obj = {
  name: 'Alice',
  greet: function() {
    return function() {
      return this.name;
    };
  },
  arrowGreet: function() {
    return () => this.name;
  }
};

const regularGreet = obj.greet(); // Returns a regular function
const arrowGreet = obj.arrowGreet(); // Returns an arrow function

console.log(regularGreet()); // Output: undefined
console.log(arrowGreet()); // Output: Alice

该greet方法返回一个尝试访问 的常规函数this.name​​,但由于它是常规函数,因此this其内部引用了全局范围(或​​在严格模式下为undefined),导致输出为undefined。

另一方面,该arrowGreet方法返回一个箭头函数。箭头函数没有自己的this上下文;相反,它们this从周围的词法范围继承,在本例中是对象obj。因此,this.name箭头函数内部正确引用了对象name的属性obj,导致输出为“ Alice ”。

因此,我们可以观察到:

箭头函数维持 的词法范围this。

常规函数有自己的this上下文,具体取决于它们的调用方式。

结论:

在 JavaScript 基本概念的旅程中,我们探索了词法作用域、闭包和关键字的动态解析的工作原理this。

词法作用域强调基于代码结构的变量和函数可访问性,提供了一种管理作用域和定义函数行为的结构化方法。

闭包作为函数及其环境之间的动态对,使函数即使在其外部函数执行完成后也能保留对变量的访问,从而为我们的代码添加了一层灵活性和持久性。

关键字的解析this阐明了函数运行的上下文。

通过掌握这些概念,JavaScript 开发人员可以更深入地了解该语言的内部工作原理,从而编写出更高效、更健壮的代码。

 

标签:JavaScript,this,Web
来源: