js闭包

ES6新增let和const两个变量声明命令,他们都具有如下特性:
1、块局作用域;
2、不存在变量提升,一定声明后才能使用;
3、暂时性死区,在代码块内使用let命令声明变量之前,该变量都是不可用的,不受外部变量影响;
4、在相同作用域范围内不允许重复声明;
const与let不同点在于:
const声明的变量不能重新赋值,也是由于这个规则,const变量声明时必须初始化,不能留到以后赋值
.const命令只是保证了变量名指向的地址不变,并不保证该地址的数据不变。

    var a=[];
    for(var i=0;i<10;i++){
        a[i]=function(){
            console.log(i);
        };
    }
    a[6](); //10    

var a=[];
for(let i=0;i<10;i++){
    a[i]=function(){
        console.log(i);
    };
}
a[6]();    //6    

我们可以看到,两个例子中,唯一的区别是前者for循环中使用var来定义i,得到的结果是10.而后者使用的是let来定义i,最终得到的结果是6.这是为什么呢?

例二前者(var i)具体执行过程如下:
var a=[];

var i=0;//由于var来声明变量i,所以for循环代码块不具备块级作用域,因此i认为是全局变量,直接放在全局变量中。
a[0]=function(){
console.log(i);//这里之所以i为i而不是0;是因为我们只是定义了该函数,未被调用,所以没有进入该函数执行环境,i当然不会沿着作用域链向上搜索找到i的值。
}// 由于不具备块级作用域,所以该函数定义就是全局作用域。

var i=1;//第二次循环,这时var i=1;覆盖了前面的var i=0;即现在i为1;
a[1]=function(){
console.log(i);//解释同a[0]函数。
}

var i=2;// 第三次循环,这时 i=2,在全局作用域中,所以覆盖了前面的i=1;
a[2]=function(){
console.log(i);
}

…第四次循环 此时i=3 这个以及下面的i不断的覆盖前面的i,因为都在全局作用域中
…第五次循环 此时i=4
…第六次循环 此时i=5
…第七次循环 此时i=6
…第八次循环 此时i=7
…第九次循环 此时i=8

var i=9;
a[9]=function(){
console.log(i);
}

var i=10;// 这时i为10,因为不满足循环条件,所以停止循环。
紧接着在全局环境中继续向下执行。
a6;//这时调用a[6]函数,所以这时随即进入a[6]函数的执行环境,即a[6]=function(){console.log(i)};执行函数中的代码 console.log(i); 因为在函数执行环境中不存在变量i,所以此时会沿着作用域链向上寻找(可参考我的博文《深入理解作用域和作用域链》),即进入了全局作用域中寻找变量i,而全局作用域中i=10覆盖了前面所有的i值,所以说这时i为10,那么a[6]的值就是10了。
  
  说明:对于例如a[1]=function(){console.log(i)};而不是a[1]=function{console.log(1)},可以在控制台中输出a[1]函数,即可得到验证。

例二后者(let i)具体执行过程如下:
var a=[];//创建一个数组a;

{ //进入第一次循环
let i=0; //注意:因为使用let使得for循环为块级作用域,此次let i=0在这个块级作用域中,而不是在全局环境中。
a[0]=function(){
console.log(i);
}; //注意:由于循环时,let声明i,所以整个块是块级作用域,那么a[0]这个函数就成了一个闭包。
}// 声明: 我这里用{}表达并不符合语法,只是希望通过它来说明let存在时,这个for循环块是块级作用域,而不是全局作用域。

讲道理,上面这是一个块级作用域,就像函数作用域一样,函数执行完毕,其中的变量会被销毁,但是因为这个代码块中存在一个闭包,闭包的作用域链中包含着(或着说是引用着)块级作用域,所以在闭包被调用之前,这个块级作用域内部的变量不会被销毁。

{ //进入第二次循环
let i=1; //注意:因为let i=1; 和 上面的let i=0;出在不同的作用域中,所以两者不会相互影响。
a[1]=function(){
console.log(i);
}; //同样,这个a[i]也是一个闭包
}
…进入第三次循环,此时其中let i=2;
…进入第四次循环,此时其中let i=3;
…进入第五次循环,此时其中let i=4;
…进入第六次循环,此时其中let i=5;
…进入第七次循环,此时其中let i=6;
…进入第八次循环,此时其中let i=7;
…进入第九次循环,此时其中let i=8;
{//进入第十次循环
let i=9;
a[i]=function(){
console.log(i);
};//同样,这个a[i]也是一个闭包
}
{
let i=10;//不符合条件,不再向下执行。于是这个代码块中不存在闭包,let i=10;在这次循环结束之后难逃厄运,随即被销毁。
}
a6;//调用a6函数,这时执行环境随即进入下面这个代码块中的执行环境:funcion(){console.log(i)};
{
let i=6;
a[6]=function(){
console.log(i);
}; //同样,这个a[i]也是一个闭包
}
a[6]函数(闭包)这个执行环境中,它会首先寻找该执行环境中是否存在 i,没有找到,就沿着作用域链继续向上到了其所在的代码块执行环境,找到了i=6,于是输出了6,即a6;的结果为6。这时,闭包被调用,所以整个代码块中的变量i和函数a6被销毁。

解构
什么是解构赋值?
解构赋值允许你使用类似数组或对象字面量的语法将数组和对象的属性赋给各种变量。这种赋值语法极度简洁,同时还比传统的属性访问方法更为清晰。
通常来说,你很可能这样访问数组中的前三个元素:
var first = someArray[0];
var second = someArray[1];
var third = someArray[2];
如果使用解构赋值的特性,将会使等效的代码变得更加简洁并且可读性更高:

var [first, second, third] = someArray;

数组与迭代器的解构
以上是数组解构赋值的一个简单示例,其语法的一般形式为:

[ variable1, variable2, ..., variableN ] = array;

这将为variable1到variableN的变量赋予数组中相应元素项的值。如果你想在赋值的同时声明变量,可在赋值语句前加入var、let或const关键字,例如:

var [ variable1, variable2, ..., variableN ] = array;
let [ variable1, variable2, ..., variableN ] = array;
const [ variable1, variable2, ..., variableN ] = array;

事实上,用变量来描述并不恰当,因为你可以对任意深度的嵌套数组进行解构:

var [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo);
// 1
console.log(bar);
// 2
console.log(baz);
// 3

此外,你可以在对应位留空来跳过被解构数组中的某些元素:

var [,,third] = ["foo", "bar", "baz"];
console.log(third);
// "baz"

而且你还可以通过“不定参数”模式捕获数组中的所有尾随元素:

var [head, ...tail] = [1, 2, 3, 4];
console.log(tail);
// [2, 3, 4]

当访问空数组或越界访问数组时,对其解构与对其索引的行为一致,最终得到的结果都是:undefined。
console.log([][0]);
// undefined
var [missing] = [];
console.log(missing);
// undefined
请注意,数组解构赋值的模式同样适用于任意迭代器:
function* fibs() {
var a = 0;
var b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
var [first, second, third, fourth, fifth, sixth] = fibs();
console.log(sixth);
// 5
解构值不是对象、数组或迭代器
当你尝试解构null或undefined时,你会得到一个类型错误:
var {blowUp} = null;
// TypeError: null has no properties(null没有属性)
然而,你可以解构其它原始类型,例如:布尔值、数值、字符串,但是你将得到undefined:
var {wtf} = NaN;
console.log(wtf);
// undefined
你可能对此感到意外,但经过进一步审查你就会发现,原因其实非常简单。当使用对象赋值模式时,被解构的值需要被强制转换为对象。大多数类型都可以被转换为对象,但null和undefined却无法进行转换。当使用数组赋值模式时,被解构的值一定要包含一个迭代器。
默认值
当你要解构的属性未定义时你可以提供一个默认值:
var [missing = true] = [];
console.log(missing);
// true
var { message: msg = “Something went wrong” } = {};
console.log(msg);
// “Something went wrong”
var { x = 3 } = {};
console.log(x);
// 3

前言
让我们来仔细地看看ES6所带来的更清晰的变量声明与赋值语法。现今的变量声明语法十分的直接:左边是一个变量名,右边可以是一个数组:[]的表达式或一个对象:{}的表达式,等等。解构赋值允许我们将右边的表达式看起来也像变量声明一般,然后在左边将值一一提取。听起来有点迷糊?让我们一起看看下面的例子就好。
数组的解构赋值
现在假设我们有一个value变量,其值为[1, 2, 3, 4, 5]。然后我们想给数组的前三个元素分别声明一个变量。传统的做法是单独声明和赋值每一个变量:
var value = [1, 2, 3, 4, 5];
var el1 = value[0];
var el2 = value[1];
var el3 = value[0];

有了这几个新变量,我们原本的value现在可以被表示为[el1, el2, el3, 4, 5],因为我们现在并不关心后两个元素,所以也可以说被表示为[el1, el2, el3]。那么现在,ES6允许我们在左边使用这个表达式来达到和上一个代码块一样的效果:
var value = [1, 2, 3, 4, 5];
var [el1, el2, el3] = value;

右边不必一定是变量名:
var [el1, el2, el3] = [1, 2, 3, 4, 5];

左边也不必一定是声明:
var el1, el2, el3;
[el1, el2, el3] = [1, 2, 3, 4, 5];

这使我们通过仅仅使用两个变量名,就可以交换两个变量的值,这是从前的JavaScript不可能办到的事情:
[el1, el2] = [el2, el1];

解构赋值也是可嵌套的:
var value = [1, 2, [3, 4, 5]];
var [el1, el2, [el3, el4]] = value;

ES6中,返回一个数组的函数更像一个头等公民了:
function tuple() {
return [1, 2];
}

var [first, second] = tuple();

同样可以通过简单地在指定位置省略变量来忽略数组中的某个元素:
var value = [1, 2, 3, 4, 5];
var [el1, , el3, , el5] = value;

这使得从正则表达式里取出匹配的分组的过程十分得简洁:
var [, firstName, lastName] = “John Doe”.match(/^(w+) (w+)$/);

更进一步,默认值同样也可以被指定:
var [firstName = “John”, lastName = “Doe”] = [];

需要注意的是默认值只会在对undefined值起作用,下面的例子中firstName和lastName都将是null:
var [firstName = “John”, lastName = “Doe”] = [null, null];

rest参数(…变量名)让事情变得更有趣,它使你可以得到数组中“剩余“的元素。下面这个例子中,tail变量将接收数组中”剩余“的元素,为[4, 5]:
var value = [1, 2, 3, 4, 5];
var [el1, el2, el3, …tail] = value;

不幸的是,ES6中rest参数的实现非常原始,rest参数之后不能再有其他参数(即只能是最后一个参数)。所以下面例子中的这些非常有用模式,在ES6中是不可能的(会报错):
var value = [1, 2, 3, 4, 5];
var […rest, lastElement] = value;
var [firstElement, …rest, lastElement] = value;

对象的解构赋值
现在,你已经对数组的解构赋值有了清晰的认识,让我们来看看对象的解构赋值。它们几乎以同样的方式工作,仅仅是从数组变成了对象:
var person = {firstName: “John”, lastName: “Doe”};
var {firstName, lastName} = person;

ES6允许变量名与对应的属性名不一致。下面的例子中,name变量将会被声明为person.firstName的值:
var person = {firstName: “John”, lastName: “Doe”};
var {firstName: name, lastName} = person;

深层嵌套的对象也不会有问题:
var person = {name: {firstName: “John”, lastName: “Doe”}};
var {name: {firstName, lastName}} = person;

你还可以嵌套些数组在里面:
var person = {dateOfBirth: [1, 1, 1980]};
var {dateOfBirth: [day, month, year]} = person;

或者一些其他的:
var person = [{dateOfBirth: [1, 1, 1980]}];
var [{dateOfBirth}] = person;

和数组解构赋值一样,对象解构赋值也可以使用默认值:
var {firstName = “John”, lastName: userLastName = “Doe”} = {};

默认值同样也只会在对undefined值起作用,下面的例子中firstName和lastName也都将是null:
var {firstName = “John”, lastName = “Doe”} = {firstName: null, lastName: null};

函数参数的解构赋值
ES6中,函数的参数也支持解构赋值。这对于有复杂配置参数的函数十分有用。你可以结合使用数组和对象的解构赋值。
function findUser(userId, options) {
if (options.includeProfile) …
if (options.includeHistory) …
}

通过ES6,这会看上去更清晰简洁:
function findUser(userId, {includeProfile, includeHistory}) {
if (includeProfile) …
if (includeHistory) …
}

最后
ES6的解构赋值给JavaScript的语法带来了更多的现代化。它在减少了代码量的同时,增加了代码的可读性和表现力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值