1. ECMAScript 与 JavaScript 的关系?
ES 通常可以看作是 JavaScript 的标准化语言规范。但实际上JavaScrpit 是ECMAScript的扩展语言。
在ECMAScript中只是提供了最基本的语法,通俗点说就是约束了我们的代码该如何编写,例如:如何定义变量和函数,怎么样去实现分支循环语句。它只是停留在语言层面,并不能直接用来完成应用中的实际功能开发。
JS 实现了 ES 的语言标准。JS 还在此基础上新增了一些拓展,使得我们可以在浏览器可以操作 DOM/BOM,在 Node 环境中可以读写文件等操作。
在浏览器当中的JavaScript由以下三个部分组成:
- ECMAScript:核心
- Web APIs : DOM(文档对象模型),BOM(浏览器对象模型)等
在 Node 当中的JavaScript由以下三个部分组成:
- ECMAScript:核心
- Node APIs : fs,net , etc 等。
所以在JavaScript中,语言本身就是ECMAScript。
2.ES6 为什么指的是 ESMAScript 2015
名称 | 标准版本 | 发行时间 |
---|---|---|
ECMAScript 2019(ES2019) | 10 | 2019年6月 |
ECMAScript 2018(ES2018) | 9 | 2018年6月 |
ECMAScript 2017(ES2017) | 8 | 2017年6月 |
ECMAScript 2016(ES2016) | 7 | 2016年6月 |
ECMAScript 2015(ES2015) | 6 | 2015年6月 |
ECMAScript 5.1(ES5.1) | 5.1 | 2011年6月 |
ECMAScript 5(ES5) | 5 | 2009年12月 |
ECMAScript 4(ES4) | 4 | 被放弃 |
ECMAScript 3(ES3) | 3 | 1999年12月 |
ECMAScript 2(ES2) | 2 | 1998年6月 |
ECMAScript 1(ES1) | 1 | 1997年6月 |
- ES 的版本更迭从 ES5 跨向 ES6 这个阶段发生了巨变,中间间隔了 6 年之久(不谈 5.1
- 6 年里,刚好是 Web 界快速发展的几年,ES6 一经发布,带来的更新内容特别多,因此算作一个新阶段的起始点。
- ES6 发布时的版本应该是 ECMAScript 6,但从 2015 这年开始 ES 的版本不再使用版本号命名了,而是使用年号,因此 ES6 又叫 ESCMAScript 2015。
- 从 2015 年开始,ES 的更新频率加速,达到每年一更,且规律是每年的 6 月时更新。
3. ECMAScript 2015(ES6)新特性介绍
ES6 是 ECMAScript 标准的代表版本,原因如下:
- 相比于 ES5.1 的变化比较大
- 自此,标准命名规则发生变化
目前有很多开发者还喜欢用 ES6 这个名称泛指从 ES5.1 以后所有的新版本。
例如 “使用 ES6 的 async 和 await”,实际上 async 是 ES2017 中制定的标准。
因此我们需要注意分辨文章中的 ES6 是特指 ES2015 还是 泛指 ES2015之后的所有新标准。
接下来我们来重点介绍 ES2015 在 ES5.1 基础上的变化,变化归纳为 4 类。
- 解决原有语法上的一些问题或者不足。如:let 和 const 提供的块级作用域。
- 对原有语法进行增强。如:解构、展开、参数默认值、模板字符串。
- 全新的对象、全新的方法、全新的功能。如:Promise、
- 全新的数据类型和数据结构。如:Symbol、Set、Map。
4.ES2015 let ,const 与块级作用域
块级作用域:顾名思义,指得就是在代码当中的一个成员能够起作用的范围。在ES2015之前,ES当中只有两种作用域,分别是全局作用域和函数作用域。
ES2015之后新增了一个新的块级作用域,通俗的说,块指的是我们代码中花括号所包裹起来的范围,如 if/for 的花括号内。
1.let声明的变量只能在所声明的代码块中被访问到
if(true){
var foo = "wjp"
}
console.log(foo); //wjp
if(true){
let foo2 = "wjp"
}
console.log(foo2) //undefined
上面代码很简单,下面看一个for循环的计数器:这个双层嵌套 应该是一个3*3的循环,应该打印9次,但结果只有3次。原因很简单,这里两个i都是用var关键字声明的,他们都不是块级作用域的成员,而是全局成员。所以在这里内层声明的i就会覆盖外层的i。
等到内层循环执行完过后i=3,外层拿到的i就是仍然是全局的i ,也就是3,就不满足循环条件。
for(var i=0;i<3;i++){
for(var i=0;i<3;i++){
console.log(i)
}
console.log('内层结束---i=',i)
}
//0,1,2 内层结束---i= 3,
把var关键字换成let,let声明的变量只能在当前循环的代码块当中生效,
for(let i=0;i<3;i++){
for(let i=0;i<3;i++){
console.log(i)
}
console.log('内层结束---i=',i)
}
//0 1 2 内层结束---i= 0
//0 1 2 内层结束---i= 1
//0 1 2 内层结束---i= 2
虽然,let关键字解决了在循环嵌套中计数器重名导致的问题,但还是不建议使用,因为不利于阅读。
还有一个典型的例子,我们循环注册事件时,在事件的处理函数中访问循环的计数器,这种情况下使用var 就会出现问题 ,比如用给对象添加方法模拟一下:
var elements = [ {},{},{} ];
for(var i=0;i<elements.length;i++){
elements[i].onClick = function(){
console.log(i)
}
}
elements[1].onClick() //3
因为在循环执行完毕后,当前全局作用域内i = 3,所以无论是哪一个元素执行onclick方法打印出来的都是3,ES2015以前这也是闭包的一个典型应用场景:其实闭包也就是借助函数作用域摆脱了全局作用域的影响。
var elements = [ {},{},{} ];
for(let i=0;i<elements.length;i++){
elements[i].onClick = (function(index){
return function(){
console.log(index)
}
})(i)
}
elements[1].onClick()
现在有了块级作用域后就不必这么麻烦了。将声明计数器 i 的var关键字改为 let。这样 i 就只能在块级作用域内被访问。
2.另外,在for循环中还有一个特别之处,在for循环内部实际上会有两层块级作用域。
因为在for循环内部会有两层块级作用域,循环体当中的是内层独立的作用域,外层是for循环体本身的作用域
例如:
我们添加一个使用let 的for循环,在循环体内部再声明一个 i = 'foo',控制台中会输出三个 foo,可以说明这两个 i 不会冲突,它们互不影响,所以它们是再两个作用域当中的。
for(let i=0;i<3;i++){
let i="foo";
console.log(i)
}
//foo foo foo
我们可以把它拆解开:先let 一个i = 0; 如果它小于3,在它的块级作用域内用let声明一个 i = "foo" 并打印,然后i++。依次执行。
拆解后可以很轻易的分析到:let i = "foo",它实际上是if 这个块级作用域内的一个局部变量,而我们外部的计数器就是外部循环的块形成的局部变量。所以它们互补影响。所以就能理解为什么说有两层嵌套的作用域了。
其实这就是上面循环的完整过程。
let i = 0;
if(i<3){
let i = "foo";
console.log(i)
};
i++
if(i<3){
let i = "foo";
console.log(i)
};
i++
if(i<3){
let i = "foo";
console.log(i)
};
i++
3.let不会出现变量提升的情况
ES2015中 let和const取消了变量提升的特性,从语法层面就要求我们必须先声明后使用。
为什么不在原有的var 身上做一些升级而是定义新的关键字,因为如果直接升级var的话就会导致以前的项目无法正常工作。
4.const
const关键字一般用来声明一个只读的恒量(常量)。它是在let的基础上多了一个“只读” 属性。即变量一旦使用 const 声明后就不允许再被修改。
1.变量一旦使用 const 声明后就不允许再被修改。
2.不能被修改指的是 在声明赋值过后 不能再重新指向新的内存地址。并不是不能修改常量中的属性成员。
3.声明的同时必须设置初始值,声明和赋值 不能像var和let一样 放到两个语句当中。
// 错误示例 1
const name = 'mn'
name = 'nm'
// 错误示例 2
const name
name = 'nm'
不允许修改的是变量指向的内存地址,而不是变量本身的值。
// 正确示例
const obj = {}
obj.name = 'mn'
// 错误示例
obj = {}
最佳实践:基本放弃使用var,主要使用const,对于一定会被修改的值使用let。
5.数组的解构
具体实现:把以前我们定义声明变量名的地方修改为一个数组的[ ] , [ ]里面的就是我们 需要提取出来的数据 所存放的变量名。内部就会按照变量名出现的位置分配数组当中所对应的值。
const arr = [100, 200, 300]
// 不使用解构
const foo = arr[0]
const bar = arr[0]
const baz = arr[0]
// 使用解构 1
const [foo, bar, baz] = arr
// 使用解构 2 -> 只获取某个位置的值
const [, , baz] = arr
console.log(baz) // 300
// 使用解构 3 -> 提取数组中剩余所有值,该写法仅可用于最后一个位置
const [foo, ...rest] = arr
console.log(rest) // [200, 300]
// 使用解构 4 -> 获取数量少于数组内实际数量
const [foo] = arr
console.log(foo) // 100
// 使用解构 5 -> 获取数量大于数组内实际数量
const [foo, bar, baz, more] = arr
console.log(more) // undefined
// 使用解构 6 -> 设置变量默认值,当未提取出值时默认给予该值
const [foo, bar, baz, more = 'default more'] = arr
console.log(more) // default more
新语法在很多场景下会给我们带来很大的便捷。
例如:使用split去拆分字符串,然后获取指定位置的值,以前我们需要定义一个中间变量做一个过渡。通过解构就可以简化这个过程;
const path = "/foo/bar/baz";
const temp = path.split("/"); //需要定义一个变量 temp 存放数组
const rootdir = temp[1];
console.log(rootdir);
// 不需要需要定义临时变量,直接结构数组取出相对应的数据
const [ ,rootdir2] = path.split("/");
console.log(rootdir2)
6.对象的解构
与数组相同的是,对象解构也是在定义声明变量名的地方加{},{ } 里同样也是提取出来的数据存放的变量名。不过它是根据属性名来提取,而不是位置。
例如:
const obj = {name:"curry",number:"30"};
let { name } = obj; //这里所使用的name,它的作用就是提取了obj中name属性值,然后放到了name变量中。
console.log(name)
其他特点和数组的解构是一样的,比如它没有匹配到的值会返回undefined,也可以设置默认值。
还有一个特殊的情况:因为我们解构的变量名它同时又是去匹配我们被解构对象的属性名的。如果当前作用域存在同名变量就会产生冲突的。例如:
const name = "xs";
const obj = {name:"curry",number:"30"};
let { name } = obj;
console.log(name)
因为obj中的name属性它必须要由name才能提取处理,所以这个冲突不可避免,这个时候我们可以使用重命名的方式去提取,例如:
const name = "xs";
const obj = {name:"curry",number:"30"};
let { name:lala } = obj; //解构位置的成员名后面加上: 然后跟上新的名称
console.log(lala)
做法就是在解构位置的成员名后面加上: 然后跟上新的名称,此时就会有两个名称,冒号左边是用来匹配对象属性名来提取值。右边是提取到的值最终放入变量的名称。这样我们可以任意去起变量名称,不会再有冲突出现。
此时也可以在右边变量名后面再继续跟随 = 来设置对应的默认值。
解构对象的应用场景就更多了,不过大部分场景下也都是为了简化我们的代码。例如我们代码当中如果大量用到了console.log的方法:
就可以利用解构把log方法解构出来,然后使用独立的log方法。
const { log } = console;
log(111)
6.模板字符串字面量
传统定义字符串,需要定义在单引号或双引号中。ES2015中增加了模板字符串的方式,使用反引号来标识。
注意:如果我们需要在字符串当中使用反引号,可以通过\斜线去转义。
和传统字符串模板字符串有用的新特性:
1.模板字符串支持多行,可以直接在字符串当中敲回车出入换行符,而不需要像传统的字符串那样需要用/n来表示。(这对于我们输出html字符串很有帮助)
2.模板字符串支持插值表达式的方式在字符串中去嵌入所对应的数组。(会比之前字符串拼接要方便一点,也更直观一点,不易出错!)
const name = "curry";
let str = `hello ${name}`
console.log(str)
当然这里${ }里面的内容就是标准的js语句。就是说这里不仅可以嵌入变量,还可以 嵌入任何标准的js语句,这个语句的返回值就会被输出到字符串当中插值表达式存在的位置。,例如:
const name = "curry";
let str = `hello ${name} ${2+3} ---- ${Math.random(0,10)}`
console.log(str)
模板字符串高级用法:
就是在定义模板字符串前添加一个标签,这个标签就是一个特殊的函数,添加这个标签就是调用这个函数。
const name = "Tom";
const gender = false;
function myTagFunc(strings,name,gender){
console.log(strings); //[ 'hey,', ' is a ', '.' ]
const sex = gender?"man":"woman"
return strings[0] + name + strings[1] + sex + strings[2]
}
let str = myTagFunc`hey,${name} is a ${gender}.`
console.log(str) //hey,Tom is a woman.
标签函数的特性:
参数:
- 接收到的第一个参数是一个数组。因为在模板字符串当中可能有嵌入的表达式,所以strings就是按照表达式分割过后的静态内容。
- 除了这个数组以外,函数还接收模板字符串中嵌入的所有表达式的返回值。(例如这里使用的name,gender)
返回值:标签函数的返回值就是模板字符串的值
作用:对模板字符串进行加工。
7.字符串的扩展方法
三个特别常用的字符串方法:includes,startsWith,endsWith。
它们都是用来判断字符串当中是否含有指定的内容。
includes---字符串是否包含指定内容;
startsWith---字符串是否以指定内容为开头;
endsWith---字符串是否以指定内容为结尾;
const str = "Error: foo is not defined.";
console.log(
str.includes("foo"),
str.startsWith("Error"),
str.endsWith(".")
)
8.函数参数
参数默认值:
ES2015以前我们想要为函数的参数定义默认值时,我们需要在函数体中通过逻辑代码来实现。
大多数人喜欢的短路运算的方式:
function foo( enable ){
enable = enable || true;
console.log( enable )
}
foo()
但其实这种方法有个明显的错误,就是当我们传入的值是false的时候,也会使用默认值。应该先判断enable是否等于undefined,再去觉决定是否使用默认值。
因为参数默认值的定义就是,在没有传递实际参数时所使用的值,那没有传递实参的话,得到的就应该是undefined。所以应该判断是否为undefined:
function foo( enable ){
enable = enable === undefined ? true : enable
console.log( enable )
}
foo(false)
不过有了参数默认值的新功能以后,这一切就会变得简单得多了,直接在形参的后面通过 = 去设置默认值
它只会在方法调用时没有传递参数,或者参数为undefined时被使用:
function foo( bar , bar2 , enable = 999 ){
console.log( bar , enable,bar2)
}
foo()
注意:如果有多个参数的话,带有默认值的参数一定要放到最后。因为参数是按照次序传递的。
剩余参数:
在js中的方法,有时候它的参数个数并不固定,比如console.log方法,它们的参数是未知的。在以前我们都是通过arguments对象去接收所有参数的,arguments都是一个伪数组对象。
现在ES2015中新增了...操作符,它有两个作用,这里我们使用的是它的rest作用,也就是剩余操作符。
那么此时这个形参就会以数组的形式,去接收 从当前形参 位置开始往后所有的实参:
function foo( num1,num2,...args ){
console.log( args ) //[ 3, 4, 5, 6 ]
}
foo(1,2,3,4,5,6)
这种方式就可以取代arguments 去接收无限参数的操作。
注意:因为接收的是所有参数,所以这种操作符只能出现在 所有参数的最后一个,且只能出现一次。
...操作符除了能够用来 收起剩余数据的rest用法。还有一种spread的用法(展开)。
展开操作符的用法有很多,这里先了解与函数相关的数组参数展开。比如有一个数组,想要把数组中的每一项按照次序依次传入到console.log方法中。
最笨的办法:
const arr = [1,2,3,4];
console.log(
arr[0],
arr[1],
arr[2],
arr[3],
);
但如果数组中的元素个数不固定,在以前可以使用apply方法,以数组形式接收实参列表。
console.log.apply(console,arr)
现在,就很简单了,直接调用console.log方法,使用...操作符展开数组。 它可以把数组中的每一个成员按照次序传入到参数列表中。
console.log(...arr)
9.箭头函数
简化函数定义和增加新特性
示例1.
const inc = n => n + 1
console.log(inc(100)) // 101
示例2. 多个参数时 参数要用括号(),函数体不加{}时,表达式的结果就是返回值
const inc = (n, m) => n + m
示例3. 带花括号的箭头函数的返回值需手动 return 返回
const inc = (n, m) => {
return n + m
}
箭头函数的this指向:
箭头函数本身是没有this和arguments的,在箭头函数中引用this实际上是它被调用时箭头函数所在上层作用域的this。
注意:对象是不能形成独立的作用域的。
例1:
const obj = {
name:"wjp",
sayHi:function(){
const f1 = ()=>{
console.log(this)
};
f1()
}
}
const o = obj.sayHi;
o(); //window对象
obj.sayHi(); //obj对象
o() :o变量是定义在全局作用域中,所以o()执行的时候f1箭头函数所在作用域的this指向window对象;
obj.sayHi():f1箭头函数执行时,say函数指向obj,所以f1中的this指向obj对象;
例2:
var obj = {
pro: {
getPro: ()=>{
console.log(this);
}
}
}
obj.pro.getPro() //window对象
obj.pro.getPro()执行时,因为getPro箭头函数定义时,它的上一级是pro对象,不能形成单独的作用域,上一层obj对象也不能形成单独的作用域,所以getPro函数执行时this指向window。
例3:
const obj = {
name:"wjp",
sayAsync:function(){
setTimeout(function(){
console.log(this)
},1000)
}
}
obj.sayAsync()
obj.sayAsync函数执行后,会加载一个异步延时任务,setTimeout内部的回调函数最终执行是在全局执行上下文当中的,而不是在sayAsync函数作用域。
const obj = {
name:"wjp",
sayHi:()=>{
console.log(this)
},
sayAsync:function(){
setTimeout(()=>{console.log(this)},1000)
}
}
obj.sayAsync()
将setTimeout内部的回调函数替换为箭头函数后,箭头函数内部的this指向就是被定义时所处的作用域的this指向,也就是sayAsync函数的this指向,所以obj.sayAsync()执行后一秒,打印obj对象。
10.ES2015 对象字面量语法升级
属性名和变量名相同,可省
函数声明语法可精简,可省略冒号和function关键字。 ( 需要注意的是,这里的方法中的this和普通函数function中的this相同,指向调用者自身。
计算属性名:表达式的返回值作为对象的键名,对象的属性名可动态添加 [表达式]
const bar = '123'
const obj = {
foo: 123,
bar, // 和 bar:bar 等价
method1 () { console.log(this) } // 和 method1: function() 等价
[bar]: 123
}
11.ES2015 对象的扩展方法
Object.assign:
将多个源对象中的属性复制到一个源对象当中。如果对象直接有相同属性源对象中的属性会覆盖目标对象的属性。(源对象和目标对象都是普通对象)
const source1 = {
a:123,
b:123,
}
const target = {
a:456,
c:456,
}
const resust = Object.assign( target , source1 )
console.log(resust == target) //true
console.log( target ) //{ a: 123, c: 456, b: 123 }
它的返回值就是target目标对象。它们完全相等。也就说它的返回值并不是重新生成新对象。
Object.is:用来判断两个值是否相等o
通常我们判断两个变量是否相等,会使用两等 ==,或者三等 ===。两者的区别就是 "==" 会在比较之前自动转换数据类型。比如 0 == false 这种情况是成立的。"==="则会严格去比较两者之间数值是否相同,这些因为0和false类型不同,则不成立。
“===”运算符也有两个特殊情况:+0 === -0是没有办法比较的。(对于我们应用开发基本不考虑这个情况);
其次是NaN === NaN,两个NaN在===比较的时候是不相等的。因为以前认为NaN是非数字,有无限种可能,所以他们不相等。但在今天看来NaN就是一个特别的值,所以两个NaN应该是完全相等的
console.log(
// 0 == false // => true
// 0 === false // => false
// +0 === -0 // => true
// NaN === NaN // => false
Object.is(+0, -0), // => false
Object.is(NaN, NaN) // => true
)
12. ES2015 Proxy
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
ES2015 之前要监视某个对象中的属性读写,我们可以使用 ES5 Object.defineProperty。
ES2015 中 Proxy 是专门为对象设置访问代理器的,其中代理可理解为门卫,我们进出屋子都要经过这个代理。通过 Proxy 就可以轻松监视属性的读写,它也比 defineProperty 更强大。
const person = {
name:"zce",
age:20
}
// Proxy构造函数的第一个参数就是需要被代理的目标对象。
// 第二个参数也是一个对象,称之为代理的处理对象:这个对象的get方法用来监视属性的访问,set方法来监视对象当中设置属性的过程。
// get方法的返回值就是外部访问对象这个属性得到的结果。
const personProxy = new Proxy(person , {
get(target,property){
console.log(target,property);
return "100"
},
set(target,property,value){
// console.log(target,property,value);
console.log(target,property,value);
target[property] = value;
return "属性写入成功!";
}
});
// 通过代理对象访问实际对象的age属性。
console.log(personProxy.age)
personProxy.age = 999;
// console.log(personProxy,person )
注意:要使
Proxy
起作用,必须针对Proxy
实例(也就是personProxy
对象)进行操作,而不是针对目标对象(person)进行操作。如果处理对象handler
没有设置任何拦截,那就等同于直接通向原对象。
Proxy对比Object.defineProperty
1. defineProperty 只能监视对象属性的读写,Proxy 能够监视到更多对象操作,如:delete、对象方法的调用等,示例如下:
const person = {
name:"zce",
age:20
}
const personProxy = new Proxy(person , {
deleteProperty(target,property){
console.log("删除了 ",target,property);
delete target[property]
}
});
delete personProxy.name;
console.log(personProxy,person)
通过delete personProxy.name 删除掉代理对象中的name属性,打印person对象,可以看到源对象现在为 { age: 20 };
Proxy 中处理对象的 deleteProperty 方法能够监视目标对象中的 delete 操作。此外有更多方法都能够监视到目标对象的属性异动。
2. Proxy更好支持数组对象的监视
这里set执行两次是因为push其实相当于执行了两个操作,一个是添加元素,一个是修改它的length属性。
const list = [ ];
const personProxy = new Proxy(list , {
set(target,property,value){
console.log(target,property,value);
target[property] = value;
return "属性写入成功!";
}
});
personProxy.push(100);
//[] '0' 100 //proxy内部会自动根据push操作推算出它应该所处的下标。
// [ 100 ] 'length' 1
除了push方法,数组的其他操作方式也是一样的。
3.Proxy是以非侵入的方式去监视对象的读写
一个已经定义好的对象,它不需要对它本身做任何操作,就可以监视到它内部成员的读写。而Object.defineProperty则需要通过特定的方式单独去定义对象中那些需要被监视的属性,那对于一个已经存在的对象,要想监视它的属性,需要做很多额外的操作。(这个优势需要大量实践使用慢慢体会。)
13. ES2015 Reflect
reflect是一个静态类,不能够通过new关键字来构建实例对象,只能够通过调用它内部的静态方法去使用,如同 Math 对象一样。
Reflect
对象与Proxy
对象一样,都是 ES6 为了操作对象而提供的新 API,Reflect
对象的设计目的:
- 将
Object
对象的一些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上。现阶段,某些方法同时在Object
和Reflect
对象上部署,未来的新方法将只部署在Reflect
对象上。也就是说,从Reflect
对象上可以拿到语言内部的方法。 - 修改某些
Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。 - 让
Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。 Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。
Reflect 内部封装了一系列针对对象的底层操作(目前 13 个尚在使用的方法)。这些静态方法的方法名是和 Proxy 能够监视到对象的方法名一致的,其实 Reflect 的这些方法就是 Proxy 处理对象的默认实现:
const obj = {
name:"wjp",
age:18
};
const proxy = new Proxy(obj,{
get(target,property){
return Reflect.get(target,property)
}
});
console.log(proxy.name)
//它等价于
const obj = {
name:"wjp",
age:18
};
const proxy = new Proxy(obj,{
});
console.log(proxy.name)
proxy处理对象默认实现的逻辑就是调用了reflect对象中对应的方法。就是说 :我们没有定义get方法,就等同于在处理对象内部定义了get方法,然后在内部将参数原封不动的交给给了reflect的get方法,结果是一样的。
也就是说:我们在实现自定义get方法或set方法时,更标准的做法是,先实现我们所需的监视逻辑,再返回 通过reflect相对应的方法的结果。
Reflect 最大的价值就是它统一了一套用于操作对象的 API。举例如下:
const obj = {
name:"wjp",
age:18
};
// 之前我们需要使用不同的关键词或对象API
// console.log( 'name' in obj ); //true
// console.log( delete obj['age'] ); //true
// console.log( Object.keys(obj) ); //['name']
// 统一使用 Reflect,体验更合理更舒适
console.log( Reflect.has(obj,'name') ); //true
console.log( Reflect.deleteProperty(obj,"age") ); //true
console.log( Reflect.ownKeys(obj) ); //['name']
我们在操作对象时,可能会使用Object 对象上的一些方法,例如Object.defineProperty方法,也有可能使用delete或是in这样的操作符,这些对于新手来说 可能会太乱了,因为没有一些什么规律。
reflect就解决了这样的问题,它统一了对象的操作方式。
...操作符
...操作符除了能够用来 收起剩余数据的rest用法。还有一种spread的用法(展开)。
前面例子中有用到,这里写出来只是着重点一下 这个操作符的用法