详解JS 作用域与作用域链、IIFE模式、js执行过程

news2025/4/17 9:09:03

文章目录

    • 一、什么是作用域
    • 二. 全局作用域、函数作用域、块级作用域
      • 全局作用域
      • 函数作用域
        • 注意 if、for循环、while循环变量
      • 块级作用域
    • 二、什么是作用域链
      • 1. 什么是自由变量
      • 2.什么是作用域链
      • 3. 关于自由变量的取值
    • 三、IIFE模式
      • 由来
      • 语法
        • 基本语法
        • 带参
    • 四、JavaScript 执行过程
      • 编译阶段
      • 执行阶段
      • 调用栈
      • js执行流程图解

前言:在学习模块化的时候,遇到IIFE模式为模块提供了私有空间,涉及到闭包,以及作用域,所以来复习一下相关内容。

一、什么是作用域

作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。可能这两句话并不好理解,我们先来看个例子:

function outFun() {
    var temp = "内层变量";
}
outFun();//要先执行这个函数,否则根本不知道里面是啥
console.log(temp); // Uncaught ReferenceError: inVariable is not defined

从上面的例子可以体会到作用域的概念,变量 temp在全局作用域没有声明 ,所以在全局作用域下取值会报错。我们可以这样理解:作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是 隔离变量,不同作用域下同名变量不会有冲突。

ES6 之前 JavaScript 没有块级作用域,只有全局作用域函数作用域。ES6 的到来,为我们提供了块级作用域,可通过新增命令 let 和 const 来体现。

二. 全局作用域、函数作用域、块级作用域

作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行

全局作用域

在代码中任何地方都能访问到的对象拥有全局作用域,一般来说以下几种情形拥有全局作用域:

  1. 最外层函数 和在最外层函数外面定义的变量拥有全局作用域
  • 全局作用域访问不了函数作用域的变量等
  • 函数作用域能访问全局变量等
var outVariable = "我是最外层变量"; //最外层变量
function outFun() { //最外层函数
	console.log(outVariable)
    var inVariable = "内层变量";
    function innerFun() { //内层函数
        console.log(inVariable);
    }
    innerFun();
}
console.log(outVariable); //最外层变量
outFun(); //最外层函数
console.log(inVariable); //内层变量在外层访问不到 inVariable is not defined
innerFun(); //内层函数在外层访问不到 innerFun is not defined
  1. 所有末定义直接赋值的变量自动声明为拥有全局作用域
function outFun2() {
    variable = "未定义直接赋值的变量";
    var inVariable2 = "内层变量2";
}
outFun2();//要先执行这个函数,否则根本不知道里面是啥
console.log(variable); //未定义直接赋值的变量拥有全局作用域
console.log(inVariable2); //inVariable2 is not defined
  1. 所有 window 对象的属性拥有全局作用域

一般情况下,window 对象的内置属性都拥有全局作用域,例如 window.name、window.location、window.top 等等。

全局作用域有个弊端:如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样就会 污染全局命名空间, 容易引起命名冲突。

函数作用域

也就是局部作用域,是指声明在函数内部的变量,和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部。

function doSomething(){
    var blogName="浪里行舟";
    function innerSay(){
        alert(blogName);
    }
    innerSay();
}
alert(blogName); //脚本错误
innerSay(); //脚本错误

注意 千万不要以为大括号封起来就一定是局部作用域

注意 if、for循环、while循环变量

在javascript里if内部定义的变量、for、while循环相关变量 就会变为当前执行环境的变量,比如

var a = 'jack';
if(true) {
    var a = 'frank';
}
console.log(a); //frank

for(var i = 0;i<3;i++) {
    break;
}
console.log(i); //0
k = 5;
while(k>1) {
    k--;
    var d = 10;
}
console.log(k); //4
console.log(d); //10

块级作用域

ES6提出块级作用域,可通过新增命令 let 和 const 声明,所声明的变量在指定块的作用域外无法被访问。块级作用域在如下情况被创建:

  • 在一个函数内部
  • 在一个代码块 { } 内部

特点:

  1. let声明只在声明所在的块级作用域内有效
{
  var a = 1;
  let b = 2
}
console.log(a); //1
console.log(b); //Uncaught ReferenceError: b is not defined
  1. 声明变量不会提升到代码块顶部
console.log(a); //undefined
var a = 10
//上面的相当于下面
var a 
console.log(a); //undefined
a = 10

//let定义的变量没有变量提升,直接报错
console.log(b) //Uncaught ReferenceError: Cannot access 'b' before initialization
let b = 20
  1. 不可重复声明
// 只要let定义的变量,就不能再以任何形式定义,会报错
let test = 'aaa'
//let test = 'bbb'
var test = 'ccc'
  1. 循环中的块级作用域绑定

数据每一个方法打印当前索引

var arr = []
for(var i=0;i<10;i++){
  arr.push(function(){
    console.log(i)
  })
}
arr.forEach(function(item){
  item(); //10,10,10,10......
})

为了解决i都变成10的这个问题,以前用的解决办法是立即调用函数表达体IIFE

var arr = []
for(var i=0;i<10;i++){
  arr.push((function(val){
    return function(){
      console.log(val)
    }
  })(i))
}
arr.forEach(function(item){
  item(); //0,1,2,3......
})

而现在es6 let和const提供的块级绑定让我们无须再这样折腾

let声明模仿上面的IIFE所做的一切来简化循环过程。

var arr = []
for(let i=0;i<10;i++){
  arr.push(function(){
    console.log(i)
  })
}
arr.forEach(function(item){
  item(); //0,1,2,3......
})

二、什么是作用域链

1. 什么是自由变量

首先认识一下什么叫做 自由变量 。如下代码中,console.log(a)要得到 a 变量,但是在当前的作用域中没有定义 a(可对比一下 b)。当前作用域没有定义的变量,这成为 自由变量 。自由变量的值如何得到 —— 向父级作用域寻找(注意:这种说法并不严谨,下文会重点解释)。

var a = 100
function fn() {
    var b = 200
    console.log(a) // 这里的a在这里就是一个自由变量
    console.log(b)
}
fn()

2.什么是作用域链

当你要访问一个变量时,首先会在当前作用域下查找,如果当前作用域下没有查找到,则返回上一级作用域进行查找,直到找到全局作用域,这个查找过程形成的链条叫做作用域链

3. 关于自由变量的取值

关于自由变量的值,上文提到要到父作用域中取,其实有时候这种解释会产生歧义。

var x = 10
function fn() {
  console.log(x)
}
function show(f) {
  var x = 20
  f()
}
show(fn)

在 fn 函数中,取自由变量 x 的值时,要到哪个作用域中取?——要到创建 fn 函数的那个作用域中取。

要到 创建这个函数 的那个域,而不是调用的函数。

比如:

var a = 10
function fn() {
  var b = 20
  function bar() {
    console.log(a + b) //30
  }
  return bar
}
var x = fn() //这里得到的是bar函数
b = 200
x() //30
// a先在当前作用域bar函数中找,没有,则向父级作用域fn中找,没有,再向上找到全局作用域,var a = 10,获取到a;b先在当前作用域bar函数中找,没有,则向父级作用域fn中找,得到b的值20,所以a+b = 30

如果fn中没有var b = 20,则结果是210

三、IIFE模式

由来

(immediately invoked function expression)立即调用的函数表达式
IIFE的目的是为了隔离作用域,防止污染全局命名空间
实际上,IIFE的出现是为了弥补JS在scope方面的缺陷:JS只有全局作用域(global scope)、函数作用域(function scope),从ES6开始才有块级作用域(block scope)。对比现在流行的其他面向对象的语言可以看出,JS在访问控制这方面是多么的脆弱!那么如何实现作用域的隔离呢?在JS中,只有function才能实现作用域隔离,因此如果要将一段代码中的变量、函数等的定义隔离出来,只能将这段代码封装到一个函数中。
在我们通常的理解中,将代码封装到函数中的目的是为了复用。在JS中,当然声明函数的目的在大多数情况下也是为了复用,但是JS迫于作用域控制手段的贫乏,我们也经常看到只使用一次的函数:这通常的目的是为了隔离作用域了!既然只使用一次,那么立即执行好了!既然只使用一次,函数的名字也省掉了!这就是IIFE的由来。

语法

基本语法

//最常用
(function () {
  // code
})();

(function(){  
  // code
}());

!function () {
  // code
}();

带参

var a = 2;
(function IIFE(global){
    var a = 3;
    console.log(a); // 3
    console.log(global.a); // 2
})(window);
 
console.log(a); // 2

循环中的块级作用域绑定中,获取每个真正的 i 最初的解决办法就是自调用函数

有了ES6的块级作用域,则将替代IIFE模式

四、JavaScript 执行过程

JavaScript 执行过程分为两个阶段,编译阶段执行阶段。在编译阶段 JS 引擎主要做了三件事:词法分析、语法分析和代码生成;编译完成后 JS 引擎开始创建执行上下文(JavaScript 代码运行的环境),并执行 JS 代码。

编译阶段

对于解释型语言(例如:JavaScript )来说,在JavaScript代码被执行之前,首先需要进行代码的解析

  1. 编译阶段完成两件事情:创建执行上下文和生成可执行代码
  2. 执行上下文就包括变量环境和词法环境和this指向等,创建执行上下文的过程:
    如果是普通变量的话,js引擎会将该变量添加到变量环境中并初始化为undefined
    如果是函数声明的话,js引擎会将函数定义添加到变量环境中,然后将函数名执行该函数的位置(内存)
  3. 接着,js引擎就会把其他的代码编译为字节码,生成可执行代码

编译先创建上下文并传创建变量环境,词法环境,可执行代码,将执行上下文压入执行栈中。执行当前上下文环境可执行代码

变量环境: 通过var声明或者function(){}声明的变量存在这里
词法环境: 通过let, const, try-catch创建的变量存在这里
可执行代码:变量声明提前后剩下的代码

  1. 词法分析
    JS 引擎会将我们写的代码当成字符串分解成词法单元(token)
  2. 语法分析
    语法分析阶段会将词法单元流(数组),也就是上面所说的token, 转换成树状结构的 “抽象语法树(AST)”
  3. 代码生成
    将AST转换为可执行代码的过程称为代码生成

执行阶段

js引擎开始执行可执行代码,按照顺序一行一行执行,当遇到函数或者变量时,会在变量环境中寻找,找不到的话就会报错。全局执行上下文首先入栈,遇到函数执行上下文则压入栈中,后进先出的方式执行。

调用栈

调用栈,是一种拥有 LIFO(后进先出)数据结构的栈,被用来存储代码运行时创建的所有执行上下文。javascript利用栈这种数据结构管理执行上下文。

当 JavaScript 引擎第一次遇到你的脚本时,它会创建一个全局的执行上下文并且压入当前执行栈。每当引擎遇到一个函数调用,它会为该函数创建一个新的执行上下文并压入栈的顶部

引擎会执行那些执行上下文位于栈顶的函数。当该函数执行结束时,执行上下文从栈中弹出,控制流程到达当前栈中的下一个上下文。

在没有块级作用域之前,只有变量环境

var a = 0;
function add(a + b) {
    returan a + b;
}
function sum(c) {
    return c + add(2, 3);
}
sum(a);

调用栈:
add函数执行上下文-> 变量环境:a = 2; b = 3
sun函数执行上下文-> 变量环境:c = 0
全局执行上下文-> 变量环境:a=0; function add(){}; function sum(){}

可以看到调用栈如果不能有序退出那么就会造成栈溢出,这种情况一般会发生在递归调用结束条件有问题情况等等。

ES6引入块级作用域,引入了词法环境。我们可以简单地认为,var以及function声明的变量加入到环境变量,而let以及const声明的变量加入到词法环境当中。

var a = 0
let b = 1
function foo() {
    var a = 1
    let b = 2
    if (true) {
        let b = 3
        console.log(a, b)
    }
}
foo() // 1, 3

调用栈:
全局执行上下文-> 变量环境:a=0; function foo(){}; 词法环境: b=1
foo函数执行上下文-> 变量环境:a = 1; 词法环境:b = 2;b=3

js执行流程图解

function getName() {
    const year = getYear();
 
    const name = 'Lynn';
    console.log(`${name} ${year} years old this year`);
}
 
function getYear() {
    return 18;
}
 
getName(); 

浏览器执行javascript代码的流程如下图所示:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/717107.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 数据结构B 树

目录 1. 常见的搜索结构 2. B树概念 3. B-树的插入分析 4. B-树的插入实现 4.1 B-树的节点设计 4.2 插入key的过程 4.4 B-树的简单验证 4.5 B-树的性能分析 4.6 B-树的删除 5. B树和B*树 5.1 B树 5.2 B*树 5.3 总结 6. B-树的应用 6.1 索引 6.2 MySQL索引简介 6…

浅谈建筑项目中的智能照明系统的设计

张心志 安科瑞电气股份有限公司 上海嘉定 201801 【摘要】&#xff1a;建筑智能照明工程中智能照明控制系统发展迅速&#xff0c;具有舒适性和节能性两方面优势。智能照明控制系统已经处于模块化高速发展阶段&#xff0c;如今更好的控制方案成为制约系统发展的瓶颈。文章在研…

工作总是忙不完是什么原因?时间管理软件帮你解决问题

随着市场竞争的激烈&#xff0c;有不少上班族都感受到了工作的压力&#xff0c;每天越来越多的工作任务需要完成。于是我们经常会听到这样的抱怨&#xff1a;“工作太多了&#xff0c;总是忙不完&#xff01;”那么工作总是忙不完是什么原因呢&#xff1f;其实一般来说工作总是…

GB50255-2014电气装置安装工程电力变流设备施工及验收规范

为了保证电力变流设备安装工程的施工质量&#xff0c;促进施工技术的提高&#xff0c;保证电力变流设备的安全稳定运行&#xff0c;制定本规范。 本规范适用于电力系统中除高压直流输电和柔性交流输电以外的电力变换设备的施工、调试及验收。 电力变流设备的施工、调试及验收…

linux epoll/select使用区分和实例对比

Linux内核poll&#xff0c;ppoll&#xff0c;epoll&#xff0c;select代码位置&#xff1a; poll&#xff0c;ppoll&#xff0c;select相关内核实现在在fs/select.c中; epoll_ctl和epoll_wait相关函数在fs/eventpoll.c中 epoll实测不支持监听普通文件&#xff0c;select可以…

医院中医临床护理中医理论考试题库及答案

本题库是根据最新考试大纲要求&#xff0c;结合近年来考试真题的重难点进行汇编整理组成的全真模拟试题&#xff0c;考生们可以进行专项训练&#xff0c;查漏补缺巩固知识点。本题库对热点考题和重难点题目都进行了仔细的整理和编辑&#xff0c;相信考生在经过了针对性的刷题练…

测试员拒绝当“背锅侠”,软件质量不是“测”出来的!

以前有个朋友跟我说&#xff1a;做测试这行就是在隐忍中负重前行。因为测试是软件发布前的最后一个环节&#xff0c;被称作质量出口儿&#xff0c;测试环节之前所有未做好的工作所带来的不良后果&#xff0c;似乎都能被轻松甩锅给测试人员。软件一旦出现问题&#xff0c;我们经…

django疫情防控常态化下疫情物资管理可视化系统-计算机毕设 附源码73893

django疫情防控常态化下疫情物资管理可视化系统 摘 要 信息化社会内需要与之针对性的信息获取途径&#xff0c;但是途径的扩展基本上为人们所努力的方向&#xff0c;由于站在的角度存在偏差&#xff0c;人们经常能够获得不同类型信息&#xff0c;这也是技术最为难以攻克的课题。…

YOLOv5改进系列(13)——更换激活函数之SiLU,ReLU,ELU,Hardswish,Mish,Softplus,AconC系列等

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制

javaWeb中的Ajax_待后期增加

前言&#xff1a; Ajax是一种在JavaWeb开发中常用的技术&#xff0c;通过它可以实现异步通信和动态加载数据&#xff0c;提升用户体验。 正文&#xff1a; 首先我们得明白异步通信&#xff0c;客户端发出请求后可以继续执行其他操作 由于原生的Ajax过于复杂 so&#xff1a; …

FastDFS【SpringBoot操作FastDFS、复习】(三)-全面详解(学习总结---从入门到深化)

目录 SpringBoot操作FastDFS 复习&#xff1a; SpringBoot操作FastDFS 由GitHub大牛tobato在原作者YuQing与yuqih发布的JAVA客户端基 础上进行了大量重构工作&#xff0c;并于GitHub上发布了FastDFS-Client1.26.5。 主要特性 1 对关键部分代码加入了单元测试&#xff0c;便于…

leetcode 21.合并两个有序链表

⭐️ 往期相关文章 &#x1f4ab;链接1&#xff1a;链表中倒数第k个结点(快慢指针问题) &#x1f4ab;链接2&#xff1a;leetcode 876.链表的中间结点(快慢指针问题) &#x1f4ab;链接3&#xff1a;leetcode 206.反转链表 &#x1f4ab;链接4&#xff1a;leetcode 203.移除链…

蓝牙Beacon+Loa网关推出人员隔离监管解决方案

三年新冠已经过去&#xff0c;以空间换时间的防疫策略让疑似、确诊、有接触史人员得以有效控制&#xff0c;其中居家隔离的防疫措施对新冠的防控是行之有效的。 不过&#xff0c;在“画地为牢”的隔离区也有些需隔离观察人员私自外出&#xff0c;对防疫工作带来相当程度上的影…

数据库性能测试报告总结模板

目录 1计划概述 2参考资料 3术语解释 4系统简介 5测试环境 6测试指标 7测试工具和测试策略 8测试数据收集 9测试结果数据以及截图 9.1Jmeter性能指标 9.2硬件指标图 10 测试结论 需要完整报告模板记得文章末尾找我哦。 1计划概述 目的&#xff1a;找出系统潜在的…

Android Studio中配置aliyun maven库

Android Studio中配置aliyun maven库 在项目的根build.gradle里面&#xff08;不是module&#xff09;buildscriptde对应位置添加配置&#xff1a; buildscript {repositories {maven {url http://maven.aliyun.com/nexus/content/groups/public/allowInsecureProtocol true…

深入了解gradio库的Interpretation模块

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

解码大数据时代的信息安全:低代码开发平台的先锋之道

前言 随着大数据时代的到来&#xff0c;信息安全成为了不可忽视的重要议题。随之而来的是技术的迅速发展和创新&#xff0c;为信息安全领域带来了挑战&#xff0c;也为其提供了前所未有的机遇。在这个背景下&#xff0c;低代码开发平台作为一种创新的解决方案崭露头角&#xff…

速看,多设备上使用 WhatsApp的最完整指南

速看&#xff01;多设备上使用 WhatsApp的最完整指南&#xff01; 在过去的几年里&#xff0c;WhatsApp一直在不断更新。最近它正在推出一项新功能&#xff1a;允许移动和非手机设备使用该服务&#xff0c;而不需要将注册的手机连接到互联网。也就是说&#xff0c;您可以不再需…

cancal 同步mysql数据到es中

1.环境&#xff1a; windocs service2012 、 jdk版本1.8 、canal版本1.5、mysql版本5.7、 注意&#xff1a;canal版本1.5需要的jdk是1.8 如果你下载的是canal1.6&#xff0c;jdk是1.8&#xff0c;那样会报错。 下载地址 Releases alibaba/canal GitHub 下载并上传到服…

CAP结构体之字节对齐

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…