ES6 简介

Posted A-L-Kun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ES6 简介相关的知识,希望对你有一定的参考价值。

ES6 简介(二)

五、 函数扩展

1、 函数参数默认值

1.1 基本用法

ES6 之前,不能直接为函数的参数指定默认值,只能采用变通的方法。

function log(x, y) 
  y = y || \'World\';
  console.log(x, y);


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

缺点:

  • 如果参数 y 赋值了,但是对应的布尔值为 false ,则该赋值不起作用。就像上面代码的最后一行,参数 y 等于空字符,结果被改为默认值。

修改:

if (typeof y === \'undefined\') 
  y = \'World\';

ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

function log(x, y = \'World\') 
  console.log(x, y);


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

优点:

  • 阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;
  • 有利于将来的代码优化,即使未来的版本在对外接口中,彻底拿掉这个参数,也不会导致以前的代码无法运行。

注意:

  • 参数变量是默认声明的,所以不能用 let 或 const 再次声明;

    function foo(x = 5) 
      let x = 1; // error
      const x = 2; // error
    
    
  • 使用参数默认值时,函数不能有同名参数;

    // 不报错
    function foo(x, x, y) 
      // ...
    
    // 报错
    function foo(x, x, y = 1) 
      // ...
    
    // SyntaxError: Duplicate parameter name not allowed in this context
    
  • 参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。

    let x = 99;
        
    function foo(p = x + 1) 
      console.log(p);
    
        
    foo() // 100
        
    x = 100;
        
    foo() // 101
    

1.2 与解构赋值默认值结合使用

function foo(x, y = 5) 
  console.log(x, y);


foo() // undefined 5
foo(x: 1) // 1 5
foo(x: 1, y: 2) // 1 2
foo() // TypeError: Cannot read property \'x\' of undefined

这里只使用了对象的解构赋值默认值,没有使用函数参数的默认值。只有当函数 foo 的参数是一个对象时,变量 x 和 y 才会通过解构赋值生成。如果函数 foo 调用时没提供参数,变量 x 和 y 就不会生成,从而报错。通过提供函数参数的默认值,就可以避免这种情况。

与函数默认值结合使用:

function foo(x, y = 5 = ) 
  console.log(x, y);


foo() // undefined 5

使用示例:

function fetch(url,  body = \'\', method = \'GET\', headers =   = ) 
  console.log(method);

fetch(\'http://example.com\')
// "GET"

双重默认值

1.3 指定参数的必要性

利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。

function throwIfMissing() 
  throw new Error(\'Missing parameter\');


function foo(mustBeProvided = throwIfMissing()) 
  return mustBeProvided;


foo()
// Error: Missing parameter

可以将参数默认值设为 undefined ,表明这个参数是可以省略的

function foo(optional = undefined)  ··· 

2、 rest 参数

ES6 引入rest 参数(形式为 ...变量名 ),用于获取函数的多余参数,这样就不需要使用 arguments 对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。

const ft = (...args) => 
    console.log(typeof args);
    return args;

console.log(ft(1, 2, 3))

rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。

注意,arguments 其为一个伪数组,不是数组,而 rest 参数 其为一个数组。

使用示例:

// arguments变量的写法
function sortNumbers() 
  return Array.prototype.slice.call(arguments).sort();

// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();

3、 name 属性

函数的name属性,返回该函数的函数名

function foo() 

foo.name 
// "foo"

这个属性早就被浏览器广泛支持,但是直到 ES6,才将其写入了标准。

需要注意的是,ES6 对这个属性的行为做出了一些修改。如果将一个匿名函数赋值给一个变量,ES5 的 name 属性,会返回空字符串,而 ES6 的 name 属性会返回实际的函数名。

var f = function () ;
// ES5f.name ""
// ES6f.name "f"

上面代码中,变量 f 等于一个匿名函数,ES5 和 ES6 的 name 属性返回的值不一样。

如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的 name 属性都返回这个具名函数原本的名字。

const bar = function baz() ;
// ES5bar.name "baz"
// ES6bar.name "baz"

Function 构造函数返回的函数实例, name 属性的值为 anonymous 。

(new Function).name 
// "anonymous"

bind 返回的函数, name 属性值会加上 bound 前缀。

function foo() ;
foo.bind().name 

// "bound foo"(function()).bind().name 
// "bound "

4、 箭头函数

ES6允许使用“箭头”( => )定义函数。

var f = v => v;
// 等同于
var f = function (v) 
  return v;
;

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

var f = () => 5;
// 等同于
var f = function ()  return 5 ;

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) 
  return num1 + num2;
;

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用 return 语句返回。

var sum = (num1, num2) =>  return num1 + num2; 

如果箭头函数只有一行语句,且不需要返回值,可以采用下面的写法,就不用写大括号了。

let fn = () => void doesNotReturn();

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用 return 语句返回。

// 报错
let getTempItem = id =>  id: id, name: "Temp" ;
// 不报错
let getTempItem = id => ( id: id, name: "Temp" );

箭头函数的一个用处是简化回调函数。

// 正常函数写法
var result = values.sort(function (a, b) 
  return a - b;
);

// 箭头函数写法
var result = values.sort((a, b) => a - b);

使用注意点

(1)函数体内的 this对象,就是定义时所在的对象,而不是使用时所在的对象。

  • function foo() 
        setTimeout(() => 
            console.log(\'id:\', this.id);
        , 100);
    
    let id = 21;
    foo.call( id: 42 );
    // id: 42
    
    // 将上面函数装换为 es5 代码
    // ES5
    function foo() 
      const _this = this;
      setTimeout(function () 
        console.log(\'id:\', _this.id);
      , 100);
    
    

(2)不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用 yield命令,因此箭头函数不能用作 Generator 函数。

六、 模块化

1、 概述

历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的 require 、Python 的 import ,甚至就连 CSS 都有 @import ,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。

在 ES6 之前,社区制定了一些模块加载方案,最主要的有CommonJSAMD两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。

// CommonJS模块
let  stat, exists, readfile  = require(\'fs\');
// 等同于
let _fs = require(\'fs\');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

上面代码的实质是整体加载 fs 模块(即加载 fs 的所有方法),生成一个对象( _fs ),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。

ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,再通过 import 命令输入。

// ES6模块
import  stat, exists, readFile  from \'fs\';

静态加载

2、 导入导出

模块功能主要由两个命令构成:exportimport 。 export 命令用于规定模块的对外接口, import 命令用于输入其他模块提供的功能。

2.1 export

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用 export 关键字输出该变量。下面是一个 JS 文件,里面使用 export 命令输出变量。

// profile.js
export const firstName = \'Michael\';
export const lastName = \'Jackson\';
export const year = 1958;
// 也可以这样
const firstName = \'Michael\';
const lastName = \'Jackson\';
const year = 1958;
export  firstName, lastName, year ;

export 命令除了输出变量,还可以输出函数或类(class)。

通常情况下, export 输出的变量就是本来的名字,但是可以使用 as 关键字重命名。

function v1()  ... 
function v2()  ... 
export 
  v1 as streamV1,
  v2 as streamV2,
  v2 as streamLatestVersion
;

需要特别注意的是, export 命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系。

// 报错
export 1;
// 报错
var m = 1;
export m;

// 应该要这样写
export var m = 1;
export m;

最后, export 命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错, import 命令也是如此。

2.2 import

使用export 命令定义了模块的对外接口以后,其他JS文件就可以通过import命令加载这个模块。

// main.js
import  firstName, lastName, year  from \'./profile.js\';
function setName(element) 
  element.textContent = firstName + \' \' + lastName;


// 也可以修改变量的名字
import  lastName as surname  from \'./profile.js\';
  • import 命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。
  • import 后面的 from 指定模块文件的位置,可以是相对路径,也可以是绝对路径, .js 后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
  • 由于 import 是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

加载所有模块:

import \'lodash\';

目前阶段,通过 Babel 转码,CommonJS 模块的 require 命令和 ES6 模块的 import 命令,可以写在同一个模块里面,但是最好不要这样做。因为 import 在静态解析阶段执行,所以它是一个模块之中最早执行的。

3、 模块的整体加载

除了指定加载某个输出值,还可以使用整体加载,即用星号指定一个对象,所有输出值都加载在这个对象上面。

下面是一个 circle.js 文件,它输出两个方法 area 和 circumference 。

// circle.js
export function area(radius) 
  return Math.PI * radius * radius;


export function circumference(radius) 
  return 2 * Math.PI * radius;

加载模块:

import * as circle from \'./circle\';


console.log(\'圆面积:\' + circle.area(4));
console.log(\'圆周长:\' + circle.circumference(14));

4、 模块的继承

模块之间也可以继承。

假设有一个 circleplus 模块,继承了 circle 模块。

// circleplus.js
export * from \'circle\';
export var e = 2.71828182846;

export default function(x) 
  return Math.exp(x);

5、 import()

前面介绍过,import命令会被JavaScript 引擎静态分析,先于模块内的其他语句执行( import 命令叫做“连接” binding 其实更合适)。所以,下面的代码会报错。

// 报错
if (x === 2) 
  import MyModual from \'./myModual\';

那我们可不可以根据需求来加载呢?

  1. 使用 require 来进行动态加载

    const path = \'./\' + fileName;
    
    const myModual = require(path);
    
  2. 使用 import() 来加载

    const main = document.querySelector(\'main\');
    
    import( ./section-modules/$someVariable.js )
      .then(module => 
        module.loadPageInto(main);
      )
      .catch(err => 
        main.textContent = err.message;
      );
    

注意:

  • import() 加载模块成功以后,这个模块会作为一个对象,当作 then 方法的参数。因此,可以使用对象解构赋值的语法,获取输出接口。

  • 如果模块有 default 输出接口,可以用参数直接获得。

  • import(\'./myModule.js\')
    .then(myModule => 
      console.log(myModule.default);
    );
    
  • 加载多个模块

    Promise.all([
      import(\'./module1.js\'),
      import(\'./module2.js\'),
      import(\'./module3.js\'),
    ])
    .then(([module1, module2, module3]) => 
       ···
    );
    

使用场景:

  • 按需加载
  • 条件加载
  • 动态的模块路径

七、 es6 编程风格

1、 块级作用域

使用 let 取代 var:

  • ES6 提出了两个新的声明变量的命令:letconst 。其中,let 完全可以取代 var ,因为两者语义相同,而且let没有副作用。

全局常量和线程安全:

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

const 优于 let 有几个原因:

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

2、 字符串

静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。

// bad
const a = "foobar";
const b = \'foo\' + a + \'bar\';

// acceptable
const c =  \'foobar\' ;

// good
const a = `foobar`;
const b =  `foo$abar`;

3、 结构赋值

使用数组成员对变量赋值时,优先使用解构赋值。

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 ) 

如果函数返回多个值,优先使用对象的解构赋值,而不是数组的解构赋值。这样便于以后添加返回值,以及更改返回值的顺序。

// bad
function processInput(input) 
  return [left, right, top, bottom];


// good
function processInput(input) 
  return  left, right, top, bottom ;


const  left, right  = processInput(input);

4、 对象

单行定义的对象,最后一个成员不以逗号结尾。

多行定义的对象,最后一个成员以逗号结尾。

// bad
const a =  k1: v1, k2: v2, ;
const b = 
  k1: v1,
  k2: v2
;

// good
const a =  k1: v1, k2: v2 ;
const b = 
  k1: v1,
  k2: v2,
;

对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用 Object.assign 方法。

// bad
const a = ;
a.x = 3;

// if reshape unavoidable
const a = ;
Object.assign(a,  x: 3 );

// good
const a =  x: null ;
a.x = 3;

如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义。

// bad
const obj = 
  id: 5,
  name: \'San Francisco\',
;
obj[getKey(\'enabled\')] = true;

// good
const obj = 
  id: 5,
  name: \'San Francisco\',
  [getKey(\'enabled\')]: true,
;

另外,对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写。

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

使用 Array.from 方法,将类似数组的对象转为数组。

const nodes = Array.from(argument);

6、 函数

立即执行函数可以写成箭头函数的形式。

(() => 
  console.log(\'Welcome to the Internet.\');
)();

那些使用匿名函数当作参数的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了 this。

// bad
[1, 2, 3].map(function(x) 
  return x * x;
);

// good
[1, 2, 3].map((x) => 
  return x * x;
);

// best
[1, 2, 3].map(x => x * x);

箭头函数取代Function.prototype.bind,不应再用 self/_this/that 绑定 this。

// bad
const self = this;
const boundMethod = function(...params) 
  return method.apply(self, params);


// acceptable
const boundMethod = method.bind(this);

// best
const boundMethod = (...params) => method.apply(this, params);

简单的、单行的、不会复用的函数,建议采用箭头函数。如果函数体较为复杂,行数较多,还是应该采用传统的函数写法。

所有配置项都应该集中在一个对象,放在最后一个参数,布尔值不可以直接作为参数。

// bad
function divide(a, b, option = false ) 


// good
function divide(a, b,  option = false  = ) 

不要在函数体内使用 arguments 变量,使用 rest 运算符(...)代替。因为 rest 运算符显式表明你想要获取参数,而且 arguments 是一个类似数组的对象,而 rest 运算符可以提供一个真正的数组。

// bad
function concatenateAll() 
  const args = Array.from(arguments);
  return args.join(\'\');


// good
function concatenateAll(...args) 
  return args.join(\'\');

使用默认值语法设置函数参数的默认值。

// bad
function handleThings(opts) 
  opts = opts || ;


// good
function handleThings(opts = ) 
  // ...

7、 Map 结构

注意区分 Object 和 Map,只有模拟现实世界的实体对象时,才使用 Object。如果只是需要 key: value的数据结构,使用 Map 结构。因为 Map 有内建的遍历机制。

let map = new Map(arr);

for (let key of map.keys())   // 遍历键
  console.log(key);


for (let value of map.values())   // 遍历值
  console.log(value);


for (let item of map.entries())   // 遍历键值对
  console.log(item[0], item[1]);

8、 Class

总是用 Class,取代需要 prototype 的操作。因为 Class 的写法更简洁,更易于理解。

// bad
function Queue(contents = []) 
  this._queue = [...contents];


Queue.prototype.pop = function() 
  const value = this._queue[0];
  this._queue.splice(0, 1);
  return value;


// good
class Queue 
  constructor(contents = []) 
    this._queue = [...contents];
  
  pop() 
    const value = this._queue[0];
    this._queue.splice(0, 1);
    return value;
  

使用 extends实现继承,因为这样更简单,不会有破坏 instanceof 运算的危险。

// bad
const inherits = require(\'inherits\');
function PeekableQueue(contents) 
  Queue.apply(this, contents);

inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function() 
  return this._queue[0];


// good
class PeekableQueue extends Queue 
  peek() 
    return this._queue[0];
  

9、 模块

首先,Module 语法是 JavaScript 模块的标准写法,坚持使用这种写法。使用 import 取代 require

// bad
const moduleA = require(\'moduleA\');
const func1 = moduleA.func1;
const func2 = moduleA.func2;

// good
import  func1, func2  from \'moduleA\';

使用 export 取代 module.exports

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

如果模块只有一个输出值,就使用export default ,如果模块有多个输出值,就不使用 export defaultexport default 与普通的 export不要同时使用。

不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。

// bad
import * as myObject from \'./importModule\';

// good
import myObject from \'./importModule\';

如果模块默认输出一个函数,函数名的首字母应该小写。

function makeStyleGuide() 


export default makeStyleGuide;

如果模块默认输出一个对象,对象名的首字母应该大写。

const StyleGuide = 
  es6: 
  
;

export default StyleGuide;

以上是关于ES6 简介的主要内容,如果未能解决你的问题,请参考以下文章

ES6简介

ES6 简介

ES6 简介

ES6 简介

00.简介ES6

图学ES6-1.ECMAScript 6简介