什么是范围?
范围是一种规则,在代码编译阶段确定,定义变量和函数的可访问范围。全局变量有全局作用域,而局部变量有局部作用域。Js是一种没有块级作用域的语言(花括号代码块包括if和for语句或单个花括号代码块不能形成局部作用域),所以js的局部作用域只由函数花括号中定义的代码块形成,即函数作用域。
什么是范围链?
范围链是范围规则的实现。通过范围链的实现,变量可以被访问,函数可以在其范围内被调用。
范围链是一个只能单向访问的链表。这个链表中的每个节点都是一个执行上下文的变量对象(执行代码时的活动对象)。链表的头部(可以访问的第一个节点)始终是被调用和执行的函数的变量对象(活动对象),尾部始终是全局活动对象。
范围链的形成?
我们从一段代码的执行来看范围链的形成过程。
fun01 () { console.log('我是fun01 . ');fun 02();} fun02()函数{ console.log('我是fun 02 . ');} fun 01();
数据访问过程
如上图所示,程序访问一个变量时,根据作用域链的单向访问特性,先在头节点的AO中查找,再在下一个节点的AO中查找,最多查找全局AO。在这个过程中,如果你找到了,你会找到它,如果你没有找到,你会报告一个未定义的错误。
延长范围链
从上面范围链的形成可以看出,链中的每个节点都是当前函数在被调用和执行时的AO,形成节点的另一种方式是“扩展范围链”,即在范围链的头部插入一个我们想要的对象范围。有两种方法可以扩展范围链:
1 .带语句
fun01(){ with(document){ console . log('我是fun 01,我在文档范围内.')} } fun 01();
2.2.try-catch语句的catch块
fun01 () {尝试{ console.log('会发生一些异常.')} catch(e){ console . log(e)} } fun 01();
ps:个人觉得对with语句的使用需求不大,try-catch的使用要看需求。个人觉得这两种东西用的不多,但是在完成这部分的过程中,他们在范围链层面萌生了一点不成熟的性能优化建议。
范围链引发的性能优化的一点不成熟的建议
1.减少可变范围链的访问节点
在这里,我们定义了一个名为“搜索距离”的排名,它表示程序访问一个未定义的变量并在作用域链中通过的节点数。因为如果在当前节点没有找到变量,跳转到下一个节点进行搜索,判断搜索到的变量是否存在于下一个节点。“搜索距离”越长,需要做的“跳跃”动作和“判断”动作越多,资源开销越大,从而影响性能。这种性能差距用少量的变量搜索操作可能不会带来太多的性能问题,但如果多次进行变量搜索,性能对比就很明显了。
(函数(){控制台。time()varfind=1//这个find变量需要在四个作用域链节点中找到:function fun(){ function fun(){ varfunnv=1;var fun nvv=2;funnn(){ var I=0 while(I=100000000){ if(find){ I } } } funnn()} funn()} fun()console . timeend()})()
(function(){ console . time()} fun fun(){ fun funn(){ var funnv=1;var fun nvv=2;funnn()函数{ var I=0 var find=1//此find变量仅在当前节点搜索,而(I=10000000){ if(find){ I } } funnn()} funn()} fun()控制台。timeend()})()
实验在mac pro的chrome浏览器下进行,进行了1亿次搜索操作。
实验结果:前者运行5次的平均时间为85.599ms,后者运行5次的平均时间为63.127 ms。
2.避免范围链中的AO节点上有太多变量定义
变量定义过多导致性能问题的主要原因是在寻找变量的过程中“判断”操作的成本较高。我们使用与进行性能比较。
(function(){ console . time()} fun fun(){ fun funn(){ var funnv=1;var fun nvv=2;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的帮助下,通过使用文档来执行扩展范围链。由于文档下的可变属性较多,可以测试多元范围链节点下搜索的性能差异。
实验结果:5次平均时间为558.802ms,但如果删除with和document,5次平均时间为0.956 ms。
当然这两个实验都是在我们假设的极端环境下进行的,结果仅供参考!
关于闭包
1.什么是结束?
函数对象可以通过作用域链相互关联,函数体(变量和函数声明)中的数据可以存储在函数作用域中,这在计算机科学文献中被称为“闭包”。由于函数体中的数据隐藏在动作链中,所以函数似乎“包装”了数据。从技术角度来看,js的函数都是闭包:函数都是对象,都与作用域链有关,函数中的数据都存储在函数的作用域中。
2.闭包的几种实现
实现方式是在函数B内部定义函数A,当函数A执行时,访问函数B内部的变量对象,那么B就是闭包。如下所示:
如上两张图所示,是在chrome浏览器中查看闭包的方法。这两种方法有一个共同的外部函数outerFun(),其中定义了一个内部函数innerFun(),内部函数访问外部函数的数据。不同的是,第一种模式的innerFun()是在externafun()中调用的,它是在同一个执行上下文中声明和调用的。第二种方式是在externafun()之外调用innerFun(),声明它与被调用的不在同一个执行上下文中。第二种方式恰好是js使用闭包的共同特点:通过闭包的这一特点,函数的内部数据可以在其他执行上下文中访问。
我们使用的一种更常见的方法是:
//闭包实例函数outer fun () {var outer v1=10函数outer f1 () {console.log('我是outer f1 . ')}函数innerFun(){ var inner v1=outer v1 outer f1()} return inner fun//return to inner fun()内部函数} var fn=outer fun()//return接收到的inner fun()函数fn() //执行接收到的内部函数inner fun(),其作用域链如下:
3.闭包的好处和使用场景
js的垃圾收集机制大致可以总结为:如果当前执行上下文结束,并且上下文中没有其他对数据的引用,执行上下文就会弹出调用栈,其中的数据等待被垃圾收集。但是,当我们仍然通过其他执行上下文中的闭包引用执行上下文中的数据时,引用的数据将不会被垃圾收集。就像上面代码中的outv1一样,当我们在全局上下文中仍然通过调用innerFun()来访问引用outv1时,在执行outerFun之后,outv1不会被垃圾收集,而是保存在内存中。此外,extra v1看起来像是extra fun的私有内部变量吗?除了innerFun(),我们不能随意访问outerV1。因此,综上所述,这种闭包的使用场景可以总结如下:
(1)变量的持久性。
(2)使功能对象更好的封装,内部数据私有化。
举一个变量持久性的例子:
让我们假设当一个需求被满足时,一个函数被编写来执行一个函数,比如id自增或计算函数被调用。普通青年这样写:
var count=0函数count fun () {return count}的写入实现了该函数,但该计数被暴露,可能被其他代码篡改。这一次封青春会这样写:
函数count fun(){ var count=0 return fun(){ return count } } var a=count fun()a()这样计数就不会被意外篡改,调用函数时计数会增加1一次。如果结合“每次调用一个函数,就会创建一个新的执行上下文”,这个计数的安全性也体现如下:
fun count fun(){ var count=0 return { count : fun(){ count },reset : fun(){ count=0 },Print count : fun(){ console . log(count)} } var a=count fun()var b=count fun()a.count()a . count()b . count()b . reset()a . Print count()//Print:2因为a . count()被调用了两次。b.printCount() //Print: 0,因为调用了b.reset()。以上是闭包提供的变量持久化和封装的体现。
4.关闭的注意事项
因为闭包中的变量不像其他普通变量那样被垃圾收集,而是始终存在于内存中,所以大量的闭包可能会导致性能问题。
以上就是本文的全部内容。希望本文的内容能给大家的学习或工作带来一些帮助,也希望多多支持我们!