对Javascript范围和闭包的深入理解

行动范围

范围是一系列的变量和函数,在Javascript函数声明所有的变量在函数体都是可见的,随着全球范围和地区范围的Javascript,但没有阻滞范围、局部变量的优先级高于全局变量,了解潜规则的Javascript在领域的作用下通过几个例子(这也是在面试前经常问的问题)。



1。提前声明变量

例1:


var范围;
功能scopetest(){
console.log(范围);
var范围
}
ScopeTest(); / /未定义



这里的输出是未定义的,没有报告错误,因为前面提到的函数中的声明总是在函数体中可见,上面的函数相当于:


var范围;
功能scopetest(){
Var scope;
console.log(范围);
范围
}
ScopeTest(); / /地方



注意,如果忘记VaR,则变量被声明为全局变量。



2。无阻塞级范围



与其他常用语言不同,Javascript中没有块级范围:


功能scopetest(){
var范围= };
如果(范围实例对象){
var j=1;
对于(var i = 0;i < 10;i + +){
/ / console.log(我);
}
console.log(我); / /输出10
}
console.log(J); / /输出1

}




在Javascript中,变量的范围是函数级,即函数中的所有变量都在整个函数中定义。这也带来了一些我们不注意的潜规则。


var范围;
功能scopetest(){
console.log(范围); / / 1。
var范围= 否;
console.log(范围); / / 2
}



在输出值是未定义的,简直是疯了啊,我们已经定义了全局变量的值啊,这个地方不应该是hello吗事实上,上面的代码相当于:


var范围;
功能scopetest(){
变量范围;
console.log(范围); / / 1。
范围;
console.log(范围); / / 2
}



声明提前,全局变量的优先级低于本地变量。不难理解为什么基于这些规则的输出未定义是未定义的。

作用域链

在Javascript中,每个函数都有自己的执行上下文。当代码在环境中执行时,它将创建变量对象的范围链,范围链是一个对象列表或对象链,这保证了可变对象的有序访问。

作用域链的前面是当前的代码执行环境变量对象,这通常称为活动对象,变量查找将从第一个链开始,如果属性对象包含一个变量,则停止搜索,如果不到范围链将继续搜索,直到找到全局对象为止:

逐步查找范围链也会影响程序的性能。变量范围链越长,对性能的影响就越大。这也是我们避免尽可能使用全局变量的一个主要原因。

关闭

基本概念

范围是理解闭包的先决条件,这意味着外部范围内的变量始终可以在当前范围内访问。


功能createclosure(){
var;
返回{
setstr:函数(){
名称玫瑰;
},
GetStr:函数(){
返回名称;
}
}
}
VaR建造新createclosure();
builder.setstr();
console.log(builder.getstr()); / /玫瑰:你好



上面的示例返回函数中的两个闭包。这两封保持外部范围的参考,所以无论他们在哪里,他们总是可以访问调用的外部函数的变量,函数定义在一个函数添加外部函数的活动对象的作用域链,所以在上面的例子中可以通过内部函数访问外部函数的性质,这是也可以通过Javascript模拟私有变量。

注意:因为闭包将附加函数附加到函数的范围(内部匿名函数携带外部函数的范围),所以闭包将占用比其他函数更多的内存空间。过度使用可能会导致内存占用增加。

闭包中的变量



当使用闭包时,由于范围链机制的影响,闭包只能得到内部函数的最后一个值,这是一个副作用,即如果内部函数处于循环中,则变量的值总是最后一个值。




该实例不合理,存在一定的延迟因素,这里主要是说明现有封闭循环中存在的问题。
功能timemanage(){
对于(var i = 0;i < 5;i + +){
setTimeout(){()函数(
console.log(我);
},1000)
};
}



上面的程序没有按照预期的1-5个输入数,但是5个中有5个。


功能createclosure(){
var结果{ };
对于(var i = 0;i < 5;i + +){
结果{函数(){
还我;
}
}
返回结果;
}



对createclosure返回(){ 0 }()为5,与createclosure返回值(){ 4 }()仍然是5。通过以上两个例子可以看出,内部功能和使用周期时的倒闭问题:因为每个函数的作用域链保存外部函数(timemanage,createclosure)的活动对象,因此,他们引用同一个变量i,当外部函数返回值。5,所以每个函数在i值中是5。

那么如何解决这个问题呢我们可以通过匿名包装(匿名自执行函数表达式)强制返回预期的结果:


功能timemanage(){
对于(var i = 0;i < 5;i + +){
(函数(数字){
setTimeout(){()函数(
console.log(努姆);
},1000);
})(一);
}
}



或者在一个关闭的匿名函数中返回匿名函数赋值。


功能timemanage(){
对于(var i = 0;i < 10;i + +){
setTimeout((function(e){)
返回函数(){
console.log(E);
}
}(一),1000)
}
}
/ / TimeManager();输出1、2、3、4、5
功能createclosure(){
var结果{ };
对于(var i = 0;i < 5;i + +){
结果{函数=(数字){
返回函数(){
console.log(努姆);
}
}(一);
}
返回结果;
}
/ / createclosure(){ 1 }(1)输出;createclosure(){ 2 }(2)输出



无论是匿名包装器还是通过匿名嵌套函数,其原理是函数的结果是通过值传递的,所以将变量i的值复制到参数值中,在匿名函数中创建一个匿名函数来返回数字,这样每个函数都有一个拷贝的数字而不相互影响。

这在闭包中



要特别注意闭包中的使用,稍有不慎可能会造成问题。通常我们理解这个对象是基于函数绑定来运行的,而全局函数中的这个对象是窗口对象。当函数被用作对象中的方法调用时,这等于这个对象(这个完成过程中的待办事项)。因为匿名函数的范围是全局的,这个封闭的对象通常指向全局对象窗口:


var范围;
var对象{ { {
作用域:本地
GetScope:函数(){
返回函数(){
返回this.scope;
}
}
}



打电话object.getscope()()返回全局而不是我们预期的价值的地方。前面我们说过闭包中的内部匿名函数将承载外部函数的作用域。为什么我们没有得到这个外部函数呢每个函数被调用时,会自动创建这个论点,在搜索内部匿名函数,我们要搜索变量的活动对象,所以停止寻找外部函数,它不能直接访问在函数外部的变量。总之,要特别注意一个函数的调用作为一个在封闭物体的方法。此方法中的内部匿名函数的这一点指向全局变量。



幸运的是,我们可以很简单地解决这个问题。我们只需要将外部函数范围的这个值存储到一个可以由闭包访问的变量中。


var范围;
var对象{ { {
作用域:本地
GetScope:函数(){
var =;
返回函数(){
返回that.scope;
}
}
}
object.getscope的返回值()()()()的地方。




内存和性能



关闭包含相同的函数执行上下文的作用域链的参考,因此,会有负面影响,当函数的活动对象和执行环境被破坏,因为仍有必要参考的活动对象,导致活动对象不能被破坏,这意味着关闭功能占用比普通空间更大的内存,IE浏览器可能会导致内存泄漏,如下:




功能bindevent(){
VaR目标= document.getelementbyid(元素);
target.onclick =函数(){
console.log(目标的名字);
}
}



上面示例中的匿名函数生成对外部对象目标的引用。只要匿名函数的存在,参考不会消失,和外部功能目标对象将不会被摧毁,造成循环引用。解决的办法是减少循环引用外部变量和手动复位对象创建target.name副本:


功能bindevent(){
VaR目标= document.getelementbyid(元素);
变量名= target.name;
target.onclick =函数(){
console.log(名称);
}
目标= null;
}



如果闭包中有外部变量的访问,它无疑会增加标识符的查找路径。在某种情况下,也会导致性能损失,前面提到的解决方案是:将外部变量保存到局部变量中,缩小范围链的搜索长度。

总结:Javascript的闭包并不是唯一的,但在Javascript中有其独特的表现形式,我们可以在Javascript中使用一些私有变量的闭包定义,甚至可以模拟块级范围,但是在使用闭包的过程中,我们还需要了解存在的问题,这样可以避免不必要的问题。