ECMAScript6语法快速入门(二)

一、数据结构

1.1 Object

我们可以向Object对象添加任何属性,所以Object也是一个容器或集合:

let obj = new Object("hello");
console.log(obj); // [String: 'hello']

obj.name = "jeff";
console.log(obj.name); // jeff

obj.print = function() {
    console.log("I'm object");
}
obj.print();    // I'm object

1.2 数组

JavaScript中使用Array类创建数组对象:

// 使用Array类创建数组
let cars1 = new Array("Saab", "Volvo", "BMW");

// 使用Array类创建数组
let cars2 = new Array();
cars2[0] = "Saab";  // 支持[]方式访问和赋值,下标从0开始
cars2[1] = "Volvo";
cars2[2] = "BMW";

// 简写方式
let cars3 = ["Saab", "Volvo", "BMW"];
console.log(cars3.length); // 3

// 数组中的元素可以为不同的类型
let a = [1, "x", true];
console.log(a[1]); // x
console.log(a[5]); // undefined,访问数组中不存在的元素会返回undefined,不会报错

a.testProp = "xyz";      // 可以向Array对象添加任意属性
console.log(a.testProp); // xyz

a.print = function() { console.log("I'm a"); }
a.print();  // I'm a

1.2.1 Array类完整的属性

属性描述
length设置或获取数组元素的个数
prototype允许你向数组对象添加属性或方法

1.2.2 Array类完整的方法

方法描述
concat()连接两个或更多的数组,并返回结果
copyWithin()从数组的指定位置拷贝元素到数组的另一个指定位置中
entries()返回数组的可迭代对象
every()检测数值元素的每个元素是否都符合条件
fill()使用一个固定值来填充数组
filter()检测数值元素,并返回符合条件所有元素的数组
find()返回符合传入测试(函数)条件的数组元素
findIndex()返回符合传入测试(函数)条件的数组元素索引
forEach()数组每个元素都执行一次回调函数
from()通过给定的对象中创建一个数组
includes()判断一个数组是否包含一个指定的值
indexOf()搜索数组中的元素,并返回它所在的位置
isArray()判断对象是否为数组
join()把数组的所有元素放入一个字符串
keys()返回数组的可迭代对象,包含原始数组的键(key)
lastIndexOf()搜索数组中的元素,并返回它最后出现的位置
map()通过指定函数处理数组的每个元素,并返回处理后的数组
pop()删除数组的最后一个元素并返回删除的元素
push()向数组的末尾添加一个或更多元素,并返回新的长度
reduce()将数组元素计算为一个值(从左到右)
reduceRight()将数组元素计算为一个值(从右到左)
reverse()反转数组的元素顺序
shift()删除并返回数组的第一个元素
slice()选取数组的一部分,并返回一个新数组
some()检测数组元素中是否有元素符合指定条件
sort()对数组的元素进行排序
splice()从数组中添加或删除元素
toString()把数组转换为字符串,并返回结果
unshift()向数组的开头添加一个或更多元素,并返回新的长度
valueOf()返回数组对象的原始值

1.2.3 数组的复制

let a1 = [1, 2, 3];
let a2 = a1;

a2[1] = 4;

console.log(a1[1]); // 4

从上面的例子中,我们发现改变数组a2[1]的值时,数组a1[1]的值也随着改变了,这说明a1和a2指向的是同一块内存区域,这个在C/C++中就是指针的概念,let a2 = a1; 做的是浅拷贝操作。

如果需要做深拷贝,也就是将数组a1的所有元素克隆一份给a2,可以通过下面的两种方式:

let a1 = [1, 2, 3];
let a2 = a1.concat()

a2[1] = 4;

console.log(a1[1]); // 2
let a1 = [1, 2, 3];
let a2 = [...a1]; // 也可以写成 let [...a2] = a1;

a2[1] = 4;

console.log(a1[1]); // 2

1.3 Map

Map对象用来存储键值对(key-value),并且能够记住键的原始插入顺序。任何对象都可以作为键或值。
在Map中Key是唯一的。

let myMap = new Map();
 
let keyObj = {};
let keyFunc = function() {};
let keyString = "a string";
 
// 添加键
myMap.set(keyString, "和键'a string'关联的值");
myMap.set(keyObj, "和键keyObj关联的值");
myMap.set(keyFunc, "和键keyFunc关联的值");
 
console.log(myMap.size); // 3
 
// 读取值
console.log(myMap.get(keyString));    // 和键'a string'关联的值
console.log(myMap.get("a string"));   // 和键'a string'关联的值
console.log(myMap.get(keyObj));       // 和键keyObj关联的值
console.log(myMap.get(keyFunc));      // 和键keyFunc关联的值
 

console.log(myMap.get({}));            // undefined, 因为keyObj !== {}
console.log(myMap.get(function() {})); // undefined, 因为keyFunc !== function () {}

console.log(myMap.size);  // 3,返回有多少个键值对

// 和Array一样,可以添加任意属性和方法
myMap.test = "a";
console.log(myMap.test);

myMap.print = function() {console.log("I'm map");}
myMap.print();  // I'm map

1.3.1 Map与Array相互转换

Array => Map

let map = new Map([
    [1, "one"],
    [2, "two"]
]);

console.log(map);  // Map { 1 => 'one', 2 => 'two' }

Map => Array

let map = new Map();
map.set(1, "one");
map.set(2, "tow");

let arr2 = Array.from(map);
console.log(arr2);   // [ [ 1, 'one' ], [ 2, 'two' ] ]

1.3.2 Map的复制

Map在直接赋值的时候会遇到和Array同样的“浅拷贝”的问题,如:

let map1 = new Map();
map1.set(1, "one");
map1.set(2, "tow");

let map2 = map1;

map2.set(1, "three");

console.log(map1.get(1));  // three,修改map2会导致map1的值也被修改了

可以通过下面的方式完成Map的深拷贝:

let map1 = new Map();
map1.set(1, "one");
map1.set(2, "tow");

let map2 = new Map(map1); 

map2.set(1, "three");

console.log(map1.get(1));  // one
console.log(map2.get(1));  // three

1.4 Set

Map是键值对的集合,而Set则只是键(key)的集合。

Set中的每个元素都是唯一的。

任何对象都可以作为Set的元素。

let keys = [1,1,2,3,"str"];
let s = new Set(keys);

// 自动去重了
console.log(s);  // Set { 1, 2, 3, 'str' }

// 新增使用Add
s.add(9);
s.add(function(){});
console.log(s);  // Set { 1, 2, 3, 'str', 9, [Function] }

// 删除使用delete
s.delete(1);
console.log(s);  // Set { 2, 3, 'str', 9, [Function] }

1.5 迭代器

前面介绍Object、Array、Map、Set这些容器的时候,都避开了一个话题:遍历。本节主要介绍如何遍历JavaScript中的容器或集合。

迭代器(Iterator)就是一个接口,为各种不同的数据结构提供统一的遍历访问机制。任何数据结构只要实现Iterator 接口,就可以完成遍历操作。

在学习如何自定义迭代器之前,我们先学习一下如何遍历JavaScript常用的数据集合:

1.5.1 遍历字符串

let str = "hello";

for(let v of str) {
  console.log(v);
}

/*
h
e
l
l
o
*/

1.5.2 遍历数组

let arr = [1, "str", 2];
for (let v of arr) {
    console.log(v);
}
/*
1
str
2
*/

1.5.3 遍历Map

let map = new Map([
    [1, "one"],
    [2, "two"],
    [3, function () { }]
]);

for (let [k, v] of map) {
    console.log(k + ":" + v);
}

/*
1:one
2:two
3:function () { }
*/

二、解构赋值

解构(Destructuring)赋值分为“数组的解构赋值”和“对象的解构赋值”。

2.1 数组的解构赋值

let [a, b, c] = [1, 2, 3];   // 根据位置依次取值
// a = 1
// b = 2
// c = 3


let [foo, [[bar], baz]] = [1, [[2], 3]];
// foo = 1
// bar = 2
// baz = 3


let [ , , third] = ["foo", "bar", "baz"];
// third = "baz"


let [head, ...tail] = [1, 2, 3, 4];
// head = 1
// tail = [2,3,4]


let [x, y, ...z] = ["a"];  
// x = "a"
// y = undefined
// z = []


let [foo] = [];        // foo = undefined
let [bar, foo] = [1];  // foo = undefined


let [bar, foo = true] = [1]; // 可以赋默认值
// bar = 1
// foo = true


let [x, y] = [1, 2, 3];
// x = 1
// y = 2


let [a, [b], d] = [1, [2, 3], 4];
// a = 1
// b = 2
// d = 4

2.2 对象的解构赋值

let { foo, bar } = { foo: "aaa", bar: "bbb" };  // 和顺序没关系,根据属性名来取值
// foo = "aaa"
// bar = "bbb"


let { foo } = { bar: 'baz' };
// foo = undefined


// 对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量
// 如:console对象有log方法,我们可以这样使用:
const { log } = console;
log('hello') // hello

三、promise、async、await

promise、async、await这三个关键字都和异步编程有关。

3.1 Promise

Promise翻译成中文就是“承诺”的意思,声明一个Promise就是立下了一个承诺,无论怎么样,都会给被承诺人一个结果,而且这个结果是板上钉钉的,不会再变。

Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。

声明Promise对象时需要传入一个函数对象作为参数,这个函数对象的2个参数也是函数对象(resolve, reject),resolvereject不需要开发者定义,Javascript引擎会自动生成这2个函数。

当Promise对象生成后会立即变成pending状态,调用resolve函数会将Promise对象标记为fulfilled状态,而调用reject函数则会将当前Promise对象标记rejected状态。

resolverejected函数,我们只能调用它们中的一个,不能即调用resolve又调用rejected。如果我们先调用了resolve,此时Promise状态会标记为fulfilled,然后又调用了rejected函数,此时Promise状态并不会再改变,仍然使fulfilled状态,因为承诺的结果是板上钉钉的,不会再变。建议将resolvereject作为最后一行代码调用,简单起见,可以在这2个函数前面加上return, 即return resolve();return reject();

// Promise的参数为一个函数对象,函数有2个参数resolve, reject
let promise = new Promise((resolve, reject) => {
    // 做一些耗时的操作,比如网络请求
    // 这里我们使用一个延时器来模拟耗时的网络请求
    setTimeout(() => { // 延时1000ms之后成功
        resolve("ok");
    }, 1000);

    // 成功则调用 resolve();
    // 失败则调用 reject();
});

那么,我想在这个异步操作完成之后,再根据结果(是成功了,还是失败了)来继续做下一件事情,那我们该怎么做了?
Promise对象提供了then方法,该方法接受2个函数对象作为参数:
第一个回调函数是Promise对象的状态变为resolved时调用;
第二个回调函数是Promise对象的状态变为rejected时调用。
其中,第二个函数是可选的,不一定要提供。每个回调函数都可以接受一个参数,这个参数就是上一步调用resolve或reject时传入的。

// 上面的耗时操作完成之后,我们可能还需要根据结果来继续做一些事情
// 此时就可以使用then,then函数有2个参数,分别为2个函数对象。
// 上一步操作中,如果调用resolve(data),则then函数第一个函数对象参数会被调用;
// 如果调用reject(data),则then函数第二个函数对象参数会被调用
promise.then((data) => {
    console.log("resolve1: " + data);
    return "hello";
}, (data) => {
    console.log("reject1: " + data);
});

在这个例子中,我在第一个参数中直接通过return返回了"hello"字符串,那这个返回值的意义在哪里了?还有其他人可以使用到这个返回值吗?
是的,还可以继续使用。因为then的返回值是一个Promise对象,虽然我只是使用的return "hello";,并没有new Promise,但JavaScript引擎会自动包装成一个Promise对象,等同于:

promise.then((data) => {
    console.log("resolve1: " + data);
    return new Promise((resolve, reject) => {
        return resolve("hello");
    });
}, (data) => {
    console.log("reject1: " + data);
});

到这儿了,我们知道了then的返回的是一个Promise对象。既然then返回的是Promise对象,那么Promise就可以继续then呀,然后一直then下去…这样我们就可以将一系列异步的操作串联起来了:

let p = new Promise((resolve, reject) => {
    // ...
    // resolve(); 或 reject();
})
.then(() => { 
    // ...
})
.then(() => {
    // ...
 })
.then(() => { 
    // ...
});

但多个异步操作串联执行,还有一点需要注意,我们看下面的例子:

let promise = new Promise((resolve, reject) => {
    setTimeout(() => { // 延时1000ms
        resolve("ok1");
    }, 1000);

}).then((data) => {
    console.log("resolve1: " + data);

    setTimeout(() => { // 延时1000ms
        return "ok2";
    }, 1000);

}).then((data) => {
    console.log("resolve2: " + data);
});


我们期望的输出是:

resolve1: ok1
resolve2: ok2

但实际的输出却是:

resolve1: ok1
resolve2: undefined

问题出在第二个setTimeout模拟的耗时操作,我们以为程序会等第二个setTimeout执行完了再执行第二个then,但事实上setTimeout也是一个异步操作,虽然其延时了一秒执行其回调函数,但setTimeout这条语句却马上执行完成了,导致第一个then没有任何返回,针对这种情况,我们需要将代码改成下面的:

let promise = new Promise((resolve, reject) => {
    setTimeout(() => { // 延时1000ms
        resolve("ok1");
    }, 1000);

}).then((data) => {
    console.log("resolve1: " + data);

    return new Promise((resolve, reject) => {
        setTimeout(() => { // 延时1000ms
            resolve("ok2");
        }, 1000);
    });

}).then((data) => {
    console.log("resolve2: " + data);
});

3.2 Promise异常捕获

Promise对象还提供了catch方法,用来捕获异常。在介绍catch前,我们先看看下面的代码:

let promise = new Promise((resolve, reject) => {
    throw new Error("an error");
    resolve("ok");
}).then((data) => {
    console.log("resolve: " + data);
}, (data) => {
    console.log("reject: " + data);
});

// 输出:
// reject: Error: an error

我们在Promise中人为抛出了一个异常,但是程序却还是没有中止,而是运行到了reject过程中去了。

这是因为Promise默认会捕获其操作过程中的异常,如果有异常发生,其状态就会自动变成rejected,还记得前面说过Promise状态一旦确定就不会再改变了吧,所以即便后面的resolve("ok");执行了,也不会改变promise状态(事实上throw语句后的代码并没有机会执行)。

那么,假如我们没有写reject回调函数会怎么样了?看看下面的代码:

let promise = new Promise((resolve, reject) => {
    throw new Error("an error");
    resolve("ok");
}).then((data) => {
    console.log("resolve: " + data);
});

上面的代码中由于没有指定异常处理函数,所以程序抛出了异常信息,中止执行了。

另外,Promise的异常是会一直向下传递的,直到最后有人处理,如果始终没人处理,程序就会抛出异常信息,然后中止:

let promise = new Promise((resolve, reject) => {
    throw new Error("an error");
    resolve("ok");
}).then((data) => {
    console.log("resolve1: " + data);
}).then((data) => {
    console.log("resolve2: " + data);
}, (data) => {
    console.log("reject2: " + data);
});

// 输出:
// reject: Error: an error

上面的代码中,第一个then没有处理异常,异常向下传递给第二个then, 第二个then处理了该异常,程序继续运行。

现在理解Promise.catch()方法就容易多了。catch()方法其实就是.then(null, rejectiion).then(undefined, rejection)的别名,用于指定发生错误时的回调函数。

我们一般总是建议,Promise 对象后面要跟catch()方法,这样可以处理 Promise 内部发生的错误。catch()方法返回的还是一个 Promise 对象,因此后面还可以接着调用then()方法。

3.3 async, await

async 是ES7才有的与异步操作有关的关键字,需要和Promise配合使用,async函数返回一个 Promise对象,可以使用then方法添加回调函数:

async function helloAsync() {
    return "helloAsync";
}

console.log(helloAsync())

helloAsync().then(v => {
    console.log(v);
})

// 输出:
// Promise { 'helloAsync' }
// helloAsync

await关键字能用在被async标记的函数体内,async函数执行时,如果遇到await就会先暂停执行,等到触发的异步操作完成后,恢复async函数的执行并返回解析值。

function testAwait() {
    return new Promise((resolve) => {
        setTimeout(function () {
            console.log("testAwait");
            resolve();
        }, 1000);
    });
}

async function helloAsync() {
    await testAwait();
    console.log("helloAsync");
}
helloAsync();

// 输出:
// testAwait
// helloAsync
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

china_jeffery

你的鼓励是我前进的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值