一、 let和const命令
1. let命令
① let命令的用法类似于var,但其所声明的变量只在let命令所在的代码块内有效
{
let a=0;
var b=1;
for(let i=0;i<5;i++){}
console.log(i);//i is not defined
}
console.log(a);//a is not defined
console.log(b);
② 用var定义的变量在for循环的函数中每次只能取得最终值,而用let定义的变量在for循环的函数中可以取得不同的i值
var a=[];
for(let i=0;i<5;i++){
a[i] = function(){
console.log(i);
}
}
a[3]();//3
③ 不存在变量提升
console.log(foo);//undefined
var foo = 2;
console.log(bar);//报错ReferenceError
let bar = 1;
④ let不允许在同一作用域内重复声明同一个变量(var和let分别声明也不行
)
function func(arg){
let arg;
}
func();//报错
function func(arg){
{
let arg;
}
}
func();//不报错
⑤ let不允许在声明之前使用变量。只要块级作用域内存在let命令,它所声明的变量就"绑定"在这个区域(死区),不会受外部的影响
let temp = 11;
if(true){
temp = 234;//temp is not defined
let temp;
temp = 2;
console.log(temp);//2
}
function bar(x=1,y=x){
return [x,y];
}
bar();//[1,1]
function abb(x=y,y=1){
return [x.y];
}
abb();//报错(y未声明,故不能使用)
⑥ 若在声明前检测一个变量的类型则会报错。但整个块级作用域都没有声明该变量则检测其类型为undefined。
⑦ 若在一个块级作用域内未声明所需变量,此时会去父块级作用域中查找该变量,若有,则该变量的取值为父级块级作用域中该变量的值
⑧ 块级作用域:在es5中,无块级作用域会有两种麻烦出现:a,内存变量可能会覆盖外层变量。b,用来计数的循环变量泄漏为全局变量
//es5中的两种情况
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined(由于变量提升,故tmp值为undefined)
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
//es6
function f1(){
let n=5;
if(true){
let n=10;
}
console.log(n);//5
}
⑨ 块级作用域与函数声明:ES5规定函数只能在顶级作用域和函数作用域之中声明。不能在块级作用域声明。在ES6中,明确允许在块级作用域之中声明函数。但是函数声明语句的行为类似于let,在块级作用域以外不可引用。(注意:ES6浏览器的执行和规定有出入,具体看文档。也是因为这方面故不推荐在块级作用域中声明函数。若确实需要,应写成函数表达式(即将函数赋值给一个变量),而不是函数声明语句(即一般函数声明))
⑩ ES6块级作用域必须有大括号,如果没有大括号,js引擎则认为不存在块级作用域(不存在块级作用域便不可声明变量)
if(true) let x = 1;//报错
if(true){//不报错
let x=1;
}
2. const命令
本质:const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不能动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址。而对于复合类型的数据(主要是对象和数组),变量指向的是内存地址,保存的只是一个指向实际数据的指针。const只是保证该内存地址是不变的,而对于内存地址中保存的数据是否可变就不能完全控制了
① const声明一个只读常量,一旦声明,常量的值不能改变
② const声明的变量不得改变值,即const一旦声明变量就必须立即初始化,不能留到以后赋值
③ const的作用域与let命令相同,只在声明所在的块级作用域内有效。
④ const命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。
⑤ 不可重复声明
⑥ 对于下述例子中,若想真正冻结对象,则使用object.freeze()
方法,此时不可为对象添加属性/方法。除了冻结对象,也可冻结对象的属性
//对于本质中复合类型的例子
const foo={};
foo.prop = 123;
console.log(foo.prop);//123
foo = {};// TypeError: "foo" is read-only
const a=[];
a.push("hello");//正确
a.length = 0;//正确
a=["1"];//错误
//针对⑥的例子
const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;
//冻结对象的属性
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
3. 顶层对象的属性
顶层对象中,在浏览器环境指的是
window
对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。
实际上,顶层对象的属性和全局变量挂钩是有问题的。① 没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的)② 程序员很容易不知不觉地就创建了全局变量(比如打字出错)③ 顶层对象的属性是到处可以读写的,这非常不利于模块化编程
ES6中,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
二、 变量的解构赋值
- ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值。这被称为解构。本质上,其属于"模式匹配"。只要等号两边的模式相同,左边的变量就会被赋予对应的值。若解析不成功,则变量的值为undefined
- 在解构赋值中,扩展运算符必须放在最后一个
//模式匹配例子
//1-1
let [a,b,c] = [1,2,3];
console.log(a,b,c);//1,2,3
//1-2
let [foo,[[bar],baz]] = [1,[[2],3]]
console.log(foo,bar,baz);//1,2,3
//1-3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
//1-4
let [x, , y] = [1, 2, 3];
x // 1
y // 3
//1-5
let [x,...y] = [1,2,3,4]
x//1
y//[2,3,4]
//1-6
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
① 数组的解构赋值
① 等号的右边不是可遍历的结构会报错(数组、对象)
② 若左边是多个变量,则右边必须以数组进行赋值
③ 不完全解构:等号左边的模式只匹配一部分的等号右边的数组。(即若右边与左边不匹配也可以解析成功(但格式必须相同,例如:左边是数组,右边是数字,此时无法匹配成功))
let [x,y] = [1,2,3];
x//1
y//2
let [a,[b],c] = [1,[2,3],4]
a//1
b//2
c//4
④ 解构赋值允许指定默认值。只有当赋值为undefined时会采用默认值
let [foo=true] = []
foo//true
let [x=1] = [undefined];
x//1
let [x=1] = [null];
x//null
⑤ 如果默认值是一个表达值,那么这个表达值是惰性求值的,只有在真正用到时才会求值。
function f(){
//该方法只有在用到时才会执行
return "1111"
}
let [x=f()] = [1];
⑥ 默认值可以引用解构赋值的其他变量,但该变量必须已经声明过。
let [x=1,y=x] = []//x=1,y=1
let [x=y,y=1]=[]; // ReferenceError: y is not defined
② 对象的解构赋值
① 对象的解构与数组有一个重要的不同:数组的元素是按次序排列的,但是对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let {foo,bar,baz}={bar:"123",foo:"345"};
foo//345
bar//123
baz//undefined
② 对象的解构赋值,可以将先有对象的方法赋值给某个变量
let {sin,cos} = Math();
sin(1);//可以求出1的sin值
let {log} = console;
log("123");//输出123
③ 如果变量名与属性名不一致,必须写成下述例子中的样子。
//1-1
let {foo:baz} = {foo:'aaa',bar:'sss'};
baz//aaa
foo//foo is not defined
//1-2
let {a:c,b:d} = {b:1,a:3}
console.log(c,d)//3,1
//1-3
let {a:c,b:d} = {b:1,c:3}
console.log(c,d)//undefined,1
④ 解构也可以用于嵌套结构的对象。但下述1-1例中不能对p赋值。若相对p赋值必须要如1-2写(注意:要给某一个模式赋值,该模式必须要和等号右边对象中模式相同,否则无法赋值成功)
//1-1
let obj = {
p:[
'hello',
{y:"world"}
]
}
let {p:[x,{y}]} = obj;
x//hello
y//world
p// p is not defined
//1-2
let {p,p:[x,{y}]} = obj;
x//hello
y//world
p//['hello',{y:"world"}]
//1-3
const node = {
loc: {
start: {
line: 1,
column: 5
}
}
};
let { loc, loc: { start }, loc: { start: { line }} } = node;
line // 1
loc // Object {start: Object}
start // Object {line: 1, column: 5}
⑤ 对象的解构赋值可以取到继承的属性
const obj1 = {};
const obj2 = { foo: 'bar' };
Object.setPrototypeOf(obj1, obj2);
const { foo } = obj1;
foo // "bar"
⑥ 对象的结构可以指定默认值.默认值生效的条件是:对象的属性的取值为undefined
var {x=3} = {}
x//3
var {y=4}={null}
y//null
var {x:y=3}={}
y//3
var {x:y=3}={x:5}
y//5
x//x is not defined
⑦ 若要将一个声明的变量用于解构赋值,必须在赋值时带
()
。若不带括号,js引擎会将{x}
理解为一个代码块。
let x;
{x} = {x:1}//错误
({x} = {x:1})//正确
⑧ 解构赋值允许等号左边的模式中不放置任何变量。该写法语法没有错误,但没有意义。
⑨ 由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
let arr=[1,2,3];
let {0:first,[arr.length-1]:last}=arr;
first;//1
last;//3
⑩ 字符串可以解构赋值,是因为字符串被转换成了一个类似数组的对象。类似数组的对象都有一个length属性,因此还可以对这个属性进行操作
const [a,b,c,d,e] = "hello";
a//h
b//e
c//l
d//l
e//o
let {length:len} = "hello";
len//5
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
函数的参数可以使用解构赋值。函数的参数的解构也可以使用默认值。默认值中两个例子需要注意(1-1,1-2)。在默认值例子中,传来的值替换了等号右边的对象,而默认值在左右不同方。故两个例子有不同的结果
//虽然add函数的参数是一个数组,但在传入参数的时候被解构成变量x和y
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
//默认值
//1-1
function move({x=0,y=0}={}){
return [x,y];
}
move({x:2,y:8});//[2,8]
move({x:3})//[3,0]
move({});//[0,0]
move();//[0,0]
//1-2
function move({x,y}={x:0,y:0}){
return [x,y];
}
move({x:2,y:8});[2,8]
move({x:3});[3,undefined]
move({});//[undefined,undefined]
move();//[0,0]