本帖最后由 大蓝鲸小蟀锅 于 2020-3-14 22:25 编辑
2020年前端开发必备技能 -- 扫盲ES6
所谓es6语法新特性,面世已经有好几年了。不管是平时开发过程中还是面试提及时,都得很熟练呀!
一、var -> let -- const
首先注意使用 var 声明变量存在的问题。
1、用 var 声明的变量默认会被声明到全局上(es6之前,前端只有全局作用域和函数级作用域),导致太过污染全局变量( 因为给变量取名字,对于开发者来讲真的很难!)
2、用var 声明的变量会导致变量提升( 即:某变量是在代码下方定义,代码上方也能访问,尽管提升的是声明,不是赋值 ),对于js单线程来讲,代码是一行一行从上往下执行的,如果变量能在未定义之前使用,是不是有点不太靠谱?同样,导致变量提升的操作还有使用 function、import
3、用 var 声明的变量可以重名、可以随意变换数据类型. 这一点也是被Java等强类型语言所鄙弃的地方。当然,也是有解决方案的,比如:typescript的出现,它可以将原先的弱类型向强类型方向转
以上三点存在的问题,let和const都能解决。
let和const认为:一对大括号 { } ,就是一个作用域,所谓块级作用域的最小单位,就是一对大括号。之前使用var配合自执行函数能解决的问题。现在let和const可以轻松解决
问:let和const 什么时候用比较好呢?
答:
尽可能的先考虑使用 const,如果这个值需要改变,再更换let。
const更适合常量。
如果const声明的是一个对象,后面对该对象进行添加修改属性,当然也是可以的,因为操作的引用地址仍是原先的地址。
二、解构赋值
1、解构时,等号前后格式要一致(比如解构对象时,等号前后必须都是对象形式;解构数组时,等号前后必须都是数组的形式)
2、解构的方式都是根据key来实现的(取的key要和被取得值中的要一一对应)
3、可以用冒号:来重新赋值,用等号 = 赋默认值
[JavaScript] 纯文本查看 复制代码 const { description: desc, age = 14 } = { name: '黑马', description: '学IT选传智' }
console.log(desc, age) // 学IT选传智 14
以上代码依次输出:学IT选传智 14,其中description属性就是后面被取值中的属性;desc 是重新命名的;age 是给默认值的
三、剩余运算符 - 展现形式就是三个点 ...
1、也有地方称为展开运算符,用于展开对象,展开数组
2、可用于解构,函数参数;用于剩余时,特点:只能用在最后一项,并且展示形式都是数组
[JavaScript] 纯文本查看 复制代码 let [...args] = ['黑马', 14, '三鸿路']; // slice
console.log(args);
console.log(Object.prototype.toString.call(args))
以上代码输出:['黑马', 14, '三鸿路'] [object Array]
以上是数组,以下是对象:
[JavaScript] 纯文本查看 复制代码 // 对象的剩余运算符
let { name, ...args } = { name: '黑马', age: 14 };
console.log(args); // {age: 14}
用于函数参数时也只能放在最后一项,函数体里使用时也是一个数组。
做展开运算符时:可用于合并数组,对象
[JavaScript] 纯文本查看 复制代码 let a1 = [1,2,3];
let a2 = [4,5,6,1,2,3];
console.log([...a1,...a2]);
输出: [1,2,3,4,5,6,1,2,3]
也常常配合set,做数组去重
如下:
[JavaScript] 纯文本查看 复制代码 console.log(new Set([...a1,...a2]));
输出:[1,2,3,4,5,6]
四、set和map
1、set和map的特点是里面不能放重复值
特点:没有key属性
[JavaScript] 纯文本查看 复制代码 let set = new Set([1,2,3,3,2,1]);
console.log(Object.keys(set)); // 没有key属性
输出:[ ]
几个常用的方法:add 添加某一项;clear 可以把所有项清除;delete 可删除某一项; has 方法可以判断某一项在不在该set内,返回布尔值
另外,展开运算符和set常用于俩数组间求交集、差集
交集:
[JavaScript] 纯文本查看 复制代码 let a1 = [1, 2, 3]
let a2 = [4, 5, 6, 1, 2, 3]
let s1 = new Set([...a1])
let s2 = new Set([...a2])
let a3 = [...s2].filter((item => s1.has(item)));
console.log(a3)
先用set去重,再使用展开运算符用数组包裹,可以使用数组的filter过滤方法,函数体里使用s1的has方法,可取出s2和s1中的交集 [1,2,3]
差集:
只需让filter函数体加一个 取反:!s1.has(item) ,即可得出s2和s1的差集:[4,5,6]. 特别方便。
另外可以看下WeakSet和WeakMap,区别是后者的键可以放对象(弱引用),即垃圾回收机制不将该对象考虑在内。也就是说一旦不需要,WeakMap里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。
展开运算符也常用于浅拷贝和es6提供的Object.assign 是一样的效果
浅拷贝:对象或者数组拷贝前和拷贝后有关系,就是浅拷贝
深拷贝:对象或数组拷贝前和拷贝后没有关系,就是深拷贝
[JavaScript] 纯文本查看 复制代码 let a = {name: 'heima', age: 14}
let b = {...a}
console.log(a, b)
如果是多层,一个对象进行添加修改属性时会影响另一个对象。则不是我们想要的,开发时常用JSON.parse 和 JSON.stringify配合使用
如下代码:
[JavaScript] 纯文本查看 复制代码 let school = { name: 'heima', age: 14, a: { b: 2 }, fn: () => { }, c: undefined, reg: /\d+/ };
newObj = JSON.parse(JSON.stringify(school)); // ?
newObj.a.b = 100;
console.log(school, newObj);
// {name: 'heima',age: 14,a: { b: 2 },fn: [Function: fn],c: undefined,reg: /\d+/}
// { name: 'heima', age: 14, a: { b: 100 }, reg: {} }
根据输出结果可以看出:使用JSON.stringify和JSON.parse确实能拷贝出一份新的对象,但是有三种数据类型会被丢失:undefined、函数、Symbol
下面给出一种自己实现的深拷贝代码:
[JavaScript] 纯文本查看 复制代码
const deepClone = (value, hash = new WeakMap) => {
if (value == null) return value; // 排除掉null 和undefine 的情况
if (typeof value !== 'object') return value; // 这里包含了函数类型
if (value instanceof RegExp) return new RegExp(value);
if (value instanceof Date) return new Date(value);
// .....
// 拷贝的人可能是一个对象 或者是一个数组 (循环) for in
let instance = new value.constructor; // 根据当前属性构造一个新的实例
if (hash.has(value)) { // 先去hash中查看一下是否存在过 ,如果存在就把以前拷贝的返回去
return hash.get(value); // 返回已经拷贝的结果
}
hash.set(value, instance);// 没放过就放进去
// 用一个对象来记忆
for (let key in value) { // 一层
if (value.hasOwnProperty(key)) { // 将hash 继续向下传递 保证这次拷贝能拿到以前拷贝的结果
instance[key] = deepClone(value[key], hash); // 产生的就是一个新的拷贝后的结果
}// 过滤掉原型链上的属性
}
return instance
};
let obj = { a: 1 };
obj.b = obj; // 如果obj 已经被拷贝了一次 那下次 在用到obj的时候 直接返回就好了 不需要再次拷贝了
console.log(deepClone(obj));
以上,一开始是先判断用户传入类型,不是对象或者数组的直接抛出;根据所给值的constructor可以拿到当前值的所属类型
另外顺嘴提一下常见判断数据所属数据类型的方法:
typeof :常用,但是对对象和数组输出都是 ‘object’
instanceof:常用,判断当前数据所属的是哪一个类型(node instanceof Element)
Object.prototype.toString.call():使用toString方法,最保险、最可靠,返回[object Number/Array],经常使用slice(8, -1) -> Number、Array
constructor:构函函数,new {}.constructor -> 上例使用的就是此种方式
五、Reflect
1、是为了更方便操作对象而提供的api
2、把一些明显属于语言内部的方法(比如:Object.defineProperty),放到Reflect对象上,现阶段某些方法在Object和Reflect上都有部署,未来的新方法只部署在Reflect上供开发者使用
3、它的好处,是更合理。可以返回此次操作的状态的布尔值
举个栗子1:
[JavaScript] 纯文本查看 复制代码 const obj = {};
Reflect.set(obj, 'age', 10)
console.log(Reflect.get(obj, 'age'))
如上代码,可以通过Reflect给空对象obj进行添加属性,第一个参数表示准备给谁加属性,第二个参数表示加什么属性,第三个参数表示加的属性的值什么
举个栗子2:
判断某个属性在不在该对象上,通常可以使用 in 关键字;当然,Reflect上面也部署了has方法,第一个参数是哪个对象,第二个参数表示你要判断的那个属性
[JavaScript] 纯文本查看 复制代码 console.log('a' in {a:1});
console.log(Reflect.has({name: 'care', age: 10}, 'name'))
举个栗子3:
上文提到的Object.defineProperty可以用来给对象添加某个属性,并且赋值,同样Reflect也可以
但是如果遇到该对象被冻结时,这两者表现差别很大。前者直接报错,后面只是返回false,这也是开头提到的,使用Reflect更合理
[JavaScript] 纯文本查看 复制代码 const obj = {a:1}
Object.freeze(obj); // 这个属性就能不能配置了 冻结
let flag = Reflect.defineProperty(obj,'a',{
value:100
})
Object.defineProperty(obj, 'a', {
value: 101
})
console.log(flag);
举个栗子4:
在es6中,给Object加了两个有关原型上的方法。setPrototypeOf getPrototypeOf 前者是用来给某个值设置原型指向的,后者是获取某个值的原型
跟之前的写法,给某个值设置__proto__的效果是一样的
Reflect.setPrototypeOf方法用于设置目标对象的原型,返回一个布尔值,表示是否设置成功
六、Symbol
这是es6引入的一种新的原始数据类型,表示独一无二的值
初始化Symbol时可以传入字符串或者是数字类型
[JavaScript] 纯文本查看 复制代码 const s1 = Symbol("zf"); // number or string
const s2 = Symbol("zf");
console.log(s1 === s2);
输出:false
七、对对象和数组新增的api
es6中,对对象和数组提供了很多的api,使用方式比较浅显,自己熟练操作一下就好
请移步新增操作数组的api
好啦,以上就是es6里面需要熟练掌握的东西啦。欢迎交流。
|