ES6不常见的新特性
DevUI是面向企业中后台产品的开源前端解决方案,其设计价值观基于高效、开放、可信、乐趣四种自然与人文相结合的理念,旨在为设计师、前端开发者提供标准的设计体系,并满足各类落地场景,是一款企业级开箱即用的产品。
感谢我们的DevUI社区贡献者TongxinXie提供的优质好文!
ES6背景介绍
什么是ECMAScript
ECMA International :ECMA国际(前身为欧洲计算机制造商协会),是一个专门为技术制定标准的组织。 ECMAScript是一种由ECMA在标准ECMA-262中定义的脚本语言规范。 ECMAScript是 JavaScript的标准规范,JavaScript是 ECMAScript的具体实现。
ECMAScript就是 JavaScript中的语法规范:
- 语法 -- 解析规则,关键字,语句,声明,操作等;
- 类型 -- Boolean, Number, String, Undefined, Null, Symbol, BigInt等;
- 原型和继承;
- 内置对象和函数的标准库--JSON,Math,数组方法,对象内省的方法等等;
为什么不能叫JavaScript呢?
- 商标版权问题:Java 是 Sun 公司的商标。根据授权,只有Netscape 可以合法使用JavaScript这个名字。且JavaScript已经被 Netscape公司注册为商标。
- 保持标准的开放和中立性:ECMA想告诉大家,这门语言的制定者是ECMA,而不是 Netscape公司,从而有利于保证这门语言的开放性和中立性。
ECMA发展历程
ES6是什么
ES5 其实泛指 ECMAScript 6.0 大版本以前的 JavaScript 标准的统称,概念范围基本等同于ECMAScript5.1版。 ES6 其实泛指ECMAScript 6.0 版以后的 JavaScript 的下一代标准的统称,概念范围涵盖了 ES2015~ES2023。
ES6特性总览
ES6不常见特性
1. 从后往前查找数组(ES2023)
findLast() 和 findLastIndex():用法跟 find() 和 findIndex() 完全一致,区别是从数组的最后一个元素开始向前查找。
ts
const list= [1,2,3,4,5];
// 从前往后遍历
const resultFoward = list.find(element => element > 2); // 3
const indexFoward = list.findIndex(element => element > 2); // 2
//从后往前遍历
const resultBackward = list.findLast(element => element > 2); // 5
const indexBackward = list.findLastIndex(element => element > 2); // 4
2. 不改变原数组的新方法(ES2023)
数组新增无副作用方法:toReversed(), toSorted(), toSpliced()
ts
let original = [1, 3, 2, 4];
// 不改变原数组,返回处理后的新数组
const toReversed = original.toReversed();
console.log(original); // [ 1, 3, 2, 4 ]
console.log(toReversed); // [ 4, 2, 3, 1 ]
const toSorted = original.toSorted();
console.log(original); // [ 1, 3, 2, 4 ]
console.log(toSorted); // [ 1, 2, 3, 4 ]
const toSpliced = original.toSpliced(0,2,'a');
console.log(original); // [ 1, 3, 2, 4 ]
console.log(toSpliced); // [ 'a', 2, 4 ]
修改指定索引值的复制方法版本:with(index,value)
ts
//在数组元素上使用
const origin = [1, 3, 2, 4];
const withed = original.with(1,'a');
console.log(original); // [ 1, 3, 2, 4 ]
console.log(withed); // [ 1, 'a', 2, 4 ]
//在非数组元素上使用, with() 方法会读取 this 上的 length 属性,之后读取 this 上的每个整数键并写入到新数组中,同时 value 会被写入指定的 index
const arrayLike = {
length: 4,
unrelated: "foo",
0: 5,
2: 4,
3: 6
};
console.log(Array.prototype.with.call(arrayLike, 0, 1));// [ 1, undefined, 4, 6 ]
回顾数组原有方法
- 不会改变原数组的方法:concat(), join(), slice(), filter(), map(), toString()
ts
let original = [1, 3, 2, 4];
const sliced = original.slice(1);
console.log(original); // [ 1, 3, 2, 4 ]
console.log(sliced); // [ 3, 2, 4 ]
- 会改变原数组的方法:push()/unshift(), pop()/shift(), splice(), reversed(), sort()
ts
let original = [1, 3, 2, 4];
const sorted = original.sort();
console.log(original); // [ 1, 2, 3, 4 ]
console.log(sorted); // [ 1, 2, 3, 4 ]
3. 类的私有实例字段(ES2022)
ES2022以前,类的实例中的属性都是可以被访问的,为了防止实例被污染,通过#来声明私有属性
ts
class Child{
#name = 'Tom';
name = 'Jerry';
print(){
console.log(this.#name);
}
}
const child = new Child();
child.print(); // Tom
console.log(child.#name); // SyntaxError: Private field '#name' must be declared in an enclosing class
console.log(child.name); // Jerry
4. 类的私有方法和访问器(ES2022)
ts
class Child{
#name = 'Tom';
name = 'Jerry';
// 私有访问器
get #fullName(){
return `${this.name} Smith`;
}
get fullName(){
return `${this.name} Williams`;
}
// 私有实例方法
#getName(){
return this.#name;
}
getName(){
return this.#name;
}
}
const child = new Child();
console.log(child.#fullName); // SyntaxError: Private field '#name' must be declared in an enclosing class
console.log(child.fullName); // Jerry Williams
console.log(child.#getName()); // SyntaxError: Private field '#name' must be declared in an enclosing class
console.log(child.getName()); // Tom
5. 类的静态字段和静态方法(ES2022)
使用符号static来声明静态字段和方法,静态类字段和方法属于整个类,并非某一具体的实例
ts
class Child{
static NAME = 'Tom';
static getName(){
return this.NAME;
}
}
const child = new Child();
console.log(child.NAME); // undefined
console.log(child.getName()); // TypeError: child.getName is not a function
console.log(Child.NAME); // Tom
console.log(Child.getName()); // Tom
6. 类的静态字段和私有静态方法(ES2022)
ts
class Child {
// 静态字段
static NAME = 'Tom';
// 静态私有字段
static #NAME = 'Jerry';
// 静态方法
static getName(){
return this.NAME;
}
// 静态私有方法
static #getName(){
return this.#NAME;
}
}
// 实例无法访问静态字段和静态方法,公有私有都不行
const child = new Child();
console.log(child.NAME); // undefined
console.log(child.#NAME); // Private field '#NAME' must be declared in an enclosing class
console.log(child.getName()); // TypeError: child.getName is not a function
console.log(child.#getName()); // TypeError: child.getName is not a function
// 类只能访问静态公有字段和静态公有方法,无法访问静态私有字段和静态私有方法
console.log(Child.NAME); // Tom
console.log(Child.#NAME); // Private field '#NAME' must be declared in an enclosing class
console.log(Child.getName()); // Tom
console.log(Child.#getName()); // Private field '#getName' must be declared in an enclosing class
7. at() 负索引查找(ES2022)
取数组的倒数元素:.at()
ts
const arr = [1, 2, 3, 4, 5];
// 老方法获取第一个元素
console.log(arr[0]); // 1
// 老方法获取最后一个元素
console.log(arr[arr.length - 1]); // 5
// 获取倒数第一个元素
console.log(arr.at(-1)); // 5
// 获取倒数第二个元素
console.log(arr.at(-2)); // 4
// 获取第一个元素
console.log(arr.at(0)); // 1
// 处理动态数组
console.log(arr.map((i) => (i = i - 1)).at(-1)); // 4
8. 空值合并(ES2020)
旧版本给属性设置默认值的两种方式,但这两种方式有明显弊端,它会覆盖所有的假值,如(0, '', false)
ts
let defaultString = '';
let age = defaultString ? defaultString : '18';
console.log(age); // ''
let defaultHeight = 0;
let height = defaultHeight || 10;
console.log(height); // 10
空位合并操作符( ?? ):对假值的处理更加的全面,当且仅当表达式左侧为 undefined 或 null,就返回其右侧默认值
ts
// 空位合并操作符不会覆盖假值
let defaultVal = 0;
let value = defaultVal ?? 10;
console.log(value); // 0
// undefined 或 null 的场景
let x = null;
let y = x ?? 10;
console.log(y); // 10
9. 逻辑赋值运算符(ES2021)
逻辑运算符 ( &&, ||, ??),赋值表达式 ( =)
ts
a && (a = b);
// 合并写法
a &&= b;
c || (c = e);
// 合并写法
c ||= e;
f ?? (f = g);
// 合并写法
f ??= g;
// 当age的值为null 或 undefined 时,赋值defaultAge
const defaultAge = 18;
let age = null;
age ??= defaultAge;
console.log(age); // 18
10. 数字分隔符(ES2021)
数字组之间使用 _ 作为分隔符来使其在视觉上更具可读性,尤其对于大数场景
ts
1000000000; // 这是多少?
1_000_000_000; // 十亿
1_234_500; // 1234500
console.log(1_000_000_000 === 1000000000); // true
11. 去掉开头结尾空格文本(ES2019)
trimStart() 和 trimEnd() 方法,用于去掉字符串前后的空格
ts
let str = ' This is an apple ';
str.trim(); // 'This is an apple'
str.trimStart(); // 'This is an apple '
str.trimEnd(); // ' This is an apple'
12. flat(),flatMap()(ES2019)
flat() 可指定深度的展开数组
ts
let arr = [1,2,[3,4,[5,6,[7,8]]]];
const flatArr = arr.flat();
console.log(flatArr); // 从外层开始展开数据,默认深度为1,得到输出:[1,2,3,4,[5,6,[7,8]]]
const flatArrDeep = arr.flat(2);
console.log(flatArrDeep); // 从外层开始展开数据,指定深度为2,得到输出:[1,2,3,4,5,6,[7,8]]
const flatArrInfinity = arr.flat(Infinity);
console.log(flatArrInfinity); // 从外层开始展开数据,指定深度为Infinity,得到输出:[1,2,3,4,5,6,7,8]
flatMap(),等价于在调用 map() 方法后再调用深度为 1 的 flat() 方法
ts
let arr = [1,2,3,4,5,6,7,8];
const map = arr.map(num => [num * 2]);
console.log(map); // [[ 2 ],[ 4 ],[ 6 ],[ 8 ],[ 10 ], [ 12 ],[ 14 ], [ 16 ]]
const result = arr.flatMap(num => [num * 2]);
console.log(result); // [2,4,6,8,10,12,14,16];
13. 装饰器(Decorator)
是Javascript类(class)的一个增强功能,是面向对象的语言的一种常见用法。
装饰器是一种函数,使用:@ + 函数名。装饰器函数的返回值,是一个新版本的装饰对象,但也可以不返回任何值(void)。 类 类的属性 类的方法 属性存取器(accessor)
ts
type Decorator = (value:Input, context:{
kind: string;
name: string | symbol;
access: {
get?(): unknown;
set?(value: unknown): void;
};
private?:boolean;
static?: boolean;
addInitializer?(initializer: () => void):void;
}) => Output | void;
装饰器的执行步骤如下。 计算各个装饰器的值,按照从左到右,从上到下的顺序。 调用方法装饰器。 调用类装饰器。
业务应用案例 - 方法装饰器
写一个方法装饰器 EventTrack 实现前端埋码平台的事件埋码逻辑
ts
export function EventTrack(eventId:EVENT_LIST,eventType: EVENT_TYPE,eventDesc:string) {
return (target: Object, key: string, descriptor: PropertyDescriptor) => {
const origin = descriptor.value;
descriptor.value = function(...args:any) {
try {
// 埋点代码
}catch(err) {}
return origin.apply(this, args);
};
return descriptor;
}
}
业务代码内使用此方法装饰器,装饰器的优点为:不污染原有方法业务,不用插入多余代码影响原有代码逻辑
ts
@EventTrack(EVENT_LIST.START_VIRUS,EVENT_TYPE.CUSTOM, EVENT_DESC[EVENT_LIST.START_VIRUS].SELECT_ENGINE)
someFunc(){
// 业务代码
}
业务应用案例 - 属性装饰器
通过属性装饰器实现:根据传入的事件来控制某一个公共头部组件 app-version-operation 中,各类操作按钮是否显示的逻辑。
首页,我们写一个属性装饰器 Detection, 通过 Detection 检测并设置当前对象私有属性的值。后续就可以通过判断该私有属性是否有值来判断对应事件是否传入。
ts
export function Detection(target, name) {
let event = new EventEmitter<any>();
Object.defineProperty(target, name, {
get() {
// 设置私有属性的值
this['#' + name] = true;
return event;
}
});
}
完成 Detection 的实现后,我们再通过装饰器 HasEvent 来控制是否显示的属性值。
ts
export function HasEvent(eventName) {
return (target, name) => {
Object.defineProperty(target, name, {
get(){
// 判断是否存在此私有属性
return this['#' + eventName];
}
});
}
}
在组件中,如果有传入baseline事件,我们通过装饰器 Detection 来设置私有变量 #baseline 为 true。然后我们再通过装饰器 HasEvent 来对变量 showBaseline进行赋值,如果私有变量 #baseline 为 true,那么showBaseline的值也会设置为true。页面上就会展示baseline操作对应的按钮。
ts
@Output() @Detection baseline;
@HasEvent('baseline') showBaseline;
组件侧,只用判断是否传入事件即可同时控制按钮是否显示。不需要显示此操作按钮的场景,直接不绑定事件即可。简化了组件使用的复杂度。
ts
<app-version-operation (baseline)="baselineEvent()"></app-version-operation>
🔥 加入我们
DevUI是面向企业中后台产品的开源前端解决方案,其设计价值观基于高效、开放、可信、乐趣四种自然与人文相结合的理念,旨在为设计师、前端开发者提供标准的设计体系,并满足各类落地场景,是一款企业级开箱即用的产品。
- DevUI Design 官网: devui.design/home
- GitHub仓库-Angular: github.com/DevCloudFE/...
- GitHub仓库-Vue: github.com/DevCloudFE/...
如果你今天刚刚加入我们,可以先看看官网上的示例组件,你可以在左侧导航栏中切换想要查看的组件,然后通过右侧的快速前往在不同Demo之间切换。
如果你准备添加 Vue DevUI,请前往快速开始文档,只需要几行代码。
如果你对我们的开源项目感兴趣,并希望参与共建,欢迎加入我们的开源社区,关注DevUI微信公众号:DevUI 。
文 / DevUI社区贡献者 TongxinXie
文献引用
- ecma-international.org/publication... ECMA官网
- developer.mozilla.org/en-US/docs/... MDN
- es6.ruanyifeng.com/#docs/intro 阮一峰 ECMAScript 6 入门
- zh.wikipedia.org/wiki/ECMASc... ECMA维基百科