1.作用域
作用域其实就代表了变量合法的使用范围。
作用域分为全局作用域、函数作用域和块级作用域。
Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。
var n=999;
function f1(){
alert(n);
}
f1(); // 999
但是,在函数外部自然无法读取函数内的局部变量。
function f1(){
var n=999;
}
alert(n); // error
这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
function f1(){
n=999;
}
f1();
alert(n); // 999
2.作用域链
一个变量在当前作用域没有被定义,但被使用了,会向上级作用域,一层层寻找,直至找到为止。
这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。
所有自由变量的查找,是在函数定义的地方,向上级作用域查找,而不是在执行的地方!!!!!!
function f1(){
var n=999;
function f2(){
alert(n); // n在这个作用域内就是自由变量,999
}
}
3.闭包
闭包就是能够读取其他函数内部变量的函数。
在A函数中创建了一个B函数,那么B函数就是闭包。B函数可以访问到A函数中的局部变量。
闭包的作用:
- ,在外部访问到函数内部的变量
js作用域中,函数内部可以调用函数外部的变量,反之,却不可以,可以使用闭包解决,在外部访问到函数内部的变量。function outer() { const a = 100; return function inner() { console.log(a) } } let fn = outer(); const a = 200; fn(); //100
- 让函数内部的变量值始终保持在内存中。
一般情况下, 函数执行完后,局部活动对象就会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象),但是闭包情况下,比如下面这个代码,函数f1执行完后,变量n不会被摧毁,因为f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。所以闭包,会占用更多的占用内存。function f1(){ var n=999; return function f2(){ n++ alert(n); } } var result=f1(); result(); // 1000 result(); // 1001 //函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
3. 闭包的几种表现形式
根据闭包的定义,我们知道,无论通过何种手段,只要将内部函数传递到所在的词法作用域以外,它都会持有对原始作用域的引用,无论在何处执行这个函数都会使用闭包。接下来,本文将详细介绍闭包的7种形式。
函数作为返回值返回
function outer() {
const a = 100;
return function inner() {
console.log(a)
}
}
let fn = outer();
const a = 200;
fn(); //100
将内部函数赋值给一个外部变量
这是将函数作为返回值的一种变形。
(将F作用域里的函数N赋值给全局作用域的inner,所以F执行之后,inner保持了对F作用域里的引用)
var inner;
function F(){
var b = 'local';
inner = functionN(){
return b;
};
};
F();
console.log(inner());//local
函数作为参数
闭包可以通过函数参数传递函数的形式来实现
const b = 100;
function fn(){
console.log(b);
}
function print(fn) {
const b = 200;
fn();
}
print(fn); //100
IIFE
由前面的示例代码可知,外部函数都是在声明后立即被调用,因此可以使用IIFE来替代。但是,要注意的是,这里的Inner()只能使用函数声明语句的形式,而不能使用函数表达式。
function Inner(fn){
console.log(fn());
}
(function(){
var b = 'local';
Inner(function(){
return b;
})
})();
闭包问题
在闭包问题上,最常见的一个错误就是循环赋值的错误。
如果在for循环中添加匿名函数
var btn = document.getElementsByTagName("button")
for(var i = 0;i< btn.length;i++) {
btn[i].addEventListener('click',function(){
alert(i)
}) //这也是闭包
}
上面这段代码就形成了一个闭包:
当点击按钮后,匿名函数执行 alert i 语句的时候,由于匿名函数里面没有i这个变量,所以这个i他要从父级函数中寻找i,而父级函数中的i是全局变量,当找到这个i的时候,是for循环完毕的i,也就是3,所以弹出的是3.
解决这个问题有两种方法。
方法1:
//立即执行函数
//通过function垫了一层函数作用域
var btn = document.getElementsByTagName("button")
for(var i = 0;i< btn.length;i++) {
(function(i){
btn[i].addEventListener('click',function(){
alert(i); //3,3,3
})
})(i);
}
方法二:
//使用let,形成一个块级作用域
var btn = document.getElementsByTagName("button")
for(let i = 0;i< btn.length;i++) {
btn[i].addEventListener('click',function(){
alert(i); //0,1,2
})
}
//let定义的i为块级作用域的变量,每次循环会形成块级作用域,
//点击按钮时,函数执行,向上级作用域寻找i的值,就找到了块级作用域中i的值
闭包的应用
闭包提供api访问被隐藏的数据
//隐藏数据
//做一个简单的cache工具
function createCache() {
const data = {};//数据,被隐藏,不被外界访问
return {
set: function (key, val) { //提供api访问被隐藏的数据
data[key] = val
},
get: function (key) {
return data[key]
}
}
}
const c = new createCache();
c.set('a',1000);
console.log(c.get('a'))
来源:CSDN
作者:Live wild
链接:https://blog.csdn.net/weixin_43473114/article/details/103846611