js里面的闭包是一个难点也是它的一个特色,是我们必须掌握的js高级特性,那么什么是闭包呢?它又有什么作用呢?
1,提到闭包我们这里先讲解一下js作用域的问题
js的作用域分两种,全局和局部,基于我们所熟悉的作用域链相关知识,我们知道在js作用域环境中访问变量的权利是由内向外的,内部作用域可以获得当前作用域下的变量并且可以获得当前包含当前作用域的外层作用域下的变量,反之则不能,也就是说在外层作用域下无法获取内层作用域下的变量,同样在不同的函数作用域中也是不能相互访问彼此变量的。
(1)变量在函数内声明,变量为局部作用域。
局部变量:只能在函数内部访问。
// 此处不能调用 carName 变量
function myFunction() {
var carName = "Volvo";
// 函数内可调用 carName 变量
}
(2)变量在函数外定义,即为全局变量。
全局变量有 全局作用域: 网页中所有脚本和函数均可使用。
ar carName = " Volvo";
// 此处可调用 carName 变量
function myFunction() {
// 函数内可调用 carName 变量
}
(3)如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量。
以下实例中 carName 在函数内,但是为全局变量。
// 此处可调用 carName 变量
function myFunction() {
carName = "Volvo";
// 此处可调用 carName 变量
}
2,闭包有3个特性:
①函数嵌套函数
②函数内部可以引用函数外部的参数和变量
③参数和变量不会被垃圾回收机制回收
3,闭包存在两种的主要形式:
(1)、函数作为返回值
function fn(){
var str = 'abc';
return function(){
return str
}
}
var fn1 = fn();
console.log(fn1) //abc
在这段代码中,fn()中的返回值是一个匿名函数,这个函数在fn()作用域内部,所以它可以获取fn()作用域下变量str的值,将这个值作为返回值赋给全局作用域下的变量fn1,实现了在全局变量下获取到局部变量中的变量的值
(2)、经典闭包循环
function fn(){
var num = 3;
return function(){
var n = 0;
console.log(++n);
console.log(++num);
}
}
var fn1 = fn();
fn1(); //1 4
fn1()//1 5
一般情况下,在函数fn执行完后,就应该连同它里面的变量一同被销毁,但是在这个例子中,匿名函数作为fn的返回值被赋值给了fn1,这时候相当于fn1=function(){var n = 0 … },并且匿名函数内部引用着fn里的变量num,所以变量num无法被销毁,而变量n是每次被调用时新创建的,所以每次fn1执行完后它就把属于自己的变量连同自己一起销毁,于是乎最后就剩下孤零零的num,于是这里就产生了内存消耗的问题
(3)、定时器与闭包
for(var i=0;i<5;++i){
setTimeout(()=>{
console.log(i,'000')
},100)
}
for(var i=0;i<5;++i){
(function(i){
setTimeout(()=>{
console.log(i,'000')
},100)
})(i)
}
引入闭包来保存变量i,将setTimeout放入立即执行函数中,将for循环中的循环值i作为参数传递,100毫秒后同时打印出1 2 3 4 5
(4)、闭包作为参数传递
var num = 10
var fn = function (x) {
if (x>num) {
console.log(x, 'num值')
}
}
void function (fn1) {
var num = 100
fn1(30)
}(fn)
拓展一下参数传递:
1.1:参数传递方式
函数参数的传递方式有两种,一个是传值传递,一个是传址传递。
当函数参数是原始数据类型时(字符串,数值,布尔值),参数的传递方式为传值传递。也就是说,在函数体内修改参数值,不会影响到函数外部。
var a = 1;
function keith(num) {
num = 5;
}
keith(a);
console.log(a); //1
上面代码中,全局变量a是一个原始类型的值,传入函数keith的方式是传值传递。因此,在函数内部,a的值是原始值的拷贝,无论怎么修改,都不会影响到原始值。
但是,如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的是原始值的地址,因此在函数内部修改参数,将会影响到原始值。
var arr = [2, 5];
function keith(Arr) {
Arr[0] = 3;
}
keith(arr);
console.log(arr[0]); //3
上面代码中,传入函数keith的是参数对象arr的地址。因此,在函数内部修改arr第一个值,会影响到原始值。
注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。
var arr = [2, 3, 5];
function keith(Arr) {
Arr = [1, 2, 3];
}
keith(arr);
console.log(arr); // [2,3,5]
上面代码中,在函数keith内部,参数对象arr被整个替换成另一个值。这时不会影响到原始值。这是因为,形式参数(Arr)与实际参数arr存在一个赋值关系。
1.2:同名参数
如果有同名参数,则取最后面出现的那个值,如果未提供最后一个参数的值,则取值变成undefined。
function keith(a, a) {
return a;
}
console.log(keith(1, 3)); //3
console.log(keith(1)); //undefined
如果想访问同名参数中的第一个参数,则使用arguments对象。
function keith(a, a) {
return arguments[0];
}
console.log(keith(2)); //2