什么是作用域?

作用域是一种规则,在代码编译阶段就确定了,规定了变量与函数的可被访问的范围。全局变量拥有全局作用域,局部变量则拥有局部作用域。 js是一种没有块级作用域的语言(包括if、for等语句的花括号代码块或者单独的花括号代码块都不能形成一个局部作用域),所以js的局部作用域的形成有且只有函数的花括号内定义的代码块形成的,既函数作用域。
什么是作用域链?
作用域链是作用域规则的实现,通过作用域链的实现,变量在它的作用域内可被访问,函数在它的作用域内可被调用。
作用域链是一个只能单向访问的链表,这个链表上的每个节点就是执行上下文的变量对象(代码执行时就是活动对象),单向链表的头部(可被第一个访问的节点)始终都是当前正在被调用执行的函数的变量对象(活动对象),尾部始终是全局活动对象。
作用域链的形成?
我们从一段代码的执行来看作用域链的形成过程。
function fun01 () {
console.log('i am fun01...');
fun02();
}
function fun02 () {
console.log('i am fun02...');
}
fun01();
数据访问流程
如上图,当程序访问一个变量时,按照作用域链的单向访问特性,首先在头节点的AO中查找,没有则到下一节点的AO查找,最多查找到尾节点(global AO)。在这个过程中找到了就找到了,没找到就报错undefined。
延长作用域链
从上面作用域链的形成可以看出链上的每个节点是在函数被调用执行是向链头unshift进当前函数的AO,而节点的形成还有一种方式就是“延长作用域链”,既在作用域链的头部插入一个我们想要的对象作用域。延长作用域链有两种方式:
1.with语句
function fun01 () {
with (document) {
console.log('I am fun01 and I am in document scope...')
}
}
fun01();
2.try-catch语句的catch块
function fun01 () {
try {
console.log('Some exceptions will happen...')
} catch (e) {
console.log(e)
}
}
fun01();
ps:个人感觉with语句使用需求不多,try-catch的使用也是看需求的。个人对这两种使用不多,但是在进行这部分整理过程中萌发了一点点在作用域链层面的不成熟的性能优化小建议。
由作用域链引发的关于性能优化的一点不成熟的小建议
1.减少变量的作用域链的访问节点
这里我们自定义一个名次叫做“查找距离”,表示程序访问到一个非undefined变量在作用域链中经过的节点数。因为如果在当前节点没有找到变量,就跳到下一个节点查找,还要进行判断下一个节点中是否存在被查找变量。“查找距离”越长,要做的“跳”动作和“判断”动作也就越多,资源开销就越大,从而影响性能。这种性能带来的差距可能少数的几次变量查找操作不会带来太多性能问题,但如果是多次进行变量查找,性能对比则比较明显了。
(function(){
console.time()
var find = 1 //这个find变量需要在4个作用域链节点进行查找
function fun () {
function funn () {
var funnv = 1;
var funnvv = 2;
function funnn () {
var i = 0
while(i <= 100000000){
if(find){
i++
}
}
}
funnn()
}
funn()
}
fun()
console.timeEnd()
})()
(function(){
console.time()
function fun () {
function funn () {
var funnv = 1;
var funnvv = 2;
function funnn () {
var i = 0
var find = 1 //这个find变量只在当前节点进行查找
while(i <= 100000000){
if(find){
i++
}
}
}
funnn()
}
funn()
}
fun()
console.timeEnd()
})()
在mac pro的chrome浏览器下做实验,进行1亿次查找运算。
实验结果:前者运行5次平均耗时85.599ms,后者运行5次平均耗时63.127ms。
2.避免作用域链内节点AO上过多的变量定义
过多的变量定义造成性能问题的原因主要是查找变量过程中的“判断”操作开销较大。我们使用with来进行性能对比。
(function(){
console.time()
function fun () {
function funn () {
var funnv = 1;
var funnvv = 2;
function funnn () {
var i = 0
var find = 10
with (document) {
while(i <= 1000000){
if(find){
i++
}
}
}
}
funnn()
}
funn()
}
fun()
console.timeEnd()
})()
在mac pro的chrome浏览器下做实验,进行100万次查找运算,借助with使用document进行的延长作用域链,因为document下的变量属性比较多,可以测试在多变量作用域链节点下进行查找的性能差异。
实验结果:5次平均耗时558.802ms,而如果删掉with和document,5次平均耗时0.956ms。
当然,这两个实验是在我们假设的极端环境下进行的,结果仅供参考!
关于闭包
1.什么是闭包?
函数对象可以通过作用域链相互关联起来,函数体内的数据(变量和函数声明)都可以保存在函数作用域内,这种特性在计算机科学文献中被称为“闭包”。既函数体内的数据被隐藏于作用于链内,看起来像是函数将数据“包裹”了起来。从技术角度来说,js的函数都是闭包:函数都是对象,都关联到作用域链,函数内数据都被保存在函数作用域内。
2.闭包的几种实现方式
实现方式就是函数A在函数B的内部进行定义了,并且当函数A在执行时,访问了函数B内部的变量对象,那么B就是一个闭包。如下:
如上两图所示,是在chrome浏览器下查看闭包的方法。两种方式的共同点是都有一个外部函数outerFun(),都在外部函数内定义了内部函数innerFun(),内部函数都访问了外部函数的数据。不同的是,第一种方式的innerFun()是在outerFun()内被调用的,既声明和被调用均在同一个执行上下文内。而第二种方式的innerFun()则是在outerFun()外被调用的,既声明和被调用不在同一个执行上下文。第二种方式恰好是js使用闭包常用的特性所在:通过闭包的这种特性,可以在其他执行上下文内访问函数内部数据。
我们更常用的一种方式则是这样的:
//闭包实例
function outerFun () {
var outerV1 = 10
function outerF1 () {
console.log('I am outerF1...')
}
function innerFun () {
var innerV1 = outerV1
outerF1()
}
return innerFun //return回innerFun()内部函数
}
var fn = outerFun() //接到return回的innerFun()函数
fn() //执行接到的内部函数innerFun()
此时它的作用域链是这样的:
3.闭包的好处及使用场景
js的垃圾回收机制可以粗略的概括为:如果当前执行上下文执行完毕,且上下文内的数据没有其他引用,则执行上下文pop出call stack,其内数据等待被垃圾回收。而当我们在其他执行上下文通过闭包对执行完的上下文内数据仍然进行引用时,那么被引用的数据则不会被垃圾回收。就像上面代码中的outerV1,放我们在全局上下文通过调用innerFun()仍然访问引用outerV1时,那么outerFun执行完毕后,outerV1也不会被垃圾回收,而是保存在内存中。另外,outerV1看起来像不像一个outerFun的私有内部变量呢?除了innerFun()外,我们无法随意访问outerV1。所以,综上所述,这样闭包的使用情景可以总结为:
(1)进行变量持久化。
(2)使函数对象内有更好的封装性,内部数据私有化。
进行变量持久化方面举个栗子:
我们假设一个需求时写一个函数进行类似id自增或者计算函数被调用的功能,普通青年这样写:
var count = 0
function countFun () {
return count++
}
这样写固然实现了功能,但是count被暴露在外,可能被其他代码篡改。这个时候闭包青年就会这样写:
function countFun () {
var count = 0
return function(){
return count++
}
}
var a = countFun()
a()
这样count就不会被不小心篡改了,函数调用一次就count加一次1。而如果结合“函数每次被调用都会创建一个新的执行上下文”,这种count的安全性还有如下体现:
function countFun () {
var count = 0
return {
count: function () {
count++
},
reset: function () {
count = 0
},
printCount: function () {
console.log(count)
}
}
}
var a = countFun()
var b = countFun()
a.count()
a.count()
b.count()
b.reset()
a.printCount() //打印:2 因为a.count()被调用了两次
b.printCount() //打印出:0 因为调用了b.reset()
以上便是闭包提供的变量持久化和封装性的体现。
4.闭包的注意事项
由于闭包中的变量不会像其他正常变量那种被垃圾回收,而是一直存在内存中,所以大量使用闭包可能会造成性能问题。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!
# js作用域和闭包
# js
# 闭包
# 作用域
# 作用域链
# Javascript的作用域、作用域链以及闭包详解
# 详解JavaScript作用域、作用域链和闭包的用法
# JavaScript闭包与作用域链实例分析
# 深入理解Javascript中的作用域链和闭包
# JS闭包、作用域链、垃圾回收、内存泄露相关知识小结
# 浅析JavaScript作用域链、执行上下文与闭包
# javascript从作用域链谈闭包
# 深入Javascript函数、递归与闭包(执行环境、变量对象与作用域链)使用详解
# JavaScript中的作用域链和闭包
# JavaScript深入理解作用域链与闭包详情
# 是在
# 都是
# 的花
# 是一种
# 过程中
# 则是
# 不多
# 链表
# 第二种
# 不成熟
# 的是
# 体内
# 是一个
# 就会
# 都有
# 太多
# 就像
# 在这个
# 都在
# 第一个
相关文章:
南京网站制作费用,南京远驱官方网站?
建站IDE高效指南:快速搭建+SEO优化+自适应模板全解析
建站主机如何选?性能与价格怎样平衡?
如何在Golang中指定模块版本_使用go.mod控制版本号
如何快速启动建站代理加盟业务?
如何挑选最适合建站的高性能VPS主机?
建站之星免费模板:自助建站系统与智能响应式一键生成
怎么用手机制作网站链接,dw怎么把手机适应页面变成网页?
宝塔面板如何快速创建新站点?
如何在服务器上配置二级域名建站?
如何正确选择百度移动适配建站域名?
如何快速选择适合个人网站的云服务器配置?
如何制作网站标识牌,动态网站如何制作(教程)?
如何用虚拟主机快速搭建网站?详细步骤解析
XML的“混合内容”是什么 怎么用DTD或XSD定义
无锡制作网站公司有哪些,无锡优八网络科技有限公司介绍?
建站之星好吗?新手能否轻松上手建站?
如何在西部数码注册域名并快速搭建网站?
如何在Golang中使用replace替换模块_指定本地或远程路径
建站之星官网登录失败?如何快速解决?
制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?
香港服务器租用费用高吗?如何避免常见误区?
制作网站的公司有哪些,做一个公司网站要多少钱?
小型网站制作HTML,*游戏网站怎么搭建?
如何在建站宝盒中设置产品搜索功能?
长春网站建设制作公司,长春的网络公司怎么样主要是能做网站的?
最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?
义乌企业网站制作公司,请问义乌比较好的批发小商品的网站是什么?
官网网站制作腾讯审核要多久,联想路由器newifi官网
网站制作壁纸教程视频,电脑壁纸网站?
c++怎么用jemalloc c++替换默认内存分配器【性能】
车管所网站制作流程,交警当场开简易程序处罚决定书,在交警网站查询不到怎么办?
C#怎么创建控制台应用 C# Console App项目创建方法
青岛网站设计制作公司,查询青岛招聘信息的网站有哪些?
Android自定义控件实现温度旋转按钮效果
宝塔建站助手安装配置与建站模板使用全流程解析
微信网站制作公司有哪些,民生银行办理公司开户怎么在微信网页上查询进度?
Python lxml的etree和ElementTree有什么区别
已有域名建站全流程解析:网站搭建步骤与建站工具选择
大连网站制作费用,大连新青年网站,五年四班里的视频怎样下载啊?
深圳 网站制作,深圳招聘网站哪个比较好一点啊?
建站之星代理如何优化在线客服效率?
陕西网站制作公司有哪些,陕西凌云电器有限公司官网?
较简单的网站制作软件有哪些,手机版网页制作用什么软件?
高防服务器租用首荐平台,企业级优惠套餐快速部署
天津个人网站制作公司,天津网约车驾驶员从业资格证官网?
建站之星安装模板失败:服务器环境不兼容?
如何通过万网虚拟主机快速搭建网站?
如何在Golang中引入测试模块_Golang测试包导入与使用实践
如何在建站之星绑定自定义域名?
*请认真填写需求信息,我们会在24小时内与您取得联系。