ECMAScript 6新特性概述

ECMAScript 6也被成为ECMAScript 2015,是即将完成的ECMAScript标准,12bet,预计将于今年6月完成,ES6对语言进行了重大的升级,相对于ES5有了很多的新特性。之前已经了解了不少ES6的内容,例如今天对于这些内容做一些总结。

ES6中添加了对的支持,引入了class关键字(其实class在JavaScript中一直是保留字,现在终于派上用场了)。JS本身就是面向对象的,ES6中提供的类实际上只是JS原型模式的包装,所以对于ES5的原型继承是支持的,而且还支持调用父类方法、12bet,实例化对象、静态方法和构造函数。

class SkinnedMesh extends THREE.Mesh {  
  constructor(geometry, materials) {
    super(geometry, materials);
    this.idMatrix = SkinnedMesh.defaultMatrix();
    this.bones = [];
    this.boneMatrices = [];
  }
  update(camera) {
    //直接调用父类方法
    super.update();
  }
  //支持get、set
  get boneCount() {
    return this.bones.length;
  }
  set matrixType(matrixType) {
    this.idMatrix = SkinnedMesh[matrixType]();
  }
  static defaultMatrix() { //静态方法
    return new THREE.Matrix4();
  }
}

相信对于上面的代码,属性Java或C++的肯定是似曾相识了。

增强对象字面量

对象字面量被增强了(enhanced object literal),12博体育,写法更加简洁与灵活,同时在定义对象的时候能够做的事情更多了。具体表现在:

  • 12bet,可以在对象字面量里面定义原型
  • 定义方法可以不用function关键字
  • 12bet,属性赋值简写
  • 直接调用父类方法
  • 使用表达式(动态)计算属性名称

这样,对象字面量和类定义是十分相近的,对于面向对象编程来说是非常方便的。

var obj = {  
    // 定义隐式原型
    __proto__: theProtoObj,
    // ‘handler: handler’的简写方式
    handler,
    // 定义方法(不需要function关键字)
    toString() {
     // 调用父类方法
     return "d " + super.toString();
    },
    // (动态)计算属性名称
    [ 'prop_' + (() => 42)() ]: 42
};

箭头操作符

箭头操作符(=>)是ES6中的重大变化,就像Chttps://www.liuwanlin.info/superlin%e7%9a%84%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-52/和Java 8中的lambda表达式一样,箭头操作符可以简化函数的书写,操作符左边是输入的参数,右边是进行的操作和返回值。此外它的另外一个重要特点就是:this关键字和包含它的函数拥有同样的词法作用域

先来看一个不使用箭头操作符的例子:

function Car() {  
  var self = this; //保存this,用于内部函数
  self.speed = 0;
  setInterval(function goFaster() {
    //在函数内部,this的作用域已经改变,但是可以用外部保存的self来指向外部的this
    self.speed += 5;
      console.log('速度: ' + self.speed);
  }, 1000);
}
var car = new Car();  

如果使用箭头操作符,代码可以重写为:

function Car() {  
  this.speed = 0;
  //使用ES6的箭头函数
  setInterval(() => {
    this.speed += 5; //this指向外部的Car,也就是和外部函数有相同的词法作用域
    console.log('速度: ' + this.speed);
  }, 1000);
}

字符串模板

其他的一些脚本语言,例如Python、PHP等都有字符串模板,字符串模板提供了动态构造字符串的能力,而不是使用拼接的方式,这样能够有效的防止注入攻击和构造高级的数据结构。其语法十分简单:${name}

//产生一个随机数
var num=Math.random();  
//将这个数字输出到console
console.log(`your num is ${num}`);  

块作用域

对于C++/Chttps://www.liuwanlin.info/superlin%e7%9a%84%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-52//Java开发者来说,Javascript的声明提升(Hoisting)是很费解的,ES5中变量拥有全局或本地函数作用域,但是却没有块作用域(catch是例外,可以看做是块作用域),要实现块作用域只能例如立即执行函数来实行,例如下例所示。

function outputNumbers(count){  
  (function(){
    for (var i=0;i<count;i++){
        alert(i);
    }
  })();
  alert(i)//undefined
}
outputNumbers(3);  

ES6中可以通过let关键字来定义块级作用域变量,还可以通过const来定义常量。

var num = 0; //全局变量
for (let i = 0; i < 10; i++) { //i是块级变量  
  num += i;
  console.log('块中i的值: ' + i);
}
console.log('i是否定义?: ' + (typeof i !== 'undefined')); //false  

解构

这一点对于了解Python的是比较熟悉的,解构赋值能够将返回的对象赋值给多个接收的变量,而且还可以深度的匹配,并且支持失效弱化

//数组匹配
var list = [ 1, 2, 3 ];  
var [ a, , b ] = list;//a=1,b=3  
[ b, a ] = [ a, b ];//交换
//对象匹配
var { op, lhs, rhs } = getASTNode()
//深度对象匹配
var { op: a, lhs: { op: b }, rhs: c } = getASTNode()
//参数上下文匹配
function g ({ name, val }) {  
    console.log(name, val)
}
g({ name: "bar", val: 42 })
//失效弱化
var list = [ 7, 42 ];  
var [ a = 1, b = 2, c = 3, d ] = list;//a=7,b=42,c=3,d=undefined  

增强参数处理

ES6中对于参数处理进行了增强,主要有以下几种增强:

  • 默认参数
  • 不定参数:能够设定要使用的参数并接受不定数量的未命名参数
  • 拓展参数:允许传递数组或者类数组直接做为函数的参数而不用通过apply
//默认参数
function f (x, y = 7, z = 42) {  
    return x + y + z;
}
//不定参数
function f (x, y, …a) {  
    return (x + y) * a.length;
}
//拓展参数
function f(x, y, z) {  
  return x + y + z;
}
// 将数组元素解析为参数
f(...[1,2,3]) == 6  

迭代

ES6中新增了三种形式的迭代:

  • iterator:允许像 CLI IEnumerable 或者 Java Iterable 一样自定义迭代器。
  • for-of操作符:与for-in类似,只不过每次循环遍历的不是索引而是值。
  • generator:一种特殊的iterator,循环流程可以暂停和继续,除了迭代,也支持异步编程。
//iterator
let fibonacci = {  
    [Symbol.iterator]() {
        let pre = 0, cur = 1
        return {
           next () {
               [ pre, cur ] = [ cur, pre + cur ]
               return { done: false, value: cur }
           }
        }
    }
}
for (let n of fibonacci) {  
    if (n > 1000)
        break
    console.log(n)
}

generator通过使用function*yield简化迭代器的编写, 形如function*的函数声明返回一个生成器实例,generator是迭代器的子类型,迭代器包括附加的nextthrow,这使得值可以回流到generator中,所以,yield是一个返回或抛出值的表达式形式。

//generator
var fibonacci = {  
  [Symbol.iterator]: function*() {
    var pre = 0, cur = 1;
    for (;;) {
      var temp = pre;
      pre = cur;
      cur += temp;
      yield cur;
    }
  }
}
for (var n of fibonacci) {  
  if (n > 1000)
    break;
  console.log(n);
}

Map,Set 和 WeakMap,WeakSet

新加的集合类型,提供了更加方便的获取属性值的方法,不用像以前一样用hasOwnProperty来检查某个属性是属于原型链上的呢还是当前对象的。同时,在进行属性值添加与获取时有专门的getset 方法。

// Sets
var s = new Set();  
s.add("hello").add("goodbye").add("hello");  
s.size === 2;  
s.has("hello") === true;
// Maps
var m = new Map();  
m.set("hello", 42);  
m.set(s, 34);  
m.get(s) == 34;  

javascript 有时候我们会把对象作为一个对象的键用来存放属性值,12博体育,普通集合类型比如简单对象会阻止垃圾回收器对这些作为属性键存在的对象的回收,有造成内存泄漏的危险。WeakMaps和WeakSet提供不会泄露的对象键(对象作为键名,而且键名指向对象)索引表(注:所谓的不会泄露,指的是对应的对象可能会被自动回收,回收后WeakMaps自动移除对应的键值对,有助于防止内存泄露)

// Weak Maps
var wm = new WeakMap();  
wm.set(s, { extra: 42 });  
wm.size === undefined
// Weak Sets
var ws = new WeakSet();  
ws.add({ data: 42 });  
// 由于所加入的对象没有其他引用,故在此集合内不会保留之。

Symbol类型

符号(Symbol) 能够实现针对对象状态的访问控制,允许使用string(与ES5相同)或symbol作为键来访问属性。Symbol 通过调用symbol函数产生,它接收一个可选的名字参数,该函数返回的symbol是唯一的。之后就可以用这个返回值做为对象的键了。Symbol还可以用来创建私有属性,外部无法直接访问由symbol做为键的属性值。

(function() {
  // 创建symbol
  var key = Symbol("key");
  function MyClass(privateData) {
    this[key] = privateData;
  }
  MyClass.prototype = {
    doStuff: function() {
      ... this[key] ...
    }
  };
})();
var c = new MyClass("hello")  
c["key"] === undefined//无法访问该属性,因为是私有的  

模块

Java中有import,C++中有include,很多的语言都有语言的模块化,这对于解决依赖,降低代码耦合度是很重要的。虽然Javascript有很多第三方的关于模块化加载的规范(AMD、CMD、CommonJS),但是ES6之前并没有在语言级别上定义模块,这也是一直被人们所诟病的。幸好ES6中有了模块化的的特性,并且通过importexport关键字来就可以很好的管理依赖了。

// point.js
module "point" {  
    export class Point {
        constructor (x, y) {
            public x = x;
            public y = y;
        }
    }
}
// myapp.js
//声明引用的模块
module point from "https://www.liuwanlin.info/point.js";  
//这里可以看出,尽管声明了引用的模块,还是可以通过指定需要的部分进行导入
import https://www.liuwanlin.info/files/point from "point";
var origin = new Point(0, 0);  
console.log(origin);  

模块加载器

模块加载器支持:

  • 动态加载
  • 状态隔离
  • 全局命名空间隔离
  • 编译挂钩
  • 嵌套虚拟化

默认的模块加载器是可配置的,也可以构建新的加载器,对在隔离和受限上下文中的代码进行求值和加载。

// 动态加载 - ‘System’ 是默认的加载器
System.import('lib/math').then(function(m) {  
  alert("2π = " + m.sum(m.pi, m.pi));
});
// 创建一个执行沙箱- 新的加载器
var loader = new Loader({  
  global: fixup(window)
});
loader.eval("console.log('hello world!');");
// 直接操作模块缓存
System.get('jquery');  
System.set('jquery', Module({$: $})); // 警告:此部分的设计尚未最终定稿  

Promises

异步编程是Javascript的语言特色之一,但是也带来了新的问题,需要不断的回调,代码结构上要不断缩减,这就是所谓的回调黑洞。为了解决这个问题,可以链式的处理回调和处理错误Promises出现了。目前已经有很多的第三方库支持Promises,例如 QwhenWinJSRSVP.js等,还有jQuery中的dederred对象也支持Promises。而ES6也引入了Promises,当你发起一个异步请求,并绑定了.when(), .done()等事件处理程序时,其实就是在应用promises模式。

function fetchAsync (url, timeout, onData, onError) {  
    //…
}
let fetchPromised = (url, timeout) => {  
    return new Promise((resolve, reject) => {
        fetchAsync(url, timeout, resolve, reject)
    })
}
Promise.all([  
    fetchPromised("https://backend/foo.txt", 500),
    fetchPromised("https://backend/bar.txt", 500),
    fetchPromised("https://backend/baz.txt", 500)
]).then((data) => {
    let [ foo, bar, baz ] = data
    console.log(`success: foo=${foo} bar=${bar} baz=${baz}`)
}, (err) => {
    console.log(`error: ${err}`)
})

元操作

ES6中提供了两种元操作:代理(Proxy)反射

Proxy可以监听对象身上发生了什么事情,并在这些事情发生后执行一些相应的操作。一下子让我们对一个对象有了很强的追踪能力,同时在数据绑定方面也很有用处。

//监听get
let target = {  
    foo: "Hello, foo"
}
let proxy = new Proxy(target, {  
    get (receiver, name) {
        return `Hello, ${name}!!`;
    }
})
proxy.foo   === "Hello, foo"  
proxy.world === "Hello, world!!"
//监听set
let target = {  
    foo: "Hello, foo"
}
let proxy = new Proxy(target, {  
    set (receiver, property, name) {
         console.log(property, 'is changed to', value);
        receiver[property] = value;
    }
})
proxy.foo = "Hello";//控制台输出:foo is changed to Hello  

反射在对象上暴露了运行时级别的元操作,从效果上来说,这是一个反代理API,并允许调用与代理陷阱中相同的元操作。实现代理非常有用。

let obj = { a: 1 }  
Object.defineProperty(obj, "b", { value: 2 })  
obj[Symbol("c")] = 3  
Reflect.ownKeys(obj) // [ "a", "b", Symbol(c) ]  

其他

除了上面提到的之外,ES6还有一些其他特性:

  • MathNumberStringObject 的新API
  • 二进制和八进制字面量
  • Unicode 统一码
  • 可子类化的内建对象
  • 尾调用:保证尾部调用时栈不会无限增长,这使得递归算法在面对未作限制的输入时,能够安全地执行。

参考

Author image
关于 superlin
Beijing, CN 主页
The reason why a great man is great is that he resolves to be a great man.
 
 
默认颜色 边栏居左 边栏居右