ES6基础

. 概念和发展

1. 什么是ES

ECMAScript 是根据 MCMA-262 标准,实现的通用脚本语言规范。
1997 年, JavaScript 1.1 作为一个草案提交给欧洲计算机制造商协会( ECMA )。第 39 技术委员会 (TC39)被委派来 标准化一个通用、跨平台、中立于厂商的脚本语言的语法和语义 ,锤炼出了 ECMA-262 第一版,定义了名为 ECMAScript 的全新脚本语言。
2009 年, ECMA-262 第五版 (ES5) 发布。
2015 年, ECMA-262 第六版 (ES6 或者叫 ES 2015 语言规范 )

2. ECMAScriptJavaScript的关系与区别

ECMAScript JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的
ECMAScript 方言还有 JScript ActionScript )。在日常场合,这两个词是可以互换的。
1996 11 月, JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织
ECMA ,希望这种语言能够成为国际标准。次年, ECMA 发布 262 号标准文件( ECMA-262 )的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript
JavaScript ECMA-262 的含义多得多,一个完整的 JavaScript 实现应该由以下三个部分组成:
1. 核心( ECMAScript ):提供核心语言功能,基于 ES 规范。
它规定了语言的组成部分:语法、类型、语句、关键字、保留字、操作符、对象
2. 文档对象模型( DOM ):提供访问和操作网页内容的方法和接口。
DOM Document Object Model 的缩写, DOM 把整个网页映射为一个多层节点结构。
HTML 页面中的每一个组成部分都是某种类型的节点。这些节点又包含着不同类型的数据。
3. 浏览器对象模型( BOM ):提供与浏览器交互的方法和接口。
BOM Browser Object Model 的缩写。
浏览器提供了可访问和操作浏览器窗口的浏览器对象模型。

. let命令

1. 基本用法

ES6 新增了 let 命令,用来声明变量。它的用法类似于 var ,但由它声明的变量,只在 let 命令所在的代码内有效。
let a;
let a,b,c;
let a = 1,b = 'hello',c = [];

2. 特点

1. let命令只在声明所在的块级作用域内有效。 

{
    var a = 1;
    let b = 10;
}
console.log('a', a); // a 1
console.log('b', b); // b is not defined -- Error

2. let命令声明的常量不存在变量提升,存在暂时性死区,必需先声明、后使用。

var tmp = new Date();
function f() {
    console.log("111", tmp); // tmp is not defined -- Error
    let tmp = "hello world"; // var tmp = "hello world";
    console.log("222", tmp);
}
f();
console.log("333", tmp);

3. let声明的变量不可重复声明。

var message = "Hello!";
var age = 25;
// 以下两行都会报错
let message = "Hi!";
const age = 30;

3. 块级作用域

ES5 只有全局作用域和函数作用域,没有块级作用域。这带来很多不合理的场景。

1. 内层变量可能会覆盖外层变量

var tmp = new Date();
function f() {
    console.log(tmp);
    if (false) {
        var tmp = 'hello world';
    }
}
f(); // undefined

2. 用来计数的循环变量泄露为全局变量

var s = 'hello';
for(var i = 0; i< s.length; i++){
    console.log("i",i);
}
console.log("i",i);
ES6 中, let 实际上为 JavaScript 新增了块级作用域。 ES6 允许块级作用域的任意嵌套。
// 示例一
function f1() {
    let n = 5;
    if (true) {
        let n = 10;
    }
    console.log(n); // 5
}
f1();

// 实例二
var a = [];
    for (var i = 0; i < 10; i++) {
        a[i] = function () {
        console.log("msg1",i);
    };
}
a[6](); // msg1 10
console.log("msg2",i); // msg2 10

var a = [];
for (let i = 0; i < 10; i++) {
    a[i] = function () {
        console.log("msg1",i);
    };
}
a[6](); // msg1 6
console.log("msg2",i); // i is not defined -- Error

. const命令

const 声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415;
console.log(PI); // 3.1415
PI = 3; // Assignment to constant variable.
特点:
1. const 一旦声明变量,就必须立即初始化,不能在后续赋值。对于 const 常量来说,只声明不赋值,就会报错
if (true) {
    const MAX = 5;
}
console.log(MAX); // MAX is not defined
2. 对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址。 const 命令只保证变量名指向的地址不变,并不保证该地址的数据不变。
// 数组
const a = [];
a.push('Hello');
a.push('World');
console.log(a) // ["Hello", "World"]
a = ['CSDN']; // Assignment to constant variable.
// 对象
const foo = {};
foo.prop = 123;
console.log(foo.prop) // 123
foo = {}; // Assignment to constant variable.

. 解构赋值

1. 引入

ES6 允许按照一定模式,从数组, Object 对象, String... 中提取值,这被称为解构
Destructuring );然后将提取出来的值赋值给变量,这个过程被称为解构赋值。
// ES6以前,为变量赋值,只能直接指定值。
var a = 1;
var b = 2;
var c = 3;
var a = 1,b = 2,c = 3;
// ES6允许通过相同的结构,解构赋值
// “模式匹配”,只要等号两边的模式、结构相同,左边的变量就会被赋予对应的值。
var [a, b, c] = [1, 2, 3];

2. 数组的解构赋值

// 一维数组
let [a, , c] = [1, 2, 3];
a // 1
c // 3
// 多维数组
let [a, [[b], c]] = [1, [[2], 3]];
a // 1
b // 2
c // 3
// 解构失败(等号两边的模式不同):
let [a, [b]] = [1];
console.log(a);
console.log(b);

1.不完全解构

不完全结构:等号两边的模式相同,但变量的数量和解构的值的数量不同):
a. 需要被赋值变量的数量 > 解构的值的数量
let [a] = [];
console.log(a);
let [a, b] = [1];
console.log(a);
console.log(b);
// 没有数据可以赋值的变量,默认值为undefined。
b. 需要赋值变量的数量 < 解构的值的数量
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.带默认值

解构赋值允许变量指定默认值。
var [x = 'a'] = [];
console.log(x); // x='a'
[x, y = 'b'] = ['a',]; // x='a', y='b'
console.log(x);
console.log(y);
[x, y = 'b'] = ['a', undefined]; // x='a', y='b'
console.log(x);
console.log(y);
// 如果解构的值严格等于undefined,变量的默认值生效。
// 如果解构的值不严格等于undefined,变量的值为指定的值。
// ES6严格相等运算符(===)
[x, y = 'b'] = ['a', null]; // x='a', y='null'
console.log(x);
console.log(y);

3.对象的解构赋值

// 根据对象的key对应
let { username: username, password : password} = { username: "aaa", password:
"bbb" };
let { username, password } = { username: "aaa", password: "bbb" };
console.log(username);
console.log(password);

// 对象的解构与数组有一个重要的不同 数组的元素是按次序排列的,变量的取值由它的位置决定;
let { password, username } = { username: "aaa", password: "bbb" };
console.log(username);
console.log(password);

// 变量名和属性名不一致
let { user, pass } = { username: "aaa", password: "bbb" };
console.log(user);
console.log(pass);

/**
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。
真正被赋值的是后者,而不是前者。
**/
var res = {
    'ok':1,
    'data':{
    'top':[1,2,3],
    'center':[2,3,4]
},
    'mis':'请求成功'
};
let {ok,data,mis} = res;
const node = {
    loc:{
        start:{
            line: 1,
            column: 5
        }
    }
};
let {loc, loc : { start }, loc : { start: { line }}} = node; // key主要用于中间匹配用

4. 字符串的解构赋值

const [a,b,c,d,e] = "hello";
console.log(a,b,c,d,e);
// 数组、类似数组的对象都有一个length属性,
let {length : len01} = [1,2,3,4,5];
console.log(len01);
let {length : len02} = 'hello';
console.log(len02);

5. 数值Number和布尔值的解构赋值(了解)

解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象。
// 由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined;
// Cannot destructure property `prop` of 'undefined' or 'null'
let { prop: y } = null;
// Cannot destructure property `prop` of 'undefined' or 'null'
// 解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
// 实际进行的是对象的解构赋值

// 数值
console.log(Number.prototype); // prototype可以获取到当前对象下的属性,包括构造函数 方法

let {toString: s,valueOf: v} = 123;
console.log(s);
console.log(v);

let boo = (s === Number.prototype.toString);
console.log(boo);
boo = (v === Number.prototype.valueOf);
console.log(boo);

// 布尔值
console.log(Boolean.prototype);

let {toString: s,valueOf: v} = true;
console.log(s);
console.log(v);

6. 函数参数的解构赋值

函数的参数也可以使用解构赋值。
function add([x, y]){
    return x + y;
}
let arr = [1,2];
console.log(add(arr));// 3
函数参数的解构也可以使用默认值。
function move({x = 0, y = 0}) {
    return [x, y];
}
console.log(move({x: 3, y: 8})); // [3, 8]
console.log(move({x: 3})); // [3, 0]
console.log(move({})); // [0, 0]
console.log(move()); // Cannot destructure property `x` of 'undefined' or 'null'.

function move({x = 0, y = 0}={}) {
    return [x, y];
}
console.log(move()); // [0, 0]

7. 解构赋值的用途

1. 交换变量
let [x, y] = [111,222];
[x, y] = [y, x];
console.log([x, y]); // [222, 111]
2. 获取函数的多个返回值
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
console.log([a, b, c]);

// 返回一个对象
function example() {
    return {
        a: 1,
        b: 2
    };
}
var { a, b } = example();
console.log({ a, b });
3. 函数参数的定义
解构赋值可以方便地将一组参数与变量名对应起来。
// 参数是一组有序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 参数是一组无序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
4. 提取 JSON 数据
        解构赋值对提取JSON 对象中的数据,尤其有用。
let jsonData = {
    id: 42,
    status: "OK",
    data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id);
console.log(status);
console.log(number);

let [a,b] = number;
console.log(a);
console.log(b);
5. 函数参数的默认值
        指定参数的默认值,就避免在函数体内部再写var foo = config.foo || 'default foo'; 这样的语句。
jQuery.ajax = function (url, {
    async = true,
    beforeSend = function () {},
    cache = true,
    complete = function () {},
    crossDomain = false,
    global = true,
    // ... more config
}) {
    // ... do stuff
};
6. 遍历 Map 结构
// 任何部署了Iterator接口的对象,都可以用for...of循环遍历。
// Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就非常方便。
var map = new Map();
map.set('first', 'hello');
map.set('second', 'world');

for (let [key, value] of map) {
    console.log(key + " is " + value);
}
// first is hello
// second is world

// 如果只想获取键名,或者只想获取键值,可以写成下面这样。
// 获取键名
for (let [key] of map) {
    // ...
}

. 字符串模板

// 传统的JavaScript语言,输出模板通常是这样写的。
$('#result').append(
    'There are <b>' + basket.count + '</b> ' +
    'items in your basket, ' +
    '<em>' + basket.onSale +
    '</em> are on sale!'
);
// 这种写法相当繁琐不方便,各种引号 加号 转义符号 效率低

// ES6引入了模板字符串解决这个问题。
$('#result').append(`
    There are <b>${basket.count}</b> items
    in your basket, <em>${basket.onSale}</em>
    are on sale!
`);
模板字符串( template string )是增强版的字符串,用反引号( ` )标识。
它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
// 普通字符串
`In JavaScript '\n' is a line-feed.`

// 多行字符串
`In JavaScript this is
not legal.`
console.log(`string text line 1
string text line 2`);

// 字符串中嵌入变量
var name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`

// 如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。
var greeting = `\`Yo\` World!`;

. 函数参数的默认值

ES6 之前,不能直接为函数的参数指定默认值,
ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
// 示例一
function log(x, y = 'World') {
    console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

// 示例二
function Point(x = 0, y = 0) {
    this.x = x;
    this.y = y;
}

let p = new Point();
console.log(p);// 0,0

p = new Point(1,2);
console.log(p);1,2

. rest参数

// ES6引入rest参数(形式为“...变量名”),用于获取函数的多个参数,可以不再使用arguments对象。
// arguments对象:可以在函数内访问所有的实参

function f01() {
    console.log(arguments[0]);
    console.log(arguments[1]);
    console.log(arguments[2]);
}
f01(12, 23, 33); // 12 23 33

// rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function f02(...values) {
    let sum = 0;
    for (var val of values) {
        sum += val;
    }
    return sum;
}

let result = f02(2, 5, 8) // 15
console.log(result);

// 注意:rest参数之后不能再有其他参数(即rest参数只能是最后一个参数),否则会报错。
function f(a, ...b, c) {
    // ...
}

// 函数的length属性代表函数的参数个数。
// length属性,不包括已指定默认值的参数
// length属性,不包括rest参数。

let len = (function(a) {}).length; // 1
console.log(len);

len = (function (a = 5) {}).length; // 0
console.log(len);

len =(function (a, b, c = 5) {}).length; // 2
console.log(len);

len =(function(...a) {}).length; // 0
console.log(len);

len = (function(a, ...b) {}).length ; // 1
console.log(len);

. 扩展运算符

// 扩展运算符(spread)是三个点(...)。
// 它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(...[1, 2, 3]);// 1 2 3

console.log(1, ...[2, 3, 4], 5);// 1 2 3 4 5

console.log([...document.querySelectorAll('body')]);// [<body>]

// 该运算符主要用于向函数传递实际参数。
// (rest参数定义函数的形式参数;扩展运算符提供实际参数)
function f02(...values) {
    let sum = 0;
    for (var val of values) {
        sum += val;
    }
return sum;
}
//普通参数
let result = f02(2, 5, 8) // 15
console.log(result);

//扩展运算符参数
result = f02(...[3,6,9]) // 18
console.log(result);

//普通参数与扩展运算符参数结合使用
result = f02(666, ...[1,4,7]) // 678
console.log(result);
应用:

1. 复制数组

const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a3] = a1;

2. 合并数组

// 扩展运算符提供了数组合并的新写法。
let arr1 = ['a', 'b'];
let arr2 = ['c'];
let arr3 = ['d', 'e'];
// ES5的合并数组
let result = arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
// ES6的合并数组
result = [...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

3. 解构赋值结合

// 扩展运算符可以与解构赋值结合起来,用于生成数组。
// 扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。
const [...butLast, last] = [1, 2, 3, 4, 5];
console.log([...butLast, last]);

const [first, ...middle, last] = [1, 2, 3, 4, 5];
console.log([first, ...middle, last]);

const [first, ...rest] = [1, 2, 3, 4, 5];
console.log([first, ...rest]);

4. 字符串

// 扩展运算符还可以将字符串转为真正的数组。
let arr = [...'hello']
console.log(arr);
// [ "h", "e", "l", "l", "o" ]

5. 转为真正的数组

任何 Iterator 接口的对象,都可以用扩展运算符转为真正数组。
var nodeList = document.querySelectorAll('body');
var array = [...nodeList];
console.log(array);
// 上面代码中,querySelectorAll方法返回的是一个nodeList对象。
// 它不是数组,而是一个类似数组的对象。
// 这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了Iterator接口。
// 对于那些没有部署Iterator接口的类似数组的对象,扩展运算符就无法将其转为真正的数组。
let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};
let array = [...arrayLike];
// object is not iterable (cannot read property Symbol(Symbol.iterator))
console.log(array);
// 上面代码中,arrayLike是一个类似数组的对象,但是没有部署Iterator接口,扩展运算符就会报错。
// 这时,可以改为使用Array.from方法将arrayLike转为真正的数组。

6. MapSet结构

// 扩展运算符内部调用的是数据结构的Iterator接口,
// 因此只要具有Iterator接口的对象,都可以使用扩展运算符
// Map结构:
let map = new Map([
    [1, 'one'],
    [2, 'two'],
    [3, 'three'],
]);
let keyArray = [...map.keys()];
console.log(keyArray);
let valueArray = [...map.values()];
console.log(valueArray);

 . 箭头函数

// ES6允许使用“箭头”(=>)定义函数。
v => v;
let f = v => v;
// 该函数等同于:
let f = function(v) {
    return v;
};

// 通过对比发现,上述定义的箭头函数是拥有一个参数、一个返回语句的函数
// 箭头函数是函数式编程的一种体现,将更多的关注点放在输入和输出的关系,省去过程中的一些因素。
// 箭头函数相当于匿名函数。

// 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
let f = () => 5;
// 等同于
let f = function () { return 5 };
let sum = (num1, num2) => num1 + num2;
// 等同于
let sum = function(num1, num2) {
    return num1 + num2;
};

// 如果箭头函数的代码块不仅只有return语句,就要使用语句块将它们括起来。
let sum = (num1, num2) => {
    console.log(num1 + num2);
    return num1 + num2;
};

// 由于大括号在箭头函数中被解释为代码块,
// 所以如果箭头函数返回一个对象,必须在对象外面加上括号。
let getTempItem = id => ({ id: id, name: "Temp" });
// 等同于
let getTempItem = function (id) { return { id: id, name: "Temp" } };
// 箭头函数可以与解构赋值结合使用。
const full = ({ first, last }) => first + ' ' + last;
// 等同于
function full(person) {
    return person.first + ' ' + person.last;
}

// 箭头函数使得表达更加简洁。
const isEven = n => n % 2 == 0;
const square = n => n * n;
// 上面代码只用了两行,就定义了两个简单的工具函数。
// 如果不用箭头函数,可能就要占用多行,而且还不如现在这样写醒目。

// 箭头函数简化回调函数。
// 正常函数写法
[1,2,3].map(function (x) {
    return x * x;
});
[1,4,9]
// 箭头函数写法
[1,2,3].map(x => x * x);

// rest参数、扩展运算符、箭头函数结合。

const numbers = (...nums) => nums;
let result = numbers(1, 2, 3, 4, 5);
console.log(result);

// [1,2,3,4,5]
result = numbers(...[6, 7, 8, 9, 0]);
console.log(result);

// [6, 7, 8, 9, 0]
const headAndTail = (head, ...tail) => [head, tail];
result = headAndTail(1, 2, 3, 4, 5);
console.log(result);
// [1,[2,3,4,5]]
箭头函数有几个使用注意点。
(1 )函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
(2 )不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
(3 )不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用 Rest 参数代替。
// 上面四点中,第一点尤其值得注意。
// 普通函数中this对象的指向是可变的,但是在箭头函数中,它是固定的。

var id = 21;

function fn01() {
    setTimeout(function () {
        console.log("-- fn01 id-- : ", this.id);
    }, 100);
}
fn01.call({id: 42});

// 如果是普通函数,执行时this应该指向全局对象window,这时应该输出21。

function fn02() {
    setTimeout(() => {
        console.log("-- fn02 id-- : ", this.id);
    }, 100);
}
fn02.call({id: 42});

// 上面代码中,setTimeout的回调函数是一个箭头函数,
// 这个箭头函数的定义生效是在foo函数生成时,而它的真正执行要等到100毫秒后。
// 箭头函数导致this总是指向函数定义生效时所在的对象(本例是{id: 42}),所以输出的是42。
// 箭头函数可以让setTimeout里面的this,绑定定义时所在的作用域,而不是指向运行时所在的作用域。

. Iteratorfor...of循环

Iterator (遍历器)的概念
JavaScript 原有的表示 集合 的数据结构,主要是数组( Array )和对象( Object ),ES6 又添加了 Map Set 。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是 Map Map 的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器( Iterator )就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。
有些数据结构原生具备 Iterator 接口(比如数组),即不用任何处理,就可以被 for...of 循环遍历,有些就不行(比如对象)。原因在于,这些数据结构原生部署了 Symbol.iterator 属性,另外一些数据结构没有。凡是部署了 Symbol.iterator 属性的数据结构,就称为部署了遍历器接口。调用这个接口,就会返回一个遍历器对象。
console.log(Array.prototype);
console.log(Object.prototype);
console.log(Map.prototype);
console.log(Set.prototype);

// ES6规定,默认的Iterator接口部署在数据结构的Symbol.iterator属性,
// 或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)。
// 调用Symbol.iterator方法,就会得到当前数据结构默认的遍历器生成函数。
// Symbol.iterator本身是一个表达式,返回Symbol对象的iterator属性,这是一个预定义好的、类型为Symbol的特殊值,所以要放在方括号内。

const arr = ['red', 'green', 'blue'];
let iterator = arr[Symbol.iterator]();

// for...of循环
// ES6借鉴C++、Java、C#和Python语言,引入了for...of循环,作为遍历所有数据结构的统一的方法。
// 一个数据结构只要部署了Symbol.iterator属性,就被视为具有iterator接口,就可以用for...of循环遍历它的成员。
// 也就是说,for...of循环内部调用的是数据结构的Symbol.iterator方法。
// for...of循环可以使用的范围包括数组、Set和Map结构、某些类似数组的对象(比如arguments对象、DOM NodeList对象)、Generator对象,以及字符串。

// 数组原生具备iterator接口,for...of循环本质上就是调用这个接口产生的遍历器

const arr = ['red', 'green', 'blue'];
let iterator = arr[Symbol.iterator]();
for (let v of iterator) {
    console.log(v);
}
for (let v of arr) {
    console.log(v);
}

// JavaScript原有的for...in循环,遍历获得对象的键名。{name:123,pass:456}
// ES6提供for...of循环,遍历获得键值。
var arr = ['a', 'b', 'c', 'd'];
for (let a in arr) {
    console.log(a);
}
for (let a of arr) {
    console.log(a);
}

// 类似数组的对象

// 类似数组的对象包括好几类。
// 下面是for...of循环用于字符串、DOM NodeList对象、arguments对象的例子。
// 字符串
let str = "hello";
for (let s of str) {
    console.log(s);
}
// DOM NodeList对象
let container = document.querySelector(".parent");
// 获取tabs
let tabs = container.querySelectorAll(".tab");
for (let tab of tabs) {
    tab.style.color = "blue";
}

// arguments对象
function printArgs() {
    for (let x of arguments) {
        console.log(x);
    }
}
printArgs('a', 'b');

 十一. Promise对象

Promise 的含义
Promise 是异步编程的一种解决方案,比传统的解决方案 —— 回调函数和事件 —— 更合理和更强大。
它由社区最早提出和实现, ES6 将其写进了语言标准,统一了用法,原生提供了 Promise 对象。
所谓 Promise
简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。
从语法上说, Promise 是一个对象,从它可以获取异步操作的消息。
Promise 对象有以下两个特点。
(1 )对象的状态不受外界影响,由异步操作的状态决定。
Promise 对象代表一个异步操作,有三种状态: Pending (进行中)、 Resolved (已完成)和 Rejected
(已失败)。
只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
这也是 Promise 这个名字的由来,它的英语意思就是 承诺 ,表示其他手段无法改变。
(2 )一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise 对象的状态改变,只有两种可能:从 Pending 变为 Resolved 和从 Pending 变为 Rejected
只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。
就算改变已经发生了,你再对 Promise 对象添加回调函数,也会立即得到这个结果。
这与事件( Event )完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

 

      // 基本用法
      // ES6规定,Promise对象是一个构造函数,用来生成Promise实例。
      let promise = new Promise(function (resolve, reject) {
        // ... some code
        if (/*异步操作成功*/ ) {
          resolve(value);
        } else {
          reject(error);
        }
      });
      // Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。
      // 它们是两个函数,由JavaScript引擎提供,不用自己部署。
      // resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为    Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
      // reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为 Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
      // Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。
      promise.then(
        function(value) {
          // success code
        },
        function(error) {
          // failure code
        }
      );
      // then方法可以接受两个回调函数作为参数。
      // 第一个回调函数是Promise对象的状态变为Resolved时调用,
      // 第二个回调函数是Promise对象的状态变为Reject时调用。
      // 其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。
      // 写法一
      let promise = new Promise(function(resolve, reject) {
        reject(new Error('test'));
      });
      promise.catch(function(error) {
        console.log(error);
      });
      // 写法二
      let promise = new Promise(function(resolve, reject) {
        try {
          throw new Error('test');
        } catch(e) {
          reject(e);
        }
      });
      promise.catch(function(error) {
        console.log(error);
      });
      // 比较上面两种写法,可以发现reject方法的作用,等同于抛出错误。
      // promise通过reject方法抛出一个错误,就被catch方法指定的回调函数捕获。
      // 一般来说,不要在then方法里面定义Reject状态的回调函数(即then的第二个参数),而是使用catch方法来捕获异常。
      promise.then(
          function(value) {
              // success code
          }
        ).catch(
          function(error) {
              // failure code
          }
      );
实例:加载图片文件显示到页面上
// 加载图片 return promise
function imgLoad(src) {
    return new Promise(function(resolve, reject) {
        let img = document.createElement('img');
        img.className = 'm-img';
        img.src = src;
        img.onload = function(){
            resolve(img);
        }
        img.onerror = function(err){
            reject(err);
        }
    });
}
// 将单个图片添加到界面上
function showImg(img){
    let p = document.createElement('p');
    p.appendChild(img);
    document.body.appendChild(p);
}

// 调用,使用then处理结果(应该还使用catch捕获异常,这里省略)
imgLoad('http://www.baidu.com/dog.jpg').then(showImg);
imgLoad('http://www.baidu.com/ha.jpg').then(showImg);
imgLoad('http://www.baidu.com/panda.jpg').then(showImg);
扩展:

1. Promise.all()

// 以上面的实例为基础,了解Promise.all()的用法
// 添加多个图片到文件
function showImgs(imgs){
    imgs.forEach(img=>{
        document.body.appendChild(img);
    })
}

// 所有图片加载完成后再添加到界面
Promise.all([
    imgLoad('http://www.baidu.com/dog.jpg'),
    imgLoad('http://www.baidu.com/ha.jpg'),
    imgLoad('http://www.baidu.com/panda.jpg')
]).then(showImgs);

2. Promise.race()

// 以上面的实例为基础,了解Promise.race()的用法
// 一个图片加载完成后就添加到界面
Promise.race([
    imgLoad('http://www.baidu.com/dog.jpg'),
    imgLoad('http://www.baidu.com/ha.jpg'),
    imgLoad('http://www.baidu.com/panda.jpg')
]).then(showImg);

十二. Module

ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS AMD 两种。前者用于服务器,后者用于浏览器。 ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS AMD 规范,成为浏览器和服务器通用的模块解决方案。 ES6 模块不是对象,而是通过export 命令显式指定输出的代码,再通过 import 命令输入。
模块功能主要由两个命令构成: export import export 命令用于规定模块的对外接口, import命令用于输入其他模块提供的功能。
// 在HTML网页中,ES5 脚本的常规用法
<!-- 页面内嵌JavaScript脚本 -->
<script type="application/javascript">
// module code
</script>
<!--引入外部JavaScript脚本 -->
<script type="application/javascript" src="path/myModule.js"></script>
export 命令 &import 命令
// 1.导出变量
export let A = 123;

// 2.导出方法
export let test = function(){
    console.log('test');
};

// 3.导出对象
export const student = {
    name: 'Megan',
    age: 18
};

// or 先命名变量、方法、对象,再导出
let A = 123;
let test = function(){console.log('test')};
const student = {
    name: 'magan',
    age: 18
}

export {A,test,student};
export {A as x, test as y, student as c};

// ES6模块不是对象,而是通过export命令显式指定输出的代码,输入时也采用静态命令的形式。
// 页面内嵌ES6模块
<script type="module">
import { stat, exists, readFile } from 'path/myModule';
// other code
</script>

// 引入变量,方法,对象
import {A,test,student} from '../xxx.js' // 引入的名称和导出的名称要一致

// 引入部分(按需引入)
import {A} from '../xxx.js'

// 引入全部
import * as Test from '../xxx.js'

// ------------------------------------------------------------------------------
/*
从前面的例子可以看出,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。
如果用户希望快速上手,不愿意阅读文档,去了解模块有哪些属性和方法。
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认
输出。
本质上,export default就是输出一个叫做default的变量或方法,然后系统允许你为它取任意名字。
*/
// 例一
let A = 123;
let test = function(){console.log('test')};
const student = {
    name: 'Megan',
    age: 18
}
export default{
    A,
    test,
    student
}
// 这种写法,引入的时候不需要跟导出的名称一样,它将导出的对象命名的权力交给了引入方
import Test1 from '../xxx.js';
console.log(Test1.A);
console.log(Test1.test);
console.log(Test1.Hello);

// 例二
export default function foo03() {
    console.log(" foo03() ");
}
import x from '../xxx.js'
x();
注意:
1. export 语句,要写在最顶层,不可写在函数或者代码块内部
function foo() {
    console.log(" -- foo() -- ");
    // export {foo}; // -- Error Unexpected token export
}
export {foo};
2. 在一个文件或模块中, export import 可以有多个, export default 仅有一个。通过 export 向外
暴露的成员,在导入时需要对应的变量名,并且必需要加 { } ,通过 export default 向外暴露的成
员,在导入时可以使用任意变量来接收,不能加 { }
let a = 123;
export a; // --Error
// 正确写法
export {a};

export default function foo03() {
    console.log(" foo03() ");
}
import {x} from '../xxx.js' // -- Error 会去模块中查找名称为x的变量
// 正确写法
import x from '../xxx.js'
3. import 语句可以与 export 语句写在一起
export {foo01, foo02} from '../xxx.js';
/*
约等于下面两段语句,
不过上面的导入导出复合写法,模块中没有导入 foo01 与 foo02
上面代码中,export和import语句结合在一起,写成一行。
但是从可读性考虑,不建议采用这种写法,而应该采用标准写法。
*/
import {foo01, foo02} from "./xxx.js";
export {foo01, foo02};

export * from '../xxx.js';
// 这里会忽略xxx.js中的default方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: ES6是ECMAScript 2015的简称,是JavaScript的一个重要的更新版本。深入理解ES6 PDF是一本通过深入研究ES6标准来理解其中各种语法和特性的书籍。 这本书分为十八章,从ES6基础语法和新特性入手,详细讲解了ES6的语法、语义和使用方法。其中包括了箭头函数、简化的对象字面量、模板字面量、解析器、新的控制结构、类和继承、生成器和迭代器、异步操作、Symbol和迭代器、集合和迭代器、Map和Set、新的数组方法、Promise和异步操作、代理和反射、模块化和导入机制,每一章都是围绕特定的内容展开细致的解读。 对于学习ES6开发者来说,这本书是一份非常有价值的资料。读者可以从中深入了解ES6语言架构和语法规范,更加深入地理解JavaScript基础和高级语言特性。对于从ES5升级到ES6开发者来说,该书是学习ES6语言的最佳手册,还可以通过实例代码快速掌握ES6语言特性。 总之,深入理解ES6 PDF是一本非常专业且详细的技术书籍,可以帮助读者深入了解ES6语言特性,并掌握新的编程技巧。 ### 回答2: ES6Javascript的下一代语言标准,它引入了许多新的语法、特性和API,使得编程变得更加简单、高效、灵活和快捷。深入理解ES6 PDF是一本ES6入门指南,它帮助读者系统地学习ES6的核心知识和技能,了解ES6的语法、模块、类、迭代器、生成器、Promise等核心概念及其实践应用。 阅读深入理解ES6 PDF,首先需要了解ES6的基本语法和新特性,包括箭头函数、模板字符串、解构赋值、默认参数、rest参数等。其次,需要学习ES6的模块化设计,了解如何实现模块导出、导入、依赖管理等功能,以便更好地组织代码和提高代码的复用性。另外,深入理解ES6 PDF也介绍了ES6的面向对象编程特性,包括类的声明、方法的定义、继承、多态等,以及ES6的迭代器、生成器、Promise等核心概念及其实践应用,使得读者可以更加深入地掌握ES6的精髓和应用。 综上所述,深入理解ES6 PDF是一本非常好的ES6入门指南,它帮助读者了解ES6的核心知识和技能,带领读者建立ES6的编程思想和风格,以便更好地应对现代Web应用开发的挑战,同时也是学习ES6语言的必备指南。 ### 回答3: 《深入理解ES6》是一本详细介绍ES6新特性的书籍。它主要介绍了模板字符串、解构赋值、箭头函数、类、模块化开发ES6的新语法。通过深入阅读这本书,能够更好地理解和运用ES6的新特性。 其中,模板字符串是ES6引入的一个非常重要的新特性,它可以让我们更方便地处理字符串拼接和换行,同时还支持常用的字符串操作方法。 解构赋值则是ES6中的另一个重要特性,它可以让我们更快捷地从一个对象或数组中提取出需要的值,这对于提高代码的可读性和复用性都非常有帮助。 箭头函数是ES6中的又一个新特性,它可以让我们更愉悦地编写函数,并且它的this指向也更加方便和易懂。同时也是优化了ES5语言中函数语法臃肿和拖沓的问题。 类是ES6中一个重要的新特性,它可以让我们更加方便地实现面向对象编程,这是在ES5中较为繁琐的过程,同时也减少了ES5类的一些使用致命局限。 最后,模块化开发也是ES6中的一个重要特性,它可以让我们更加方便地组织和管理代码,同时也减少了代码间的相互污染,使得代码更加易于维护和扩展。 总之,《深入理解ES6》这本书为我们深入了解ES6的新特性提供了很好的指导和方向,并且还有配套的演示代码,非常值得一读。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值