你需要知道的30个ES6—ES12开发技巧!(2)

简介: 又是一顿爆肝,又是一篇万字长文,重新梳理了一下ES6——ES12的常用新特性,很多特性在开发中还是很实用的,希望对你有一点点帮助!文章内容较多,建议先收藏在学习呢!ECMAScript 是一种由 Ecma 国际通过 ECMA-262 标准化的脚本程序设计语言,这种语言被称为 JavaScript 。简单来说,ECMAScript 是 JavaScript 的标准与规范,JavaScript 是 ECMAScript 标准的实现和扩展。

(2)不绑定 this

箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。

var id = 'GLOBAL';
var obj = {
  id: 'OBJ',
  a: function(){
    console.log(this.id);
  },
  b: () => {
    console.log(this.id);
  }
};
obj.a();    // 'OBJ'
obj.b();    // 'GLOBAL'
new obj.a()  // undefined
new obj.b()  // Uncaught TypeError: obj.b is not a constructor
复制代码


对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。需要注意,定义对象的大括号{}是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。


同样,使用call()、apply()、bind()等方法也不能改变箭头函数中this的指向:

var id = 'Global';
let fun1 = () => {
    console.log(this.id)
};
fun1();                     // 'Global'
fun1.call({id: 'Obj'});     // 'Global'
fun1.apply({id: 'Obj'});    // 'Global'
fun1.bind({id: 'Obj'})();   // 'Global'
复制代码


(3)不可作为构造函数

构造函数 new 操作符的执行步骤如下:

  1. 创建一个对象
  2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)
  3. 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)
  4. 返回新的对象

实际上第二步就是将函数中的this指向该对象。 但是由于箭头函数时没有自己的this的,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。


(4)不绑定 arguments

箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。


6. 扩展运算符


扩展运算符:...  就像是rest参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。

spread 扩展运算符有以下用途

(1)将数组转化为用逗号分隔的参数序列:

function  test(a,b,c){
    console.log(a); // 1
    console.log(b); // 2
    console.log(c); // 3
}
var arr = [1, 2, 3];
test(...arr);
复制代码


(2)将一个数组拼接到另一个数组:

var arr1 = [1, 2, 3,4];
var arr2 = [...arr1, 4, 5, 6];
console.log(arr2);  // [1, 2, 3, 4, 4, 5, 6]
复制代码


(3)将字符串转为逗号分隔的数组:

var str='JavaScript';
var arr= [...str];
console.log(arr); // ["J", "a", "v", "a", "S", "c", "r", "i", "p", "t"]
复制代码


7. Symbol


ES6中引入了一个新的基本数据类型Symbol,表示独一无二的值。它是一种类似于字符串的数据类型,它的特点如下:

  • Symbol的值是唯一的,用来解决命名冲突的问题
  • Symbol值不能与其他类型数据进行运算
  • Symbol定义的对象属性不能使用for...in遍历循环,但是可以使用Reflect.ownKeys 来获取对象的所有键名


let s1 = Symbol();
console.log(typeof s1); // "symbol"
let s2 = Symbol('hello');
let s3 = Symbol('hello');
console.log(s2 === s3); // false
复制代码


基于以上特性,Symbol 属性类型比较适合用于两类场景中:常量值和对象属性


(1)避免常量值重复

getValue 函数会根据传入字符串参数 key 执行对应代码逻辑:

function getValue(key) {
  switch(key){
    case 'A':
      ...
    case 'B':
      ...
  }
}
getValue('B');
复制代码


这段代码对调用者而言非常不友好,因为代码中使用了魔术字符串(Magic string,指的是在代码之中多次出现、与代码形成强耦合的某一个具体的字符串或者数值),导致调用 getValue 函数时需要查看函数代码才能找到参数 key 的可选值。所以可以将参数 key 的值以常量的方式声明:


const KEY = {
  alibaba: 'A',
  baidu: 'B',
}
function getValue(key) {
  switch(key){
    case KEY.alibaba:
      ...
    case KEY.baidu:
      ...
  }
}
getValue(KEY.baidu);
复制代码


但这样也并非完美,假设现在要在 KEY 常量中加入一个 key,根据对应的规则,很有可能会出现值重复的情况:

const KEY = {
  alibaba: 'A',
  baidu: 'B',
  tencent: 'B'
}
复制代码


这就会出现问题:

getValue(KEY.baidu) // 等同于 getValue(KEY.tencent)
复制代码


所以在这种场景下更适合使用 Symbol,不需要关心值本身,只关心值的唯一性:

const KEY = {
  alibaba: Symbol(),
  baidu: Symbol(),
  tencent: Symbol()
}
复制代码


(2)避免对象属性覆盖

函数 fn 需要对传入的对象参数添加一个临时属性 user,但可能该对象参数中已经有这个属性了,如果直接赋值就会覆盖之前的值。此时就可以使用 Symbol 来避免这个问题。创建一个 Symbol 数据类型的变量,然后将该变量作为对象参数的属性进行赋值和读取,这样就能避免覆盖的情况:


function fn(o) { // {user: {id: xx, name: yy}}
  const s = Symbol()
  o[s] = 'zzz'
}
复制代码


8. 集合 Set


ES6提供了新的数据结构Set(集合)。它类似于数组,但是成员的值都是唯一的,集合实现了iterator接口,所以可以使用扩展运算符和 for…of 进行遍历。

Set的属性和方法:

属性和方法 概述
size 返回集合的元素个数
add 增加一个新的元素,返回当前的集合
delete 删除元素,返回布尔值
has 检查集合中是否包含某元素,返回布尔值
clear 清空集合,返回undefined
//创建一个空集合
let s = new Set();
//创建一个非空集合
let s1 = new Set([1,2,3,1,2,3]);
//返回集合的元素个数
console.log(s1.size);       // 3
//添加新元素
console.log(s1.add(4));     // {1,2,3,4}
//删除元素
console.log(s1.delete(1));  //true
//检测是否存在某个值
console.log(s1.has(2));     // true
//清空集合
console.log(s1.clear());    //undefined
复制代码


由于集合中元素的唯一性,所以在实际应用中,可以使用set来实现数组去重:

let arr = [1,2,3,2,1]
Array.from(new Set(arr))  // {1, 2, 3}
复制代码


这里使用了Array.form()方法来将数组集合转化为数组。

可以通过set来求两个数组的交集和并集:

// 模拟求交集 
let intersection = new Set([...set1].filter(x => set2.has(x)));
// 模拟求差集
let difference = new Set([...set1].filter(x => !set2.has(x)));
复制代码


用以下方法可以进行数组与集合的相互转化:

// Set集合转化为数组
const arr = [...mySet]
const arr = Array.from(mySet)
// 数组转化为Set集合
const mySet = new Set(arr)
复制代码


9. Map


ES6提供了Map数据结构,它类似于对象,也是键值队的集合,但是它的键值的范围不限于字符串,可以是任何类型(包括对象)的值,也就是说, Object 结构提供了“ 字符串—值” 的对应, Map 结构提供了“ 值—值” 的对应, 是一种更完善的 Hash 结构实现。 如果需要“ 键值对” 的数据结构, Map 比 Object 更合适。Map也实现了iterator接口,所以可以使用扩展运算符和 for…of 进行遍历。

Map的属性和方法:


属性和方法 概述
size 返回Map的元素个数
set 增加一个新的元素,返回当前的Map
get 返回键名对象的键值
has 检查Map中是否包含某元素,返回布尔值
clear 清空Map,返回undefined
//创建一个空 map
let m = new Map();
//创建一个非空 map
let m2 = new Map([
 ['name', 'hello'],
]);
//获取映射元素的个数
console.log(m2.size);          // 1
//添加映射值
console.log(m2.set('age', 6)); // {"name" => "hello", "age" => 6}
//获取映射值
console.log(m2.get('age'));    // 6
//检测是否有该映射
console.log(m2.has('age'));    // true
//清除
console.log(m2.clear());       // undefined
复制代码


需要注意, 只有对同一个对象的引用, Map 结构才将其视为同一个键:

let map = new Map(); 
map.set(['a'], 555); 
map.get(['a']) // undefined
复制代码


上面代码的set和get方法, 表面是针对同一个键, 但实际上这是两个值, 内存地址是不一样的, 因此get方法无法读取该键, 所以会返回undefined。


由上可知, Map 的键实际上是跟内存地址绑定的, 只要内存地址不一样, 就视为两个键。 这就解决了同名属性碰撞( clash) 的问题,在扩展库时, 如果使用对象作为键名, 就不用担心自己的属性与原来的属性同名。


如果 Map 的键是一个简单类型的值( 数字、 字符串、 布尔值), 则只要两个值严格相等, Map 将其视为一个键, 包括0和 - 0。 另外, 虽然NaN不严格相等于自身, 但 Map 将其视为同一个键。

let map = new Map(); 
map.set(NaN, 123); 
map.get(NaN) // 123 
map.set(-0, 123); 
map.get(+0) // 123 
复制代码


10. 模块化


ES6中首次引入模块化开发规范ES Module,让Javascript首次支持原生模块化开发。ES Module把一个文件当作一个模块,每个模块有自己的独立作用域,那如何把每个模块联系起来呢?核心点就是模块的导入与导出。


(1)export 导出模块

  • 正常导出:
// 方式一
export var first = 'test';
export function func() {
    return true;
}
// 方式二
var first = 'test';
var second = 'test';
function func() {
    return true;
}
export {first, second, func};
复制代码


  • as关键字:
var first = 'test';
export {first as second};
复制代码


as关键字可以重命名暴露出的变量或方法,经过重命名后同一变量可以多次暴露出去。

  • export default

export default会导出默认输出,即用户不需要知道模块中输出的名字,在导入的时候为其指定任意名字。

// 导出
export default function () {
  console.log('foo');
}
// 导入
import customName from './export-default';
复制代码


注意: 导入默认模块时不需要大括号,导出默认的变量或方法可以有名字,但是对外无效。export default只能使用一次。


(2)import 导入模块

  • 正常导入:
import {firstName, lastName, year} from './profile';
复制代码
复制代码


导入模块位置可以是相对路径也可以是绝对路径,.js可以省略,如果不带路径只是模块名,则需要通过配置文件告诉引擎查找的位置。

  • as关键字:
import { lastName as surname } from './profile';
复制代码


import 命令会被提升到模块头部,所以写的位置不是那么重要,但是不能使用表达式和变量来进行导入。

  • 加载整个模块(无输出)
import 'lodash'; //仅仅是加载而已,无法使用
复制代码


  • 加载整个模块(有输出)
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
复制代码


注意: import * 会忽略default输出


(3)导入导出复合用法

  • 先导入后导出
export { foo, bar } from 'my_module';
// 等同于
import { foo, bar } from 'my_module';
export { foo, boo};
复制代码


  • 整体先导入再输出以及default
// 整体输出
export * from 'my_module';
// 导出default,正如前面所说,export default 其实导出的是default变量
export { default } from 'foo';
// 具名接口改default
export { es6 as default } from './someModule';


相关文章
|
3月前
|
JavaScript 前端开发
ES6学习(6)
ES6学习(6)
|
3月前
|
网络架构
ES6学习(5)
ES6学习(5)
|
前端开发 JavaScript
每天3分钟,重学ES6-ES12系列文章汇总
每天3分钟,重学ES6-ES12系列文章汇总
67 0
|
Docker 容器
es应用笔记1-es部署
es应用笔记1-es部署
119 0
|
JavaScript 前端开发
每天3分钟,重学ES6-ES12(十八)ES Module(二)
每天3分钟,重学ES6-ES12(十八)ES Module
82 0
|
JavaScript 前端开发
每天3分钟,重学ES6-ES12(十八)ES Module(一)
每天3分钟,重学ES6-ES12(十八)ES Module
85 0
|
JavaScript 前端开发
每天3分钟,重学ES6-ES12(六)ES7 ES8 新增内容
每天3分钟,重学ES6-ES12(六)ES7 ES8 新增内容
121 0
|
JavaScript 前端开发 Java
每天3分钟,重学ES6-ES12(八)ES11 ES12新增内容
每天3分钟,重学ES6-ES12(八)ES11 ES12新增内容
111 0
每天3分钟,重学ES6-ES12(七)ES10 新增内容
每天3分钟,重学ES6-ES12(七)ES10 新增内容
90 0
|
缓存 JavaScript 算法
每天3分钟,重学ES6-ES12(十八) CJS
每天3分钟,重学ES6-ES12(十八) CJS
90 0