前端知识学习 - ES6语法

ES6的语法的简单了解

2017-02-11 | 阅读

常用es6语法简要介绍

ECMAScriptJavaScript的关系。JavaScript的创造者是 Netscape公司,在1996年将JavaScript提交给国际标准化组织ECMA,而一年后,ECMA发布了该语言的第一个版本,规定了浏览器脚本语言标准,并将这种语言称为ECMAScript

标准名为ECMAScript,因为JavaSun的商标,而JavaScript属于Netscape

箭头函数Arrows

箭头函数是使用 =>语法的函数简写形式。与普通函数不同,箭头函数和其上线文中的代码共享一个this

// Expression bodies
// 表达式体
var odds = evens.map(v => v + 1);
var nums = evens.map((v, i) => v + i);
var pairs = evens.map(v => ({even: v, odd: v + 1}));

// Statement bodies
// 语句体
nums.forEach(v => {
  if (v % 5 === 0)
    fives.push(v);
});

// Lexical this
// 具有词法作用域的 this
var bob = {
  _name: "Bob",
  _friends: ["Amy", "Bob", "Cinne", "Dylan", "Ellen"],
  printFriends() {
    this._friends.forEach(f =>
      console.log(this._name + " knows " + f));
  }
}

类 Classes

ES6的类是基于prototype的面向对象的简单语法糖,这使得面向对象的开发更加方便。class定义的类支持基于prototype的继承、super调用、实例和静态方法以及构造函数。

//类的定义
class Animal {
	//ES6中新型构造器
    constructor(name) {
        this.name = name;
    }
    //实例方法
    sayName() {
        console.log('My name is '+this.name);
    }
}
//类的继承
class Programmer extends Animal {
    constructor(name) {
    	//直接调用父类构造器进行初始化
        super(name);
    }
    program() {
        console.log("I'm coding...");
    }
}
var animal=new Animal('dummy'),
wayou=new Programmer('wayou');
animal.sayName();//输出 ‘My name is dummy’
wayou.sayName();//输出 ‘My name is wayou’
wayou.program();//输出 ‘I'm coding...’

增强对象字面量

对象字面量被增强,写法更加简单灵活:

  • 可以在对象字面量里定义原型
  • 定义方法可以不用function关键字
  • 直接调用分类方法

这样对象字面量就与类的概念更加吻合,更加方便面向对象的开发:

//通过对象字面量创建对象
var human = {
    breathe() {
        console.log('breathing...');
    }
};
var worker = {
    __proto__: human, //设置此对象的原型为human,相当于继承human
    company: 'freelancer',
    work() {
        console.log('working...');
    }
};
human.breathe();//输出 ‘breathing...’
//调用继承来的breathe方法
worker.breathe();//输出 ‘breathing...’

字符串模板

字符串模板允许使用 反引号( ` )来创建字符串。

字符串模板中可以包含这种形式的变量 ${variable},可以包含换行,也可以进行转义。

var num=Math.random();
console.log(`your num is ${num}`);
`In JavaScript '\n' is a line-feed.`
`In JavaScript this is not legal.`

结构Destructuring

Destructuring 结构允许赋值时进行模式匹配的赋值,以支持数组和对象的赋值或者基于数组与对象的赋值。如果对应的值不存在,则默认赋值为undefined

// 列表匹配
var [a, , b] = [1,2,3];

// 对象匹配
var { op: a, lhs: { op: b }, rhs: c }
       = getASTNode()

// 对象匹配简写形式
var {op, lhs, rhs} = getASTNode()

function today() { return { d: 2, m: 3, y: 2015 }; }
var { m: month, y: year } = today(); // month = 3, year = 2015

// 也可以作为参数使用
function g({name: x}) {
  console.log(x);
}
g({name: 5})

// 失效弱化解构,结果查询不到时定义为 undefined
var [a] = [];
a === undefined;

// 具备默认值的失效弱化解构
var [a = 1] = [];
a === 1;

默认参数+不定参数+参数展开

函数的参数可以设置默认值 :

function f(x, y=12) {
  return x + y;
}
f(3) == 15

函数参数可以使用 ...运算符来声明不定参数。不定参数用于在一定情况下替代arguments,更加灵活方便的处理参数。...可以用于将多个参数绑定到一个数组中

function f(x, ...y) {
  // y is an Array
  return x * y.length;
}
f(3, "hello", true) == 6

...运算符也可以用于将一个数组拆解成连续的多个参数 :

function f(x, y, z) {
  return x + y + z;
}
// Pass each elem of array as argument
f(...[1,2,3]) == 6

let 与 const 操作符

letconst拥有块级作用域,let声明变量,而const声明常量,即不能修改。 但是 var继续存在,且不支持块级作用域:

function f() {
  {
    let x;
    {
      // okay, block scoped name
      const x = "sneaky";
      // error, const
      x = "foo";
    }
    // error, already declared in block
    let x = "inner";
  }
}

迭代器和 for .. of

迭代器对象允许创建自定义迭代器。 而for..of是基于迭代器的迭代,不需要实现一个数组,且支持像LINQ一样的惰性设计模式。

let fibonacci = {
  [Symbol.iterator]() {
    let pre = 0, cur = 1;
    return {
      next() {
        [pre, cur] = [cur, pre + cur];
        return { done: false, value: cur }
      }
    }
  }
}

for (var n of fibonacci) {
  // truncate the sequence at 1000
  if (n > 1000)
    break;
  console.log(n);
}

实现迭代器 :

IteratorResult是迭代的结果类型,包含两个变量,done表示是否结束,如果为true,则不处理这次结果,且不继续遍历。

Iterator , 为迭代器对象,其中有一个函数为 next,且next的返回结果为 IteratorResult ,即使用这个next函数来进行遍历。

Iterable : 支持迭代器的对象, 一个对象要支持迭代器,需要定义方法 [Symbol.iterator] ,且方法的返回值为一个迭代器对象Iterator.

Generators

前面所述的迭代器实现,嵌套层次比较复杂,难以阅读,所以使用 generators来简化迭代器的编写。generator是迭代器的派生类。

使用 function*函数声明来返回一个generatoryield是一个返回值或抛出值的表达式形式。

var fibonacci = {
  [Symbol.iterator]: function*() {// 声明一个generator
    var pre = 0, cur = 1;
    for (;;) {
      var temp = pre;
      pre = cur;
      cur += temp;
      if(cur > 100)return // 直接使用return中断遍历
      yield cur;// 抛出返回值。
    }
  }
}

for (var n of fibonacci) {
  // truncate the sequence at 1000
  if (n > 1000)
    break;
  console.log(n);
}

generator也可以用于处理异步程序,通过yield引入了协程。

Unicode

全面非破坏性地支持Unicode

// same as ES5.1
"𠮷".length == 2

// new RegExp behaviour, opt-in ‘u’
"𠮷".match(/./u)[0].length == 2

// ES5.1的写法是`反斜杠+u+码点`,新的形式可以通过添加一组大括号`{}`来表示超过四字节的码点
"\u{20BB7}"=="𠮷"=="\uD842\uDFB7"

// new String ops
"𠮷".codePointAt(0) == 0x20BB7

// foo-of 以码位为单位进行迭代
for(var c of "𠮷") {
  console.log(c);
}

Modules 模块

ES6在语言层面上支持使用模块来进行组件定义。模块的功能主要由两个命令构成 : exportimport

export命令用于规定模块的对外接口 ,一个模块就是一个独立的文件,该文件内部的所有成员,外部无法获取。 可以这样输出成员:

//profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';

或者这样输出

var firstName = 'Michael';
var lastName = 'Jackson';
export {firstName, lastName};

可以通过as来修改命名:

export {n as m};

通过export输出的接口,与其对应的值是动态绑定关系,可以取到模块内部实时的值。

export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);

foo变量会在500毫秒后变成baz

使用export命令定义接口后,其他JS文件通过import命令加载这个模块 :

import {firstName, lastName} from './profile';

需要注意的是,import命令带有提升效果,会提升到整个模块的头部,首先执行,所以以下写法是正确的。

foo();
import { foo } from 'my_module';

import语句会执行所加载的模块,而import 'lodash';这种写法,不会执行模块。

export default 命令,为模块指定默认输出,引入者不需要知道模块内的名字。export default命令只能执行一次:

export default function crc32() { // 输出
  // ...
}

import crc31 from 'crc32'; // 输入

因为模块输出默认为crc32函数,所以我们直接引入,并可以进行改名。

Map + Set + WeakMap + WeakSet

// Sets
var s = new Set();
s.add("hello").add("goodbye").add("hello");
s.size === 2;
s.has("hello") === true;

// Weak Maps
var wm = new WeakMap();
wm.set(s, { extra: 42 });
wm.size === undefined

// Weak Sets
var ws = new WeakSet();
ws.add({ data: 42 });

新增了 WeakMap和WeakSet集合,集合中的对象可以自动回收。

proxies

代理可以创造一个具备对象全部可用行为的对象。可用于拦截、对象虚拟化、日志、分析等。可以代理普通对象,也可以代理函数对象:

// 代理一个普通对象

var handler = {
  get: function (target, name) {
    return target[name] + name;
  }
};
var target = {world : "hello "};
var p = new Proxy(target, handler);
console.log(p.world)

Symbols 符号

符号能够实现针对对象状态的访问控制,允许使用string或者symbol作为键来访问属性。

Es6中引入了新的数据类型,即Symbol,新的方法名用于避免属性名的冲突。

二进制和八进制字面量

加入对二进制和八进制字面量的支持

0b111110111 === 503 // true
0o767 === 503 // true

Promises

Promise是一个异步编程的库,将执行与处理分离:

function loadImageAsync(url) {
  return new Promise(function(resolve, reject) {
    var image = new Image();

    image.onload = function() {
      resolve(image);
    };

    image.onerror = function() {
      reject(new Error('Could not load image at ' + url));
    };

    image.src = url;
  });
}

使用时,创建一个promise对象,使用resolve标记成功,而reject标记失败。

promise.then(function(result) {
	//promise成功的话会执行这里
    console.log(result); // "Stuff worked!"
}).catch(function(err) {
	//promise失败会执行这里
    console.log(err); // Error: "It broke"
});

而对于promise对象,使用then添加成功回调,使用catch来添加失败回调。

async

基于PromiseGenerator,在es7中,终于提出了一个更好的异步处理方案,async。实例:

var fs = require('fs');

var readFile = function (fileName){
  return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
      if (error) reject(error);
      resolve(data);
    });
  });
};

var asyncReadFile = async function (){
  var f1 = await readFile('/etc/fstab');
  var f2 = await readFile('/etc/shells');
  console.log(f1.toString());
  console.log(f2.toString());
};

使用async时, 需要声明函数为async, 同时通过 await来获取一个 Promise对象, 而获取的结果 f1也就是 readFile函数中 的resolve(data).

如果Promise对象拥有reject返回值,需要用try catch来抓取:

async function myFunction() {
  try {
    await somethingThatReturnsAPromise();
  } catch (err) {
    console.log(err);
  }
}

虽然async属于es7语法,但是现在已经可以通过babel使用。