1. 简介:ES6, 全称 ECMAScript 6.0 ,是 JavaScript 的下一个版本标准,2015.06 发版。
ES6 主要是为了解决 ES5 的先天不足,比如 JavaScript 里并没有类的概念,但是目前浏览器的 JavaScript 是 ES5 版本,大多数高版本的浏览器也支持 ES6,不过只实现了 ES6 的部分特性和功能。
2.ES6新特性:
2.1. let 和 const:
let 声明的变量只在 let 命令所在的代码块内有效;
const 声明一个只读的常量,一旦声明,常量的值就不能改变。
1).let:命令的特性:代码块内有效;不能重复声明;不存在变量提升
//代码块内有效
{
let a = 10;
var b = 20;
}
console.log(b); // 20
console.log(a); // ReferenceError: a is not defined
// 不能重复声明
let c = 10;
var d = 33;
var d = 34;
console.log(d); // 34
let c = 22; // SyntaxError: Identifier 'c' has already been declared
// 不存在变量提升
console.log(a); // undefined
var a = 10;
console.log(b); // ReferenceError: b is not defined
let b = 20;
2). const 声明一个只读变量,声明之后不允许改变。意味着,一旦声明必须初始化,否则会报错。
const c = 12;
c = 13; // TypeError: Assignment to constant variable 不能修改值
const d; // SyntaxError: Missing initializer in const declaration 一旦声明必须初始化
2.2 解构赋值:
解构赋值是对赋值运算符的扩展。
他是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
在代码书写上简洁且易读,语义更加清晰明了;也方便了复杂对象中数据字段获取。
数组解构:
let [a, b, c] = [1, 2, 3]; // a = 1; b=2;c=3;
let [a,[b],[[c]]] = [1,[2],[[3]]]; //a = 1;b=2;c=3; 可嵌套赋值
let [a,,c] = [1,2,3]; //a = 1; b = 3;可间隔赋值
// 当解构模式有匹配结果,且匹配结果是 undefined 时,会触发默认值作为返回结果。
let [a = 1,b] = []; // a = 1; b= undefined 不完全解构,a有默认值
let [a = 3, b = a] = []; // a = 3;b = 3;
let [a = 1,b] = [2,3]; // a = 2; b = 3;
let [a, ...b] = [1, 2, 3]; // a = 1; b = [2,3] 剩余运算符
//在数组的解构中,解构的目标若为可遍历对象,皆可进行解构赋值。
let [a, b, c] = 'Ann'; // a = 'A';b='n';c='n';
对象解构
let { name, age} = { name: 'Ann', age: 10} // name = 'Ann';age = 10;
let obj = {p: ['Ann', {y: 'Hello'}] };
let {p: [x, { y }] } = obj; // x = 'Ann'; y = 'Hello' 嵌套
let obj = {p: ['Ann', {y: 'Hello'}] };
let {p: [x, { }] } = obj; // x = 'Ann' 可忽略
let obj = {p: [{y: 'Hello'}] };
let {p: [{ y }, x ] } = obj; // x = undefined; y = 'Hello' 不完全解构
let {a, ...b} = {a: 10, b: 20, c: 30}; // a = 10; b = {b:20,c:30} 剩余运算符
let {a = 10, b = 5} = {a: 3}; // a = 3; b = 5; 默认值
2.3 Symbol
ES6 引入了一种新的原始数据类型 Symbol ,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。
ES6 数据类型除了 Number 、 String 、 Boolean 、 Objec t、 null 和 undefined ,还新增了 Symbol 。
Symbol 函数栈不能用 new 命令,因为 Symbol 是原始数据类型,不是对象。可以接受一个字符串作为参数,为新创建的 Symbol 提供描述,用来显示在控制台或者作为字符串的时候使用,便于区分。
Symbol.for() 类似单例模式,首先会在全局搜索被登记的 Symbol 中是否有该字符串参数作为名称的 Symbol 值,如果有即返回该 Symbol 值,若没有则新建并返回一个以该字符串参数为名称的 Symbol 值,并登记在全局环境中供搜索。
Symbol.keyFor() 返回一个已登记的 Symbol 类型值的 key ,用来检测该字符串参数作为名称的 Symbol 值是否已被登记。
let sy1 = Symbol('sy');
let sy2 = Symbol('sy');
console.log(sy1 == sy2) //false
let person = {};
person[sy1] = 'Ann';
console.log(person[sy1]) // 'Ann'
// 不同的Symbol 共享
let sym1 = Symbol('sym');
let sym2 = Symbol.for("sym");
let sym3 = Symbol.for("sym");
console.log(sym1 == sym2); // false
console.log(sym3 == sym2); // true
// 返回一个已登记的 Symbol 类型值的 key
let sym4 = Symbol.for("sym4");
console.log(Symbol.keyFor(sym4)); // "sym4" 只针对Symbol.for
console.log(Symbol.keyFor(sym1)); // undefined
2.3 Map 与 Set
1). Map 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
Maps 和 Objects 的区别:
- 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
- Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
Map 中的 key:
var myMap = new Map();
//key为字符串
var keyString = "a string";
myMap.set(keyString, "和键'a string'关联的值");
myMap.get(keyString); // "和键'a string'关联的值"
myMap.get("a string"); // "和键'a string'关联的值" 因为 keyString === 'a string'
var keyObj = {};
myMap.set(keyObj, "和键 keyObj 关联的值");
console.log(myMap.get(keyObj)); // "和键 keyObj 关联的值"
console.log(myMap.get({})); // undefined, 因为 keyObj !== {}
console.log(myMap.get('{}')); // undefined
var keyFun = function () {};
myMap.set(keyFun, '和键 keyFun 关联的值');
console.log(myMap.get(keyFun)); //'和键 keyFun 关联的值'
console.log(myMap.get(function () {})); // undefined
var keyNaN = NaN;
myMap.set(keyNaN, "not a number");
console.log(myMap.get(NaN)); // "not a number"
console.log(myMap.get(keyNaN)); // "not a number"
// 虽然 NaN 和任何值甚至和自己都不相等(NaN !== NaN 返回true),NaN作为Map的键来说是没有区别的。
Map 的迭代
for...of
var myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
// 将会显示两个 log。 一个是 "0 = zero" 另一个是 "1 = one"
for (var [key, value] of myMap) {
console.log(key + " = " + value);
}
let myMap1 = myMap.entries();
console.log(typeof myMap1); // object
for (var [key, value] of myMap1) {
console.log(key + " = " + value);
}
/* 这个 entries 方法返回一个新的 Iterator 对象,它按插入顺序包含了 Map 对象中每个元素的 [key, value] 数组。 */
// 将会显示两个log。 一个是 "0" 另一个是 "1"
for (var key of myMap.keys()) {
console.log(key);
}
/* 这个 keys 方法返回一个新的 Iterator 对象, 它按插入顺序包含了 Map 对象中每个元素的键。 */
// 将会显示两个log。 一个是 "zero" 另一个是 "one"
for (var value of myMap.values()) {
console.log(value);
}
forEach()
var myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");
myMap.set(3, "three");
myMap.set(2, "two");
// 将会显示四个 logs。
/*
0 = zero
1 = one
3 = three
2 = two
*/
myMap.forEach(function(value, key) {
console.log(key + " = " + value);
}, myMap)
Map 对象的操作
Map 与 Array的转换
var kvArray = [["key1", "value1"], ["key2", "value2"]];
// Map 构造函数可以将一个 二维 键值对数组转换成一个 Map 对象
var myMap = new Map(kvArray);
console.log(myMap); // {"key1" => "value1", "key2" => "value2"}
// 使用 Array.from 函数可以将一个 Map 对象转换成一个二维键值对数组
var outArray = Array.from(myMap);
console.log(outArray); // [ ["key1", "value1"], ["key2", "value2"]]
Map 的克隆
var myMap1 = new Map([["key1", "value1"], ["key2", "value2"]]);
var myMap2 = new Map(myMap1);
console.log(myMap1 == myMap2) // false Map 对象构造函数生成实例,迭代出新的对象。
Map 的合并
var first = new Map([[1, 'one'], [2, 'two'], [3, 'three'],]);
var second = new Map([[1, 'uno'], [2, 'dos']]);
// 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的,对应值即 uno,dos, three
var merged = new Map([...first, ...second]);
console.log(merged); // {1 => "uno", 2 => "dos", 3 => "three"}
2). Set 对象
Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用。
Set 中的特殊值
Set 对象存储的值总是唯一的,所以需要判断两个值是否恒等。有几个特殊值需要特殊对待:
- +0 与 -0 在存储判断唯一性的时候是恒等的,所以不重复;
- undefined 与 undefined 是恒等的,所以不重复;
- NaN 与 NaN 是不恒等的,但是在 Set 中只能存一个,不重复。
let mySet = new Set();
console.log(mySet.add(1)); // Set(1) {1}
console.log(mySet.add(5)); // Set(2) {1, 5}
console.log(mySet.add(5)); // Set(2) {1, 5} 这里体现了值的唯一性
console.log(mySet.add("some text"));
// Set(3) {1, 5, "some text"} 这里体现了类型的多样性
var o = {a: 1, b: 2};
console.log(mySet.add(o));
console.log(mySet.add({a: 1, b: 2}));
// Set(5) {1, 5, "some text", {…}, {…}}
// 这里体现了对象之间引用不同不恒等,即使值相同,Set 也能存储
let mySet1 = new Set();
console.log(mySet1.add(+0)); // Set(1) {0}
console.log(mySet1.add(-0)); // Set(1) {0}
console.log(mySet1.add(undefined)); // Set(2) {0, undefined}
console.log(mySet1.add(undefined)); // Set(2) {0, undefined}
console.log(mySet1.add(NaN)); // Set(3) {0, undefined, NaN}
console.log(mySet1.add(NaN)); // Set(3) {0, undefined, NaN}
类型转换
// Array 转 Set
var mySet = new Set(["value1", "value2", "value3"]);
// 用...操作符,将 Set 转 Array
var myArray = [...mySet];
console.log(mySet); // Set(3) {"value1", "value2", "value3"}
console.log(myArray); // ["value1", "value2", "value3"]
// String 转 Set
var mySet1 = new Set('hello'); // Set(4) {"h", "e", "l", "o"}
// 注:Set 中 toString 方法是不能将 Set 转换成 String
console.log(mySet1); // Set(4) {"h", "e", "l", "o"}
Set 对象作用
数组去重,并集,交集,差集
// 数组去重
var mySet = new Set([1, 2, 3, 4, 4]);
console.log([...mySet]); // [1, 2, 3, 4]
// 并集
var a1 = new Set([1, 2, 3]);
var b1 = new Set([4, 3, 2]);
var union = new Set([...a1, ...b1]); // {1, 2, 3, 4}
console.log(union);// Set(4) {1, 2, 3, 4}
// 交集
var a2 = new Set([1, 2, 3]);
var b2 = new Set([4, 3, 2]);
var intersect = new Set([...a2].filter(x => b2.has(x)));
console.log(intersect); // Set(2) {2, 3}
// 差集
var a3 = new Set([1, 2, 3]);
var b3 = new Set([4, 3, 2]);
var difference = new Set([...a3].filter(x => !b3.has(x))); // {1}
console.log(difference); // Set(1) {1}
2.4 Reflect 与 Proxy
Proxy 与 Reflect 是 ES6 为了操作对象引入的 API 。
Proxy 可以对目标对象的读取、函数调用等操作进行拦截,然后进行操作处理。它不直接操作对象,而是像代理模式,通过对象的代理对象进行操作,在进行这些操作时,可以添加一些需要的额外操作。
Reflect 可以用于获取目标对象的行为,它与 Object 类似,但是更易读,为操作对象提供了一种更优雅的方式。它的方法与 Proxy 是对应的。
1).Proxy
一个 Proxy 对象由两个部分组成: target 、 handler 。在通过 Proxy 构造函数生成实例对象时,需要提供这两个参数。 target 即目标对象, handler 是一个对象,声明了代理 target 的指定行为。
let target = {
name: 'Tom',
age: 24
}
let handler = {
get: function(target, key) {
console.log('getting '+key);
return target[key]; // 不是target.key
},
set: function(target, key, value) {
console.log('setting '+key);
target[key] = value;
}
}
let proxy = new Proxy(target, handler);
proxy.name = 'Ann'; // 实际执行 handler.set
proxy.age = 25; // 实际执行 handler.set
console.log(proxy); // Proxy {name: "Ann", age: 25}
console.log(proxy.name); // getting name "Ann"
console.log(proxy.age); // getting age 25
// target 可以为空对象
let targetEpt = {};
let proxyEpt = new Proxy(targetEpt, handler);
// 调用 get 方法,此时目标对象为空,没有 name 属性
console.log(proxyEpt.name);
// getting name
// undefined
// 调用 set 方法,向目标对象中添加了 name 属性
proxyEpt.name = 'Tom'; // setting name
// 再次调用 get ,此时已经存在 name 属性
console.log(proxyEpt.name);
// getting name
// "Tom"
// 通过构造函数新建实例时其实是对目标对象进行了浅拷贝,因此目标对象与代理对象会互相影响
console.log(targetEpt); // {name: "Tom"}
// handler 对象也可以为空,相当于不设置拦截操作,直接访问目标对象
let targetEmpty = {};
let proxyEmpty = new Proxy(targetEmpty,{});
proxyEmpty.name = "Tom";
console.log(targetEmpty); // {name: "Tom"}
实例方法
get(target, propKey, receiver)
get() 方法可以继承。
// get() 方法可以继承。
let proxy = new Proxy({}, {
get(target, propKey, receiver) {
// 实现私有属性读取保护
if(propKey[0] === '_'){
throw new Erro(`Invalid attempt to get private "${propKey}"`);
}
console.log('Getting ' + propKey);
return target[propKey];
}
});
console.log(proxy); // Proxy {}
let obj = Object.create(proxy);
console.log(obj.name)
// Getting name
// undefined
set(target, propKey, value, receiver)
用于拦截 target 对象上的 propKey 的赋值操作。如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用。
let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// 对于满足条件的 age 属性以及其他属性,直接保存
obj[prop] = value;
}
};
let proxy= new Proxy({}, validator)
proxy.age = 100;
console.log(proxy.age); // 100
proxy.age = 'oppps' // 报错 The age is not an integer
proxy.age = 300 // 报错 The age seems invalid
第四个参数 receiver 表示原始操作行为所在对象,一般是 Proxy 实例本身。
const handler = {
set: function(obj, prop, value, receiver) {
console.log(receiver); // Proxy {}
obj[prop] = receiver;
}
};
const proxy = new Proxy({}, handler);
proxy.name= 'Tom';
console.log(proxy.name=== proxy) // true
const exam = {};
Object.setPrototypeOf(exam, proxy);
exam.name = "Tom";
console.log(exam); // {}
console.log(exam.name === exam) ;// true
注意,严格模式下,set代理如果没有返回true,就会报错。
apply(target, ctx, args)
用于拦截函数的调用、call 和 reply 操作。target 表示目标对象,ctx 表示目标对象上下文,args 表示目标对象的参数数组。
function sub(a, b){
return a - b;
}
let handler = {
apply: function(target, ctx, args){
console.log('handle apply');
return Reflect.apply(...arguments);
}
}
let proxy = new Proxy(sub, handler)
console.log(proxy(2, 1));
// handle apply
// 1
has(target, propKey)
用于拦截 HasProperty 操作,即在判断 target 对象是否存在 propKey 属性时,会被这个方法拦截。此方法不判断一个属性是对象自身的属性,还是继承的属性。
未完待续...