一、支持大部分 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 #PrivateStaticFieldstatic #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 = ShotTargetp.distanceTo(np) = 5instance count : 255static property Version : 2D2Parameter "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';elsepadString = 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 {//leftPartlet leftRepeat = Math.trunc(leftWidth / padStrWidth);let leftRest = leftWidth - leftRepeat * padStrWidth;let leftStr = padString.repeat(leftRepeat) +padString.substr(0, leftRest);//rightPartlet 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('无法设置属性')} elsereturn 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 1313------------------------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;elsesepChar = sepChar.toString()[0];if (byteWidth == undefined)byteWidth = this.DefaultSepLineWidth;if (typeof byteWidth != 'number' ||byteWidth < 0)throw new Error('byteWidth 必须是非负数');elsebyteWidth = Math.floor(byteWidth);let sepLine;if (sepChar.byteWidth() == 1)sepLine = sepChar.repeat(byteWidth);elsesepLine = sepChar.repeat(byteWidth / 2);Console.log(sepLine);};//向控制台输出分隔线Console.__proto__.PrintNamedSepLine =function(name, byteWidth, sepChar) {if (name == undefined)throw new Error('必须提供 name 参数');elsename = name.toString().replace('\n', '').replace('\r', '');if (sepChar == undefined)sepChar = this.DefaultSepLineSepChar;elsesepChar = sepChar.toString()[0];if (byteWidth == undefined)byteWidth = this.DefaultSepLineWidth;if (typeof byteWidth != 'number' ||byteWidth < 0)throw new Error('byteWidth 必须是非负数');elsebyteWidth = 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);elsesepLine = 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';elsereturn 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');elseConsole.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() 功能相反;NAConsole.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-SelectManyConsole.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/trimEndConsole.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.descriptionConsole.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.链式判断运算符;NAConsole.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.matchAllConsole.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, 赋值为 1let 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.replaceAllConsole.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 ...}
