作用域和闭包:
1、LHS和RHS查询
编译一段代码,需要js引擎和编译器(js引擎负责整个程序运行时所需的各种资源的调度,编译器只是js引擎的一部分,负责将JavaScript源码编译成机器能识别的机器指令,然后交给引擎运行)
编译的过程:分词/词法分析-->解析/语法分析-->代码生成
分词/词法分析:将一连串字符打断成有意义的片段,称为token,比如var a=2;分词的token有:var、a、=、2和;
解析/语法分析:编译器将一个token的流(数组)转换为一个抽象语法树(AST--Abstract Syntax Tree),它表示了程序的语法结构。
代码生成:编译器将上一步中生成的抽象语法树转换为机器指令,等待引擎执行。
执行:LHS(Left-hand Side)和RHS(Right-hand Side),是在代码执行阶段JS引擎操作变量的两种方式,区别在于LHS是赋值,而RHS是查询。
LHS和RHS获取变量的位置就是作用域。
2、什么是作用域?
作用域指程序中定义变量的作用域,它决定了当前执行代码对变量的访问权限。
全局作用域:程序的最外层作用域,一直存在。
函数作用域:函数作用域只有函数被定义时才会创建,包含在父级函数作用域/全局作用域中。
每段独立的执行代码块只能访问自己作用域和外层作用域中的变量,无法访问到内层作用域的变量。
3、作用域链:
当可执行代码内部访问变量时,会先查找本地作用域,如果找到目标变量即返回,否则会去父级作用域继续查找......一直找到全局作用域。把这种作用域的嵌套机制称为作用域链。
函数的参数也在函数作用域中。
4、词法作用域(静态作用域)与动态作用域
JavaScript使用的作用域类型。函数被定义的时候,它的作用域就已经确定了,和拿到哪里执行没有关系,因此词法作用域也被称为“静态作用域”。
var value = 1;function foo() {console.log(value);
}function bar() {var value = 2;foo();
}bar();// 结果是 ???
这段代码中,一共有三个作用域:全局作用域、foo的函数作用域和bar的函数作用域。
foo里面访问量本地作用域中没有定义的变量value,为了拿到这个变量要去foo的上层作用域中找。上层作用域是bar还是foo定义时的全局作用域呢?按照词法作用域中的定义,在foo被定义的时候,作用域已经被确定了,所以它的上层作用域是全局作用域,所以结果应当是1.
动态作用域:程序执行期间,对于一个名字x的使用,指向的是最近被调用但还没有终止且声明了x的过程中的这个声明。
var value = 1;function foo() {var value=3console.log(value);
}function bar() {var value = 2;foo();
}bar();// 结果是 3
总结:词法作用域(静态作用域)是关联在编译期间的,对于函数来说就是函数定义的位置决定了该函数所属范围;动态作用域是关联在程序执行期间的,对函数来说就是函数执行的位置决定了函数的所属的范围。
5、块级作用域
{....块级作用域}
注意用var定义的变量可以在块级作用域外访问到‘ES6使用let和const代替var关键字,来创建块级作用域。
if(true){
var a=1
}
console.log(a)//1if(true){
let a=2
}
console.log(a)//Reference
6、创建作用域
(1)函数作用域
function f(){//f的函数作用域
}
(2)块级作用域(使用let和const创建)
for(let i=0;i<4;i++){console.log(i)
//i的块级作用域
}
(3)块级作用域(使用try...catch...)
try{undefined()//强制产生异常
}catch(err){console.log(err)//TypeError:undefined is not a function
}
console.log(err)//err is not defined
(4)使用eval欺骗词法作用域(性能问题,不推荐)
function foo(str,a){eval(str)console.log(a,b)
}
var b=1
foo('var b=2',1)//1 2
eval(str)即eval(var b=2)会被当作在foo中有语句var b=2来执行,所以相当于在函数foo内部创建了一个变量b,所以打印出的b为2
但是在严格模式下,eval(...)有着自己的词法作用域,其中的声明无法修改其中的作用域:
function foo(str){"use strict"eval(str);console.log(a); // ReferenceError:a is not defined
}foo("var a = 2");
与eval(...)功能类似的其他函数:setTimeout(..) 和 setInterval(..) 的第一个参数可以是字符串,字符串的内容可以被解释为一段动态生成的函数代码。这些功能已经过时且不被提倡。不要使用他们。new Function(..) 函数的行为也很类似,最后一个参数可以接受代码字符串,并将其转为动态生成的函数(前面的参数是这个新生成的函数的形参)。这种构建函数的语法比 eval(..) 略微安全一些,但也要尽量避免使用。
(5)with(性能问题,不推荐)
eval(..) 函数如果接受了一个含有一个或多个声明的代码,就会修改其所处的词法作用域,而 with 声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域。
function foo(obj){with(obj){a = 2;}
}var o1 = {a:3
};
var o2 = {b:3
};
//分别定义了o1和o2两个对象,o1中只有属性a,o2中只有属性bfoo(o1);
console.log(o1.a); //2
//with会单独创建自己的作用域,在o1中找到了属性a,a=2即将值2赋给了属性afoo(o2);
console.log(o2.a); //undefined
//with创建独属于自己的作用域o2,o2中没有属性a,然后在上层作用域中查找,也没有找到变量a,所以会单独创建全局变量a,并赋值为2(此时是正常的 LHS 表示法查找)
console.log(a); // 2 ---不好,a 被泄露到全局作用域上了
总结:
JavaScript 中有两个机制可以“欺骗”词法作用域:eval(..) 和 with。前者可以对一段包含一个或多个声明的“代码”字符串转换为动态作用域,并借此来修改已经存在的词法作用域(在运行时)。后者本质上是通过将一个对象的引用当作作用域来处理,将对象的属性当作作用域中的标识符来处理,从而创建了一个新的词法作用域(同样是在运行时)。
这两个机制的副作用是引擎无法在编译时对作用域查找进行优化,因为引擎只能谨慎地认为这样的优化是无效的。使用这其中任何一个机制都将导致代码运行变慢。不要使用它们。
7、作用域的应用场景
模块化:
立即使用函数表达式(Immediately Invoked Function Expression简写IIFE)
//module1.js
(function(){var a=1console.log(a)
})();//module2.js
(function(){var a=2console.log(a)
})()(function(global){if(global...){//is browser}else if(global...){//is nodejs}
})(window)
8、闭包
能够访问其他函数内部变量的函数称为闭包。闭包就是函数内部定义的函数,被返回了出去并在外部调用。闭包的执行看起来是绕过了作用域的监管机制,从外部也能获取到内部作用域的信息。
8.1、闭包的应用场景(需要维护内部变量)
单例模式:
一个类只能有一个实例。实现方式是先判断实例是否存在,如果存在直接返回该实例,不存在则返回新创建的实例。单例的好处是避免重复实例化带来的内存开销:
function Singleton(){this.data='singleton'
}
Singleton.getInstance=(function(){var instance;return function(){if(instance){return instance}else{instance=new Singleton()return instance}}
})();
var sa=Singleton.getInstance()
var sb=Singleton.getInstance()
console.log(sa===sb)//true
console.log(sa.data)//'singleton'
模拟私有属性
function getPrivateAttribute(){var _name='John'var _age=24return function(){return {getName:function(){return _name},getAge:function(){return _age}}}
}
var obj=getPrivateAttribute()()//返回了{getName:function(){return _name},getAge:function(){return _age}}
console.log(obj.getName())//John
console.log(obj.getAge())//24
console.log(obj._age)//undefined
柯里化
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数。
常见的bind方法就是用柯里化的方法来实现:
Function.prototype.myBind=function(context=window){if(typeof this!=='function')throw new Error('Error')let selfFunc=thislet args=[...arguments].slice(1)return function F(){//因为返回了一个函数,可以new F(),所以需要判断if(this instance of F){return new selfFunc(...args,arguments)}else{//bind可以实现类似这样的代码f.bind(obj,1)(2),所以需要将两边的参数拼接起来return selfFunc.apply(context,arg.concat(arguments))}}
}
8.2、闭包带来的问题
内存泄露(详见JavaScript内存泄露和垃圾回收机制)
9、总结
(1)JavaScript语言层面原生支持:全局作用域、块级(函数)作用域:全局作用域程序运行就有,函数作用域只有定义函数的时候才有,它们之间是包含的关系。
(2)词法(静态)作用域和动态作用域:静态作用域指函数定义的时候就确定了作用域,动态作用域指函数执行的时候才会确定。eval(str)和with(obj)会欺骗词法作用域,eval(str)会将str作为语句执行,with则是创建独属于obj的作用域,如果找不到会继续向上层作用域查找,找不到则会创建全局变量,找到则重新给变量赋值。因此evel和with造成引擎无法在作用域查找时进行优化,造成性能问题,不推荐使用。
(3)作用域之间是可以嵌套的,嵌套关系称为作用域链。
(4)可执行代码在作用域中查询变量时,只能查询本地作用域及上层作用域,不能查找内部的作用域。JS引擎搜索变量时,会先查询本地作用域,找到立即返回,找不到会去上层作用域中查找,层层往上,直到全局作用域。
(5)有权访问另一个函数内部的函数,称为闭包。闭包的本质是利用了作用域的机制,来达到外部作用域访问内部作用域的目的。缺点是可能造成内存泄露。
相关文章:
作用域和闭包:
1、LHS和RHS查询编译一段代码,需要js引擎和编译器(js引擎负责整个程序运行时所需的各种资源的调度,编译器只是js引擎的一部分,负责将JavaScript源码编译成机器能识别的机器指令,然后交给引擎运行)编译的过程…...
Vue常见面试题?
1、说说你对SPA单页面的理解,它的优缺点是什么? SPA(single-page application)仅在Web页面初始化时加载相应的HTML、JavaScript和CSS。一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机…...

前端借助Canvas实现压缩图片两种方法
一、具体代码 1、利用canvas压缩图片方法一 // 第一种压缩图片方法(图片base64,图片类型,压缩比例,回调函数)// 图片类型是指 image/png、image/jpeg、image/webp(仅Chrome支持)// 该方法对以上三种图片类型都适用 压缩结果的图片base64与原类型相同// …...

2023年美赛C题Wordle预测问题二建模及Python代码详细讲解
更新时间:2023-2-19 相关链接 (1)2023年美赛C题Wordle预测问题一建模及Python代码详细讲解 (2)2023年美赛C题Wordle预测问题二建模及Python代码详细讲解 (3)2023年美赛C题Wordle预测问题三、四…...

【算法】双指针
作者:指针不指南吗 专栏:算法篇 🐾或许会很慢,但是不可以停下来🐾 文章目录1.双指针分类2.双指针思想3.双指针应用1.双指针分类 常见问题分类 (1) 对于一个序列,用两个指针维护一段区间, 比如快速排序。 …...

Flutter-Widget-学习笔记
Widget 是整个视图描述的基础。 参考:https://docs.flutter.dev/resources/architectural-overview Widget 到底是什么呢? Widget 是 Flutter 功能的抽象描述,是视图的配置信息,同样也是数据的映射,是 Flutter 开发框…...

easyExcel 写复杂表头
写模板 模板图片: 实体类(这里没有用Data 是因为Lombok和easyExcal的版本冲突,在导入读取的时候获取不到值) package cn.iocoder.yudao.module.project.controller.admin.goods.vo;import com.alibaba.excel.annotation.ExcelI…...
关于线程池的执行流程和拒绝策略
使用线程池的好处为: 降低资源消耗:减少线程的创建和销毁带来的性能开销。 提高响应速度:当任务来时可以直接使用,不用等待线程创建 可管理性: 进行统一的分配,监控,避免大量的线程间因互相抢…...
【李忍考研传】二、约定
因为收学生证用了好些时间,李忍把学生证都交给班长后,就赶忙跑去食堂。远远地,他就看到那个瘦小的身影立在食堂正门前,那是他们约定每天午餐集合的地方。 “你咋这么慢啊……” “害!帮班长收东西耽误了点时间&#…...
2023-2-19 刷题情况
修改两个元素的最小分数 题目描述 给你一个下标从 0 开始的整数数组 nums 。 nums 的 最小 得分是满足 0 < i < j < nums.length 的 |nums[i] - nums[j]| 的最小值。nums的 最大 得分是满足 0 < i < j < nums.length 的 |nums[i] - nums[j]| 的最大值。nu…...
LeetCode笔记:Weekly Contest 333
LeetCode笔记:Weekly Contest 333 1. 题目一 1. 解题思路2. 代码实现 2. 题目二 1. 解题思路2. 代码实现 3. 题目三 1. 解题思路2. 代码实现 4. 题目四 比赛链接:https://leetcode.com/contest/weekly-contest-333 1. 题目一 给出题目一的试题链接如下…...
元数据管理 1
1、关于元数据管理原则说法正确的是 (知识点: 三月份模拟题)A.确保员工了解如何访问和使用元数据。B.制定、实施和审核元数据标准,以简化元数据的集成和使用。C.创建反馈机制,以便数据使用者可以将错误或过时的元数据反馈给元数据管理团队。D.以上都对正…...
统计二进制中比特1的个数
快速统计比特1的数量int CountBitOnes(int32_t n) {int result 0;for(;n;result) {n & n-1;}return result; }原理很简单,n-1会将n中最靠近结尾的1减一,这样n&n-1,n中最靠近结尾的1就变成了0;假设n 0b xxxxxxxx100n - 1…...
第三方实现跑马灯和手写实现跑马灯
目录第三方实现跑马灯手写实现跑马灯手写实现跑马灯【整体代码】自己细心研究一下上述代码第三方实现跑马灯 https://vue3-marquee.vercel.app/guide.html#changes-from-v2https://evodiaaut.github.io/vue-marquee-text-component/ 手写实现跑马灯 CSS部分 <style>.m…...

React Native Cannot run program “node“问题
概述 前几天mac重装系统了,用Android studio重新构建React native项目时,报Cannot run program "node"错误。 电脑系统为macOS 12.6.3 (Monterey),M1 Pro芯片。设备信息如下图所示: 完整错误信息如下图所示ÿ…...

python基于vue微信小程序 房屋租赁出租系统
目录 1 绪论 1 1.1课题背景 1 1.2课题研究现状 1 1.3初步设计方法与实施方案 2 1.4本文研究内容 2 2 系统开发环境 4 2.1 2.2MyEclipse环境配置 4 2.3 B/S结构简介 4 2.4MySQL数据库 5 2. 3 系统分析 6 3.1系统可行性分析 6 3.1.1经济可行性 6 3.1.2技术可行性 6 3.1.3运行可行…...
ThreadPoolExecutor管理异步线程笔记
为什么使用线程池? 线程的创建和销毁都需要不小的系统开销,不加以控制管理容易发生OOM错误。避免线程并发抢占系统资源导致系统阻塞。具备一定的线程管理能力(数量、存活时间,任务管理) new ThreadPoolExecutor(int …...

MotoSimEG-VRC教程:动态输送带创建以及示教编程与仿真运行
目录 任务描述 简易输送带外部设备创建 输送带模型添加与配置 工件安装到输送带 输送带输送工件程序编写与仿真运行 任务描述 在MotoSimEG-VRC中创建1条输送带,并且能够实现将工件从输送带起始点位置处输送到结束点位置处。 简易输送带外部设备创建 在MotoS…...

PyTorch 并行训练 DistributedDataParallel完整代码示例
使用大型数据集训练大型深度神经网络 (DNN) 的问题是深度学习领域的主要挑战。 随着 DNN 和数据集规模的增加,训练这些模型的计算和内存需求也会增加。 这使得在计算资源有限的单台机器上训练这些模型变得困难甚至不可能。 使用大型数据集训练大型 DNN 的一些主要挑…...
Golang实现ttl机制保存内存数据
ttl(time-to-live) 数据存活时间,我们这里指数据在内存中保存一段时间,超过期限则不能被读取到,与Redis的ttl机制类似。本文仅实现ttl部分,不考虑序列化和反序列化。 获取当前时间 涉及时间计算,这里首先介绍如何获取…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...

el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...

Keil 中设置 STM32 Flash 和 RAM 地址详解
文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...

三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
Linux中INADDR_ANY详解
在Linux网络编程中,INADDR_ANY 是一个特殊的IPv4地址常量(定义在 <netinet/in.h> 头文件中),用于表示绑定到所有可用网络接口的地址。它是服务器程序中的常见用法,允许套接字监听所有本地IP地址上的连接请求。 关…...