一、let和const命令
let和var的联系和区别:
1、都用来定义变量;但是let有块级作用域,只在它声明的作用域内有效;
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10复制代码
***:变量i是由var声明的,在全局内都有效。所以每一次循环的每一次赋值都指向的同一个变量i,故输出的值是最后的赋值。
例子2:
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6复制代码
***: i 是let声明的,只在本轮循环内有效,所以每一次循环都是一次新的变量赋值,故输出为对应循环的值。
例子3:
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc复制代码
***:for循环还有一个特殊之处,设置循环变量的那部分是父作用域,而在循环体内部是一个单独的自用域。
2、let不存在变量提升
var声明会发生变量提升,即变量可以在声明之前使用,值为undefined。
let改变了这一语法,即只可以在变量声明之后使用,否则会报错。
例子:
// var 的情况
console.log(foo); // 输出undefined
var foo = 2;
// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;复制代码
3、暂时性死区
es6中明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭的作用域。如果在声明之前就去使用这些变量,就会报错。
总之,暂时性死区的本质是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取。只有等到声明变量的那一行代码出现,才可以获取和使用该变量。否则就会报错。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}复制代码
4、不允许重复声明
let不允许在相同作用域内,重复声明同一个变量。
函数参数的作用域和函数体内的作用域是分离的,但是参数作用域会带入函数体,所以参数已经声明的变量,不能用let再次声明。
function func(arg) {
let arg;
}
func() // 报错
function func(arg) {
{
let arg;
}
}
func() // 不报错复制代码
const常量
1、const声明一个只读的常量,一旦声明,常量的值就不能改变。这意味着,const声明时,必须立即赋值,否则会报错。
2、本质:
const所保证的,并不是变量的值不得改动,而是变量所指向的那个内存地址所保存的数据不得改动。
所以对于复合类型的数据,const保证的是当前的指针是固定的(即总是指向同一个内存地址),至于它指向的数据结构是不是可变的,就完全不受控制了。
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only复制代码
二、变量的解构赋值
1、数组的解构赋值
(1) es6允许按照一定模式(对应位置),从数组和对象中提取值,对变量进行赋值,这被成为解构。
例子1:
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"复制代码
例子2:
如果解构不成功,那变量的值就是undefined.
let [foo] = [];
let [bar, foo] = [1];复制代码
例子3:
如果等号右边不是数组(或者,严格的说,不是可遍历的解构)(仅限于数组的解构赋值),那么将会报错。
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};复制代码
(2) 解构赋值允许指定默认值
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
let [x = 1] = [null]; //x=null
复制代码
es6内部使用严格相等运算符(===),来判断一个位置是否有值。所以,只有一个数组成员严格等于undefined,默认值才会生效。
2、对象的解构赋值
(1) 对象和数组的解构赋值有一个重要的不同。数组--顺序;对象的属性没有次序,变量必须与属性同名,才能取到相应的值。
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"复制代码
(2)对象的解构赋值的内部机制,是先找到同名属性,在赋值给对应的变量。真正被赋值的是后者,而不是前者。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined复制代码
(3)如果解构模式是被嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。
// 报错
let {foo: {bar}} = {baz: 'baz'};复制代码
3、字符串的解构赋值
这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"复制代码
4、数值和布尔值的解构赋值
(1)解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true复制代码
(2)解构赋值的规则是,只要等号右边的值不是对象和数组,就先将其转为对象。由于undined和null都无法转为对象,故会报错。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError复制代码
5、函数参数也可以用解构赋值
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]复制代码
三、Set和Map数据结构
1、Set
(1)基本用法:Set本身是一个构造函数,用来生成Set数据结构。它类似于数组,但是每个成员都是唯一的,没有重复的值。
const s = new Set();
[2, 3, 5, 4, 5, 2, 2].forEach(x => s.add(x));
for (let i of s) {
console.log(i);
}
// 2 3 5 4复制代码
a、Set函数可以接受一个数组作为参数,用来初始化。
// 例一
const set = new Set([1, 2, 3, 4, 4]);
[...set]
// [1, 2, 3, 4]
// 例二
const items = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
items.size // 5复制代码
b、Set可以去除重复项
// 去除数组的重复成员
[...new Set(array)]
// 去除字符串里的重复项
[...new Set('ababbc')].join('')
// "abc"
复制代码
(2)Set实例的属性和方法
---属性:
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。
---操作方法:
add(value)
:添加某个值,返回 Set 结构本身。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为Set
的成员。clear()
:清除所有成员,没有返回值。
s.add(1).add(2).add(2);
// 注意2被加入了两次
s.size // 2
s.has(1) // true
s.has(2) // true复制代码
***:将Set结构转为数组的方法:
Array.from 和 ...
const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);复制代码
---遍历方法:
keys()
:返回键名的遍历器values()
:返回键值的遍历器entries()
:返回键值对的遍历器forEach()
:使用回调函数遍历每个成员
⚠️:Set的遍历顺序就是插入顺序。
a、keys()
,values()
,entries()
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]复制代码
Set结构的实例默认可以遍历,默认同values方法。
let set = new Set(['red', 'green', 'blue']);
for (let x of set) {
console.log(x);
}
// red
// green
// blue复制代码
b、forEach()
let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9复制代码
c、遍历的应用
扩展运算符...内部使用for...of循环,所以也可以用于Set结构。
//...和Set相结合,可以数组去重
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]复制代码
...将Set结构转为数组,所以也可以使用数组的map方法和filter方法
let set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// 返回Set结构:{2, 4, 6}复制代码
因此Set可以很轻易的实现数组的并集,交集,差集。
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}复制代码
2、Map
(1) 含义和基本用法:
js里的对象Object,本质上是键值对的集合。但是,只能用字符串作为键名。所以,使用上有很大的限制。
Es6里的Map数据结构,类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以用作键。
总结,object提供了“字符串-值”的对应,Map提供了“值-值”的对应。如果你需要键值对的数据结构,Map比object合适。
const m = new Map();
const o = {p: 'Hello World'};
m.set(o, 'content')
m.get(o) // "content"复制代码
a、作为构造函数,Map可以接受一个数组作为参数。
const map = new Map([
['name', '张三'],
['title', 'Author']
]);
map.size // 2
map.has('name') // true
map.get('name') // "张三"复制代码
b、如果对同一个键多次赋值,后一次的值会覆盖前一次的值。
const map = new Map();
map
.set(1, 'aaa')
.set(1, 'bbb');
map.get(1) // "bbb"复制代码
d、如果读取一个未知的键,返回‘undefined’
e、⚠️:只有对同一个对象的引用,Map才将其视为同一个键。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined复制代码
上面的set和get方法,表面上是针对同一个键,但实际上是2个值,内存地址不一样。所以get无法读取该键。
同理,同样的值的两个实例,在 Map 结构中被视为两个键。
const map = new Map();
const k1 = ['a'];
const k2 = ['a'];
map
.set(k1, 111)
.set(k2, 222);
map.get(k1) // 111
map.get(k2) // 222复制代码
上面代码中,变量k1和k2的值是一样的,但是在Map结构中被视为2个键。
由上可知,Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题。
如果 Map 的键是一个简单类型的值(数字、字符串、布尔值),则只要两个值严格相等,Map 将其视为一个键,比如0
和-0
就是一个键,布尔值true
和字符串true
则是两个不同的键。另外,undefined
和null
也是两个不同的键。虽然NaN
不严格相等于自身,但 Map 将其视为同一个键。
(2)实例的属性和方法
---属性:
size:返回Map结构的成员总数。
const map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size // 2复制代码
---操作方法:
set(key,value)
:设置key对应的value,返回整个Map结构。get(key)
:获取key对应的键值,如果找不到key,则返回undefineddelete(key)
:删除某个值,返回一个布尔值,表示删除是否成功。has(key)
:返回一个布尔值,表示该值是否为Map的成员。clear()
:清除所有成员,没有返回值。
---遍历方法:
keys()
:返回键名的遍历器。values()
:返回键值的遍历器。entries()
:返回所有成员的遍历器。forEach()
:遍历 Map 的所有成员。
⚠️:Map的遍历顺序就是插入顺序。
const map = new Map([
['F', 'no'],
['T', 'yes'],
]);
for (let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
复制代码
a、Map 结构的默认遍历器接口,就是entries
方法。
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for (let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"复制代码
b、Map 结构转为数组结构,比较快速的方法是使用扩展运算符(...
)。
const map = new Map([
[1, 'one'],
[2, 'two'],
[3, 'three'],
]);
[...map.keys()]
// [1, 2, 3]复制代码
c、结合数组的map
方法、filter
方法,可以实现 Map 的遍历和过滤(Map 本身没有map
和filter
方法)。
const map0 = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
const map1 = new Map(
[...map0].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}复制代码
四、Promise对象
1、Promise的含义
Promise是异步编程的解决方案,比传统的解决方案---回调函数和事件,更合理更强大。
所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件的结果(通常是一个异步操作)。
Promise对象有以下2个特点:
(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有3种状态:
pending--进行中,fulfilled--已成功,rejected--已失败。只有异步操作的结果,可以决定当前是哪种状态,其他任何操作都不可以改变该状态。
(2)一旦状态改变,就不会在变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有2种:pending-->fulfilled,pending-->rejected
只要状态发生改变,状态就凝固了,不会在变,会一直停留在这个结果。
2、基本用法
(1) Promise对象是一个构造函数,用来生成Promise实例。
Promise对象接受一个函数作为参数,该函数的2个参数分别是:resolve,reject;
resolve:pending-->fulfilled,异步操作成功时的回调,并将异步操作的结果,作为参数传递出去。
reject:pending-->rejected,异步操作失败时的回调,并将异步操作报出的错误当作参数传递出去。
const promise = new Promise(function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});复制代码
promise.then(function(value) {
// success
}, function(error) {
// failure
});复制代码
(2) Promise对象的简单例子
function timeout(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms, 'done');
});
}
timeout(100).then((value) => {
console.log(value);
});复制代码
(3)Promise新建后会立即执行
let promise = new Promise((resolve, reject) => {
console.log('Promise');
resolve();
});
promise.then(() => {
console.log('resolved.');
});
console.log('Hi!');
// Promise
// Hi!
// resolved复制代码
上面代码中,Promise新建后会立即执行,所以首先输出Promise。then方法指定的回调函数将在当前同步任务执行完毕之后才会执行,所以最后输出resolved.
3、Promise.prototype.then()
then方法的作用是:为Promise实例添加 状态改变 时的 回调函数。第一个参数是resolved状态的回调函数,第二个参数(可选)是rejected状态的回调函数。
then方法返回的是一个新的promise实例(注意,不是原来的那个)。所以可以采用链式调用,即then方法后面在调用另一个then方法。
4、Promise.prototype.catch()
用于指定发生错误时的回调。(对应状态为rejected)
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});复制代码
5、Promise.prototype.finally()
finally()方法用于指定不管Promise对象最后状态如何,都会执行的操作。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});复制代码
6、Promise.all()
Promise.all()方法用于将多个Promise实例,包装成一个新的Promise实例。
const p = Promise.all([p1, p2, p3]);复制代码
p
的状态由p1
、p2
、p3
决定,分成两种情况。
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
7、Promise.race()
Promise.race
方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.race([p1, p2, p3]);复制代码
上面代码中,只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数
8、Promise.resolve()
将现有对象转为Promise对象。
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))复制代码
如果不带有任何参数,则返回一个状态为resolved状态的Promise对象。
9、Promise.reject()
Promise.reject(reason)
方法也会返回一个新的 Promise 实例,该实例的状态为rejected
。
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))
p.then(null, function (s) {
console.log(s)
});
// 出错了复制代码
10、Promise.try()
异步执行异步,同步执行同步。