----------写于2019年6月21日
目录
1let与const
相比var,let有以下不同
(1)let只在所在块级作用域有效
var只有两种作用域形式,一是全局作用域,另外一种是局部的函数作用域。而let则有块级作用域的形式。
这种特性,最常用的是在for循环的结构中,如下,
for(let i=0; i<10; i++){
console.log(i);
}
console.log(i);
结果是
可见for后边圆括号里边let声明的变量 i 作用域只在for循环的内部有效,其中这也说明,for后边括号内的语句实际上也在后边的 {} 块级作用域内。
var a=[];
for(let i=0; i<10; i++){
a[i] = function(){
console.log(i);
}
}
a[3]();
上边的代码,变量i是let声明的,当前的i只在本轮循环中有效,所以每次循环的i其实都是一个新的值。
疑问:
- 既然是每次循环都是一个新的值,那么为什么还能实现i的值自增呢?
这是因为JavaScript引擎会自动保存上一次循环的i值,自增后赋给本次循环的i
- i的作用域只在块级 {} 中,为什么a[3](),还能访问到i的值?
??????
(2)let没有变量的声明提前
{
let a = 2;
console.log("a--->",a);
}
会报错,这也会产生所谓的暂时性死区。
(3)不允许重复声明
每个变量用let或者const修饰后,就不能被修饰了。
以下几种形式应注意:
- var foo = 1; let foo = 1;//重复声明报错
- let foo2 = 1; var foo2 = 2; //重复声明报错
- 函数形参中默认声明,函数体内用let声明同样的变量,会报错
let a = 1;
function fn1(a, b=a){
let a = 3; //这一步会报错
console.log("a---->", a);
console.log("b---->", b);
}
fn1(2);
- 函数的形参中默认声明,函数体内用var声明同样的变量,不会报错
let a = 1;
function fn1(a, b=a){
var a = 3; //这一步会报错
console.log("a---->", a);
console.log("b---->", b);
}
fn1(2);
可见,函数形参中默认声明的参数,实际上等价于var声明
注意:let vs const
- 其中let修饰的变量可以再次被赋值,而const修饰的变量不能被赋值。
- 其中let修饰的变量可以不用初始化,而const修饰的变量必须要初始化,否则会报错。
2变量的结构赋值
2.1数组的结构赋值
(1)一般情况,
[x, y, z] = [1, 2, 3];
结果
(2)嵌套
[x, y, z] = [1, [2,3], 4];
结果
(3)省略
[x, y, z] = [1, 2]
结果
(3)默认值
[x, y, z=1] = [1, 2];
结果
可以看出,其实结构赋值的就是将多个不同结构或者类型的赋值语句集中到一起,看起来更直观,写起来很简单。
注意:关于默认值的情况,只有等号右边的对应位置没有该变量值,或者说右边要给它赋值undefined时,才会触发左边的默认值情况。
(4)rest参数
[first,...rest] = [1,2,3,4]
结果
其实关于数组的结构赋值与函数的参数传递机制十分相似,如下图所示,只不过一个是在中括号[]中,另外一个是在函数标识符后的小括号内。
2.2对象的结构赋值
除了利用数组进行解构赋值,也可以用JSON对象进行解构赋值。对象的结构赋值机制:先找到同名属性,然后再赋值给相应的变量,真正被赋值的是属性名(键名):后的变量,如下,
{foo: bar} = {foo:"124"};
实际上是给bar赋值,这样写得结果是bar="124",除此之外其他的情况与数组的解构赋值类似,只是数组默认按索引的顺序进行赋值,而对象的按“同名属性”机制进行赋值。
另外,一个特殊的情况
{a1, a2} = {a1:"123", a2:"456"}
这种写法是
{a1:a1, a2:a2} = {a1:"123", a2:"456"}
的简写。
注意:避免跨级作用域产生的问题:
let foo;
{foo} = {foo:"123"};
此时,{foo}会被解析为块级代码段,所以使用 () 来解决
2.3圆括号问题
3函数参数的默认值
3.1参数的默认位置
当函数声明的形参部分采用默认值时,应注意,只有将默认值的形参放在最后,在函数体内部使用这些参数才不会出现问题。
function fn1(x, y=1){console.log(x, y);}
fn1(2) //1,2
function fn1(y=1,x){console.log(x, y);}
fn1(2) //undefined 2
function fn1(y=1,x){console.log(x, y);}
fn1(undefined, 2) //1,2
因为只有undefined才会触发默认值的表达式,所以只有显式表示fn1(undefined, 2)才会返回正确的结果。
3.2函数参数长度
指定的默认值后,函数参数的长度会失真。
可见,长度只会计算到第一个没有指定默认值得形参之前的参数个数。
除此之外,当含有rest参数时,函数参数的长度是不计算rest参数的,如下
3.3作用域
如果参数的默认值是一个变量,则该变量所处的作用域与其他变量的作用域规则一致。即按照局部——>全局的作用域链寻找该变量的值,如果let声明的变量,则会受到块级作用域的影响。
let a = 1;
function fn1(a, b=a){
console.log("a---->", a);
console.log("b---->", b);
}
fn1(2);
如上,fn1(a, b=a),如果在全局中调用fn1(2),实际上等价于这样的解构赋值。具体首先a,b被声明为局部变量,其次,按照解构赋值,a先被赋值为2,而b对应的位置是undefined,所以会取默认值即a的值,这是搜寻a的值会遵循局部——>全局的作用域链的思路搜寻。
var [a, b=a] = [2]
4扩展运算符——...
...数组 即可将一个数组转化为一个逗号区分的序列。通常,多用扩展运算符对数组做一些操作,
应用:
- 参数不能是数组,但可以是多个相同类型元素的函数,可以简化调用方式。如Mah.max、数组对象的push方法,
如下代码
console.log("max--->",Math.max(2, 3, 1, 10, 100, 3, 30));
var arr = [2, 3, 1, 10, 100, 3, 30];
// ES5
var max_es5 = Math.max.apply(null, arr);
console.log("max_es5--->", max_es5);
// ES6
var max_es6 = Math.max(...arr);
console.log("max_es5--->", max_es6);
结果
- 合并数组
arr1=[1,2];
arr2 = [3,4];
arr1.concat(arr2);
//[1, 2, 3, 4]
[...arr1,...arr2];
//[1, 2, 3, 4]
- 任何类似数组的对象都可以通过扩展运算符赋 ... 转化为真正的数组
所谓类似数组是指实现了Iterator接口的数据结构,如Map、Set结构,当然字符串也可以通过...转化为数组
注意:关于字符串的一些操作容易混淆,这里区分如下
- 使用扩展运算符,将字符串转化为数组的操作如上所示
- 解构赋值中,有两种形式应注意区分:
var [first,...rest] = ["fpp"];
后边的字符串是用数组包起来,说明,“fpp”作为一个整体是数组索引0对应的元素。所以结果
var [first,...rest] = "fpp";
而这种情况, 可以看出,"fpp"会被先转为数组,然后作为数组给[first,...rest]进行结构赋值,所以出现以下结果,
事实上,另外一个例子也可以证明上述的猜想,
[rest] = "fpp";
按照解构赋值,rest只会被赋值"f",结果如下,