ES6新特性 _3

一、新的数据类型 Symbol

1、概述

该数据类型表明该属性的属性名是唯一的。出现的原因是因为当引入其他人的代码时可能会造成属性名重复的现象,为此引入Symbol属性,去表明该属性的属性名是唯一的。

语法如下:

# Symbol对于数值的应用
let age = 18; # 原属性名age
let age = 19; # 引入的属性名age
age;//19  

//原属性名和引入的属性名会发生冲突,为此需要引入Symbol属性
let age = Symbol('a'); 
age; //symbol('a')
let age = 'b';
age;//b

# 注意的是Symbol不是一个对象,不可以用new关键创建实例


# Symbol对于函数的应用
# 将函数传入Symbol,返回值为该函数 toString() 方法的返回值
function Person(){
	toString (){
		return '123';
	}
}
let num = Symbol(new Person());
num;//Symbol(123)
let num = 456;
num;//456

2、Symbol.protoype.description( )

该方法用于为Symbol读出描述。

语法如下:

# 创建 Symbol('描述') 描述
let s = Symbol('这里是描述');
s;//Symbol('这里是描述')

# 调用 description()方法调出描述
s.description(); //这里是描述

3、作为属性名的Symbol

以Symbol创建的属性名,可以保证该属性名不会出现同名覆盖的情况。

let a = {};
a[mySymbol] = 'Hello';

let a = {
	[mySymbol]:'Hello';
};

a[mySymbol]; //Hello ,不会互相冲突且覆盖

4、作为常量的Symbol

使用Symbol创建常量,可以保证第一值不变,第二属性名唯一。

const COLOR_RED = Symbol("red");
const COLOR_YELLOW = Symbol("yellow");
const COLOR_BLUE = Symbol("blue");

5、更多Symbol使用

请查看文档:https://es6.ruanyifeng.com/#docs/symbol

二、新的数据结构 Set

ES6提供新的数据结构Set,Set结构类似如数组。但成员都是唯一的。即无重复值。

这里需要注意,Set 结构的键名就是键值(两者是同一个值),因此第一个参数与第二个参数的值永远都是一样的。

1、基本用法

# 生成:使用 Set( )构造函数来生成Set数据结构。
let set = new Set( );

# 添加数据:使用 Set( ).add( )方法向Set数据结构添加数据
[1,1,2,3,4,5].forEach(v=>set.add(v);

# 遍历:因为Set继承了Iterable接口,所以可以使用for of循环进行遍历
for(value of set){
	consolo.log(value);//1,2,3,4,5 无重复数
}
	
# 参数说明
# Set构造函数接受数值或数组或其他具有iterable接口的数据结构都可以
let set = new Set([1,2,3,4,5]);
# 应用场景
# 常用于将一个数组转换为无重复值的数据
let set = new Set([1,1,2,3,4,5]);
let arr = Array.form(set);
arr;//[1,2,3,4,5]
or 
let arr = [..set];
arr;//[1,2,3,4,5]

2、Set的实例属性和方法

语法如下:

# Set结构的实例属性
# Set.prototype.constructor 指向构造函数

new Set() === Set.protoype.constructor

# Set.prototype.size() 读出Set结构的实例个数
new Set([1,2,3,4]).size(); //4

# Set结构的实例方法

# 常用的四个操作方法
# Set.prototype.add(value) 添加元素到结构中,返回添加完的Set对象

new Set().add(2); //Set结构中存在2
# Set.protoype.delete(value) 从结构中删除元素,返回是否删除成功的布尔值

let set = new Set().add(3);
set.delete(3); //true
set.delete(4); //false

# Set.prototype.has(value) 检测某个值是否存在于Set结构中,返回是否存在的布尔值
let set  = new Set().add(4);
set.has(4); //true
set.has(5); //false

# Set.prototype.clear(value) 清空所有在Set结构中的元素
let set = new Set();
[1,2,3,4,5].forEach(v=>set.add(v));
set; //存在元素1,2,3,4,5
set.clear(); 
set; //清空为元素为空

# 常用的四个遍历方法

let set = new Set(['red', 'green', 'blue']);

# Set.prototype.keys():返回键名的遍历器 

for (let item of set.keys()) { 
  console.log(item);
 }
  // red
  // green
  // blue
  
# Set.prototype.values():返回键值的遍历器 因为Set继承了Iterable接口,
# 所以也可以忽略 values()方法,直接从遍历中读出值

for (let item of set.values()) {  // or for(let item of set){console.log(item);}
    console.log(item);
  }
  // red
  // green
  // blue
  
  # Set.prototype.entries():返回键值对的遍历器
  for (let item of set.entries()) {
      console.log(item);
  }
  
  //这里需要注意,Set 结构的键名就是键值(两者是同一个值),因此第一个参数与第二个参数的值永远都是一样的。
  // ["red", "red"]
  // ["green", "green"]
  // ["blue", "blue"]
  
  # Set.prototype.forEach():使用回调函数遍历每个成员
  let set = new Set([1, 4, 9]);
  set.forEach((value, key) => console.log(key + ' : ' + value))
  // 1 : 1
  // 4 : 4
  // 9 : 9
  
  # 当然拓展运算符也可以用于Set数据结构中

3、WeakSet

WeakSet和Set结构同一是一个无重复的值的结构。常用的实例属性和方法也类似。

WeakSet和Set有两个区别:
①WeakSet存放的成员只能是对象,不能存放其他类型的值。

let wk = new WeakSet();
wk.add(1); //类型错误wk.add({});//能够接受

②WeakSet 中的对象都是弱引用的。

弱引用的官方解释
在这里插入图片描述

③WeakSet中的对象为不可遍历对象。

4、WeakSet的基本用法

语法如下:

# 创建:通过构造函数构造WeakSet实例
const ws = new WeakSet();
# 赋值:通过参数赋值,参数接受数组或者其他继承iterable接口的数据类型的 “对象” 。
# 注意点
# 须注意的是 WeakSet() 结构内的成员必须为对象。
const ws1 = new WeakSet(1); //TypeError 不接受非对象参数
const ws2 - new WeakSet('1'); //TypeError 不接受非对象参数
const ws3 = new WeakSet([1,2,3]);TypeError 不接受非对象参数
const ws4 = new WeakSet([[1],[2]]); //WeakSet {[1],[2]}
# 常用方法 没有size方法因为其成员都是不可遍历的
const ws  = new WeakSet([[1]]);
# WeakSet.prototype.add(value):向 WeakSet 实例添加一个新成员。
ws.add([2]);
ws;// WeakSet{[1],[2]} 

# WeakSet.prototype.delete(value):清除 WeakSet 实例的指定成员。
ws.delete([2]);
ws;// WeakSet{[1]}

# WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在 WeakSet 实例之中
ws.has([1]); // true
ws.has([2]); //false

三、新的数据结构 Map

1、基本用法

抛出概念:为什么ES6中要提出Map数据结构?

答:在JavaScript中Object对象本质上是一个键值对的集合(Hash)结构。但是传统上只能用字符串作为键名,这对它的使用有很大的限制。

const object = {};
# 假设原意是将一个 节点对象作为键名
const element = document.getElementById('myDiv'); 
//element=[object HTMLDivElement]
object[element] = 'data';
# 以节点对象作为键名调用数据,则会发生错误
# 原因是在ES6之前会自己将键名(不管是否为对象)自动转换为字符串
object[[object HTMLDivElement]];//undefinde# 键名以字符串表达才能正常调用数据
object['[object HTMLDivElement]'];//data

为了解决上述键名必须以字符串表达的形式的问题,ES6推出了Map数据结构。

Map数据结构也是一种键值对的集合和Object一样。区别在于Map数据类型的键名表达不仅限于字符串表达。Map数据结构中键名的表达还可以是各种类型对象或值的表达。

如果你需要“键值对”的数据结构,Map 比 Object 更合适。

# 承上:对照代码段
const map = new Map();
# 假设同样也是将一个 节点对象作为键名
const element = document.getElementById('myDiv');
 map.set(element,'value');
 map.get(element); //'value' 
 //读取到value的原因是map直接把element的变量名作为键名,而非解析变量名里的内容作为键名。

具体语法如下:

# 创建:通过构造函数创建Map实例
const map = new Map( );
# 参数说明
# Map()构造函数接受一个数组作为参数。数组成员以一对对表示键与值数组[key,value]表示
const map = new Map([	//ps:这里是合法的赋值,JavaScript的数组接受不同数据类型的值
	['name','jack'],
	['age',18]
	])
map;//map{'name':'jack','age':18}

2、Map的实例属性和方法

const map = new Map(['age',20]);# Map的属性
# size 属性 获取Map数据结构的键值对个数
map.size();//1 
# Map操作方法
# 添加键值对:Map.prototype.set(key, value) 
map.set('sex','male');
map.set('name','jack');//jack

# 获取对应键的值:Map.prototype.get(key)
# os:找的是对应的key,找不到返回undefind
map.get('name');//jack
map.get('age');//18
map.get('sex');//male

# 检查结构中是否存在某个Key:Map.prototype.has(key) 返回布尔值
map.has('name') ;//true
map.has('bahevior');//false

# 删除结构中对应键与值:Map.prototype.delete(key) 返回布尔值
map.delete('name');//true
map.delete('bahevior');//false

# 遍历方法 
# Map.prototype.keys():返回键名的遍历器。
# Map.prototype.values():返回键值的遍历器。
# Map.prototype.entries():返回所有成员的遍历器。
# Map.prototype.forEach():遍历 Map 的所有成员。
# 例子跟Set结构差不多,比较简单,就不作介绍了。

详细参考:https://es6.ruanyifeng.com/#docs/set-map#WeakSet

3、Map 与其他数据结构的互相转换

语法如下:

const map = new Map([
	['name','jack'],
	['age',18],
	['sex','male']])

# 1.Map 转为数组
var arr = [...map];

# 2.数组转为Map 
var arr = [
	['name','jack'],
	['age',18],
	['sex','male']];
	
//将以键值对的数组成员的数组传入到map构造函数中
//ps:如果所有 Map 的键都是字符串,它可以无损地转为对象
//ps:如果为非字符串,会先将值读取后再转为字符串以作为键const map = new Map(arr);
	
# 3.Map转为对象//自定义转换函数function change(map){
let objcet  = Object.create(null);
    for(let [key,value] of map.entries()){
	object[key] = value;	
    }
    return object;
}

change(map);//Object {key,value....}
	
 # 4.对象转为Map
 let element = {
	'name':'jack',
	'age':18,
	'sex':'male'
};
	
//使用ES6新拓展的Object方法
# Object.entries( ) 以对象中的能遍历对象以键值对进行遍历
new Map(Object.entries(element));

# 5.Map转为JSON
# Map 转为 JSON 要区分以下两种情况
# 当Map键名都是字符串时,使用JSON.stringify()将Js的值转为JSON
//因为Map的成员对象都是以键值对表示的数组
//所以可以通过递归的方式一组一组地将Map成员对象转为Json

function change(map){
	return JSON.stringify(change(map));
}

# 当Map的键名为非字符串时
# 选择性地将其转换为数组JSON
function mapToArrayJson(map) {
  //转换为以逗号分隔的参数序列后复制成为数组
  return JSON.stringify([...map]);
  }
  let myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
  mapToArrayJson(myMap)
  // '[[true,7],[{"foo":3},["abc"]]]'

 # 6.JSON转为Map
# 一般来说,JSON转为对象,即为从JSON中使用JSON.parse读出JSON串后
# 以new Map(Object.entries(JSON串))即可转换为map对象

4、WeakMap

WeakMap与Map的结构基本类似。

const map = new Map([
	['name','jack'],
	['age',18],
	['sex','male']]);
	
const wkmap = new WeakMap([
	['name','jack'],
	['age',18],
	['sex','male']])

主要的区别有以下两点:

1.WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名。键名为弱引用,值是正常使用的。

const wkm = new WeakMap();
wkm.set(1, 2)// TypeError: 1 is not an object!
wkm.set(Symbol(), 2)// TypeError: Invalid value used as weak map key
wkm.set(null, 2)

2.WeakMap的键名所指向的对象,不计入垃圾回收机制。

即WeakMap对象 不再被引用 或 引用被清除后,WeakMap对象也会被垃圾回收机制自动回收。释放该对象所占的内存空间。而无须手动置对象为null。

总之,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。

5、WeakMap的基本用法

WeakMap 与 Map 在 API 上的区别主要是两个:

① WeakMap成员都是弱引用。所以没有遍历方法,如entries()、key( )、values() 。也没有size属性。因为WeakMap对象被垃圾回收机制忽视,只要该对象的引用被清除或没有被引用,不管WeakMap内的成员对象是否存在都会垃圾回收机制自动回收。所以大小是不固定的,ES6干脆把该属性设为无。

const wkm = new WeakMap([
	['name','jack'],
	['age',18],
	['sex','male']]);
	
wkm.forEach;//undefind
wkm.size;//undefind 12345678

②WeakMap无法手动清除,即不存在clear方法

wkm.clear; //undefind

WeakMap的应用场景:

let myWeakmap = new WeakMap();
myWeakmap.set(
  document.getElementById('logo'),
  {timesClicked: 0}
);
  
  document.getElementById('logo').addEventListener('click', function() {
      let logoData = myWeakmap.get(document.getElementById('logo'));
      logoData.timesClicked++;
  }, false);

上面代码中,document.getElementById(‘logo’)是一个 DOM 节点,每当发生click事件,就更新一下状态。我们将这个状态作为键值放在 WeakMap 里,对应的键名就是这个节点对象。一旦这个 DOM 节点删除,该状态就会自动消失,不存在内存泄漏风险。

四、代理对象 Proxy

1、Proxy对象

Proxy用于修改某些操作的默认行为,等同于在语言层面作出修改,通过Proxy去编程的方式又称为元编程。

Proxy对象实际上是一个 “代理” 对象。用于在拦截、和限制外界对它所定义的被拦截对象的访问。

语法如下:

# ps:本身Proxy对象也可以作为其他对象的原型
# 创建:new Proxy(target,handler)const proxy = new Proxy({},{});# 参数说明
# target参数为要被代理的对象。当该对象为空时,默认被代理的对象即为其声明本身的属性对象。
# handler参数为被代理对象的处理方法,handler触发为当target被拦截后触发

# 不声明代理目标 
let obj = new Proxy({},{ //target为空,拦截对象即为本身obj
	//下面会逐步介绍该handler参数里的方法
	get:function(target,propKey){
		return 35;
	},
	set:function(target,propKey){
		code......
	}})
obj.age; //此处调用虽然没有这个变量但是get方法拦截在调用变量之前,所以被拦截后返回35

# 声明代理目标
let el = {
	'name':'jack',
	'age':18,
	'sex':'male'}
	
# 定义proxy对象,并添加代理目标

let proxy = new Proxy(el,
	//target为拦截目标,propKey为目标的属性
	get:function(target,propKey){
		if(propKey==='name'){
			//拦截该属性并赋值为tom
			return 'Lili';
		}
		if(propKey==='age'){
			return 19;
		}	
		if(propKey==='sex'){
			return 'famle';
		}
	}})
	
# 通过proxy访问被拦截的对象
proxy.name;//Lili
proxy.age;//19
proxy.sex;//famle

2、Proxy实例常用的方法

此处的实例方法,即为处于Handler参数体的方法。

let proxy = new Proxy({目标对象},{.....Proxy的实例方法,用于处理目标对象});

① get( )

该方法用于拦截目标对象对某个属性的读取操作。

语法如下:

# get( )方法
# 参数说明 get(target,propKey,receiver)# target 参数为目标对象
# propKey 参数目标对象的属性
# receiver 为可选参数 它代表为proxy实例本身

let name = '张三';
let person = {name};
let proxy = new Proxy(person,{
//ps:方法也可以写成es6格式 set() 省略function关键字
	get:function(target,propKey,receiver){
		if(propKey in target){
			return target[propKey];
		}else{
			throw new ReferenceError();
		}
	}});
	proxy.name; //张三
	proxy.age;//抛出引用错误
	
	# 继承proxy
	let obj = Object.create(proxy);
	obj.name;//张三

② set( )

该方法用于拦截目标对象对某个属性的赋值操作。

语法如下:

# 参数说明 set(target,prop,value,receiver)# target 参数为目标对象
# prop 参数为目标对象的属性
# value 参数为目标对象的属性值
# receiver 为可选参数 它代表为proxy实例本身
let age = 18;
let person = {age};
let proxy = new Proxy(person,{
	//ps:方法也可以写成es6格式 set() 省略function关键字
	set:function(target,prop,value,receiver){
			if(prop in target){
				if(prop==='age'){
					if(value>20){
						throw new ReferenceError();
					}else{
						return target[prop] = value;
					}
				}
			}else{
				throw new ReferenceError();
			}
	},
	get:function(target,propKey,receiver){
		if(propKey in target){
			return target[propKey];
		}else{
			//抛出引用错误
			throw new ReferenceError();
		}
	}});
	proxy.age = 18;
	proxy.age;//18
	proxy.age = 21;
	proxy.age;//抛出引用错误
	# ps:以上也可以拓展为限制私有变量的访问。如果碰到私有变量则抛出异常

③ apply( )

该方法用于拦截函数的调用、call( )和apply( )操作。

语法如下:

# 参数说明 apply(target,ctx,args)# target 参数为目标对象
# ctx 参数为目标对象的上下文对象
# agrs参数为目标对象的参数序列
let target = function () {
  return "I am the target";
 };
 
let obj = new Proxy(target, {
  //ps:方法也可以写成es6格式 apply() 省略function关键字
  apply: function (target, ctx, args) {
    return "调用函数被拦截";
  }});
  obj();//调用函数被拦截

④ has( )

该方法用于拦截的HasProperty操作,即判断对象是否具有某个属性时,这个方法会生效。典型的操作就是in运算符。

语法如下:

# 参数说明 has(target,key)# target 参数为目标对象
# key 参数为查询目标对象的属性名let _name = 'jack';let person = {_name};let proxy = new Proxy(person,{
    has:function(target,key) {
      if (key[0]==='_') {
        return true;
      }else{
        return false;
      }
    }});//使用 in 操作符 触发了Proxy对HasProperty的拦截,从而触发has( )方法
 if('_name' in proxy){
  console.log('存在该私有属性');
 }else{
  console.log('不存在该私有属性');
 }

⑤ 更多实例方法请查看文档

https://es6.ruanyifeng.com/#docs/proxy#Proxy

3、Proxy.revocable( )

该对象方法用于返回一个可撤回的Proxy对象。

# 参数说明 Proxy,revocable(target,handler)
# target 参数为目标对象
# handler 参数为处理目标对象的函数
# 返回值说明
# 一、proxy属性为proxy实例
# 二、revoke属性为一个revoke函数,可用于回收代理权,当执行revoke函数后,proxy再也不可用。
let target = {};
let handler ={};
let {proxy,revoke} = Proxy.revocable(target,handler);
proxy.age = 19;
console.log(proxy.age); //19
revoke();
console.log(proxy.age); //调用revoke函数后,代理不可用,抛出异常

Proxy.revocable()的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就调用revoke( )函数收回代理权,不允许再次访问。

4、this问题

虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。

# Proxy的target对象的this指向Proxy
let date = new Date('2020-01-01');
let handler = {};
let proxy = new Proxy(date,handler);
# 因为this指向的是Proxy对象,而不是target参数的date对象
# 所以获取不了date对象方法 getDate()
proxy.getDate();// TypeError: this is not a Date object.
# Proxy的handler函数中的this也是指向hanlder
const handler = {
	   get:function(target,propKey,reciver){
	   console.log(this === handler);
           return 'Hello, ' + propKey;
	}}
	
let proxy = new Proxy({},handler);
proxy.world; // true , ' hello,world!'

针对上述问题的解决方案:

# 解决对象
let target= new Date('2020-01-12');
let handler= {};
let proxy = new Proxy(target,handler);
# 通过this调用获取的是proxy对象,而不是target参数中的的对象
proxy.getDate(); //ypeError: this is not a Date object.
# 解决方案
# 在handler中的get()方法中通过target参数的bind方法绑定原始对象,
# 解决原理 handler中的 get(target,propKey); target参数正是原始对象
let target= new Date('2020-01-12');
let handler = {
  get(target,propKey){
    if (propKey==='getDate') {
    	//target参数正是原始对象
      return target.getDate.bind(target);
    }

  }}
 const proxy = new Proxy(target,handler);
 proxy.getDate() ;//12  返回月份的某一天

5、super关键字

super关键字用于指向当前对象的原型对象,在面向对象的语言中,即指向当前对象的父类对象。

语法如下:

let name = 'jack';
let father = {name};
let son = {
    name:'tom',
    log(){
      console.log(super.name);
    }}
Object.setPrototypeOf(son, father);
son.log(); //jack

6、拓展:Web服务的客户端

Proxy 对象可以拦截目标对象的任意属性,这使得它很合适用来写 Web 服务的客户端。

五、Reflect

Reflect对象与Proxy对象一样,也是 ES6 为了操作对象而提供的新 API。

Proxy对象用于为对象添加代理,设置拦截。

Reflect对象用于获取和修改定义在Object上新的内部方法,以实现逐渐与Object解耦。

(Reflect对象用于拿到未来定义在Object上的新的内部方法。原意是为了逐渐对Object进行解耦。分一批新的方法在Reflect对象上。)

详细请查看: https://es6.ruanyifeng.com/#docs/reflect


评论 (0)

发表评论

上一篇: ES6新特性 _2 下一篇: ES6新特性 _4