ES6新增特性
历史
浏览器经历阶段:
- 1991- 1997:
HTML1/HTML2/HTML3/IETF(The Internet Engineering Task Force)国际互联网工程任务组 - 1997:
HTML3.2 W3C
ECMA-262 Ecmascript 脚本语言规范:
- 1995
LiveScript JavaScript - 1996
JavaScript 1.0/1.1 - 1997
Jscript - 1997.6
ECMAScript 1.0 - 1998.6
ECMAScript 2.0 - 1999.12
ECMAScript 3.0 - 2000
ECMAScript 4.0草案(未通过)TC39(technical committe 39)成员反对 - 2007
ECMAScript 4.0准备发布 但没发布 - 2008.7
ECMAScript 3.1过渡到5.0, 大会项目代号(hamony)JavaScript.next/JavaScript.next.next 2009.12
ECMAScript 5.0正式发布JavaScript.next(放入草案)ES6JavaScript.next.next(放入草案)ES7
- 2011.6
ECMAScript 5.1 - 2013.3
JavaScript.next(草案冻结) - 2013.6
JavaScript.next(草案发布) - 2015.6
ECMAScript 6.0正式发布, 从此每年6月出一个新的小版本 ECMAScript2016ECMAScript2017ECMAScript2018ECMAScript2019ECMAScript2020
let
作用域 [[scope]] 存储 AO/ GO
var的问题:
- 变量提升
undefined - 重复声明会有变量污染
ES5解决方案:
- 立即执行函数
- 但定义函数内部仍会存在变量污染的问题
kiss原则
keep it simple 保持简单/傻瓜,通过函数提纯,让功能单一,解决变量污染问题
如何解决变量污染问题?
let语法 块级作用域
常见的块级作用域:
if(1){}
for(){}
{}
关于暂时性死区:
TDZ(Temporal Dead Zone)
let特点:
- 同一作用域下,
let不允许重复声明 let不会声明提升,会产生一个暂时性死区let只能再当前的块级作用域下生效
let本质:为了JS增加一个块级作用域
块级作用域: 是没有返回值的
//let不允许重复声明function test(){let a = 1;var a = 1;}test();//报错: SyntaxError: Identifier 'a' has already been declared
//预编译的情况下已经声明a了function test(a){let a = 10;console.log(a);}test();//报错: SyntaxError: Identifier 'a' has already been declared
function test(a){{let a = 10;}console.log(a);}test();//undefined
function test(a){{let a = 10;console.log(a);}}test();//10
//let不会声明提升,会产生一个暂时性死区console.log(a);let a = 10;//ReferenceError: a is not defined
function test(){console.log(a);let a = 10;}test();//ReferenceError: a is not defined
问题1:
var a = a;console.log(a);//undefinedvar b = b;console.log(b);//ReferenceError: b is not defined
经典问题2:
//ES6语法 形参默认值//暂时性死区导致的问题//先将y赋值给x, 但y未被定义, y是在赋值之后被定义的function test(x = y, y = 2){console.log(x, y); //报错y is not defined 2}test();
如何更改才能执行?
//先赋值再引用值function test(x = 2, y = x){console.log(x, y); //2 2}test();
let引起的暂时性死区使typeof结果也更改了
console.log(typeof a); //undefined
console.log(typeof a);let a;//ReferenceError: a is not defined
特点3示例:
//let只能再当前的块级作用域下生效//不在同一作用域下的情况不生效{let a =2;}console.log(a);//ReferenceError: a is not defined
function test(){let a = 2;}console.log(a);test();//ReferenceError: a is not defined
if(1){let a = 2;}console.log(a);//ReferenceError: a is not defined
//永远死循环,永远不执行代码,但不报错for(;1;){let a = 1;}console.log(a);
//死循环结束,执行代码,报错for(;1;){let a = 1;break;}console.log(a);//ReferenceError: a is not defined
关于for循环的坑:
//i无法提升,外部无法访问for(let i = 0; i < 10; i++){}console.log(i);//ReferenceError: i is not defined
为什么打印0-9 而不是 10个10?
var arr = [];for(var i = 0; i < 10; i++){arr[i] = function(){console.log(i);}}//这里循环完但未执行 i = 10//但这里又开始循环重复赋值i 所以又开始0-9的执行for(var i = 0; i < 10; i++){arr[i]();}}
for循环里面的两个块级作用域会不会存在冲突?
//这里存在两个块级作用域for(var i = 0; i < 10; i++){i = 'a';console.log(i);}//a分析:var i = 0for(; i < 10; ){i = 'a';console.log(i);i++}//1. i = 0//2. 进入循环 被赋值为a i++//3. 'a' + 1 = 'a1' 变为 NaN//4. NaN < 10 ? false 不进循环 所以只打印一次 a
//这里i仍处于同一作用域下for(let i = 0; i < 10; i++){i = 'a';console.log(i);}//a
//说明let和var不在同一作用域下for(let i = 0; i < 10; i++){var i = 'a';console.log(i);}//SyntaxError: Identifier 'a' has already been declared
//不在同一作用域下的不影响let的声明for(let i = 0; i < 10; i++){let i = 'a';console.log(i);}//10 个 a
以上例子说明for循环里面是父级作用域,像以下情况
if(1){let a = 1;{console.log(a);}}//1
//不同作用域下let不影响if(1){let a = 1;{let a = 10;console.log(a);}}//1
//不限嵌套次数{{{{{}}}}}
在ES5中{}嵌套的函数是不合法但可以解析的,在ES6中就允许了
{function test(){}}
if(i){function test(){}}
try{function test(){}}catch(e){function test1(){}}
上述写法并不推荐,但可以用函数表达式替代声明
try{var test = function(){}}catch(e){var test2 = function(){}}
//块级作用域:是没有返回值的{return}
块级作用域等于匿名函数的调用吗? 不等于
const
定义常量,不可变的量,不期望变量被更改
const test = require('http');
const特点:
- 定义的常量必要要赋值
- 一旦定义必须赋值,值不能被更改、
- 有块级作用域,存在暂时性死区
- 常量不能重复声明
//定义的常量必要要赋值const a;console.log(a);//Uncaught SyntaxError: Missing initializer in const declaration
//存在暂时性死区{const a = 12;}console.log(a);//Uncaught ReferenceError: a is not defined
//存在暂时性死区,且不能变量提升{console.log(a);const a = 12;}//Uncaught ReferenceError: a is not defined
//const不能重复声明{const a = 12;let a = 10;}console.log(a);//Uncaught SyntaxError: Identifier 'a' has already been declared
const的值是引用值情况,栈能保证不变,堆不能保证
//说明const只保证指针地址没错,但不保证地址里数据内容不被更改const obj = {};obj.name = 'zhangsan';console.log(obj);//{name: "zhangsan"}
可以通过Object.freeze()方法冻结const声明的引用值,使之不能更改引用值数据
const obj = {};Object.freeze(obj);obj.name = 'zhangsan';console.log(obj);//{name: "zhangsan"} freeze()之后=> {}
进一步封装冻结函数
function myFreeze(obj) {Object.freeze(obj);//深度递归对象里面的对象for (var key in obj) {//该对象的值为object且不能是nullif (typeof (obj[key] === 'object') && obj[key] !== null) {Object.freeze(obj[key]);}}}var person = {son: {lisi: '18',zhangsan: '19'},car: ['benz', 'mazda', 'bmw']}myFreeze(person);person.son.wangwu = '20';person.car[3] = 'toyota';console.log(person);/*** 没注释myFreeze(person);* 打印:* {son: {…}, car: Array(4)}* car: (4) ["benz", "mazda", "bmw", "toyota"]* son: {lisi: "18", zhangsan: "19", wangwu: "20"}* __proto__: Object** 注释myFreeze(person);* 打印:* {son: {…}, car: Array(3)}* car: (3) ["benz", "mazda", "bmw"]* son: {lisi: "18", zhangsan: "19"}* __proto__: Object*/
不用Object.freeze()冻结的情况:
//这里require返回的是实例化对象被常量const接收//这种引入库的写法从源头上已经不能更改该对象的内容(因为是实例化对象)const http = require('http');
内容补充1:
顶层对象指
window,顶层对象的属性
//早期的JavaScript写法存在问题但也能解析//存在不容易发现错误a = 1;console.log(a); //undefined window
//ES6为了改变,保持兼容性 允许var 不允许let const//所以建议用let或const写let a = 1;console.log(a);
内容补充2:
专业术语:
falsy假的值(虚值) 通过boolean转化为false的值
function foo(x, y) {x = x || 1;y = y || 2;console.log(x + y);}foo(); //1+2=3foo(5, 6); //5+6=11foo(5); //5+2=7foo(null, 6); //null -> false 1+6=7foo(0, 5); //0 -> false 1+5=6
ES5形参为默认值写法
function foo(x, y) {var x = typeof (arguments[0]) !== 'undefined' ? arguments[0] : 1;var y = typeof (arguments[1]) !== 'undefined' ? arguments[1] : 2;console.log(x + y);}foo(); //3foo(5, 6); //11foo(5); //7foo(null, 6); //7foo(0, 5); //5
ES6形参为默认值写法
function foo(x = 1, y = 2) {console.log(x + y);}foo(); //3foo(5, 6); //11foo(5); //7foo(null, 6); //7foo(0, 5); //5
情况1:形参默认值会影响函数内声明造成重复声明报错
function foo(x = 2) {let x = 2;console.log(x);}foo(10);//Uncaught SyntaxError: Identifier 'x' has already been declared
特殊情况:里层声明时拿不到父级作用域的变量会报错
//如果不声明里层是可以拿到父级作用域的变量的var x = 1;{console.log(x);}//1//里层一旦let声明,就会形成块级作用域,将无法访问父级作用域var x = 1;{let x = x;console.log(x);}//Uncaught ReferenceError: Cannot access 'x' before initialization//这里()里会形成单独的作用域//这里的写法类似上面第一个的写法情况//里层声明时拿不到父级作用域的变量会报错function foo(x = x) {console.log(x);}foo();//Uncaught ReferenceError: Cannot access 'x' before initialization//这里()里的z=z+1 约等于 let z = z + 1,所以拿不到父级作用域,所以z没有定义function foo(x = w + 1, y = x + 1, z = z + 1) {console.log(x, y, z);}foo();//Uncaught ReferenceError: Cannot access 'z' before initialization
惰性求值:当函数的参数为表达式的时候,会重新计算表达式的值
//优先访问里层作用域里存在的变量的值let a = 99;function foo(b = a + 1) {console.log(b);}foo(); //100a = 100;foo(); //101
解构赋值
解构赋值依然是一个赋值的过程
模式匹配(结构化赋值)
数组解构
//数组的模式匹配let [a, b, c] = [1, 2, 3];console.log(a, b, c); //1 2 3
let [d, [e, [f]]] = [1, [2, [3]]];console.log(d, e, f); //1 2 3
//解构失败//变量多,值少的情况以undefined填充let [a, [b, [c]]] = [, [2, [3]]];console.log(a, b, c);//undefined 2 3
//不完全解构//变量少,值多let [a, [b, [c, []]]] = [1, [2, [3, [4]]]];console.log(a, b, c);//1 2 3
//解构默认值//这里默认值为6let [a = 6] = [1];console.log(a); //1let [b = 6] = [];console.log(b); //6
let [a, b = 2] = [1];console.log(a, b); //1 2let [c, d = 2] = [1, undefined];console.log(c, d); //1 2let [e, f = 2] = [1, null];console.log(e, f); //1 nulllet [g, h = 2] = [1, false];console.log(g, h); //1 falselet [j, k = 2] = [1, '1'];console.log(j, k); //1 '1'
//有默认值时,有值找值,没值找默认值function test() {console.log(10);}let [x = test()] = [1];console.log(x); //1let [y = test()] = [];console.log(y); //10 undefind
let [x = 1, y = x] = [];console.log(x, y); //1 1//重复声明let a = 5;let [a = 1, b = a] = [];console.log(x, y);//Uncaught SyntaxError: Identifier 'a' has already been declared
let [x = 1, y = x] = [2];console.log(x, y); //2 2
let [x = 1, y = x] = [1, 2];console.log(x, y); //1 2
//暂时性死区let [x = y, y = 1] = [];console.log(x, y);//Uncaught ReferenceError: Cannot access 'y' before initialization
对象解构
对象的解构是不存在顺序的
let {a: a,b: b,c: c} = {a: 1,b: 2,c: 3}console.log(a, b, c);
常见对象的解构方式
const {son} = person;
对象规则解构
//数组也是特殊的对象,也能进行解构赋值let arr = [1, 2, 3];let {0: first, [arr.length - 1]: last} = arr;console.log(first, last); //1 3
默认值兼容
//默认值function fetch(url, {body: body = '',method: method = 'GET',header: header = {}} = {}) {console.log(method);}fetch('http://www.baidu.com');fetch('http://www.baidu.com', {}); //GET
简写
对象简写
var name = 'zhangsan';var age = 14;var person = {name,age,sex: 'male',eat() {console.log(1);}}console.log(person);
箭头函数
() => {};
() => a;
箭头函数的实质
- 箭头函数忽略任何形式
this指向的改变 - 箭头函数
this指向 由外层函数的作用域来决定的 - 不能作为构造函数来使用
- 没有
arguments对象,用rest(拓展运算符替代) yield命令不能生效,在generator函数中
ES5中 this指向问题:
- 默认规则
- 隐式规则
- 显示规则
new
优先级:
new > 显示 > 隐式 > 默认
示例1:
//箭头函数指向function foo() {console.log(this);return (a) => {console.log(this.a);}}var obj1 = {a: 2};var obj2 = {a: 3};var bar = foo.call(obj1);//foo()返回函数体本身bar//{a: 2}bar.call(obj2);//此时bar是一个箭头函数, call不能改变this指向,所以还是指向父级作用域的foo.call(obj1)指向obj1,打印2
示例2:
const person = {eat() {console.log(this);},drink: () => {console.log(this);}}person.eat();//隐式转换为person对象//指向调用者//{eat: ƒ, drink: ƒ}person.drink();//隐式转换箭头函数失败//箭头父级作用域决定指向window
示例3:
//箭头函数嵌套//箭头函数没有this机制的,this是外层的//指向是固定的function foo() {console.log(this);return () => {console.log(this);return () => {console.log(this);return () => {console.log(this);console.log('id', this.id);}}}}var f = foo.call({id: 1});var f1 = f.call({id: 2})()();var f2 = f().call({id: 3})();var f3 = f()().call({id: 4});//id 1//id 1//id 1
示例4:
//在箭头函数内部不存在argumentsvar test = () => {console.log(arguments);}test();//Uncaught ReferenceError: arguments is not defined
示例5:
//箭头函数在定时器内部可以通过闭包访问父级作用域的属性function foo() {console.log(arguments);var a = 1;setTimeout(() => {console.log(a);console.log(arguments);})}foo(1, 2, 3, 4, 5, 6);//Arguments(6) [1, 2, 3, 4, 5, 6, callee: ƒ, Symbol(Symbol.iterator): ƒ]//1//Arguments(6) [1, 2, 3, 4, 5, 6, callee: ƒ, Symbol(Symbol.iterator): ƒ]
案例1:
//功能:插指定入数组元素到数组的指定位置function insert(value) {return {into: function (array) {return {after: function (afterValue) {//索引+1//删除0位,添加valuearray.splice(array.indexOf(afterValue) + 1, 0, value);return array;}}}}}//插入值5到数组[1,2,3,4,6,7,8]里索引为4的位置console.log(insert(5).into([1, 2, 3, 4, 6, 7, 8]).after(4));//[1, 2, 3, 4, 5, 6, 7, 8]
//箭头函数方式改造let insert = (value) => ({into: (array) => ({after: (afterValue) => {array.splice(array.indexOf(afterValue) + 1, 0, value);return array;}})})console.log(insert(5).into([1, 2, 3, 4, 6, 7, 8]).after(4));//[1, 2, 3, 4, 5, 6, 7, 8]
箭头函数使用场景:
适合箭头表达式的情况:
- 返回值单一,只有唯一的表达式,函数内部没有
this引用 - 递归
- 事件处理函数绑定/解绑
- 内层的函数表达式需要调用
this,用var _self = this; bind(this)确保适当的this指向时var args = Array.prototype.slice.call(arguments)用箭头函数比较好
- 返回值单一,只有唯一的表达式,函数内部没有
不适合箭头表达式的情况:
- 函数声明,执行语句比较多的
- 还需要用到递归
- 还需要引用函数名
- 事件绑定,解绑定
rest
展开运算符,展开/收集
... spread / rest 运算符
收集实参:得到的是数组而不是类数组
var sum = (...args) => {console.log(args);}sum(1, 2); //[1, 2]
展开实参:得到的是单独的值
function foo(x, y, z) {console.log(x, y, z);}foo(...[1, 2, 3]); //1 2 3//ES5模拟function foo(x, y, z) {console.log(x, y, z);}// foo(...[1, 2, 3]); //1 2 3foo.apply(null, [1, 2, 3]); //1 2 3
其他上下文展开
//数组展开//ES6优势:语义化更强let a = [2, 3, 4];let b = [1, ...a, 5];console.log(b); //[1, 2, 3, 4, 5]//相当于ES5console.log([1].concat(a, [5])); //[1, 2, 3, 4, 5]
//把剩余的所有参数都收集到c变量里形成数组展示//注意:拓展运算符必须是最后一位let fn = (a, b, ...c) => {console.log(a, b, c);}fn(1, 2, 3, 4, 5, 6, 7);//1 2 [3, 4, 5, 6, 7]
//ES5:排序数组function sortNum() {return Array.prototype.slice.call(arguments).sort(function (a, b) {return a - b});}console.log(sortNum(12, 431, 24, 14, 1, 4, 125, 2, 35, 25));//[1, 2, 4, 12, 14, 24, 25, 35, 125, 431]//ES6:排序数组const sortNum2 = (...args) => args.sort((a, b) => a - b);console.log(sortNum2(12, 431, 24, 14, 1, 4, 125, 2, 35, 25));//[1, 2, 4, 12, 14, 24, 25, 35, 125, 431]
展开运算符不能通过length属性访问,只能访问实际实参的长度
//访问length属性//ES5console.log((function (a) {}).length); //1//ES6//...args不能通过length属性访问,只能访问实际实参的长度console.log(((...a) => {}).length); //0console.log(((b, ...a) => {}).length); //1console.log(((b, c, ...a) => {}).length); //2
ES2017的展开运算符
可以 实现对象展开
var obj = {a: 1,b: 2,c: 3};var obj1 = {a: 4,d: 5,e: 6}var obj2 = {...obj,...obj1}console.log(obj2);//{a: 4, b: 2, c: 3, d: 5, e: 6}
ES5写法是通过Object.assign()合并
var obj = {a: 1,b: 2,c: 3};var obj1 = {a: 4,d: 5,e: 6}var obj2 = {}Object.assign(obj2, obj, obj1);console.log(obj2);//{a: 4, b: 2, c: 3, d: 5, e: 6}
拓展
函数名
console.log(f.name); //f
console.log((new Function).name); //anonymous
console.log(foo.bind({}).name); //bound foo;
对象
const foo = 'bar';//对象名称和函数名称相同时可以简写const baz = {foo};
属性名,可以通过[]访问对应的值
var arr = [1, 23, 23, 45, 5];console.log(arr[1]); //23
属性经过一层包装,将传入的所有值进行包装变成一个字符串
//所以说,定义的属性都为字符串var arr = [1, 23, 23, 45, 5];console.log(arr['1']); //23
属性名拼接
//拼接的属性名会重写之前的属性名//先进行覆盖然后再找属性值let obj = {[a + b] : true,['hello' + b] : 123,['hello' + 'world'] : undefined}
当属性名为对象的时候,如何转换为对象?
var myObject = {};myObject[true] = 'foo';myObject[3] = 'bar';myObject[myObject] = 'baz';console.log(myObject['true']); //fooconsole.log(myObject['3']); //barconsole.log(myObject[myObject]); //bazconsole.log(myObject['[object Object]']); //baz//如何成为字符串?console.log(Boolean.prototype.toString.call(true)); //trueconsole.log(Number.prototype.toString.call(3)); //3console.log(Object.prototype.toString.call(myObject)); //[object Object]console.log(Object.prototype.toString.call(true)); //[object Boolean]
//只有一个属性,后一个属性覆盖前一个属性const a = { a: 1 };const b = { b: 2 };const obj = {[a]: 'valueA',[b]: 'valueB'}console.log(obj);//{[object Object]: "valueB"}
对象中的name属性
const person = {sayName() {console.log('hello');}}console.log(person.sayName.name); //sayName
Symbol
ES6为什么要引入symbol?
场景:
ES5对象属性经常出现重名的情况,解决对象属性名重名的问题
symbol属于原始值类型的值,而不是构造函数
- 原始值类型的值:
number/boolean/null/undefined/symbol - 引用值类型的值:
Object/Array/Function
let s1 = Symbol('foo');//是原始值包装类console.log(typeof s1); //symbol//挂不上属性console.log(s1.a); //undefined//如何区别唯一的symbol值?可以通过传参区分//作为标识符存在的console.log(s1); //Symbol(foo)
//通过生成完全不一样的不可重复的原始类型//针对拷贝属性覆盖的问题var a = Symbol('a');var b = Symbol('a');console.log(a === b); //false// Symbol的值能否被Object.assign()拷贝?//可以拷贝var test = Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' });console.log(test);//{a: "b", Symbol(c): "d"}
如何避免重名?
var obj = {a: 1};let s1 = Symbol(obj);console.log(s1);//说明对象自己调用了Object.prototype.toString()方法将自己变成字符串//也说明symbol的值永远是字符串//Symbol([object Object])
如何使用?作为属性名来用
//独一无二的属性名let name = Symbol();let person = {};// 错误写法//此写法是字符串不能用Symbol创建的变量person.name = 'zhangsan';console.log(person);//{name: "zhangsan"}//正确的写法:person[name] = 'lisi';console.log(person);//{name: "zhangsan", Symbol(): "lisi"}//常用写法:let name = Symbol();let person1 = {[name]: 'wangwu'}console.log(person1);//{Symbol(): "wangwu"}//写法三:let name = Symbol();let person = {};Object.defineProperty(person, name, {value: 'zhangsan'})console.log(person);//{Symbol(): "zhangsan"}
//挂载Symbol到变量里let name = Symbol();let eat = Symbol();let person = {[name]: 'zhangsan',[eat]: function(){}//简写[eat](){console.log(this[name]);}}
Symbol相关的几个方法
Symbol.for()拿到唯一的值
//一般来说,Symbol的值都是不同的let s1 = Symbol('foo');let s2 = Symbol('foo');console.log(s1 === s2); //false//也有特殊的情况,可以值是相同的let s3 = Symbol.for('foo');let s4 = Symbol.for('foo');console.log(s3 === s4); //true
Symbol.keyFor全局中拿到当前的key值
let s1 = Symbol.for('foo');let s2 = Symbol.for('foo');//拥有同样的标识符console.log(Symbol.keyFor(s1)); //fooconsole.log(Symbol.keyFor(s2)); //foo
试着遍历
const obj = {}let a = Symbol('a');let b = Symbol('b');obj[a] = 'hello';obj[b] = 'world';// console.log(obj);//{Symbol(a): "hello", Symbol(b): "world"}//遍历for (let i in obj) {console.log(i);//无打印结果//说明for in 不能遍历 Symbol属性的对象}
Object.getOwnPropertySymbols()新的API专门遍历symbol类型的值(唯一的遍历方法)
const obj = {}let a = Symbol('a');let b = Symbol('b');obj[a] = 'hello';obj[b] = 'world';//只针对Symbol属性的对象遍历的方法console.log(Object.getOwnPropertySymbols(obj));//[Symbol(a), Symbol(b)]
总结:
for in:遍历自身和继承的可枚举属性(不包括含Symbol类型的值)Object.key():遍历自身不包含Symbol类型的值Object.getOwnPropertySymbols():遍历自身的Symbol类型的值Object.assign():遍历自身可枚举的,包含Symbol类型的值JSON.stringify():遍历自身可枚举的属性
Symbol定义唯一方法实现一个iterator接口:
对象是不连续的且无序的数据结构,一般来说不能用for of,但是可以通过部署迭代器接口的方式来使用for of 遍历
//手动的编写一个iterator接口可以针对指定的数据类型进行迭代遍历//对象上写一个iterator接口let obj = {start: [1, 3, 2, 4],end: [5, 7, 6],//中括号包裹字符串的方式[Symbol.iterator]() {//定义指针let index = 0,//组合新数组arr = [...this.start, ...this.end],//新数组长度len = arr.length;//将新数组进行迭代return {next() {if (index < len) {return {//累加的结果value: arr[index++],done: false}} else {return {value: undefined,done: true}}}}}}for (let i of obj) {console.log(i); //1 3 2 4 5 6 7}
Reflect
是一个对象, 是JavaScript内置对象方法集合的容器
Reflect = {}
整合了ES5原型上原有的方法的ES6API(静态方法)
apply()/defineProperty()/deleteProperty()/get()/getOwnPropertyDescriptor/getPrototypeof()/has()/isExtensible()/ownKeys()/preventExtensions()/set()/setPrototypeOf()
//如何通过Reflect访问对象console.log(obj.a);console.log(Reflect.get(obj, 'a'));
利用函数式的写法重新定义Proxy构造函数,用方法去取值/赋值使得更为合理,利用底层的方法操作对象
let proxy = new Proxy(target, {get(target, prop){//1.直接访问返回//return 'This is property value' + target[prop];//2.通过函数式返回return Reflect.get(target, prop);},set(target, prop, value){//target[prop] = value;Reflect.set(target, prop, value);}});
