ES5、ES6介绍
💯声明命令 let、const
🍟1 let声明符
🎲1.1 基础特性
if(true){
let a = 'let';
var b = 'var';
}
console.log(b); //var
console.log(a); //Uncaught ReferenceError: a is not defined
---------------------------------------------------------
//使用let,声明的变量仅在块级作用域内({ })有效,最后输出的是 6。
for(let i = 0; i < 10; i++){
}
console.log(i); //Uncaught ReferenceError: i is not defined
上面代码在代码块之中,分别用let
和var
声明了两个变量。然后在代码块之外调用这两个变量,结果let
声明的变量报错,var
声明的变量返回了正确的值。这表明,let
声明的变量只在它所在的代码块有效。
// 在循环体表达式中let声明迭代变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量
var a = [];
for (let i = 0; i < 10; i++) {
a.push(function () {
console.log(i);
});
}
a[5](); // 5
// 在循环体表达式中let声明 和循环体中let 声明同名变量是不冲突的 设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。相当于每次循环{}内部都会单独开一个局部作用域
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
🎲1.2 不进行变量提升
// var 的情况
console.log(num); // 输出undefined
var num = 2;
-------------------------
// let 的情况
console.log(age); // 报错ReferenceError
let age = 16;
🎲1.3 致死域(暂时性死区)
//正常调用
let num = 10;
if (true) {
console.log(num); //10
}
------------------
//if { }内部let为独立局部作用域 访问为 20
let num = 10;
if (true) {
let num = 20;
console.log(num); //20
}
------------------
//发生致死 因为fn局部作用域中有let声明num 所以在作用域内 不能先调用再使用let声明。
let num = 10;
function fn() {
console.log(num); //Uncaught ReferenceError: Cannot access 'num' before initialization
let num = 20;
}
fn();
------------------
//typeof操作影响
typeof num;
let num; //Uncaught ReferenceError: Cannot access 'num' before initialization
当程序的控制流程在新的作用域(module function 或 block 作用域)进行实例化时,在此作用域中用let/const声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定,所以是不能被访问的,如果访问就会抛出错误。因此,在这运行流程进入作用域创建变量,到变量可以被访问之间的这一段时间,就称之为暂时死区。
es6规定 let
/const
会使区域形成封闭的作用域 若在声明之前使用变量就会发生错误, 在代码块
内使用let命令声明变量之前 该变量都无法使用,称为“暂时性死区”(temporal dead zone,简称 TDZ)。总之,暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。使用let 确保先 声明 再 使用 变量 不要混用let和var在同一个作用域链上 避免死区
🎲1.4 无法重复声明
//发生致死 因为var会变量提升。
let num = 10;
if (true) {
var num = 20; //Uncaught SyntaxError: Identifier 'num' has already been declared
console.log(num);
}
🎲1.5 块作用域 (block scope)
EcmaScript 6引入了块级作用域(block scope),块级作用域只能在块中被访问,以下两种情况可以创建块级作用域的变量。
-
在函数中
-
在被
{
和}
包裹的块中
{
var x = 10;
}
console.log(x); //10
------------------
{
let x = 10;
}
console.log(x);//x is not defined
------------------
//函数有两个代码块,都声明了变量num,运行后输出 3。这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量num,最后输出的值才是 6。
function fn() {
let num = 3;
if (true) {
let num = 6;
}
console.log(num); // 3
}
🎲1.6 块作用域下的函数声明
function a() {
console.log('我是全局作用域内声明的函数a');
}
(function () {
if (false) {
function a() {
console.log('我是块级作用域内声明的函数a');
}
}
a();
})();
----------------------
function a() {
console.log('我是全局作用域内声明的函数a');
}
(function () {
var a;
if (false) {
a = function () {
console.log('我是块级作用域内声明的函数a');
}
}
a();
})();
为了减轻因此产生的不兼容问题,ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。
- 允许在块级作用域内声明函数。
- 函数声明类似于
var
,即会提升到全局作用域或函数作用域的头部。 - 同时,函数声明还会提升到所在的块级作用域的头部。
🍟2 const声明符
const DATA = '我是常量 不能改变哦';
DATA // '我是常量 不能改变哦'
DATA = '改一个试试看';
// TypeError: Assignment to constant variable.
const
声明的变量不得改变值,这意味着,const
一旦声明变量,就必须立即初始化,不能留到以后赋值。
🎲2.1 声明时必须赋值
const X;
//Uncaught SyntaxError: Missing initializer in const declaration
🎲2.2 值为对象
const KEY_MAP = {
a:1,
b:2
}
KEY_MAP['a'] = 2; //不报错
-----------
const ARRAY = [];
ARRAY.push('something'); // 可执行
ARRAY.length = 0; // 可执行
ARRAY = ['something']; // 报错
💯解构赋值
[a, b] = [50, 100];
console.log(a);
// expected output: 50
console.log(b);
// expected output: 100
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(rest);
// expected output: [30, 40, 50]
我们有很多种不同的方式使用 JavaScript 解构。
🍟1 数组解构
数组解构是极为简单整洁的,在复制表达式的左侧使用数组字面量。数组字面量中的每个变量名称映射为解构数组的相同索引项。
🎲1.1 基础变量赋值
let foo = ['one', 'two', 'three'];
let [red, yellow, green] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // "three"
🎲1.2 声明分别赋值
你可以通过变量声明分别解构赋值。举例:首先,声明变量,然后分别赋值。
// 声明变量
let a, b;
// 然后分别赋值
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2
🎲1.3 解构默认值
如果解构取出的值是 undefined
,可以设置默认值:
let a, b;
// 设置默认值
[a = 5, b = 7] = [1];
console.log(a); // 1
console.log(b); // 7
在上面的例子中,我们给 a
和 b
设置了默认值。这种情况下,如果 a
或 b
的值是 undefined
,它将赋值默认值 5
给 a
7
给 b
🎲1.4 交换变量值
解构可以在一个解构表达式中交换两个变量值
let a = 1;
let b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
如果你想不使用解构交换变量值,将必须提供一个缓存变量或者同解构一起使用
🎲1.5 解析函数返回的数组
可以解构函数返回的数组。
function c() {
return [10, 20];
}
let a, b;
[a, b] = c();
console.log(a); // 10
console.log(b); // 20
在上面的例子中,c()
的返回值 [10, 20]
可以在单独的一行代码中使用解构解析。
🎲1.6 忽略返回值/跳过某项
你也可以跳过一些没有用的返回值。
function c() {
return [1, 2, 3];
}
let [a, , b] = c();
console.log(a); // 1
console.log(b); // 3
在罕见的情况下,你想忽略所有的值。
[, ,] = c();
当然,这是不会发生的。
🎲1.7 赋值数组剩余值给一个变量
当你使用数组解构,你可以赋值数组剩余部分给一个单独的变量。
let [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]
小心结尾的逗号语法错误,它将爆发如果在剩余元素的左侧使用结尾逗号:
let [a, ...b,] = [1, 2, 3];
// SyntaxError: rest element may not have a trailing comma
🎲1.8 嵌套数组解构
像对象一样,你也可以使用数组嵌套解构。这里有一个例子:
const color = ['#FF00FF', [255, 0, 255], 'rgb(255, 0, 255)'];
// Use nested destructuring to assign red, green and blue
// 使用嵌套解构赋值 red, green, blue
const [hex, [red, green, blue]] = color;
console.log(hex, red, green, blue); // #FF00FF 255 0 255
🍟2 对象解构
🎲2.1 基础对象解构
let x = { y: 22, z: true };
let { y, z } = x; // let {y:y,z:z} = x;的简写
console.log(y); // 22
console.log(z); // true
🎲2.2 无声明解构
你可以使用解构分别从它的声明赋值变量。这意味着在上面的例子中不需要创建变量 x
。
let y, z;
({ y, z } = { y: 1, z: 2 });
🎲2.3 赋值给新变量名
当使用对象解构时你也可以改变变量的名称,如下例子:
let o = { p: 22, q: true };
let { p: foo, q: bar } = o;
console.log(foo); // 22
console.log(bar); // true
例子中,var {p: foo} = o
获取对象 o
的属性名 p
,然后赋值给一个名称为 foo
的变量。
🎲2.4 解构默认值
如果解构取出的对象值是 undefined
你也可以设置默认值。
let { a = 10, b = 5 } = { a: 3 };
console.log(a); // 3
console.log(b); // 5
🎲2.5 赋值给新变量名的同时提供默认值
let { a: aa = 10, b: bb = 5 } = { a: 3 };
console.log(aa); // 3
console.log(bb); // 5
🎲2.6 嵌套对象和数组解构
const metadata = {
title: 'Scratchpad',
translations: [
{
locale: 'de',
localization_tags: [],
last_edit: '2014-04-14T08:43:37',
url: '/de/docs/Tools/Scratchpad',
title: 'JavaScript-Umgebung',
},
],
url: '/en-US/docs/Tools/Scratchpad',
};
let {
title: englishTitle, // 重命名
translations: [
{
title: localeTitle, // 重命名
},
],
} = metadata;
console.log(englishTitle); // "Scratchpad"
console.log(localeTitle); // "JavaScript-Umgebung"
🎲2.7 可计算对象属性名与解构
当使用解构改变对象属性的名称时,可以使用对象计算属性名。
let key = 'z';
let { [key]: foo } = { z: 'bar' };
console.log(foo); // "bar"
在上面的例子中,我们计算变量键值并改变它的名称为 foo
。
🎲2.8 同时使用数组和对象解构
在解构中数组和对象可以联合使用:
const props = [
{ id: 1, name: 'Fizz' },
{ id: 2, name: 'Buzz' },
{ id: 3, name: 'FizzBuzz' },
];
const [, , { name }] = props;
console.log(name); // "FizzBuzz"
所有的解构赋值语法是相同的,是在赋值符号左侧声明从源变量取出的值。举例来说:
let x = [1, 2, 3, 4, 5];
let [y, z] = x;
console.log(y); // 1
console.log(z); // 2
🎲2.9 注意点
(1)如果要将一个已经声明的变量用于解构赋值,必须非常小心。
// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error
上面代码的写法会报错,因为 JavaScript 引擎会将{x}
理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。
// 正确的写法
let x;
({x} = {x: 1});
上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。
(2)解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。
({} = [true, false]);
({} = 'abc');
({} = []);
上面的表达式虽然毫无意义,但是语法是合法的,可以执行。
(3)由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
上面代码对数组进行对象解构。数组arr
的0
键对应的值是1
,[arr.length - 1]
就是2
键,对应的值是3
。方括号这种写法,属于“属性名表达式”
🍟3 函数参数的解构赋值
函数的参数也可以使用解构赋值。
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
上面代码中,函数add
的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x
和y
。对于函数内部的代码来说,它们能感受到的参数就是x
和y
。
下面是另一个例子。
[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]
函数参数的解构也可以使用默认值。
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
上面代码中,函数move
的参数是一个对象,通过对这个对象进行解构,得到变量x
和y
的值。如果解构失败,x
和y
等于默认值。
注意,下面的写法会得到不一样的结果。
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
上面代码是为函数move
的参数指定默认值,而不是为变量x
和y
指定默认值,所以会得到与前一种写法不同的结果。
undefined
就会触发函数参数的默认值。
[1, undefined, 3].map((x = 'yes') => x);
// [ 1, 'yes', 3 ]
🍟4 解构的用途
变量的解构赋值用途很多。
🎲4.1 交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
上面代码交换变量x
和y
的值,这样的写法不仅简洁,而且易读,语义非常清晰。
🎲4.2 从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
🎲4.3 函数参数的定义
解构赋值可以方便地将一组参数与变量名对应起来。
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
🎲4.4 提取 JSON 数据
解构赋值对提取 JSON 对象中的数据,尤其有用。
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
上面代码可以快速提取 JSON 数据的值。
🎲4.5 函数参数的默认值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
} = {}) {
// ... do stuff
};
指定参数的默认值,就避免了在函数体内部再写var foo = config.foo || 'default foo';
这样的语句。
JavaScript: https://developer.mozilla.org/en-US/docs/Web/JavaScript