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

文章目录

    • 一、什么是作用域
    • 二. 全局作用域、函数作用域、块级作用域
      • 全局作用域
      • 函数作用域
        • 注意 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代码的流程如下图所示:
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【运维工程师学习】Debian安装

【运维工程师学习】Debian安装 1、界面说明2、选择语言3、等待探测并挂载安装介质完成4、设置主机名称、用户信息5、磁盘分区6、创建分区7、最终分区为8、安装ssh9、查看ssh状态10、查看内存大小11、查询系统磁盘及分区情况12、查看各磁盘及分区剩余13、查看ip地址 选择镜像文件…

【Linux】进程信号之信号的产生

进程信号 一 一、信号入门1、信号的一些特性2、信号的处理方式信号捕捉初识 3、Linux下的信号 二、信号的产生1、通过终端按键产生信号2、调用系统函数向进程发信号a、kill函数b、raise函数c、abort函数 3. 由软件条件产生信号4、硬件异常产生信号 结语 一、信号入门 什么是信号…

PS扣签名

打开Photoshop CS6&#xff0c;依次点击“文件”-“打开”&#xff0c;把签名照导入进来。 在“选择”菜单下点击“色彩范围”。 此时鼠标形状变成了一支笔&#xff0c;点击签名上黑色的地方&#xff0c;适当调整颜色容差&#xff0c;点击“确定”完成选择。 按住CtrlJ组…

1. Netty核心功能与线程模型详解

Netty 1. 认识Netty2. 第一个Netty程序3. Netty组件3.1 EventLoop和EventLoopGroupChannelChannelPipeline和ChannelHandlerContextChannelPipeline中ChannelHandlerChannelPipeline 本文是按照自己的理解进行笔记总结&#xff0c;如有不正确的地方&#xff0c;还望大佬多多指点…

开源免费的多数据库工具Chat2DB

Chat2DB v1.0.11使用 当前使用的版本为1.0.11&#xff0c;目前已经更新到2.0.0版本。 一.Chat2DB介绍 Chat2DB 是一款开源免费的多数据库客户端工具。 能够将自然语言转换为SQL&#xff0c;也可以将SQL转换为自然语言。 支持windows、mac本地安装&#xff0c;也支持服务器端…

哈工大计算机网络课程网络层协议详解之:Internet路由BGP协议详解

哈工大计算机网络课程网络层协议详解之&#xff1a;BGP协议详解 在之前的网络层协议中&#xff0c;我们介绍了Internet网络两个自治系统内的路由协议&#xff1a;RIP协议和OSPF协议。这两个协议应该来说是自治系统内协议的两个代表性协议&#xff0c;前一个基于距离向量路由算…

vue项目业务实现,视频监控-文件流,大屏适配方案(v-scale-screen),websocket前端

最近把以前的业务场景及解决方案整理了一下&#xff0c;具体实现的工具如下&#xff1a; 监控-视频文件流>video.js videojs-contrib-hls 大屏适配方案> v-scale-screen websocket>sockjs-client webstomp-client 视频监控-文件流 使用方法 下载video插件&#xf…

异步交互技术Ajax

Ajax 概念&#xff1a;Asynchronous JavaScr And XML 异步的JavaScript和XML作用&#xff1a; 数据交换&#xff1a;通过Ajax可以给服务器发送请求&#xff0c;并获取服务响应的数据异步交互&#xff1a;可以在不重新加载整个页面的情况下&#xff0c;与服务器交换数据并更新部…

第3章 创建项目并初始化业务数据(过程记录)

项目声明和依赖 ECommerceRecommendSystem [pom.xml] 公用的声明、依赖、插件 properties 声明 log4g&#xff1a;处理日志的框架&#xff08;日志的具体实现&#xff09;sel4g&#xff1a;简单日志门面&#xff08;简单日志的接口&#xff09;mongodb-spark&#xff1a;Mong…

用隐私换便利,,,,,,您配吗?

用隐私换便利,您配吗&#xff1f; 引言 近日&#xff0c;某高校毕业生在校期间窃取学校内网数据&#xff0c;收集全校学生个人隐私信息的新闻引发了人们对互联网生活中个人信息安全问题的再度关注。在大数据时代&#xff0c;算法分发带来了隐私侵犯&#xff0c;在享受消费生活…

TX Text Control .NET for WPF 31.SP3 Crack

.NET WPF 应用程序的文档处理 将文档编辑、创建和 PDF 生成添加到您的 WPF 应用程序中。 视窗用户界面 功能齐全的文档编辑器 TX Text Control 是一款免版税、完全可编程的丰富编辑控件&#xff0c;它在专为 Visual Studio 设计的可重用组件中为开发人员提供了广泛的文字处理功…

Matlab学习-轨迹热力图绘制

Matlab学习-轨迹热力图绘制 参考链接&#xff1a; MathWork-scatter函数使用 问题需求&#xff1a; 需要将轨迹上的点另一维信息同时显示在图上&#xff0c;比如横纵向误差等&#xff0c;这个时候画轨迹与误差的热力图就能很好同时反应位置和定位误差之间的关系&#xff1b;…

CSS圆角进化论

CSS圆角发展过程 大致经历了3个阶段&#xff0c;包括&#xff1a; 背景图片实现圆角CSS2.0标签模拟圆角CSS3.0圆角属性&#xff08;border-radius属性)实现圆角 ☛背景图片实现圆角&#xff1a;使用背景图片实现圆角的方式很多&#xff0c;实现的方式和圆角的切图方式关系密…

高级篇十六、多版本并发控制(重要)

目录 1、什么是MVCC2、快照读与当前读2.1 快照读2.2 当前读 3、复习3.1 隔离级别3.2 隐藏字段、Undo Log版本链 4、MVCC实现原理之ReadView4.1 什么是ReadView&#xff1f; 1、什么是MVCC MVCC &#xff08;Multiversion Concurrency Control&#xff09;&#xff0c;多版本并…

电脑出现0xC1900101错误怎么办?

在更新或安装Windows操作系统时&#xff0c;有时系统会提示出现了0xC1900101错误。这个错误的出现通常是源于与驱动程序相关的错误所致。那么当电脑出现0xC1900101错误时该怎么办呢&#xff1f; 为什么会出现错误代码0xC1900101&#xff1f; 通常情况下&#xff0c;有以下几个…

error: exportArchive: No signing certificate \“Mac Development\“ found

error: exportArchive: No signing certificate “Mac Development” found UNIAPP打包又遇到这个问题了, 证书过期续期的时候又遇到这个问题了(之前遇到过解决了,时间长忘了),记录一下,报错信息 error: exportArchive: No signing certificate \"Mac Development\"…

5.8.8 TCP流量控制

5.8.8 TCP流量控制 计算机网络的流量控制实际上是调节发送方的速率使得接收方能够及时处理的一个过程。 在TCP中采用的是大小可变的滑动窗口的方式进行流量控制&#xff0c;窗口大小的单位是字节。 如图 根据接收方的接收能力&#xff0c;通过接收窗口rwnd可以实现一个端到端…

C语言王国探险记之字符串+注释

王国探险记系列 文章目录&#xff08;3&#xff09; 前言 一&#xff0c;什么是字符串呢&#xff1f; 1&#xff0c;那C语言是怎么表示字符串的呢? "hello world.\n" 2&#xff0c;证明字符串的结束标志是一个 \0 的转义字符 3&#xff0c;证明字符串的结束标…

基于springboot+Redis的前后端分离项目(二)-【黑马点评】

&#x1f381;&#x1f381;资源文件分享 链接&#xff1a;https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA?pwdeh11 提取码&#xff1a;eh11 商户查询缓存&#xff0c;缓存更新策略&#xff0c;缓存穿透 商户查询缓存a.什么是缓存1.为什么要使用缓存2.如何使用缓存 b.添加商…

微信小程序分包

原生小程序分包和 uniapp 小程序分包差不多。 分包只是在原有代码结构上&#xff0c;指定那个文件夹属于分包&#xff0c;所以&#xff0c;页面地址原本路径不会修改。 比如&#xff0c;将pages/mine设为分包&#xff0c;mine下面的有页面地址pages/mine/index/index&#xf…