JavaScript作用域详解

这篇具有很好参考价值的文章主要介绍了JavaScript作用域详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

什么是作用域

作用域类型

全局作用域

局部作用域

块级作用域

ES6之前

ES6以后

作用域链

变量提升

基础概念

优先级问题

闭包

定义

特点

使用场景

封装私有变量

延长变量周期

模块化、命名空间

缓存

ES6的作用域

const、let

块级作用域

变量提升

暂时性死区

不可重复声明

箭头函数

题外话

动态作用域与词法作用域

词法作用域

动态作用域

总结


前言

作用域是JavaScript中一个重要的概念,它决定了变量和函数在代码中的可访问性和可见性。了解JavaScript的作用域对于编写高效、可维护的代码至关重要。本文将深入介绍JavaScript作用域相关的知识点,其中包括作用域类型,作用域链,变量提升以及闭包等。

什么是作用域

在之前的文章中我们对TS中的模块进行了详细的分享,TS的在防止命名污染,冲突时采用的是命名空间,当我们看过TS命名空间的编译产物后我们就会知道,实际上其实际做法是采用JS的作用域实现一个独立的区域达到解决命名冲突和变量访问的可见性作用

JS中的作用域是一个存储变量、函数以及对象的位置,每个变量、函数和对象都被存储在一个特定的作用域中,它是指变量和函数在代码中的可访问范围,作用域决定了代码中哪些部分可以访问特定的变量和函数,通过作用域,我们可以将变量和函数封装在不同的作用域中,使其在合适的范围内可访问

JavaScript作用域详解,JavaScript,面试文档,Node.js,javascript,开发语言,前端,面试,原力计划

就像上图中的示例,Foo1无法获取Foo2中的var4变量

作用域类型

常见的作用域类型包括全局作用域(Global Scope)、局部作用域(Local Scope)和块级作用域(Block Scope),JS也不例外

全局作用域

全局作用域是在整个代码中都可访问的作用域。在浏览器环境中,全局作用域通常是指window对象;在node环境下,则是globalThis或global

在全局作用域中声明的变量或函数是全局变量或全局函数,在代码任何地方都可以访问和使用

JavaScript作用域详解,JavaScript,面试文档,Node.js,javascript,开发语言,前端,面试,原力计划

比如上面图示中的Foo4可以访问到全局作用域的变量及函数

const var1 = "var1";
const var2 = "var2";
function Foo1() {
  const var3 = "var3";
}
function Foo2() {
  const var4 = "var4";
  function Foo3() {}
}

function Foo4() {
  console.log(var1, var2, Foo1, Foo2); // var1 var2 [Function: Foo1] [Function: Foo2]
}
Foo4();

局部作用域

JS中的局部作用域一般代指函数作用域(Function Scope),它是在函数内部声明的作用域,函数内部的变量和函数只能在函数内部访问,外部无法直接访问

JavaScript作用域详解,JavaScript,面试文档,Node.js,javascript,开发语言,前端,面试,原力计划

就像上述Foo3的效果,可以访问到全局以及当前所在函数的作用域的变量及函数

const var1 = "var1";
const var2 = "var2";
function Foo1() {
  const var3 = "var3";
}
function Foo2() {
  const var4 = "var4";
  function Foo3() {
    console.log(var4, Foo3); // var4 [Function: Foo3]
  }
  Foo3();
}
Foo2();

块级作用域

块级作用域是在代码块(通常是由大括号{}包裹起来的部分)内声明的作用域。比如if(){...}、for(...){...}、try{...}等

ES6之前

在ES6之前,由于变量都是使用var声明的,所以没有块级作用域此类概念,只有全局作用域和函数(局部)作用域。那么需要模拟块级作用域如何怎么操作呢?

答案是使用立即执行函数表达式(IIFE):通过将代码包装在匿名函数中并立即执行该函数,可以创建一个独立的作用域,使得内部声明的变量在函数外部不可访问

(function () {
  var var5 = "var5";
  console.log(var5); // var5
})();
console.log(var5); // var5 is not defined

ES6以后

在es6以后,官方推出了块级作用域的概念,使用let和const关键字声明的变量具有块级作用域,它们只能在声明的代码块内部访问。

if (true) {
  let var5 = "var5";
  console.log(var5); // var5
}
console.log(var5); // var5 is not defined

作用域链

作用域链(Scope Chain)是JS用于解析标识符(变量和函数)的机制,它是由多个嵌套的作用域组成的,它决定了变量和函数的查找顺序。

上面我们说到局部作用域时谈到的Foo3可以访问全局以及当前函数作用域中的标识符这个特点就归功于作用域链这个概念。当访问一个变量时,JS引擎会先从当前作用域开始查找,如果找不到这个名称的标识符则继续向上一级作用域查找,直到找到变量或达到全局作用域为止,如果在全局作用域中仍然找不到,则认为该标识符未定义。

变量提升

变量提升的概念出现在面试题中的频率十分高,对于开发者来说也是不可忽略的重要知识点;在之前的面试题文章中的Q6和Q7两题中就出现了变量提升相关的知识点

基础概念

变量提升是JS在代码执行前将变量和函数声明提升到作用域顶部的行为,它由JavaScript引擎在代码执行前的编译阶段处理。变量提升影响了整个作用域范围内的代码,它允许我们在声明之前使用变量,但是需要注意一点:只有变量声明被提升,赋值不会提升。了解了上述概念后,我们思考下面的代码:

console.log(var6); // undefined
var var6 = 10;

上面的代码相当于

var var6;
console.log(var6); // undefined
var6 = 10;

优先级问题

当同一个作用域中同时出现同名的函数和变量时,函数提升的优先级更高,也就是说函数会在变量之上声明,参考下面的代码

var a = 10;
function a() {}
console.log(a); // 10

可以看成是

var a; // 函数a
var a; // 变量a
a = function () {};// 使用function声明函数可以看成是声明+赋值
a = 10;
console.log(a); // 10

何以见得?思考以下代码

function a() {}
var a;
console.log(a); // [Function: a]

当把a的赋值去除时,函数的赋值顺序就可以得到验证,相当于:

var a; // 函数a
var a; // 变量a
a = function () {};
console.log(a); // [Function: a]

闭包

定义

在介绍垃圾回收和内存变化的文章时,我们提到了堆栈内存管理的原理。当函数开始执行时,函数中的变量以及函数会压入栈中,那么此时如果当前的作用域中有另一个函数正在使用该作用域的变量,该变量占用的内存也不会被垃圾回收机制回收,这个现象就是闭包

换句话说,闭包是指函数能够"记住"并访问其创建时的词法环境,在函数定义的词法作用域之外执行同样适用

JavaScript作用域详解,JavaScript,面试文档,Node.js,javascript,开发语言,前端,面试,原力计划

思考以下代码

const foo = (function iife() {
  const num = 10;
  function foo() {
    return num;
  }
  return foo;
})();
console.log(foo()); // 10

上述代码中使用立即执行函数iife作为外部函数的作用域,它返回内部函数foo,而foo函数使用了iife函数中的num变量,形成了闭包,最后在iife函数的外部使用foo时依然可以访问num变量

特点

  • 即使外部函数已经执行完毕,内部函数依然可以访问外部函数作用域中的变量(当栈将函数弹出时,变量依然处于内存中)
  • 闭包可以持有对外部变量的引用,使得外部变量的值在内部函数中保持活动状态(不被垃圾回收机制回收)
  • 闭包中的内部函数可以修改并更新外部变量的值
  • 闭包的函数可以获取到创建时的整个作用域链的标识符
  • 闭包可能会导致内存泄漏,被闭包引用的变量无法被垃圾回收机制处理

使用场景

封装私有变量

JS中没有TS的private关键词,无法直接定义私有变量,但是可以通过闭包产生私有环境作用域(ES2022后引入了#关键字,用于定义私有变量,相比于使用闭包,更直观和方便)

const Animal = (function () {
  const name = "dog";
  function Animal() {}
  Animal.prototype.getName = function () {
    return name;
  };
  return Animal;
})();
console.log(new Animal().getName()); // dog

延长变量周期

延长变量的生命周期也是闭包的特性之一,该效果通过内部函数对外部作用域的可访问性实现

function delayMessage(msg) {
  return function () {
    return msg;
  };
}
const msg = delayMessage("msg");
console.log(msg()); // msg

模块化、命名空间

在TypeScript模块文章中我们使用JS中的立即执行函数实现了命名空间功能,达到模块化的效果

var moduleA = (function () {
  var privateVariable = "Hello";
  // 私有函数
  function privateMethod() {
    console.log(privateVariable);
  }
  return {
    // 公共函数
    publicMethod: function () {
      privateMethod();
    },
  };
})();

moduleA.publicMethod(); // Hello
console.log(moduleA.privateVariable); // undefined

缓存

闭包还可以用于创建缓存函数,以提高函数的执行效率。缓存函数可以将输入参数与其对应的结果保存在内部,避免重复计算

function createCache() {
  var cache = {};
  return function (key, val) {
    if (!cache[key]) {
      cache[key] = val;
      console.log("保存");
    }
    return cache[key];
  };
}

var cacheModule = createCache();
cacheModule("num1", "123"); // 保存
cacheModule("num2", "456"); // 保存
cacheModule("num1", "123");
cacheModule("num2", "456");

ES6的作用域

在ES6以后引入了const、let和箭头函数,这三者对作用域分别有什么影响呢?

const、let

块级作用域

上面说到const、let的出现奠定了JS中的块级作用域的概念,使if、for等语句中也存在作用域这一功能;然而不仅仅是条件语句、循环语句,使用{...}定义的范围都是块级作用域,这意味着在块级作用域内部声明的变量只在该作用域内有效,并且在作用域外部无法访问

{
  let msg = "hello";
  console.log(msg); // hello
}
console.log(msg); // msg is not defined

变量提升

使用let和const声明的变量不会被提升到作用域的顶部,它们只能在声明后才能被访问;这点与var不太一样

暂时性死区

暂时性死区指的是在变量声明前访问变量会抛出错误。只有在变量声明语句执行完成之后,变量才会进入有效状态,才能被访问和使用。这点效果与上面的变量提升效果一样

不可重复声明

在同一个作用域中不能被重复声明,否则会报错;而使用var定义变量时后声明的会覆盖先声明的

这三个特点可以参考下面的代码:

console.log(var1); // 在赋值前使用了变量“var1”
const var1 = 11;
let var2 = 22; // 无法重新声明块范围变量“var2”
let var2 = 22;

箭头函数

箭头函数在JavaScript中仍然与普通函数一样有函数作用域的概念

题外话

动态作用域与词法作用域

作用域的种类有两种:分别是动态作用域和词法作用域(静态作用域)。上述我们介绍的是词法作用域,什么是词法作用域?

词法作用域

词法作用域是基于代码的静态结构来确定变量的访问规则。也就是说它由变量和函数在代码中的声明位置而不是调用的位置来确定,思考下面的代码

function foo1() {
  var var1 = 10; // foo2可访问的作用域
  // 声明foo2的作用域
  return function foo2() {
    console.log(var1);// 10
  };
}
var foo2 = foo1();
function foo3() {
  var var1 = 20; // foo2不可访问的作用域
  // 执行foo2的作用域
  foo2();
}
foo3();

在foo1中声明了函数foo2,在foo3中执行foo2,可以看到,foo2取的var1是声明foo2的作用域中的变量(10)。这个现象说明JS采用的是词法作用域。

动态作用域

那么反之,如果还是上述代码,foo2取的var1是执行foo2的作用域中的变量(20),就说明语言采用的是动态作用域

总结

JavaScript作用域与变量提升是编写高质量代码所必须掌握的重要概念。本文介绍了作用域的定义、作用和目的,以及JavaScript中的不同作用域类型,包括全局作用域、函数作用域和块级作用域。我们还讨论了变量提升的概念、原理和影响范围,以及作用域链和闭包的关系。此外,还探讨了ES6的作用域,并对比了其与早期作用域的区别。最后针对动态作用域与词法作用域作了一个简单的说明。

好了,以上就是文章的全部内容了,谢谢你看到了这里,如果觉得文章不错的话,还望三连支持一下,感谢支持!文章来源地址https://www.toymoban.com/news/detail-679927.html

到了这里,关于JavaScript作用域详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 学习Node.js需要哪些JavaScript知识

    Lexical Structure ( 词法 ) JavaScript 的词法(lexical grammar)。ECMAScript 源码文本会被从左到右扫描 ,并被转换为一系列的输入元素,包括 token、控制符、行终止符、注释和空白符。ECMAScript 定义了一些、字面量以及行尾分号补全的规则。 Expressions ( 表达式 ) JavaScript 中的

    2024年02月03日
    浏览(32)
  • 运行 Node.js 与浏览器 JavaScript

    浏览器和 Node.js 都使用 JavaScript 软件语言 - 但字面上的 运行时环境 是不同的。 Node.js(又名服务器端 JavaScript)与客户端 JavaScript 有许多相似之处。它也有很多差异。 尽管两者都使用 JavaScript 作为软件语言,但我们可以重点关注一些关键差异,这些差异使两者之间的软件开发

    2024年02月09日
    浏览(38)
  • 前后端交互系统:在Node.js中运行JavaScript

    在Node.js中运行JavaScript,您需要编写适用于服务器端的代码,而不是浏览器端的代码。以下是一些示例代码,用于在Node.js中创建一个简单的HTTP服务器并在浏览器中访问它: 将上述代码保存到一个名为main.js的文件中,然后在命令行中运行 node .main.js。这将创建一个简单的HTT

    2024年02月08日
    浏览(33)
  • 【34JavaScript let 和 const】JavaScript中的“let“和“const“关键字详解:作用、用法及区别

    // 使用 let 声明变量 let x = 10; // 在同一作用域内重新赋值 x = 20; // 在不同的作用域内使用 let 声明变量 function example() { let y = 30; console.log(x); // 输出:20 console.log(y); // 输出:30 } console.log(x); // 输出:20 console.log(y); // 报错:y is not defined // 使用 const 声明常量 const PI = 3.14159; // 尝

    2024年02月08日
    浏览(32)
  • 将 Jupyter Notebook 用于 JavaScript (使用 Node.js 或 Deno 环境)

    熟悉 Python 的读者, 可能同样对 IPython 或 Jupyter 等项目 (如 Jupyter Notebook) 有所耳闻. 正如其名, IPython 项目让 Python 这门语言变得 “interactive”; 如: 交互式的 Shell 程序, 对交互式数据可视化以及 GUI 工具集的支持等等 (具体可见 IPython 项目的网站). 比如在 Notebook 中, 用户可以实现

    2024年02月07日
    浏览(33)
  • Node.js安装与配置指南:轻松启航您的JavaScript服务器之旅

    Node.js作为一个基于Chrome V8引擎的JavaScript运行时环境,已经成为现代web开发中不可或缺的重要工具之一。它的出现极大地扩展了JavaScript的应用范围,使得开发者可以利用同一种语言来编写前端和后端的代码,从而实现全栈开发。Node.js具有非阻塞I/O、事件驱动的特性,使得它能

    2024年04月10日
    浏览(40)
  • 2023年最佳JavaScript框架:React、Vue、Angular和Node.js的比较

    🎉欢迎来到Java学习路线专栏~探索2023年最佳JavaScript框架:React、Vue、Angular和Node.js的比较 ☆* o(≧▽≦)o *☆嗨~我是IT·陈寒🍹 ✨博客主页:IT·陈寒的博客 🎈该系列文章专栏:Java学习路线 📜其他专栏:Java学习路线 Java面试技巧 Java实战项目 AIGC人工智能 🍹文章作者技术和水

    2024年02月11日
    浏览(38)
  • Node.js和浏览器在JavaScript运行环境方面存在一些区别和联系

    Node.js和浏览器在JavaScript运行环境方面确实存在一些区别和联系。 首先,让我们理解一下Node.js和浏览器的运行环境。Node.js是一个基于Chrome的V8引擎的服务器端JavaScript运行环境,允许开发者在服务器端运行JavaScript代码,并且提供了一系列的内置模块,如fs模块用于文件系统操

    2024年03月14日
    浏览(37)
  • 【面试题】详解JavaScript中的Map()

     前端面试题库 ( 面试必备)              推荐:★★★★★ 地址:前端面试题库 JavaScript是一种动态、解释性的编程语言,用于开发web上的动态页面和交互式应用程序。与其他编程语言相比,JavaScript拥有更加灵活的内置数据类型,并且拥有更高级别的调试和错误处理工

    2024年02月11日
    浏览(32)
  • 完成一个有趣的Web期末大作业(html、css、javascript、MySQL、Node.js)

    题目:学校老师的要求很开放,要自己做一个感兴趣的网页,要求使用基础的html、css和javascript,后端要使用数据库。 网上都是各种管理系统,看多了觉得没啥意思,要做一个自己感兴趣的网站。近几年沉迷犬夜叉这部动漫,就花了两天的时间创建了一个犬夜叉的网站,对于

    2024年02月04日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包