A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 大蓝鲸小蟀锅 于 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里面需要熟练掌握的东西啦。欢迎交流。










图片1.png (24.21 KB, 下载次数: 16)

图片1.png

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马