一、const和let命令
1.let
ES6中新加入了let命令,用来声明变量,不同于var命令的地方在于let命令声明的变量只在自身所在的代码块才有效。
{
let a = 1;
console.log(a)
//1
var b = 2;
}
console.log(a)
//Uncaught ReferenceError: a is not defined
console.log(b)
//2
这个花括号就是一个函数作用域,let在其中声明的a只能在这个作用域中使用,在外面使用会报未声明的语法错误。
其次还有个不同之处在于var命令存在变量提升。什么是变量提升呢?
console.log(a);
//undefined
var a;
a = 1;
console.log(b);
//报错ReferenceError
let b;
b = 2;
上面为什么打印的是未定义呢?
这是因为声明的时候函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部,即
var a;
console.log(a);
a = 1;
这样a已经声明,只是没有赋值,所以打印的undefined。
而let不存在变量声明,打印的b的时候b还没有声明,所以会报错。
此外,let命令的特点是不允许在相同作用域内,重复声明同一个变量:
function fn1() {
let a = 10;
var a = 1;
console.log(a);
//Uncaught SyntaxError: Identifier 'a' has already been declared
}
fn1();
function fn2() {
let b = 10;
let b = 1;
console.log(b);
//Uncaught SyntaxError: Identifier 'b' has already been declared
}
fn2();
function fn3(){
var a = 10;
var a = 1;
console.log(a);
//1
}
fn3();
只有var命令可以重复声明变量,相当于再次赋值,但这么做显然不好。
2.const
const声明一个只读的常量。一旦声明,常量的值就不能改变。
如:
const a = 3;
a = 4;
console.log(a);
//Uncaught TypeError: Assignment to constant variable.
那么也说明当我们用const声明变量的时候同时要进行初始化,否则无法赋值,对于const命令了来说当我们只声明不赋值就会报错:
const a;
a = 4;
console.log(a);
//Uncaught SyntaxError: Missing initializer in const declaration
同样的,const命令和let命令一样,不存在变量提升,不允许重复声明以及都在命令所在的作用域有效。
const实际上操作的并不是让变量的值不发生改变,而是变量指向的内存地址所保存的数据不发生改变。对于简单类型的数据(数值、字符串、布尔值),内存地址保存的数据就是简单类型的值,所以对于简单类型的数据来说值等同于常量。
而基本类型的变量是存放在栈内存(Stack)里的。
const name = "Mike";
const sex = "male";
const age = 22;
栈内存(变量标识符) | 栈内存(变量的值) |
---|---|
name | Mike |
sex | male |
age | 22 |
对于上面的变量来说,改变他们的值需要新申请一块内存空间来储存新的值,但这样对应内存空间的内存地址就会发生改变,而const不允许的就是内存地址发生改变,所以这种操作不被允许。
const name = "Mike";
name = "Tony";
console.log(name);
//Uncaught TypeError: Assignment to constant variable.
但对于引用类型来说在内存中的保存情况与基本类型不同:
const arr1 = [1,2,3];
const arr2 = [4,5,6];
const arr3 = [7,8,9];
从这个图中我们可以看出,引用类型的存储需要内存的栈区和堆区(堆区是指内存里的堆内存)共同完成,栈区内存保存变量标识符和指向堆内存中该数组的指针,也可以说是该数组在堆内存的地址。而堆内存中才保存的是数组的内容。
所以,引用类型的值是可以变的,只是变量名引用的地址不能发生改变。
const arr1 = [1,2,3];
arr1[2] = 4;
console.log(arr1);
//[1, 2, 4]
二、解构赋值
ES6允许按照一定模式,从数组和对象中提取,对变量进行赋值,这被称之为解构。
1.数组
ES5:
var a = 1;
var b = 2;
console.log(a,b);
//1,2
ES6:
var [a,b] = [1,2];
console.log(a,b);
//1,2
看这段代码:
let arr = ["a", "b", "c"];
let [name1, name2, name3] = arr;
console.log([name1, name2, name3]);
//["a", "b", "c"]
name3 = "d";
console.log(name3);
//d
const arr = ["a", "b", "c"];
const [name1, name2, name3] = arr;
console.log([name1, name2, name3]);
// ["a", "b", "c"]
name3 = "d";
console.log(name3);
//Uncaught TypeError: Assignment to constant variable.
我们都能用这种方式对变量进行赋值,本质上说,上述匹配是一种模式匹配,也就是只要等号两边的模式相同,左边的变量就能被赋予对应的值。
let [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo, bar, baz);
//1 2 3
还有两种情况就是解构失败和不完全解构。
解构失败是右边的值少:
let [a,b,c] = [1,2];
console.log([a,b,c]);
//[1, 2, undefined]
不完全解构是右边的值多:
let [x,y] = [1,2,3];
console.log([x,y]);
//[1, 2]
关于默认赋值:只有右侧严格等于undefined的时候才生效:
var [a=1,b=2,c=3] = [];
console.log(a,b,c);
//1,2,3
var [a=1,b=2,c=3] = [null];
console.log(a,b,c);
//null,2,3 因为null 并不 === undefined
对于有函数的情况下还有一种惰性求值,即只有在用到的时候才会求值:
function val() {
return 20;
}
var [a = val(), b = 2, c = 3] = [12];
console.log(a, b, c);
//12,2,3
var [a = val(), b = 2, c = 3] = [];
console.log(a, b, c);
//20,2,3
val()虽然是函数执行的模式,但是只有右边严格等于undefined的时候才会执行,所以称之为惰性求值。
2.对象也可以进行解构赋值:对象的解构赋值是按照属性名称决定的
var {a,b} = {a:1,b:2};
console.log(a,b);
//1,2
这种情况的话我们首先看右边对象有没有左边对象的属性名,有的话就把右边对应的值赋给左边对应的属性。
另一只情况:
var {a:b} = {a:1};
console.log(b);
//1
先看两边对象属性的属性名是否相同,相同的话把右边的属性值赋给左边。
在对象赋值中要注意当一个已经声明的变量用于解构赋值要非常小心
var a;
{a} = {a:1};
console.log(a);
//Uncaught SyntaxError: Unexpected token =
这里报错,因为js引擎会将{a}解析为一个代码块,代码块后面是不能接’’=’'号的,这个时候我们需要将它用括号括起来(只要不将大括号写在行首就可以避免这个问题)
var a;
({a} = {a:1});
console.log(a);
//1
和数组一样,我们对象的解构赋值的模式匹配也可以用于嵌套结构
var {name:x,age:[a,b,c]} = {name:"Mike",age:[10,20,30]};
console.log(x,a,b,c);
//Mike 10 20 30
以及我们的对象的解构赋值也有默认赋值,生效条件和数组一样都是undefined
var { z = 3 } = {};
console.log((z = 3));
//3
var { x, y = x } = { x: 1 };
console.log(x, y);
//1,1
3.字符串的解构赋值
var [m, n, o, p, q] = 'hello';
console.log(m, n, o, p, q);
//h e l l o
首先字符串’hello’用new String()方法处理成字符串对象即一个类数组,,然后与左边进行匹配赋值。
let { length: len } = "hello";
console.log(len);
因为’‘hello’'被转换为类数组,所以也有length属性。
4.数值和布尔值的解构赋值
如果等号右边是数值或是布尔值,则会先转为对象
let {toString: a} = 123;
let {toString: b} = true;
console.log( a === Number.prototype.toString);
//true
console.log( b === Boolean.prototype.toString)
//true
上面代码中,数值和布尔值的包装对象都有toString属性,因此变量a,b都能取到值。
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let {toString: x } = undefined;
let {toString: y } = null;
console.log(x);
//Uncaught TypeError: Cannot destructure property `toString` of 'undefined' or 'null'.
console.log(x);
//Uncaught TypeError: Cannot destructure property `toString` of 'undefined' or 'null'.
5.函数的解构赋值
function fn([a,b]=[10,20]){
console.log(a,b);
}
fn([1,2]);
//1,2
fn();
//10,20
function move({ x = 0, y = 0 } = {}) {
console.log([x, y]);
}
move({ x: 3, y: 8 });
// [3, 8]
move({ x: 3 });
// [3, 0]
move({});
//[0, 0]
move();
//[0, 0]
move({ x: 3, y: 8 })看{ x = 0, y = 0 },匹配赋值得[3, 8]
move({ x: 3 })看{ x = 0, y = 0 },y匹配不到,取默认值得 [3, 0]
move({})看{ x = 0, y = 0 },都匹配不到,取默认值得[0, 0]
move()看{ x = 0, y = 0 } = {},右边是空对象还是取默认值得[0,0]
function deal({ x, y } = { x: 0, y: 0 }) {
console.log([x, y]);
}
deal({ x: 3, y: 8 });
//[3,8]
deal({ x: 3 });
//[3,undefined]
deal({});
//[undefined undefined]
deal();
//[0,0]
deal({ x: 3 })看{ x, y },x=3,y没有匹配到且没有默认赋值,所以返回[3,undefined]
deal({})同理看{ x, y },x与y都没有匹配到且没有默认赋值,所以返回[undefined undefined]
deal()看{ x, y } = { x: 0, y: 0 },取默认赋值,返回[0,0]
6.解构赋值的作用
(1)交换变量
let x = 1;
let y = 2;
[x, y] = [y, x];
console.log(x,y);
//2,1
(2)从函数返回多个值
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// let [a, b, c] = [1, 2, 3]
console.log(a,b,c);
//1,2,3
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
// let { foo, bar } = {foo: 1, bar: 2}
console.log(foo, bar);
//1,2
(3)函数参数的定义
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
这样做可以方便地将一组参数与变量名对应起来
(4)提交json数据
var jsonData = {
id: 42,
status: "ok",
data: [888, 999]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
//42 "ok" [888, 999]
这样可以快速提取 JSON 数据的值。