从2015年正式发布的ES6成为JavaScript的下一代标准后,标准委员会(TC39)在每年都发布了一个ES的新版本,在每个版本里都引入了很多新特性,下面让我们全面的掌握这些ES的新特性。
Let 和Const
let用来声明变量,const用来声明常量。
如何使用
const TAG = "我是常量";
let a;
a = 2;
console.log(TAG, "a=" + a); //我是常量 a=2
四个特点
一、只在块级作用域内有效
let和const为JavaScript新增了块级作用域,通常情况下,{}包裹的代码拥有的作用域就是块级作用域,声明的变量或常量只在块级作用域内有效,外部不能访问。
if (true) { //外层块级作用域
let a = 1;
const A = 1;
if (true) { //内层块级作用域
let a = 2;
}
console.log(a,A); //(1)输出:1 , 1
}
console.log(a); //(2)Uncaught ReferenceError: a is not defined
上面有两个块级作用域,都声明了变量a,但外层块级作用域与内层块级作用域无关,所以(1)处输出的是外层的变量值1,(2)处访问了不在一个块级作用域定义的变量,所以会报错。
另外一个理解块级作用域的示例。
//for循环体内的定时器
//在ES6之前,是没有块级作用域的,变量用var声明,直接挂载在全局作用域上
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); //3、3、3
}, 100);
}
//使用var声明,for同步操作优先于setTimeout异步操作,在开始执行setTimeout的时候,for循环已经执行完,i为3,则后续每次setTimeout输出的i都是3
//使用let声明的话,则会在循环体内部形成闭包,每次for循环都会给闭包提供每次循环i的值,并且不被释放,最终setTimeout里会分别输出0、1、2
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); //0 1 2
}, 100);
}
二、暂时性死区
不能在变量和常量声明之前使用。
let和const命令会使区块形成封闭作用域,若在声明之前使用,就会报错,这个在语法上,称为“暂时性死区”(简称TDZ)。
if (true) {
tmp = "abc"; // ReferenceError
let tmp;
}
三、不能重复声明
let a = 1;
let a = 2;
//报错 SyntaxError: Identifier 'a' has already been declared
const B=1;
const B=2;
//报错 SyntaxError: Identifier 'B' has already been declared
四、不属于顶层对象
let声明的变量,全局对象(window,global,self)不能访问到
let a = 10;
console.log(window.a); //undefined
String
ES6对字符串进行了一些扩展,如下:
模板字符串
ES6新增了模板字符串(字符串
)的方式定义字符串,用来解决之前字符串很长要换行、字符串中有变量或者表达式遇到的问题,下面是具体的使用场景
//一、打印长字符串且换行,直接在模板字符串中保留格式即可
let welcome=`
你好
欢迎来到ES6
——谢谢
`
console.log(welcome);
/*
输出结果为:
你好
欢迎来到ES6
——谢谢
*/
//二、字符串中有变量或者表达式,直接在模板字符串中使用${变量/表达式}即可
let type = "ES6";
let name1 = "mango";
let name2 = "和goman";
let welcome = `欢迎${name1 + name2}来到${type}世界`;
console.log(welcome); //learn1.js?c1a0:7 欢迎mango和goman来到ES6世界
方法
String.prototype.includes()
判断字符串是否包含一个指定字符串,返回boolean类型。
const str = "ECMAScript"
console.log(str.includes("EC")); //true 找不到返回false
startsWith()和endsWith()
startsWith()用来判断字符串是否以指定字符串作为开头,返回boolean类型。
endsWith()用来判断字符串是否以指定字符串作为结尾,返回boolean类型。
const str = "ECMAScript"
console.log(str.startsWith("ECM")); //true
console.log(str.endsWith("Script")); //true
String.prototype.repeat()
将原有字符串重复n遍,得到一个新的字符串
const str = "ECMAScript";
console.log(str.repeat(3)); //ECMAScriptECMAScriptECMAScript
Number
ES6开始逐步减少全局性方法,使得语言逐步模块化,所以把一些处理数值的方法转移到了Number对象上,功能行为保持不变。
//将目标转换为整数
//ES5
parseInt("5.6") //5
//ES6
Number.parseInt("5.6") //5
//将目标转换为浮点数
//ES5
parseFloat("12.45str") //12.45
//ES6
Number.parseFloat("12.45str") //12.45
另外,为了便于开发,Number还增加了一些方法和属性
一、判断一个数值是否是整数
Number.isInteger(25) // true
Number.isInteger(25.1) // false
二、获取JavaScript最大安全值和最小安全值
Number.MAX_SAFE_INTEGER=9007199254740991
Number.MIN_SAFE_INTEGER=-9007199254740991
三、判断一个数值是否是在安全范围
Number.isSafeInteger(9007199254740992) //false
Symbol
新引入原始数据类型,用来表示独一无二的值。
声明方式
let sym = Symbol();
let sym2 = Symbol();
console.log(sym == sym2); //false 生成的值是独一无二的,所以不相等
console.log(typeof sym); //symbol typeof查看值的类型为symbol
let symWithDesc = Symbol("name"); //Symbol()括号内可以添加描述
console.log(symWithDesc.toString()); //输出:Symbol(name) 打印描述需要转换成字符串
项目中应用
一、消除魔术字符串
假如我们需要做一个点击菜单,做不同处理的功能,我们通常会这样实现。
const clickMenu = function (menu) {
switch (menu) {
case "home":
break;
case "me":
break;
}
};
clickMenu("home")
"home"这种可能会多次出现,与代码形成强耦合的字符串就是魔术字符串,在项目中我们应该尽量消除魔术字符串,下面使用Symbol消除魔术字符串
const MENU_TYPE = {
home: Symbol(),
me: Symbol(),
};
const clickMenu = function () {
switch (menu) {
case MENU_TYPE.home:
break;
case MENU_TYPE.me:
break;
}
};
clickMenu(MENU_TYPE.home);
二、作为对象独一无二的属性值
假如我们想生成一个公司人名对象,并以每个人名为key值,这时候如果有人名重名便会有问题,而Symbol能解决这个问题
const scores = {
[Symbol("张三")]: {
age: 22,
},
[Symbol("李四")]: {
age: 21,
},
[Symbol("张三")]: {
age: 20,
},
};
注意,通过Symbol定义的属性,只能通过下面两种方式进行遍历,否则无法获取属性。
for (let key of Object.getOwnPropertySymbols(scores)) {
console.log(key, key);
}
for (let key of Reflect.ownKeys(scores)) {
console.log(key, scores[key]);
}
Set和Map
为了更方便地实现数据操作,ES6新增了Set和Map两种数据结构。
Set
Se是类似于数组,但成员的值都是唯一的数据结构。
新建
新建一个存储月份的Set数据结构,可以定义一个空的Set实例,也可以是带有数组形式的默认数据。
let monthSets = new Set();
let monthSets2 = new Set(["一月","二月","三月"]);
基本使用
//添加数据
monthSets.add("一月");
monthSets.add("二月").add("三月");
console.log(monthSets); //Set(3) {"一月", "二月", "三月"}
//遍历集合Set
//forEach():使用回调函数遍历每个成员
monthSets.forEach((item) => console.log(item)); //一月 二月 三月
//for...of:直接遍历每个成员
for (const item of monthSets) {
console.log(item); //一月 二月 三月
}
//删除数据
monthSets.delete("二月");
console.log(monthSets); // Set(2) {"一月", "三月"}
monthSets.clear(); //
console.log(monthSets); // Set(0) {}
常见应用
Set数据结构在实际项目中还有很多应用场景。
let monthSets = new Set(["一月", "二月", "三月"]);
//一、快速判断数据元素是否存在
monthSets.has("一月"); //true
//二、统计数据元素个数
monthSets.size; //3
console.log(monthSets.size); //3
//三、数组去重
let arr = [1, 2, 3, 2, 3, 4, 5];
let set = new Set(arr);
console.log(set); // {1, 2, 3, 4, 5}
//四、合并去重
let arr = [1, 2, 3];
let arr2 = [2, 3, 4];
let set = new Set([...arr, ...arr2]);
console.log(set); // {1, 2, 3, 4}
//五、取数组交集
let arr1 = [1, 2, 3];
let arr2 = [2, 3, 4];
let set1 = new Set(arr1);
let set2 = new Set(arr2);
let resultSet = new Set(arr1.filter((item) => set2.has(item)));
console.log(Array.from(resultSet)); // [2, 3]
//六、取数组差级
let arr1 = [1, 2, 3];
let arr2 = [2, 3, 4];
let set1 = new Set(arr1);
let set2 = new Set(arr2);
let arr3 = arr1.filter((item) => !set2.has(item));
let arr4 = arr2.filter((item) => !set1.has(item));
console.log([...arr3, ...arr4]); //[1, 4]
WeakSet
WeakSet与Set类似,也是不重复的值的集合,但WeakSet的成员只能是对象。WeakSet引用的对象都是弱引用,如果其他对象不再引用该对象,那么垃圾回收机制就会自动回收这些对象所占用的内存,不考虑该对象还存在于WeakSet之中。
React源码中有很多地方使用到了WeakSet,例如在react-reconciler/src/ReactFiberHotReloading.new.js中。
export function markFailedErrorBoundaryForHotReloading(fiber: Fiber) {
if (__DEV__) {
if (resolveFamily === null) {
// Hot reloading is disabled.
return;
}
if (typeof WeakSet !== 'function') {
return;
}
if (failedBoundaries === null) {
failedBoundaries = new WeakSet();
}
failedBoundaries.add(fiber);
}
}
Map
Map是一种键值对集合,与对象类似,但Object只支持“字符串:值”,而Map支持“各种类型的值:值”,map给我们提供了更合适的“键值对”数据结构。
基本使用
//定义
let map = new Map();
//添加数据
let address = { address: "江苏" };
map.set("name", "ES6");
map.set(27, "年龄信息");
map.set(address, "地址信息");
console.log(map); //{"name" => "ES6", 27 => "年龄信息", {…} => "地址信息"}
//获取数据
let name = map.get("name");
let age = map.get(27);
let addressObj = map.get(address);
console.log(name, age, addressObj);
//获取成员数量
console.log(map.size); //3
//判断是否指定key成员
console.log(map.has("name")); //true
Map的遍历
map通常可以用forEach和for…of的方式进行遍历。
//定义
let map = new Map();
map.set("id", 1);
map.set("name", "mango");
map.set("address", {
province: "江苏",
city: "南京",
});
map.forEach((key, value) => console.log(key, value));
for (const [key, value] of map) {
console.log(key, value);
}
//输出 id 1 name mango address {province: "江苏", city: "南京"}
WeakMap
WeakMap与Map类似,也是用来生成键值对的集合。但WeakMap只接受对象作为键名,并且键名所指向的对象,属于弱引用对象。
更多文章内容,请关注公众号【方塘HCI】
参考资料
【1】ECMAScript简介
【2】MDN web docs
【3】ECMAScript2015~2020语法全解析
【4】阮一峰 ECMAScript6(ES6)标准入门教程 第三版
【5】JavaScript深入之词法作用域和动态作用域