一、支持大部分 ES6 特性
1.class
下面是关于 ES6 引入的经典类的试验代码:
/*参数类型异常 :用于封装参数类型异常信息*/
class ParameterTypeError extends Error {
constructor(paramName, paramType) {
if (!(typeof paramName == 'string'))
throw new ParameterTypeError('paramName', String);
if (!(paramType instanceof Function))
throw new ParameterTypeError('paramType', Function);
let msg = 'Parameter "' + paramName +
'" must be a ' + paramType.name +
' object.';
super(msg);
this.ParameterName = paramName;
this.ParameterType = paramType;
}
toString() {
return ParameterTypeError.name + ' : ' + this.message;
}
static check(param, paramName, paramType) {
if (!((param instanceof paramType) ||
(typeof param == paramType.name.toLowerCase())))
throw new ParameterTypeError(paramName, paramType);
}
}
/*经典类
它只支持如下形式:
class ClassName {
//现行(ES6)支持的类成员类型如下:
InstanceMethod() {...}
static StaticMethod() {...}
get InstanceProperty() {...}
set InstanceProperty(value) {...}
static get StaticProperty() {...}
static set StaticProperty(value) {...}
//ES2022 state4(finished) 提案增补类成员类型如下:
InstanceField //公有实例字段
#PrivateInstanceField
#PrivateInstanceMethod() {...}
get #PrivateInstanceProperty {...}
set #PrivateInstanceProperty {...}
static StaticField //公有静态字段
static #PrivateStaticField
static #PrivateStaticMethod {...}
//暂缺提案支持的成员类型如下:
static get #PrivateStaticProperty {...}
static set #PrivateStaticProperty {...}
//TC39 state3 阶段提案支持
static {...} //静态构造函数
}*/
class Point {
constructor(x, y) {
ParameterTypeError.check(x, 'x', Number);
ParameterTypeError.check(y, 'y', Number);
this.x = x;
this.y = y;
Point.count++;
}
static get count() {
if (Point._count == undefined)
Point._count = 0;
return Point._count;
}
static set count(value) {
if (Point._count == undefined)
Point._count = 0;
Point._count = value;
}
static get Version() {
return '2D';
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
distanceTo(p) {
ParameterTypeError.check(p, 'p', Point);
return Math.sqrt(
Math.pow(this.x - p.x, 2) +
Math.pow(this.y - p.y, 2));
}
static distanceBetween(p1, p2) {
ParameterTypeError.check(p1, 'p1', Point);
ParameterTypeError.check(p2, 'p2', Point);
return Math.sqrt(
Math.pow(p1.x - p2.x, 2) +
Math.pow(p1.y - p2.y, 2));
}
}
class NamedPoint extends Point {
constructor(name, x, y) {
ParameterTypeError.check(name, 'name', String);
super(x, y);
this._name = name;
}
get name() {
return this._name;
}
toString() {
return this._name + super.toString();
}
}
function classTest() {
let p = new Point(3, 4);
Console.log('p = ' + p);
let np = new NamedPoint("ShotTarget", 6, 8);
Console.log('np = ' + np);
//为只读属性赋值是无效的,但也不报错
np.name = "WTF";
Console.log('np = ' + np);
Console.log('np.name = ' + np.name);
Console.log('p.distanceTo(np) = ' + p.distanceTo(np));
Console.log('instance count : ' + Point.count);
//静态方法,会被子类继承
Console.log(NamedPoint.distanceBetween(np, p));
//实例方法,会被子类继承
Console.log(np.distanceTo(p));
//静态属性
Console.log('static property Version : ' + Point.Version);
//静态属性,会被子类继承
Console.log(NamedPoint.count);
//自定义异常
try {
p.distanceTo(33);
} catch(e) {
Console.log(e.message);
Console.log(e);
}
}
其输出如下:
p = (3, 4)
np = ShotTarget(6, 8)
np = ShotTarget(6, 8)
np.name = ShotTarget
p.distanceTo(np) = 5
instance count : 2
5
5
static property Version : 2D
2
Parameter "p" must be a Point object.
ParameterTypeError : Parameter "p" must be a Point object.
2.Proxy
//居中填充
String.prototype.padCenter =
function(targetLength, padString = ' ') {
if (typeof targetLength != 'number')
throw new TypeError('Parameter "targetLength" ' +
'must be a number object.');
if (typeof padString != 'string') {
if (padString === null)
padString = 'null';
else
padString = padString.toString();
}
let padStrWidth = padString.length;
if (padStrWidth == 0) return this;
let restWidth = targetLength - this.length;
if (restWidth <= 0) return this;
let leftWidth = Math.trunc(restWidth / 2);
let rightWidth = leftWidth + restWidth % 2;
if (padString.length == 1) {
return padString.repeat(leftWidth) + this +
padString.repeat(rightWidth);
} else {
if (leftWidth == 0)
return this + padString[0];
else {
//leftPart
let leftRepeat = Math.trunc(leftWidth / padStrWidth);
let leftRest = leftWidth - leftRepeat * padStrWidth;
let leftStr = padString.repeat(leftRepeat) +
padString.substr(0, leftRest);
//rightPart
let rightRepeat = Math.trunc(rightWidth / padStrWidth);
let rightRest = rightWidth - rightRepeat * padStrWidth;
let rightStr = padString.repeat(rightRepeat) +
padString.substr(0, rightRest);
return leftStr + this + rightStr;
}
}
}
/*Proxy handler 可对应于
Reflect.apply()
Reflect.construct()
Reflect.defineProperty()
Reflect.deleteProperty()
Reflect.get()
Reflect.getOwnPropertyDescriptor()
Reflect.getPrototypeOf()
Reflect.has()
Reflect.isExtensible()
Reflect.ownKeys()
Reflect.preventExtensions()
Reflect.set()
Reflect.setPrototypeOf()
实现想要的代理
*/
class ES6ProxyTest {
//既然 Reflect.set(target, key, value[, receiver]) 有
//要设置的属性的键和值,我们就可以通过 set 代理,在一个
//对象的定义的外部:
//1.通过键,拦截对某些属性的写入
//2.通过值,检验类型,拦截非法写入
//3.通过键,重定向属性的写入,就像为属性设置一些假名一样
static SetProxy() {
let p = new Point(3, 8);
let pp = new Proxy(p, {
set:function(target, key, value, receiver){
//Reflect.set(target, key, value[, receiver])
//target : 用于接收属性(被代理的对象)的对象
//key : 要写入的属性的键(字符串或Symbol类型)
//value : 要写入的属性新值
//receiver : 如果 target 对象的 key 属性有 setter,
// receiver 则为 setter 调用时的 this 值。
//return : 返回一个 Boolean 值,表明操作的成败
let success = Reflect.set(target, key, value, receiver);
if (success) {
//Console 在此不可用
Debug.Print('property '+ key +' on '+
target + ' set to '+ value);
}
//必须返回操作成败状态,否则报错
return success;
}
});
pp.xxx = 13;
Console.log(p.xxx);
}
//既然 Reflect.get(target, key[, receiver]) 提供了要读取
//的属性的键,我们就可以通过 get 代理,在对象的定义的外部:
//1.通过键,拦截对某些属性的读取
//2.通过键,伪造一些不存在的属性
//3.通过键,实现类同假名的属性
static GetProxy() {
var obj = new Proxy({}, {
get: function (target, key, receiver) {
//Console 在此不可用
Debug.Print(`getting ${key}!`);
//Reflect.get(target, key[, receiver])
//target : 被代理的对象
//key : 要读取的属性的键(字符串或Symbol类型)
//receiver : 如果 target 对象的 key 属性有 getter,
// receiver 则为 getter 调用时的 this 值。
//return : 属性的值。
return Reflect.get(target, key, receiver);
}
});
obj.count = 1;
++obj.count;
}
/*Reflect.apply(target, thisArg, argsList)
target : 被代理对象,请确保它是一个 Function 对象
thisArg : 函数调用时绑定的对象
argsList : 函数调用时传入的实参列表,该参数应该是一个类数组的对象。
return : 调用函数返回的结果
通过这种代理:
1.检验调用时传入的参数
2.阻止函数被调用
*/
static ApplyProxy() {
function sum (...values){
return values.reduce((pre, cur) => pre + cur, 0);
}
let sumProxy = new Proxy(sum, {
apply : function(target, thisArg, argsList){
argsList.forEach(arg => {
if(typeof arg !== "number")
throw new TypeError("所有参数必须是数字,亲!");
});
return Reflect.apply(target, thisArg, argsList);
}
});
try {
let r = sumProxy(3, 5, 'hello');
Console.log(r);
} catch(e) {
Console.log(e.message);
}
Console.log(sumProxy(3, 8, 5));
}
/*Reflect.construct(target, argsList[, newTarget])
target : 被运行的目标构造函数
argsList : 类数组,目标构造函数调用时的参数。
newTarget : 可选,作为新创建对象的原型对象的 constructor 属性,
参考 new.target 操作符,默认值为 target。
*/
static ConstructProxy() {
function sum (...values){
return values.reduce((pre, cur) => pre + cur, 0);
}
let sumProxy = new Proxy(sum, {
construct:function(target, argsList){
throw new TypeError("亲,该函数不能通过 new 调用。");
}
});
try {
let x = new sumProxy(3, 5, 7);
} catch(e) {
Console.log(e.message);
}
}
//禁止向指定单元格区域写入数据
static ForbidSetValue() {
let rng = new Range('B1:D3');
rng.Value2 = 32;
let rngProxy = new Proxy(rng, {
set : function(target, key, value, receiver) {
if (key === 'Value2') {
throw new Error('无法设置属性')
} else
return Reflect.set(target, key, value, receiver);
}
});
try {
rngProxy.Value2 = 168;
} catch(e) {
Console.log(e.message);
}
Console.log(rngProxy.Text);
}
//运行所有测试用例
static RunAll() {
let members = Object.getOwnPropertyNames(ES6ProxyTest);
let notCall = ['length', 'prototype', 'name', 'RunAll'];
for (let member of members) {
if (!notCall.includes(member)) {
Console.log(member.padCenter(56, '-'));
eval(`ES6ProxyTest.${member}()`);
}
}
}
}
在立即窗口中输入 ES6ProxyTest.RunAll();
,然后回车,其输出如下:
------------------------SetProxy------------------------
property xxx on (3, 8) set to 13
13
------------------------GetProxy------------------------
getting count!
-----------------------ApplyProxy-----------------------
所有参数必须是数字,亲!
16
---------------------ConstructProxy---------------------
亲,该函数不能通过 new 调用。
---------------------ForbidSetValue---------------------
无法设置属性
32
二、实测支持到 ES2019
0.示例代码运行环境的设置代码
因为使用了全局表达式,请将【工具】》【选项】》【编译】》【禁止全局作用域表达式】取消勾选
//获取字符串位宽(即字符串占的字符宽度)
String.prototype.byteWidth = function() {
var length = 0;
Array.from(this).map(function(char){
//字符编码大于255,说明是双字节字符
if (char.charCodeAt(0) > 255) {
length += 2;
} else {
length++;
}
});
return length;
};
{/*Console 对象的增强*/
//控制台默认分隔线字符
Console.__proto__.DefaultSepLineSepChar = '-';
//控制台默认的分隔线宽度
Console.__proto__.DefaultSepLineWidth = 56;
//向控制台输出分隔线
Console.__proto__.PrintSepLine =
function(byteWidth, sepChar) {
if (sepChar == undefined)
sepChar = this.DefaultSepLineSepChar;
else
sepChar = sepChar.toString()[0];
if (byteWidth == undefined)
byteWidth = this.DefaultSepLineWidth;
if (typeof byteWidth != 'number' ||
byteWidth < 0)
throw new Error('byteWidth 必须是非负数');
else
byteWidth = Math.floor(byteWidth);
let sepLine;
if (sepChar.byteWidth() == 1)
sepLine = sepChar.repeat(byteWidth);
else
sepLine = sepChar.repeat(byteWidth / 2);
Console.log(sepLine);
};
//向控制台输出分隔线
Console.__proto__.PrintNamedSepLine =
function(name, byteWidth, sepChar) {
if (name == undefined)
throw new Error('必须提供 name 参数');
else
name = name.toString()
.replace('\n', '')
.replace('\r', '');
if (sepChar == undefined)
sepChar = this.DefaultSepLineSepChar;
else
sepChar = sepChar.toString()[0];
if (byteWidth == undefined)
byteWidth = this.DefaultSepLineWidth;
if (typeof byteWidth != 'number' ||
byteWidth < 0)
throw new Error('byteWidth 必须是非负数');
else
byteWidth = Math.floor(byteWidth);
let sepLine = name;
let selfWidth = name.byteWidth();
if (byteWidth > selfWidth) {
let restWidth = byteWidth - selfWidth;
let leftPadWidth = Math.floor(restWidth / 2);
let rightWidth = Math.floor(restWidth / 2) +
restWidth % 2;
if (sepChar.byteWidth() == 1)
sepLine = sepChar.repeat(leftPadWidth) +
sepLine + sepChar.repeat(rightWidth);
else
sepLine = sepChar.repeat(leftPadWidth / 2) +
sepLine + sepChar.repeat(rightWidth /2);
}
Console.log(sepLine);
};
//向控制台一次输出多个值,避免手动拼接字符串
Console.__proto__.WriteAll = function() {
if (arguments.length == 0) return;
let info = Array.prototype.map
.call(arguments, v => {
if (v === null)
return 'null';
else if(v === undefined)
return 'undefined';
else
return v.toString();
})
.join('; ');
Console.log(info);
};
//向控制台作讲述式输出
Console.__proto__.Tell = function(description, value) {
if (value === null)
Console.log(description + ' : null');
else if (value === undefined)
Console.log(description + ' : undefined');
else
Console.log(description + ' : ' + value.toString());
};
}
1.ES2016_ES7 特性测试
function ES2016_ES7() {
//ES2016 只添加了两个功能:
let arr = [1, 2, 3];
//1. Array.prototype.includes()
if (typeof arr.includes == 'function') {
Console.Tell('arr.includes(2)', arr.includes(2));
Console.Tell('arr.includes(31)', arr.includes(31));
}
//2. 指数运算符
Console.Tell('3**3', 3**3);
}
2.ES2017_ES8 特性测试
function ES2017_ES8() {
let obj = { one : 1, two : 2, three : 3 };
//1.Object.values() 获取对象的所有属性值
Console.PrintNamedSepLine('Object.values(obj)');
if (typeof Object.values == 'function') {
let values = Object.values(obj);
Console.log(values instanceof Array);
Console.log(values.toString());
}
//2.Object.entries() 获取对象的属性名-值对
Console.PrintNamedSepLine('Object.entries(obj)');
if (typeof Object.entries == 'function') {
let entries = Object.entries(obj);
Console.log(entries instanceof Array);
Console.log(entries[0] instanceof Array);
Console.log(entries[0][0] + ' : ' + entries[0][1]);
for (let [k, v] of entries)
Console.log(k + ' => ' + v);
}
//3.Object.getOwnPropertyDescriptors() 获取一个对象的所有自身属性的
// 描述符。属性描述符(property descriptor)对象:
//value:就是属性的值,默认 undefined
//writable:决定属性能否被赋值
//get:访问器函数(getter),函数或 undefined,在取属性值时被调用
//set:设置器函数(setter),函数或 undefined,在设置属性值时被调用
//enumerable:决定 for in 或 Object.keys 能否枚举该属性
//configurable:决定该属性能否被删除,以及除 value 和 writable 外的
// 其他特性是否可以被修改
Console.PrintNamedSepLine('Object.getOwnPropertyDescriptors(obj)');
if (typeof Object.getOwnPropertyDescriptors == 'function') {
let x = Object.getOwnPropertyDescriptors(obj);
Console.Tell('Object.getOwnPropertyDescriptors(obj)', x);
}
//4.String.prototype.padStart()/padEnd() 字符串左/右填充
Console.PrintNamedSepLine('String.padStart/padEnd');
if (typeof String.prototype.padStart === 'function' &&
typeof String.prototype.padEnd == 'function') {
let str = 'hello';
Console.log(str.padStart(10, '+'));
Console.log(str.padEnd(10, '-'));
}
//5.结尾逗号,对象定义、数组定义和函数参数列表等等
Console.PrintNamedSepLine('尾逗号');
{
let gift = {
Name : 'Computer',
Price : 5000,
Color : 'black',
};
Console.log(JSON.stringify(gift));
let exGrilFriends = [
'Lily Morgen',
'John Smith',
'Kate William',
];
Console.log(exGrilFriends.toString());
let func = function(
firstPartNumber,
secondPartNumber,
) { Console.log(firstPartNumber + '/' + secondPartNumber); }
func(321, 123);
}
//6.ShareArrayBuffer ...
}
3.ES2018_ES9 特性测试
function ES2018_ES9() {
{//1.剩余参数
Console.PrintNamedSepLine('剩余参数');
let func = function(a, b, ...rest) {
let sum = a + b;
if (rest.length == 0)
return sum;
else {
for (let x of rest)
sum += x;
return sum;
}
};
Console.log(func(1, 2));
Console.log(func(1, 2, 3));
Console.log(func(1, 2, 3, 4));
}
{//2.正则表达式 s 模式,dotAll,即包含换行符
Console.PrintNamedSepLine('正则表达式 s 模式');
let isMatch = /hello.es9/.test('hello\nes9');
Console.log(isMatch);
isMatch = /hello.es9/s.test('hello\nes9');
Console.log(isMatch);
}
{//3.正则之命名捕获组
Console.PrintNamedSepLine('正则之命名捕获组');
let str = "Jim is 12.";
let re = /(?<name>\w+) is (?<age>\d+)/;
let matches = re.exec(str);
Console.log(matches.toString());
for (let ppt in matches)
Console.log(ppt);
Console.log(matches.groups);
for (let ppt in matches.groups)
Console.log(ppt);
Console.log('age is ' + matches.groups['age']);
}
{//4.正则表达式之反向断言
Console.PrintNamedSepLine('正则表达式之反向断言');
let s = 'he is 12 years old. he has 3 cars.';
//4.1.右匹配断言 : (?=...)
Console.log('4.1 => ' + s.match(/\d+(?= cars)/));
//4.2.右不匹配断言 : (?!...)
Console.log('4.2 => ' + s.match(/\d+(?! cars)/));
//4.3.左匹配断言 : (?<=...)
Console.log('4.3 => ' + s.match(/(?<=has )\d+/));
//4.2.左不匹配断言 : (?<!...)
Console.log('4.4 => ' + s.match(/(?<!has )\d+/));
}
{//5.正则表达式 u 模式,即字符集模式,支持\p{字符集名},
// 或\P{字符集名}形式匹配...
Console.PrintNamedSepLine('正则表达式 u 模式')
let re = /\p{White_Space}+/u;
let wsStrs = ['\n', '\r', ' ', '\n\r'];
wsStrs.forEach(x => Console.log(re.test(x)));
}
//6.async/await...
//7.promise.finally...
}
4.ES2019_ES10 特性测试
function ES2019_ES10 () {
//1.Object.fromEntries() 从对象的属性名值对数组转换回对象,
// 与 Object.entries() 功能相反;NA
Console.PrintNamedSepLine('Object.fromEntries');
if (typeof Object.fromEntries == 'function') {
let obj = { one : 1, two : 2, three : 3 };
let entries = Object.entries(obj);
let objSame = Object.fromEntries(entries);
Console.log(objSame);
}
//2.Array.prototype.flat 将数组打平
Console.PrintNamedSepLine('Array.prototype.flat([depth])');
if (typeof Array.prototype.flat === 'function') {
Console.Tell('[1, [2, 3]].flat()', JSON.stringify(
[1, [2, 3]].flat()));
Console.Tell('[1, [2, 3, [1]]].flat()', JSON.stringify(
[1, [2, 3, [1]]].flat()));
Console.Tell('[1, [2, 3, [1]]].flat(2)', JSON.stringify(
[1, [2, 3, [1]]].flat(2)));
}
//3.Array.prototype.flatMap 类同于 Linq-SelectMany
Console.PrintNamedSepLine('Array.prototype.flatMap');
if (typeof Array.prototype.flatMap === 'function') {
Console.Tell('[1, [2, 3]].flatMap(x => x)', JSON
.stringify([1, [2, 3]].flatMap(x => x)));
let strs = ['Jobs Steven', 'is', 'great'];
let elementProcessor = x => x.split(' ');
Console.log(JSON.stringify(strs) + '.flatMap(' +
elementProcessor.toString() + ') :\n' +
JSON.stringify(strs.flatMap(elementProcessor)));
Console.log('[1, 2, 3].map(x => [x * 2]) :\n' +
JSON.stringify([1, 2, 3].map(x => [x * 2])));
Console.log('[1, 2, 3].flatMap(x => [x * 2]) :\n' +
JSON.stringify([1, 2, 3].flatMap(x => [x * 2])));
}
//4.String.prototype.trimStart/trimEnd
Console.PrintNamedSepLine('String.prototype.trimStart/trimEnd');
if (typeof String.prototype.trimStart === 'function' &&
typeof String.prototype.trimEnd === 'function') {
let s = ' yes ';
Console.log(JSON.stringify(s) + '.trimStart() = ' +
JSON.stringify(s.trimStart()));
Console.log(JSON.stringify(s) + '.trimEnd() = ' +
JSON.stringify(s.trimEnd()));
}
//5.Symbol.protoype.description
Console.PrintNamedSepLine('Symbol.protoype.description');
if (Symbol.iterator.description != undefined) {
Console.log('Symbol.iterator.description = ' +
Symbol.iterator.description);
}
//6.Array.prototype.sort([compareFunction]) : 增加了一个参数
// 可以按照指定规则排序,而不是默认的将元素字符串化后排序
Console.PrintNamedSepLine('Array.prototype.sort([compareFunction])');
{
let names = ['Joe Smith', 'Jobs Steven', 'Bill Gates'];
let compFunc = (a, b) => a.length - b.length;
Console.log(JSON.stringify(names));
names.sort(compFunc);
Console.log(JSON.stringify(names));
}
}
5.ES2020_ES11 特性测试
function ES2020_ES11() {
//1.链式判断运算符;NA
Console.PrintNamedSepLine('链式判断运算符');
try {
let obj = { one : 1, two : 2, three : 3 };
//直接写下面这句会编译不过,所以在这儿用 eval
//Console.log(obj?.yes?.toExponential());
eval('Console.log(Math?.dk?.good == undefined)');
} catch { Console.log('Nonsupport'); }
//2.String.prototype.matchAll
Console.PrintNamedSepLine('String.prototype.matchAll')
if (typeof String.prototype.matchAll == 'function') {
let regex = /t(e)(st(\d?))/g;
let string = 'test1test2test3';
let allMatches = [...string.matchAll(regex)];
Console.log(allMatches.toString());
}
//3.Null判断运算符 : 如果左值是 null/undefined,使用右值
Console.PrintNamedSepLine('Null判断运算符');
try {
let x = eval('ksdfksk ?? false');
Console.log(x);
} catch { Console.log('Nonsupport'); }
//4.Bigint : 新增类型
Console.PrintNamedSepLine('Bigint 类型');
Console.log('是否支持 Bigint 类型?:' +
(typeof Bigint != 'undefined'));
//5.Promise.*...
//6.import ...
//7.export ...
}
6.ES2021_ES12 特性测试
function ES2021_ES12() {
//1.复合逻辑赋值运算符:??=/&&=/||=
Console.PrintNamedSepLine('复合逻辑赋值运算符');
try {
//以下相当于 let xx = xx ?? 1;
//即,如果 xx == null/undefined, 赋值为 1
let x = eval('let xx ??= 1; xx;');
Console.log(x);
} catch { Console.log('Nonsupport'); }
try {
//以下相当于 let yy = yy && true;
let y = eval('let yy &&= true; yy;');
Console.log(y);
} catch { Console.log('Nonsupport'); }
try {
//以下相当于 let zz = zz || true;
let z = eval('let zz ||= true; zz;');
Console.log(z);
} catch { Console.log('Nonsupport'); }
//2.数字分隔符
Console.PrintNamedSepLine('数字分隔符');
try {
let num = eval('1_5000');
Console.log(num);
} catch { Console.log('Nonsupport'); }
//3.String.prototype.replaceAll
Console.PrintNamedSepLine('String.prototype.replaceAll');
if (typeof String.prototype.replaceAll === 'function') {
Console.log("'hello world'.replaceAll('o', '_') :" +
'hello world'.replaceAll('o', '_'));
}
//4.WeakRefs & FinalizationRegistry对象 ...
//5.Promise.any & AggregateError ...
}