作用域和闭包:
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)//Reference6、创建作用域
(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部分,不考虑序列化和反序列化。 获取当前时间 涉及时间计算,这里首先介绍如何获取…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
MySQL:分区的基本使用
目录 一、什么是分区二、有什么作用三、分类四、创建分区五、删除分区 一、什么是分区 MySQL 分区(Partitioning)是一种将单张表的数据逻辑上拆分成多个物理部分的技术。这些物理部分(分区)可以独立存储、管理和优化,…...
Kafka主题运维全指南:从基础配置到故障处理
#作者:张桐瑞 文章目录 主题日常管理1. 修改主题分区。2. 修改主题级别参数。3. 变更副本数。4. 修改主题限速。5.主题分区迁移。6. 常见主题错误处理常见错误1:主题删除失败。常见错误2:__consumer_offsets占用太多的磁盘。 主题日常管理 …...
鸿蒙HarmonyOS 5军旗小游戏实现指南
1. 项目概述 本军旗小游戏基于鸿蒙HarmonyOS 5开发,采用DevEco Studio实现,包含完整的游戏逻辑和UI界面。 2. 项目结构 /src/main/java/com/example/militarychess/├── MainAbilitySlice.java // 主界面├── GameView.java // 游戏核…...
