es6学习-201803
1、ES6 规定暂时性死区和let、const语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 ES5 是很常见的,现在有了这种规定,避免此类错误就很容易了。
2、变量的解构赋值
数组
对象
3、扩展运算符(...
)
//数组 const [a, ...b] = [1, 2, 3]; a // 1 b // [2, 3] //对象 let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; x // 1 y // 2 z // { a: 3, b: 4 }
4、函数的扩展
1)函数参数的默认值
5、对象的扩展
ES6 允许在对象之中,直接写变量。这时,属性名为变量名, 属性值为变量的值。
function f(x, y) { return {x, y}; } // 等同于 function f(x, y) { return {x: x, y: y}; } f(1, 2) // Object {x: 1, y: 2} const o = { method() { return "Hello!"; } }; // 等同于 const o = { method: function() { return "Hello!"; } };
6、Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。有了Promise
对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
1)ES6 规定,Promise
对象是一个构造函数,用来生成Promise
实例。Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”,在异步操作成功时调用,并将异步操作的结果作为参数传递出去;reject
函数的作用是,将Promise
对象的状态从“未完成”变为“失败”,在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
const promise = new Promise(function(resolve, reject) { if (/* 异步操作成功 */){ resolve(value); } else { reject(error); } });
2)Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数。then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise
对象的状态变为resolved
时调用,第二个回调函数是Promise
对象的状态变为rejected
时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise
对象传出的值作为参数。
promise.then(function(value) { // success }, function(error) { // failure });
3)Promise 实例具有then
方法,也就是说,then
方法是定义在原型对象Promise.prototype
上的。可以采用链式写法,即then
方法后面再调用另一个then
方法。采用链式的then
,可以指定一组按照次序调用的回调函数。这时,前一个回调函数,有可能返回的还是一个Promise
对象(即有异步操作),这时后一个回调函数,就会等待该Promise
对象的状态发生变化,才会被调用。
getJSON("/post/1.json").then( post => getJSON(post.commentURL) ).then( comments => console.log("resolved: ", comments), //funcA err => console.log("rejected: ", err) //funcB ); /* 第一个then方法指定的回调函数,返回的是另一个Promise对象。这时,第二个then方法指定的回调函数,就会等待这个新的Promise对象状态发生变化。
如果变为resolved,就调用funcA,如果状态变为rejected,就调用funcB。 */
4)Promise.prototype.catch
方法是.then(null, rejection)
的别名,用于指定发生错误时的回调函数。
getJSON('/posts.json').then((val) => console.log('fulfilled:', val)) .catch((err) => console.log('rejected', err)); /* getJSON方法返回一个 Promise 对象,如果该对象状态变为resolved,则会调用then方法指定的回调函数;
如果异步操作抛出错误,状态就会变为rejected,就会调用catch方法指定的回调函数,处理这个错误。
另外,then方法指定的回调函数,如果运行中抛出错误,也会被catch方法捕获。 */
一般来说,不要在then
方法里面定义 Reject 状态的回调函数(即then
的第二个参数),总是使用catch
方法。
// bad promise .then(function(data) { // success }, function(err) { // error }); // good promise .then(function(data) { // success }) .catch(function(err) { // error }); /* 第二种写法要好于第一种写法,理由是第二种写法可以捕获前面then方法执行中的错误,也更接近同步的写法(try/catch)。
因此,建议总是使用catch方法,而不使用then方法的第二个参数。 */
一般总是建议,Promise 对象后面要跟catch
方法,这样可以处理 Promise 内部发生的错误。catch
方法返回的还是一个 Promise 对象,因此后面还可以接着调用then
方法。
const someAsyncThing = function() { return new Promise(function(resolve, reject) { // 下面一行会报错,因为x没有声明 resolve(x + 2); }); }; someAsyncThing() .catch(function(error) { console.log('oh no', error); }) .then(function() { console.log('carry on'); }); // oh no [ReferenceError: x is not defined] // carry on /*上面代码运行完catch方法指定的回调函数,会接着运行后面那个then方法指定的回调函数。如果没有报错,则会跳过catch方法。*/ Promise.resolve() .catch(function(error) { console.log('oh no', error); }) .then(function() { console.log('carry on'); }); // carry on
7、模块Module
模块功能主要由两个命令构成:export
和import
。export
命令用于规定模块的对外接口,import
命令用于输入其他模块提供的功能。
1)export
命令
a、一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export
关键字输出该变量。
export var firstName = 'Michael'; export var lastName = 'Jackson'; export var year = 1958; //等价于 var firstName = 'Michael'; var lastName = 'Jackson'; var year = 1958; export {firstName, lastName, year}; /*优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。*/
export
命令除了输出变量,还可以输出函数或类(class)。
b、export
命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。
// 错误写法 export 1; var m = 1;export m; function f() {};export f; // 正确写法 export var m = 1; var m = 1;export {m}; export function f() {}; function f() {};export {f};
c、通常情况下,export
输出的变量就是本来的名字,但是可以使用as
关键字重命名。(import
命令类似)
function v1() { ... } function v2() { ... } export { v1 as streamV1, v2 as streamV2 };
2)import 命令
a、使用export
命令定义了模块的对外接口以后,其他 JS 文件就可以通过import
命令加载这个模块。
import {firstName, lastName, year} from './profile.js'; //加载profile.js文件,并从中输入变量
import
命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(profile.js
)对外接口的名称相同(建议凡是输入的变量,都当作完全只读,轻易不要改变它的属性)。import
后面的from
指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js
后缀可以省略。
b、import
语句会执行所加载的模块。
import 'lodash'; //仅仅执行lodash模块,但是不输入任何值
目前阶段,通过 Babel 转码,CommonJS 模块的require
命令和 ES6 模块的import
命令,可以写在同一个模块里面,但是最好不要这样做。因为import
在静态解析阶段执行,所以它是一个模块之中最早执行的。
c、模块的整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号(*
)指定一个对象,所有输出值都加载在这个对象上面。
// circle.js export function area(radius) { return Math.PI * radius * radius; } export function circumference(radius) { return 2 * Math.PI * radius; } // xxx.js //1、逐一指定要加载的方法 import { area, circumference } from './circle'; console.log('圆面积:' + area(4)); console.log('圆周长:' + circumference(14)); //2、整体加载 import * as circle from './circle'; console.log('圆面积:' + circle.area(4)); console.log('圆周长:' + circle.circumference(14));
3)export default 命令
a、使用import
命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。export default
命令能为模块指定默认输出。
// export-default.js 默认输出是一个函数 export default function () { console.log('foo'); } // import-default.js // 其他模块加载该模块时,import命令可以为该匿名函数指定任意名字。这时import命令后面,不使用大括号。 import customName from './export-default'; customName(); // 'foo'
export default
命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default
命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应export default
命令。
b、export default
命令用在非匿名函数前,也是可以的。
export default function foo() { console.log('foo'); } // 或者写成 function foo() { console.log('foo'); } export default foo;
c、因为export default
命令其实只是输出一个叫做default
的变量,所以它后面不能跟变量声明语句。
export var a = 1;// 正确 var a = 1;export default a;// 正确 export default var a = 1;// 错误 /*上面代码中,export default a的含义是将变量a的值赋给变量default。所以,最后一种写法会报错*/ export default 42;// 正确 export 42;// 报错 /* 同样地,因为export default命令的本质是将后面的值,赋给default变量,所以可以直接将一个值写在export default之后。 上面代码中,后一句报错是因为没有指定对外的接口,而前一句指定外对接口为default。 */
4)export 与 import 的复合写法
a、如果在一个模块之中,先输入后输出同一个模块,import
语句可以与export
语句写在一起。
export { foo, bar } from 'my_module'; // 可以简单理解为 import { foo, bar } from 'my_module'; export { foo, bar }; /* 上面代码中,export和import语句可以结合在一起,写成一行。但需要注意的是,写成一行以后,foo和bar实际上并没有被导入当前模块, 只是相当于对外转发了这两个接口,导致当前模块不能直接使用foo和bar。 */
b、模块的接口改名和整体输出,也可以采用这种写法。
// 接口改名
export { foo as myFoo } from 'my_module';
// 整体输出
export * from 'my_module';//会忽略my_module模块的default
方法
// 默认接口
export { default } from 'foo';
// 默认接口也可以改名为具名接口
export { default as es6 } from './someModule';
4)跨模块常量
const
声明的常量只在当前代码块有效。如果想设置跨模块的常量(即跨多个文件),或者说一个值要被多个模块共享,可以采用下面的写法。
// constants.js 模块 export const A = 1; export const B = 3; export const C = 4; // test1.js 模块 import * as constants from './constants'; console.log(constants.A); // 1 console.log(constants.B); // 3 // test2.js 模块 import {A, B} from './constants'; console.log(A); // 1 console.log(B); // 3
5)import()函数
import
命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行。
if (x === 2) {// 报错 import MyModual from './myModual'; } /* 上面代码中,引擎处理import语句是在编译时,这时不会去分析或执行if语句,所以import语句放在if代码块之中毫无意义,因此会报句法错误,而不是执行时错误。
也就是说,import和export命令只能在模块的顶层,不能在代码块之中(比如,在if代码块之中,或在函数之中)。 */
这样的设计,固然有利于编译器提高效率,但也导致无法在运行时加载模块。在语法上,条件加载就不可能实现。如果import
命令要取代 Node 的require
方法,这就形成了一个障碍。因为require
是运行时加载模块,import
命令无法取代require
的动态加载功能。
const path = './' + fileName; const myModual = require(path); /*上面的语句就是动态加载,require到底加载哪一个模块,只有运行时才知道*/
a、有一个提案,建议引入import()
函数,完成动态加载。import(specifier):
import
函数的参数specifier
,指定所要加载的模块的位置。
import()
函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。import()
类似于 Node 的require
方法,区别主要是前者是异步加载,后者是同步加载。
b、import()
的一些适用场合
(1)按需加载:import()
可以在需要的时候,再加载某个模块。
button.addEventListener('click', event => { import('./dialogBox.js') .then(dialogBox => { dialogBox.open(); }) .catch(error => { /* Error handling */ }) }); /*上面代码中,import()方法放在click事件的监听函数之中,只有用户点击了按钮,才会加载这个模块*/
(2)条件加载:import()
可以放在if
代码块,根据不同的情况,加载不同的模块。
if (condition) { import('moduleA').then(...); } else { import('moduleB').then(...); } /*上面代码中,如果满足条件,就加载模块 A,否则加载模块 B*/
(3)动态的模块路径:import()
允许模块路径动态生成。
import(f()).then(...); /*上面代码中,根据函数f的返回结果,加载不同的模块*/
c、注意点
import()
加载模块成功以后,这个模块会作为一个对象,当作then
方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。
import('./myModule.js') .then(({export1, export2}) => { // ...· }); /*上面代码中,export1和export2都是myModule.js的输出接口,可以解构获得*/ // 如果模块有default输出接口,可以用参数直接获得。 import('./myModule.js') .then(myModule => { console.log(myModule.default); });
8、ES6 模块与 CommonJS 模块的差异(Node 应用由模块组成,采用 CommonJS 模块规范)
1)它们有两个重大差异。
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
第二个差异是因为 CommonJS 加载的是一个对象(即module.exports
属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
// lib.js export let obj = {}; // main.js import { obj } from './lib'; obj.prop = 123; // OK obj = {}; // TypeError /*上面代码中,main.js从lib.js输入变量obj,可以对obj添加属性,但是重新赋值就会报错。因为变量obj指向的地址是只读的,不能重新赋值*/
export
通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。
vue:当一个组件被定义,data
必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data
仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data
函数,每次创建一个新实例后,我们能够调用 data
函数,从而返回初始数据的一个全新副本数据对象。
2)import
后面的from
指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js
后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。如果模块名不含路径,那么import
命令会去node_modules
目录寻找这个模块。
3)CommonJS—module.exports属性
module.exports
属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports
变量。为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令—var exports = module.exports。造成的结果是,在对外输出模块接口时,可以向exports对象添加方法。
exports.area = function (r) { return Math.PI * r * r; };
注意,不能直接将exports变量指向一个值,因为这样等于切断了exports
与module.exports
的联系。
exports = function(x) {console.log(x)}; // 上面这样的写法是无效的,因为exports不再指向module.exports了。 // 下面的写法也是无效的。 // hello函数是无法对外输出的,因为module.exports被重新赋值了 exports.hello = function() { return 'hello'; }; module.exports = 'Hello world';
这意味着,如果一个模块的对外接口,就是一个单一的值,不能使用exports
输出,只能使用module.exports
输出。如果你觉得,exports
与module.exports
之间的区别很难分清,一个简单的处理方法,就是放弃使用exports
,只使用module.exports
。
module.exports = function (x){ console.log(x);};
9、字符串的扩展—模板字符串
模板字符串是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。模板字符串中嵌入变量,需要将变量名写在${}
之中。
$('#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! `);
如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。
console.log(`string text line 1 string text line 2`); /* string text line 1 string text line 2 */
$('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `); /*上面代码中,所有模板字符串的空格和换行,都是被保留的,比如<ul>标签前面会有一个换行。如果你不想要这个换行,可以使用trim方法消除它。*/ $('#list').html(` <ul> <li>first</li> <li>second</li> </ul> `.trim());
10、编程风格
1)块级作用域
a、let 取代 var:var
命令存在变量提升效用,let
命令没有这个问题
b、全局常量
在let
和const
之间,建议优先使用const
,尤其是在全局环境,不应该设置变量,只应设置常量。
const
优于let
有几个原因。一个是const
可以提醒阅读程序的人,这个变量不应该改变;另一个是const
比较符合函数式编程思想,运算不改变值,只是新建值,而且这样也有利于将来的分布式运算;最后一个原因是 JavaScript 编译器会对const
进行优化,所以多使用const
,有利于提高程序的运行效率,也就是说let
和const
的本质区别,其实是编译器内部的处理不同。
// bad var a = 1, b = 2, c = 3; // good const a = 1; const b = 2; const c = 3; // best const [a, b, c] = [1, 2, 3];
const
声明常量还有两个好处,一是阅读代码的人立刻会意识到不应该修改这个值,二是防止了无意间修改变量值所导致的错误。所有的函数都应该设置为常量。
2)静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
// bad const a = "foobar"; const b = 'foo' + a + 'bar'; // acceptable const c = `foobar`; // good const a = 'foobar'; const b = `foo${a}bar`;
3)解构赋值
a、使用数组成员对变量赋值时,优先使用解构赋值。函数的参数如果是对象的成员,优先使用解构赋值。
const arr = [1, 2, 3, 4]; // bad const first = arr[0]; const second = arr[1]; // good const [first, second] = arr; // bad function getFullName(user) { const firstName = user.firstName; const lastName = user.lastName; } // good function getFullName(obj) { const { firstName, lastName } = obj; } // best function getFullName({ firstName, lastName }) { }
b、如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。
// bad function processInput(input) { return [left, right, top, bottom]; } // good function processInput(input) { return { left, right, top, bottom }; } const { left, right } = processInput(input);
4)对象
对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写。
var ref = 'some value'; // bad const atom = { ref: ref, value: 1, addValue: function (value) { return atom.value + value; }, }; // good const atom = { ref, value: 1, addValue(value) { return atom.value + value; }, };
5)数组
使用扩展运算符(...)拷贝数组。
// bad const len = items.length; const itemsCopy = []; let i; for (i = 0; i < len; i++) { itemsCopy[i] = items[i]; } // good const itemsCopy = [...items];
6)函数
a、不要在函数体内使用 arguments 变量,使用 rest 运算符(...)代替。因为 rest 运算符显式表明你想要获取参数,而且 arguments 是一个类似数组的对象,而 rest 运算符可以提供一个真正的数组。
// bad function concatenateAll() { const args = Array.prototype.slice.call(arguments); return args.join(''); } // good function concatenateAll(...args) { return args.join(''); }
b、使用默认值语法设置函数参数的默认值。
// bad function handleThings(opts) { opts = opts || {}; } // good function handleThings(opts = {}) { // ... }
7)模块
Module 语法是 JavaScript 模块的标准写法,坚持使用这种写法。使用import
取代require,使用
export
取代module.exports
。
// bad const moduleA = require('moduleA'); const func1 = moduleA.func1; const func2 = moduleA.func2; // good import { func1, func2 } from 'moduleA'; // commonJS的写法 var React = require('react'); var Breadcrumbs = React.createClass({ render() { return <nav />; } }); module.exports = Breadcrumbs; // ES6的写法 import React from 'react'; class Breadcrumbs extends React.Component { render() { return <nav />; } }; export default Breadcrumbs;