ES6的一些新鲜类型介绍

ES6 的新特性小记。目前简单测试了一下,在 Firefox 最新版本 Night39 里面支持的还不错,其它浏览器都只支持一小部分。

ES6的一些新鲜类型介绍

Typed Array/Data View系列

在Web方面,这个暂时想不出使用场景,应该是给nodejs或native等去用的,就不多了,大致如下

  • Int8Array / Uint8Array / Unit8ClampedArray / DataView(Int8 / Unit8)
  • Int16Array / Uint16Array / DataView(Int16 / Unit16)
  • Float32Array / Float64Array / DataView(Float32 / Float64)

TypedArray相关的方法,from和of为静态方法,可以使用 Int8Array.from(args) 来调用。
TypedArray和普通Array的方法区别不大,新增了一些。由于用的不多就不说了,看方法名去理解吧,如下:

  • (static) from
  • (static) of
  • subarray
  • join
  • indexOf
  • lastIndexOf
  • slice
  • every
  • filter
  • forEach
  • map
  • reduce
  • reduceRight
  • reverse
  • some
  • sort
  • copyWithin
  • find
  • findIndex
  • fill
  • keys
  • values
  • etries

Map && WeakMap

有了Map对象,很多数据存放会方便清晰了。

WeakMap,从名字来看就是弱引用的Map,个人理解是如果值没有的了情况下,就会回收掉?比如$(element).remove()?

//新建一个Map对象
var map  = new Map();

//也可以定义初始数据
var map = new Map([ [ key1, value1 ], [ key2, value2 ] ] );

//注意参数是一个array [] 里面有 [key,value] 这样的数组
//WeakMap的创建方式和Map一样
var map  = new WeakMap();

//WeakMap的key必须为对象或者null,所以不能为字符 map.set( 'key', 'value' )会报错

常用属性方法,看名字就知道的方法不过多解释。

  • get( key )
  • set( key, value ) //说明set会返回this,就像append一样可以用链式不停set
  • has( key )
  • size
  • delete( key )
  • clear()
  • forEach( function(value , key , map){} ) // 说明:三个参数顺序分别为值、键、map本身
  • keys()
  • values()
  • entries()

说明:WeakMap 仅有 get / set / has / delete / clear 五个方法 及 size 属性

Set && WeakSet

有了Map的地方肯定也会有Set

//新建一个Set对象
var set  = new Set();

//也可以定义初始数据
var set = new Set( [ object1, object2 ] );

//WeakSet与Set创建方式一样
var set = new WeakSetp();
//WeakSet里面的成员必须是object类型

常用方法

  • add( object )
  • has( object )
  • delect( object )
  • clear()
  • size
  • forEach()
  • keys()
  • values()
  • entries()

说明:WeakSet 仅有 add / delete / has 三个方法 及 size 属性

Proxy 代理对象

代理对象看上去不错,使用方法也比自己去实现一个代理模式要简单。

比对于比较较复杂的页面的话(也可以是其它js程序),已经存在一些已知的使用场景了。如果刚好页面上有这种页面逻辑,可以试试把你原先写的一套代理模式的JavaScript代码替换为Proxy对象实现. 当然,以前有头疼的场景也可以考虑考虑~。

使用过程:

  1. 必须有一个需要被代理的对象

  2. 使用被代理的对象new一个代理

  3. 根据功能需求实现各种handder,一般来说,配置至少得实现 get 这个handder.

  4. 使用代理对象进行访问

语言难以表达清楚,看代码就是了,如果自己在编辑器里面敲一遍,比看一启启遍的效果高出99999999999倍。


//被代理的源对象,不提供直接访问
var sourceObject = {
  field1 : 'The public field',
  field2 : 'The private field',
  method1 : function(){
    return 'Source Method';
  }
}

//代理对象,提供访问,可以代理源对象的能力,也可以自行扩展
var proxyObject = new Proxy( sourceObject, {

  /**
  * get Handder 一般都是要实现的,不然就没什么意义
  *
  *@param sourceObject  被代理的对象
  *@param properties    属性名称
  *@param proxy         代理对本身,即proxyObject
  *
  * 说明:
  * 每个Handder里面的的第一个参数和最后一个参数都是 sourceObject 和 proxyObject
  *  proxyObject一般来说没什么意义通常不传
  *  中间的参数取决于handder本身响应的方法。
  */
  get : function( source, properties, proxy ) {
    console.log('Call Proxy Get Handder');
    switch (properties){
      // 假设不让用户访问原对象的field2
      case 'field2':
      return 'Not allow access to field2';
      break;
      // field3为代理定义的一个属性
      case 'field3':
      return 'Hello Proxy Custom Field';
      break;
      //这是个错误的,会陷入死循环,这里写代码说明一下,不能这么写
      case 'method2': 
      console.log(proxy);
      return proxy.method2(source);
      // 自定义方法的话,也写在getHandder里面
      case 'method3':
      return () => 'Hello Proxy Custom Function';
      // 默认代理源对象属性,没有就返回错误提示
      default :
        if(sourceObject.hasOwnProperty(properties)){
          return sourceObject[properties];
        }else{
          return 'Not Found the ' + properties;
        }
      break;
    }
  },
  
  //这是个错误的,会陷入死循环,这里写代码说明一下,不能这么写
  //Proxy一般来说只实现各不不同的haddder,如果需要自定义方法
  method2:function(source){
    return source.method1;
  },

  // set Handder 返回一个boolean(最好是永远返回true不然会抛异常,看具体场景吧)
  // 下面的handder的示例代码就不写了,有兴趣请自行研究
  set: function( source, key, value){
    console.log('Call Proxy Set Handder');
    return Math.random()*10>5;
  },

  /*
  // 下面的这些handder 一般不怎么用的上,不说了。
  has: function( source, key){
    console.log('Call Proxy Has Handder');
  },
  deletePropery:function( source, name, p ){
    console.log('Call Proxy DeletePropery Handder');
  },
  getOwnPropertyDescriptor:function( source, name, p ){
    console.log('Call Proxy GetOwnPropertyDescriptor Handder');
  },
  defineProperty:function( source, name, p ){
    console.log('Call Proxy DefineProperty Handder');
  },
  getPrototypeOf:function( source, name, p ){
    console.log('Call Proxy GetPrototypeOf Handder');
  },
  setPrototypeOf:function( source, name, p ){
    console.log('Call Proxy SetPrototypeOf Handder');
  },
  isExtensible:function( source, name, p ){
    console.log('Call Proxy IsExtensible Handder');
  },
  preventExtensions:function( source, name, p ){
    console.log('Call Proxy PreventExtensions Handder');
  },
  enumerate:function( source, name, p ){
    console.log('Call Proxy Enumerate Handder');
  },
  ownKeys:function( source, name, p ){
    console.log('Call Proxy OwnKeys Handder');
  },
  apply:function( source, name, p ){
    console.log('Call Proxy Apply Handder');
  },
  construct:function( source, name, p ){
    console.log('Call Proxy Construct Handder');
  }
  */
} );

//get Handder
console.log(proxyObject.field1);  //The public field
console.log(proxyObject.field2);  //Not allow access to field2
console.log(proxyObject.field3);  //Hello Proxy Custom Field
console.log(proxyObject.field4);  //Not Found the field4

console.log(proxyObject.method1()); //Source Method
// console.log(proxyObject.method2); // method2会产生死循环,慎之
console.log(proxyObject.method3()); //Source Method

try{
  console.log(proxyObject.method4()); //TypeError: proxyObject.method4 is not a function
}catch(e){
  console.error('Error :' + e);
}

//set Handder
try{
  proxyObject.CustomField1 = 123;
}catch(e){
  console.error('set Error :' + e);
}
try{
  proxyObject['CustomField2'] = 456;
}catch(e){
  console.error('set Error :' + e);
}try{
  proxyObject['CustomMethod3'] = function(){

  };
}catch(e){
  console.error('set Error :' + e);
}

基本的Proxy,加上本身JavaScript的灵活性,如果深度使用是可以发生很神奇的结果的。当然目前不知了,个人很看好Proxy,可能在未来的很多前框架里面Proxy会成为重要的一部分。

目前来说,本人对ES6的Proxy有一个建议和一个意见。

  1. 建议:太过简单,如果没有特殊说明,可能某个动作引发的现象你都不好追踪代码。所以各位写Proxy时一定要写好注释,必须说明变量的用途。否则后果会很严重。

  2. 意见:一个Proxy,只能代理一个对象,使用的时候会受到很多限制。因为有些比较灵活的场景经常需要在使用的多个对象之间进行切换。如果能像require那样,接受多个对象参数,就完美了。

Reflect

在各种编程语言中,基本都有反射机制,可让用户的代码高度灵活化,主要功能就是让程序不用预先知道需要怎么去做,在做的过程中去根据过程去执行过程,虽然目前JavaScript不直接支持反射,但基本各路都已经使用了各种判断逻辑去支持反射了。ES6直接对反射进行支持,提供了相应API,效果就更上一层楼了。

Reflect对象,ES6新的内置对象,提供的方法大多为静态方法,看上去和Math差不多的uitls对象。基本方法也都清晰明了(对于任何在其它语言里面使用过反射相关API的人),我就不写注释了。

  1. Reflect.get
  2. Reflect.set
  3. Reflect.has
  4. Reflect.deleteProperty
  5. Reflect.getOwnPropertyDescriptor
  6. Reflect.defineProperty
  7. Reflect.getPrototypeOf
  8. Reflect.setPrototypeOf
  9. Reflect.isExtensible
  10. Reflect.preventExtensions
  11. Reflect.enumerate
  12. Reflect.ownKeys
  13. Reflect.ownKeys, symbol order
  14. Reflect.apply
  15. Reflect.construct
  16. Reflect.construct, new.target

怎么说呢,反射是不错的API,由于JavaScript本身是弱类型,所以除了一些较有用的API方便使用以外,没有像其它语言里面的反射机制那么用处大。

比如一个常用的场景是看一个对象是否有某个属性或方法,然后使用这个属性方法,如果使用新增的反射API的话,代码可能是这样

var fn  = Reflect.has(obj, 'methodName')?Reflect.get(obj, 'methodName'):Reflect.get(self, 'defaultMethod');
fn.call();

在JavaScript里面,不使用反射API也可以实现

var fn = obj.hasOwnProperty('methodName') ? obj['methodName']:obj['defaultMethod'];
fn.call();

个人认为不使用反射,代码也不没有看不明白(反而比用反射本身清晰些),也看不出会有什么性能方面的问题,以目前个人的鼠目寸光来看,反射最大的作用就是当个util工具包,需要的时候拿过来用一下就行了,做为核心驱动功能的话,就看开发者的个人意愿了。

Promise

用于异步并发任务,应该是有几套规范的,可以自己去了解下。

jQuery自实现一套,如果原生支持,当然是更好,ES6定义的应该也是Promise A规范,貌似还多支持的更多。

比如Ajax在jQuery里面底层是使用Promise机制进行管理。实际上,只要运行较久的任务都可以用Promise进行管理

Symbol

Symbol 为ES6新定义的一种数据类型,再次说明和基本的 “objcet” 不是一种数据类型。之前说的新增的Map,Int8Array之类的类型,实际上还是 “object” 类型。使用typeof打出来是 “symbol”.

symbol类型不能与 string number类型相加(在JavaScript里面经常使用这种加法,得一个字符串)。但symbol类型可以转换成一个string类型后,再与string相加。

symbol类型如果用在对象里面的话,就是私有类型,外面访问不了,JavaScript之前一直无法用私有,现在可通过Symbol解救了。但个人感觉还是什么用,一是麻烦,二是在class或其它结构体中中不支持。想在class 里面定义一个私有的方法较难(只能做为结构体扩展,不能做为原生结构体)。

var key = Symbol("key");

function Module() {
   this[key] = 'Private Data';
}


Module.prototype = {
  get: function() {
    return this[key]
  }

  set: function(data){
    this[key] = data;
  }
};

var m = new Module()
console.log(m.get());
console.log(m.key);  //undefined
m.set('The new Data');
console.log(m.get());
console.log(m.key); //undefined

另外,Symbol自定义了一些常量,称为 “well-known symbols”,可以在对应的属性里面设置之后,相应动作就会触发,

  • hasInstance 触发 [Objcet] instanceof
    • isConcatSpreadable 触发 [Array] cancat
      • iterator 触发 [Object] iterator
        • species 触发 [RegExp] .$n操作
          • toPrimitive 触发 [Object] 计算
            • toStringTag触发 [Object] toString
          • unscopables 触发 [Object] with操作

个人觉得理解有些难,而且实际做用不是太大,且方法极不规范。所以不暂不关注了。比如使用toStringTag符修改toString时可以定义为一个字符串,而其实的Symbol,确需要定义一个方法

var a = {} ;
console.log(a.toString());         // "[object Object]"
a.toString = function(){return "ddd"}
console.log(a.toString());         // "ddd"
a[Symbol.toStringTag] = "xdnote.com";
console.log(a + '');         // "ddd xdnote"

小结:

功能性并非增加太多,灵活性增加了不少,如果各端进行一些优化,提升性能就更加不错了,不过目前看来,普及还遥遥无期~。