es6学习-201803

1、ES6 规定暂时性死区和letconst语句不出现变量提升,主要是为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。这样的错误在 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构造函数接受一个函数作为参数,该函数的两个参数分别是resolvereject。它们是两个函数,由 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

模块功能主要由两个命令构成:exportimportexport命令用于规定模块的对外接口,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变量指向一个值,因为这样等于切断了exportsmodule.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输出。如果你觉得,exportsmodule.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、全局常量

letconst之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。

const优于let有几个原因。一个是const可以提醒阅读程序的人,这个变量不应该改变;另一个是const比较符合函数式编程思想,运算不改变值,只是新建值,而且这样也有利于将来的分布式运算;最后一个原因是 JavaScript 编译器会对const进行优化,所以多使用const,有利于提高程序的运行效率,也就是说letconst的本质区别,其实是编译器内部的处理不同。

// 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;

 

posted @ 2018-03-27 15:19  Colorful_coco  阅读(214)  评论(0编辑  收藏  举报