你真的了解ES(ECMAScript)的特性么

2,310 阅读18分钟

没有人能够全面了解ES(ECMAScript)的所有特性 反正我不了解,因为ECMAScript作为一个不断发展和演进的脚本语言标准,其特性随着版本的更新而不断增加和完善。

ECMAScript,简称ES,是由Ecma国际标准化组织制定的脚本语言标准,它为JavaScript等脚本语言提供了一种规范,使得不同的浏览器和环境能够以一致的方式解释和执行代码。自1997年ECMAScript 1发布以来,该标准已经经历了多个版本的更新,每个版本都引入了新的特性和改进。

图片

以下是我对ECMAScript特性的一些归纳和总结:

ES2024 (ECMAScript 2024)

2024 年 6 月 26 日,第 127 届 ECMA 大会正式批准了 ECMAScript 2024 语言规范,下面就来看看 ECMAScript 2024 都有哪些新特性吧!

文档地址:tc39.es/ecma262/202…

  • Promise.withResolvers()Promise.withResolvers() 允许创建一个新的 Promise,并同时获得 resolve 和reject 函数
let resolve, reject;
const promise = new Promise((res, rej) => {
  resolve = res;
  reject = rej;
});

使用 Promise.withResolvers() 的例子:

    const { promise, resolve, reject } = Promise.withResolvers();  

    setTimeout(() => resolve('成功!'), 2000);  

    promise.then(value => {  
      console.log(value); // 输出: 成功!  
    });
  • 正则表达式 /v 标志,ES2024中的正则表达式v标记表示Unicode属性转义。它允许你使用\p{}语法来匹配具有特定Unicode属性的字符。例如,要匹配所有的大写字母,你可以使用\p{Lu}
const text = "Hello World! This is an Example."; 
const pattern = /\p{Lu}/gu; 
const matches = text.match(pattern); 
console.log(matches); // 输出: ['H', 'W', 'T', 'E']
  • 管道操作符(|>),管道操作符允许我们将一个函数的输出直接作为下一个函数的输入,从而简化数据转换的过程。这种特性消除了深层嵌套的函数调用,提高了代码的清晰度和可读性。

模拟 ES2024 管道操作符(|>

// 模拟管道操作符的函数 
function pipe(...funcs) { 
    return function(value) { 
        return funcs.reduce((acc, func) => func(acc), value); 
    }; 
} 
// 示例函数 
const double = x => x * 2; 
const square = x => x * x; 
const addFive = x => x + 5; 
// 使用模拟的管道操作符 
const result = pipe(double, square, addFive)(5); 
console.log(result); // 输出: 55,因为 5 -> 10 -> 100 -> 105 

// 如果管道操作符真的成为ES2024的一部分,你可能会这样写: 
// const result = 5 |> double |> square |> addFive; 
// 但请注意,这行代码在当前的JavaScript环境中不会工作。
// 假设ES2024已支持管道操作符 
const result = 5 |> double |> square |> addFive; 
console.log(result); // 输出: 55
  • 记录和元组(Records and Tuples),记录和元组是不可变的数据结构,确保它们在创建后不能被改变。记录类似于对象,但具有不可变性;元组类似于数组,同样具有不可变性。这种特性提供了一种在JavaScript中管理不可变数据的强大方式,有助于维护应用程序中的可预测状态管理。

  • 数组分组方法(Array Grouping Methods),新增Array.prototype.groupByArray.prototype.groupByToMap方法允许我们基于回调函数对数组元素进行分组,简化了数据分类的过程。这使得处理数据时更加有序和易于管理。

// 假设的 Array.prototype.groupBy 方法 
if (!Array.prototype.groupBy) { Object.defineProperty(Array.prototype, 'groupBy', { 
value: function(iteratee) { 
return this.reduce((result, value) => { 
const key = iteratee(value); 
if (!result[key]) { 
result[key] = [];
} 
result[key].push(value); 
return result; 
}, {}); 
}, 
writable: false, 
configurable: false 
}); 
} 
// 使用示例 const users = [ 
{ 'user': 'fred', 'age': 40, 'active': true }, 
{ 'user': 'pebbles', 'age': 1, 'active': true }, 
{ 'user': 'barney', 'age': 36, 'active': false }, 
{ 'user': 'fred', 'age': 40, 'active': false } 
]; 
const grouped = users.groupBy(user => user.active); console.log(grouped); 
// 输出: 
// { 
    // true: [ 
    // { 'user': 'fred', 'age': 40, 'active': true }, 
    // { 'user': 'pebbles', 'age': 1, 'active': true } 
    // ], 
    // false: [ 
    //     { 'user': 'barney', 'age': 36, 'active': false }, 
    //     { 'user': 'fred', 'age': 40, 'active': false } 
    // ] 
// }
  • Temporal API,Temporal API 提供了一种现代化的方式来处理日期和时间,解决了现有Date对象的许多不足之处。它提供了更精确、更易于理解的日期和时间处理能力,特别是在国际化和处理不同时间区域时非常有用。

请注意,以下代码是基于 Temporal API 的当前提案草案编写的,因此在实际使用时可能需要进行调整,以匹配最终规范。

// 假设 Temporal API 已经被实现 
// 创建一个日期和时间 
const now = Temporal.Now.plainDateTimeISO(); 
console.log(now); // 输出类似 Temporal.PlainDateTime 对象 
// 创建一个具体的日期和时间 
const specificDateTime = Temporal.PlainDateTime.from({ 
    year: 2023, 
    month: 10, // 注意月份是从 1 开始的 day: 1, hour: 14, minute: 30, second: 45, 
    fractionalSecond: 123_456_789n, // 使用BigInt表示纳秒 
    timeZone: "UTC", // 时区,这里使用UTC作为示例 
    calendar: "iso8601" // 日历系统,这里使用ISO 8601作为示例 
}); 
console.log(specificDateTime); // 输出类似 Temporal.PlainDateTime 对象

// 计算两个时间之间的差异 
const earlier = Temporal.PlainDateTime.from({ 
    year: 2023, 
    month: 1, 
    day: 1, 
    timeZone: "UTC" 
}); 
const later = Temporal.PlainDateTime.from({ 
    year: 2024, 
    month: 1, 
    day: 1, 
    timeZone: "UTC" 
}); 
const difference = later.subtract(earlier); 
console.log(difference); // 输出类似 Temporal.Duration 对象,表示两个时间之间的差异 

// 使用 Temporal.Duration 对象 
const duration = Temporal.Duration.from({ 
    days: 3, 
    hours: 5, 
    minutes: 10, 
    seconds: 15, 
    milliseconds: 200, 
    microseconds: 300, 
    nanoseconds: 400n 
}); 
console.log(duration); // 输出类似 Temporal.Duration 对象 
  • Atomics.waitAsyncAtomics.waitAsync()是一个静态方法,用于异步等待共享内存的特定位置并返回一个Promise。与Atomics.wait()不同,waitAsync是非阻塞的,并且可用于主线程。
// 下面来看一个简单的例子
const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT); 
const int32 = new Int32Array(sab); 
Atomics.store(int32, 0, 0); // 初始值设置为 0 // 异步等待 int32[0] 变为 1 
    Atomics.waitAsync(int32, 0, 1, 1000).then(() => { 
    console.log('Condition met!'); 
}).catch(err => { 
    console.error('Error waiting for condition:', err); 
}); 

  • ArrayBuffer.prototype.resizeArrayBuffer 实例的resize() 方法将 ArrayBuffer 调整为指定的大小,以字节为单位,前提是该ArrayBuffer 是可调整大小的并且新的大小小于或等于该 ArrayBuffer的 maxByteLength

我们先模拟 ArrayBuffer.prototype.resize 方法

if (!ArrayBuffer.prototype.resize) {  
    Object.defineProperty(ArrayBuffer.prototype, 'resize', {  
        value: function(newLength) {  
            if (typeof newLength !== 'number' || newLength < 0 || Number.isNaN(newLength)) {  
                throw new RangeError('Invalid ArrayBuffer length');  
            }  
  
            // 如果新长度与旧长度相同,则无需操作  
            if (this.byteLength === newLength) {  
                return;  
            }  
  
            // 创建一个新的 ArrayBuffer  
            const newBuffer = new ArrayBuffer(newLength);  
  
            // 复制旧 ArrayBuffer 的内容到新 ArrayBuffer  
            // 注意:这里我们假设要复制的内容是 Uint8Array,你可以根据需要调整  
            const oldView = new Uint8Array(this);  
            const newView = new Uint8Array(newBuffer);  
  
            // 复制的最小长度是旧长度和新长度中的较小者  
            const copyLength = Math.min(oldView.length, newView.length);  
            for (let i = 0; i < copyLength; i++) {  
                newView[i] = oldView[i];  
            }  
  
            // 这里不能直接替换 this 的引用,因为 ArrayBuffer 是不可变的  
            // 但我们可以返回新的 ArrayBuffer  
            return newBuffer;  
        },  
        writable: false,  
        enumerable: false,  
        configurable: true  
    });  
}  

使用ArrayBuffer.prototype.resize方法:

const buffer = new ArrayBuffer(10); // 创建一个 10 字节的 ArrayBuffer 
const resizedBuffer = buffer.resize(20); // 尝试调整大小到 20 字节 
  • ArrayBuffer.prototype.transfertransfer() 方法执行与结构化克隆算法相同的操作。它将当前 ArrayBuffer 的字节复制到一个新的 ArrayBuffer 对象中,然后分离当前 ArrayBuffer 对象,保留了当前ArrayBuffer 的大小可调整性。
// 创建一个 ArrayBuffer 并写入一些字节
const buffer = new ArrayBuffer(8);
const view = new Uint8Array(buffer);
view[1] = 2;
view[7] = 4;
 
// 将缓冲区复制到另一个相同大小的缓冲区
const buffer2 = buffer.transfer();
console.log(buffer.detached); // true
console.log(buffer2.byteLength); // 8
const view2 = new Uint8Array(buffer2);
console.log(view2[1]); // 2
console.log(view2[7]); // 4
 
// 将缓冲区复制到一个更小的缓冲区
const buffer3 = buffer2.transfer(4);
console.log(buffer3.byteLength); // 4
const view3 = new Uint8Array(buffer3);
console.log(view3[1]); // 2
console.log(view3[7]); // undefined
 
// 将缓冲区复制到一个更大的缓冲区
const buffer4 = buffer3.transfer(8);
console.log(buffer4.byteLength); // 8
const view4 = new Uint8Array(buffer4);
console.log(view4[1]); // 2
console.log(view4[7]); // 0
 
// 已经分离,抛出 TypeError
buffer.transfer(); // TypeError: Cannot perform ArrayBuffer.prototype.transfer on a detached ArrayBuffer
  • String.prototype.isWellFormed,用于测试一个字符串是否是格式正确的(即不包含单独代理项)。
if (!String.prototype.isWellFormed) {  
    String.prototype.isWellFormed = function(pattern) {  
        // 如果没有提供模式,则默认检查字符串是否非空  
        if (!pattern) {  
            return this.trim() !== '';  
        }  
  
        // 使用正则表达式检查字符串是否符合模式  
        return new RegExp(`^${pattern}$`).test(this);  
    };  
}  
  
// 示例使用  
console.log('hello'.isWellFormed(/^hello$/)); // false,因为没有指定整个字符串必须是 "hello"  
console.log('hello'.isWellFormed(/^h.llo$/)); // true,因为 "h" 后面跟着任意字符然后是 "llo"  
console.log('hello'.isWellFormed()); // true,因为没有指定模式,所以默认检查非空
  • String.prototype.toWellFormed,返回一个字符串,其中所有单独代理项都被替换为Unicode替换字符U+FFFD。
if (!String.prototype.toWellFormed) { 
    String.prototype.toWellFormed = function() { 
        // 假设“格式良好”意味着只包含字母、数字和下划线 
        // 将所有非字母数字字符替换为下划线 
        return this.replace(/[^a-zA-Z0-9]/g, '_'); 
    }; 
} 

// 示例使用 
console.log('hello_world!'.toWellFormed()); // 输出: hello_world___ 
console.log('123 abc'.toWellFormed()); // 输出: 123_abc 
console.log('__invalid__name__'.toWellFormed()); // 输出: _______name___

我们一起回顾下历史es版本,看看有什么一系列的变化

ES版本6 (ECMAScript 2015)

ES6的新特性包括:

  • let 和 constlet 和 const 关键字分别用于声明块级作用域的变量和常量。与 var 不同,let 和 const 提供了更好的作用域控制和避免变量提升, 具体可以去菜鸟教程了解下
  • 解构赋值:允许从数组或对象中提取数据并赋值给声明的变量,使得处理复杂数据结构更加便捷。
const person = { 
    name: "Bob", 
    age: 30, 
    job: "Developer" 
}; 
const { name, age } = person; 
console.log(name); // 输出: Bob console.log(age); // 输出: 30 
const [first, second, ...rest] = [1, 2, 3, 4, 5]; 
console.log(first); // 输出: 1 
console.log(rest); // 输出: [3, 4, 5]
  • 模板字符串:使用反引号(`)包裹的字符串,允许在其中嵌入变量和表达式。
let name = "Alice"; 
let greeting = `Hello, ${name}!`; 
console.log(greeting); // 输出: Hello, Alice!
  • 箭头函数:提供了更简洁的函数书写方式,并且不绑定自己的 this,解决了传统函数 this 指向问题。
const numbers = [1, 2, 3, 4]; 
const doubled = numbers.map(n => n * 2); 
console.log(doubled); // 输出: [2, 4, 6, 8]
  • 类和模块:引入了类的语法,支持面向对象编程;同时,模块语法(如ES6的importexport)使得代码的组织和重用更加灵活。
class Person { 
    constructor(name, age)
    { 
        this.name = name;
        this.age = age; 
    } 
    greet() { 
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    } 
} 
const bob = new Person("Bob", 30); 
bob.greet(); // 输出: Hello, my name is Bob and I am 30 years old.

ES7 (ECMAScript 2016)

ES7相对ES6来说变动较小,但引入了几个重要的新特性:

  • Array.prototype.includes() :在数组上添加了一个新方法includes(),用于判断数组中是否包含某个值。
let array = [1, 2, 3]; 
console.log(array.includes(2)); // 输出: true 
console.log(array.includes(4)); // 输出: false
  • 指数运算符( )**:允许使用**来进行幂运算,如2 ** 3等于8。
console.log(2 ** 3); // 输出: 8 
console.log(4 ** 0.5); // 输出: 2(因为4的平方根是2)

ES8 (ECMAScript 2017)

ES8继续扩展了JavaScript的功能:

  • async/await:提供了一种更好的处理异步操作的方式,使得异步代码看起来和同步代码一样。
async function fetchData() { 
    try { 
        let response = await fetch('https://api.example.com/data'); 
        let data = await response.json(); 
        console.log(data); 
    } catch (error) { 
        console.error('Fetch error:', error); 
    } 
} 
fetchData();
  • Object.getOwnPropertyDescriptors() :返回指定对象所有自身属性的属性描述符的键值对数组。这可以用于复制或合并对象的属性,同时保留属性的原始特性(如可枚举性、可配置性等)。
const obj = { prop1: 'value1', prop2: 'value2' }; 
const descriptors = Object.getOwnPropertyDescriptors(obj); console.log(descriptors); 
// 输出类似于:{ prop1: { value: 'value1', writable: true, enumerable: true, configurable: true }, ... }
  • Object.values() 和 Object.entries() :这两个新方法分别用于获取对象的所有值或键值对数组。
const obj = { 
    a: 'somestring',
    b: 42,
    c: false 
}; 
console.log(Object.values(obj)); // 输出: ['somestring', 42, false]
const obj = { 
    foo: 'bar', 
    baz: 42 
}; 
console.log(Object.entries(obj)); 
// 输出: [['foo', 'bar'], ['baz', 42]] 
// 使用for...of循环遍历Object.entries()的结果 
for (const [key, value] of Object.entries(obj)) { 
    console.log(`${key}: ${value}`); 
} 
// 输出: // foo: bar // baz: 42
  • 字符串填充(String.prototype.padStart() 和 String.prototype.padEnd()) :允许在字符串的开头或末尾填充字符,以达到指定的长度。
let str = '5'; 
console.log(str.padStart(3, '0')); 
// 输出: "005" 
// 如果目标长度小于或等于原字符串长度,则返回原字符串 console.log(str.padStart(2, '0')); 
// 输出: "5" 
// 填充字符串也可以包含多个字符 
console.log('abc'.padStart(10, 'xyz'));
// 输出: "xyzxyzabc"

let str = 'hello'; 
console.log(str.padEnd(10, '-')); 
// 输出: "hello-----" 
// 如果目标长度小于或等于原字符串长度,则返回原字符串 
console.log(str.padEnd(4, '-')); 
// 输出: "hello" 
// 填充字符串也可以包含多个字符 
console.log('xyz'.padEnd(10, 'abc')); // 输出: "xyzabcabca"

ES9 (ECMAScript 2018)

ES9引入了更多实用的新特性:

  • 异步迭代(Async Iteration) :允许对异步操作进行迭代,如使用for...of循环遍历异步生成器。
async function* asyncGenerator() { 
    yield 'hello'; 
    yield 'world';
} 
async function asyncConsume() { 
    for await (const value of asyncGenerator()) { 
    console.log(value); 
    } 
} 
asyncConsume(); // 输出: 'hello' 然后是 'world'
  • Promise.prototype.finally() :无论Promise是成功还是失败,finally方法都会被调用,用于执行清理操作。
Promise.resolve(123).finally(() => { 
    console.log('Promise completed');
}); 
// 输出: 'Promise completed'
Promise.reject(new Error('Failed')).finally(() => { 
 console.log('Promise completed (with error)'); 
}); // 输出: 'Promise completed (with error)'
  • 正则表达式命名捕获组(RegExp named capture groups) :允许在正则表达式中使用命名捕获组,方便从匹配结果中引用。
const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u; 
const match = regex.exec('2023-04-01'); 
const { groups: { year, month, day } } = match; 
console.log(year); // 输出: '2023' 
console.log(month); // 输出: '04' console.log(day); // 输出: '01'
  • Rest/Spread 属性(用于对象)

ES9 允许在对象字面量中使用剩余属性(...)语法,这被称为对象剩余属性(Object Rest Properties)和对象展开属性(Object Spread Properties)。

  • 对象展开属性:允许你将一个对象的所有可枚举属性,复制到当前对象之中。
javascript复制代码
	const obj1 = { foo: 'bar', x: 42 };  

	const obj2 = { ...obj1, y: 13 };  

	console.log(obj2); // 输出: { foo: 'bar', x: 42, y: 13 }
  • 注意:虽然对象展开属性在ES9中被正式标准化,但它在之前的某些JavaScript引擎中就已经得到了支持。
  • 对象剩余属性(在ES9中并未直接引入,但通常与展开属性一起讨论):在解构赋值时,可以使用剩余属性来收集对象中未被明确解构的属性。然而,需要注意的是,对象剩余属性并不是ES9的一部分,而是ES2018提案中的一部分,但它在ES2018规范中并未直接定义。不过,它通常与对象展开属性一起被讨论,并且已经在一些JavaScript环境中得到了支持。
javascript复制代码
	const { x, ...rest } = { x: 1, y: 2, z: 3 };  

	console.log(rest); // 输出: { y: 2, z: 3 }

ES10 (ECMAScript 2019)

ES10继续增强了JavaScript的能力:

  • Array.prototype.flat() 和 Array.prototype.flatMap()flat()方法用于将嵌套的数组“拉平”成一维数组,flatMap()map()flat()的结合,先映射后拉平。
  • String.prototype.trimStart() 和 String.prototype.trimEnd() :分别用于去除字符串开头和末尾的空白字符。
  • BigInt:引入了一种新的原始数据类型BigInt,用于表示任意精度的整数。

ES11 (ECMAScript 2020)

ES11进一步扩展了JavaScript的语法和功能:

  • 空值合并运算符(??) :提供了一种更简洁的方式来为变量提供默认值,仅当变量为nullundefined时才会使用默认值。
// 使用 ?? 运算符 
let foo = null ?? 'default string'; 
console.log(foo); // 输出: 'default string' 
let bar = 0 ?? 42; 
console.log(bar); // 输出: 0,因为 0 不是 null 或 undefined 
let baz = '' ?? 'some string'; 
console.log(baz); // 输出: '',因为空字符串不是 null 或 undefined 
let qux = false ?? true; 
console.log(qux); // 输出: false,因为 false 不是 null 或 undefined // 对比逻辑或(||)运算符 
let a = null || 'default string'; 
console.log(a); // 输出: 'default string' 
let b = 0 || 42; 
console.log(b); // 输出: 42,因为 0 是假值 // 使用 ?? 运算符进行链式调用 let obj = null; 
let result = obj?.someMethod() ?? 'default value'; console.log(result); // 输出: 'default value',因为 obj 是 null,?. 运算符不会调用 someMethod,?? 运算符返回右侧的值 

// 注意:?. 是可选链(Optional Chaining)运算符,与 ?? 运算符不同,但经常一起使用来处理可能为 null 或 undefined 的对象属性
  • 可选链(Optional Chaining) :允许通过链式调用访问深层嵌套的属性时,如果某个属性不存在,则不会抛出错误,而是返回undefined
const obj = { foo: { bar: { baz: 42 } } }; 
// 使用可选链安全地访问嵌套属性 
console.log(obj.foo?.bar?.baz); // 输出: 42 console.log(obj.foo?.qux?.baz); // 输出: undefined,因为 qux 是 undefined 
console.log(obj.xyz?.baz); // 输出: undefined,因为 xyz 是 undefined 

// 在函数调用中使用可选链
const objWithMethod = { method() { return 42; } }; 

console.log(objWithMethod?.method?.()); // 输出: 42 console.log(objWithMethod?.otherMethod?.()); // 输出: undefined,因为 otherMethod 是 undefined 

// 结合空值合并运算符使用 
const value = obj.foo?.bar?.baz ?? 'default value'; console.log(value); // 输出: 42 

const defaultValue = obj.foo?.qux?.baz ?? 'default value'; console.log(defaultValue); // 输出: 'default value',因为 qux 是 undefined 

// 在数组中使用可选链 
const arr = [1, 2, 3]; console.log(arr?.[2]); // 输出: 3 console.log(arr?.[3]); // 输出: undefined,因为索引 3 不存在 

// 尝试访问不存在的对象的属性 
const nonExistentObj = null; 
console.log(nonExistentObj?.someProperty); // 输出: undefined,而不是抛出 TypeError
  • Promise.allSettled() :允许你等待所有给定的Promise都完成(无论成功还是失败),并返回一个包含每个Promise结果的数组。

ES12 (ECMAScript 2021)

ES12继续改进JavaScript的语法和性能:

  • 逻辑赋值运算符(&&=、||=、??=) :提供了更简洁的语法来执行赋值操作,同时根据条件进行逻辑运算。
let a = 5; a &&= 10; // 等同于 a = a && 10; 结果 a 变为 10 
let b = 0; b ||= 10; // 等同于 b = b || 10; 结果 b 变为 10 
let c = null; c ??= 10; // 等同于 c = c ?? 10; 结果 c 变为 10
  • 数值分隔符(Numeric Separators) :允许在数字中使用下划线`
let num = 1_000_000; // 等同于 1000000 
let bigNum = 1_000_000_000_000; // 更大的数字
  • Promise.any() 方法接受一个 Promise 迭代对象(比如数组),只要其中有一个 promise 成功,就返回那个已经成功的 promise 的结果。如果所有的 promises 都失败了(或者 promise 数组为空),则返回一个拒绝的 promise。
Promise.any([ Promise.reject(0), Promise.resolve(1), Promise.reject(2) ])
.then(value => { 
  console.log(value); // 输出 1 }
).catch(error => { 
  // 如果没有任何一个 promise 成功,则会执行到这里 
});
  • WeakRef 是一个新的内置对象,允许你持有对对象的弱引用,即这些引用不会被垃圾回收机制考虑在内。这可以用来在不影响对象生命周期的情况下,引用对象。
let obj = {}; 
let ref = new WeakRef(obj); 
if (ref.deref()) { 
  // obj 仍然可用 
} else { 
  // obj 已被垃圾回收 
}

注意:WeakRef 主要用于优化性能和内存使用,特别是在与大型数据结构或长时间运行的应用程序中的缓存机制相关时。

  • 私有字段和方法(Private Fields and Methods),ES12 通过类字段语法支持了类的私有字段和方法,使用 # 前缀来标记。
class MyClass { 
    #privateField = 0; 
    #privateMethod() { 
        return this.#privateField; 
    } 
    publicMethod() { 
        return this.#privateMethod(); // 访问私有方法 
    }
} 
const obj = new MyClass(); 
console.log(obj.publicMethod()); // 访问公共方法,可以间接访问私有字段

ES13 (ECMAScript 2022)

ES13引入了多个新特性,这些特性旨在提升开发者的编程效率和代码的可读性。以下是一些主要的ES13新特性:

  • 顶层Awaitawait运算符用于暂停执行,直到 一个Promise被解决(执行或拒绝)。 以前只能在async中使用此运算符。不可以在全局作用域中直接使用await。
// 假设有一个异步函数fetchData,它返回一个Promise 
async function fetchData() { 
   return new Promise(resolve => setTimeout(() => resolve("Hello ES13!"), 1000)); 
} 
// 在模块顶层使用
await const data = await fetchData(); 
console.log(data); // 输出: Hello ES13! 
  • 类静态代码块:这些代码块只会在类被加载时执行一次
class MyClass { 
    static { 
        console.log("MyClass is being loaded."); // 这里可以执行一些初始化操作 
    } 
    static myStaticMethod() { 
         // ... 
    } 
} 
// 当MyClass被加载时,上面的静态代码块会执行 
// 注意:这个特性目前还是提案阶段,具体语法可能会有所不同
  • at() 函数at() 函数允许你使用正数或负数索引来访问数组或字符串的元素。
const arr = [1, 2, 3, 4, 5]; 
console.log(arr.at(2)); // 输出: 3 
console.log(arr.at(-1)); // 输出: 5,负数索引从末尾开始计数 
const str = "Hello"; 
console.log(str.at(0)); // 输出: H 
console.log(str.at(-1)); // 输出: o 
  • Error对象的Cause属性, 新增一个cause属性,用于指明错误发生的原因。
try { 
    // 假设这里有一些代码可能会抛出错误 
    throw new Error("Something went wrong", { cause: new Error("Initial error") });
} catch (error) { 
    console.log(error.message); // 输出: Something went wrong 
    if (error.cause) { 
        console.log(error.cause.message); // 输出: Initial error 
    } 
} 

  • 正则表达式匹配索引,可以通过指定一个/d正则表达式标志来获取匹配开始和结束的两个索引
const str = '123456';
const reg = /4/d;
res.exec(str);

consloe.log(res.exec(str));
// 输出
/**
[
    '4',
    index: 3,
    input: '1234567',
    groups: undefined,
    indicesL [[3,4],grouops: undefined]
]
*/

  • Object.hasOwn()方法,我们可以使用Object.prototype.hasOwnProperty()方法来检查对象是否具有给定的属性。
class MyClass { 
#privateField = 'private value'; 
publicField = 'public value'; 

constructor() { 
    // 初始化逻辑 
} 
// 一个方法来演示 Object.hasOwn() 的使用 
hasProperty(prop) { 
    return Object.hasOwn(this, prop); 
} 
} 
const instance = new MyClass(); console.log(instance.hasProperty('publicField')); // true console.log(instance.hasProperty('privateField')); // false,因为私有字段不可从外部访问 console.log(instance.hasProperty('someNonExistentField')); // false 

// 直接访问私有字段会抛出错误 
// console.log(instance.#privateField); // SyntaxError
// 如果我们使用Object.create(null)创建一个对象;
const obj = Object.create(null);
obj.name = 'vv';

console.log(obj.hasOwnProperty('name'));
// TypeError: obj.hasOwnProperty 不是函数

利用属性调用方法Object.prototype.hasOwnProperty.call()来解决:

const obj = Object.create(null);
obj.name = 'vv';

function objHasOwnProp(obj, propertyKey) {
    return Object.prototype.hasOwnProperty.call(obj, propertyKey);
}
console.log(objHasOwnProp(obj,'name') // true

现在可以使用其内置方法Object.hasOwn()来处理这个问题。它接受对象和属性作为参数,并且返回一个布尔值,如果指定的属性是对象的直接属性,则返回true;否则返回false。

const obj = Object.create(null);
obj.name = 'vv';
console.log(Object.hasOwn(obj,'name'); // true

ES14 (ECMAScript 2023)

  • 从数组最后查找,其实我们已经可以使用Array的find()方法来查找数组中满足指定条件的元素,也可以使用findIndex()来获取满足条件的元素的索引值。但在某些场景下,从最后一个元素开始搜索可能会更好一点。
const list = [
    {
        label: '老虎',
        value: 1,
    },
    {
        label: '狮子',
        value: 2,
    },
    {
        label: '熊猫',
        value: 3,
    }
];
const findItem = list.find(i=>i.value === 3);
const findIndex = list.findIndex(i=>i.value === 3);
console.log(findItem); // { label:'熊猫', value: 3};
console.log(findIndex); // 2;

新增特性findLast()findLastIndex()方法可以更快地找到所需的元素或索引,从而优化代码性能。

const list = [
    {
        label: '老虎',
        value: 1,
    },
    {
        label: '狮子',
        value: 2,
    },
    {
        label: '熊猫',
        value: 3,
    }
];
const findItem = list.findLast(i=>i.value === 3);
const findIndex = list.findLastIndex(i=>i.value === 3);
console.log(findItem); // { label:'熊猫', value: 3};
console.log(findIndex); // 2;
  • Array.prototype.toSortedtoSorted()具有与sort()相同的签名,但它创建一个新的数组,而不是在原数组上进行操作。

let arr = [5,4,2,3,1]
arr === arr.sort(); // true - [1, 2, 3, 4, 5]

arr === arr.toSorted(); // false - [1, 2, 3, 4, 5]

toSorted()sort()一样,也接受一个可选的参数(比较函数)

const numbers = [10, 5, 2, 7, 3, 9, 1, 6, 4]; 
const sortedNumbers = numbers.toSorted((a, b) => { 
  return b - a; 
}); 
console.log(sortedNumbers); // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

toSorted()也可以应用于对象数组

const list = [
    {
        label: '老虎',
        value: 3,
    },
    {
        label: '狮子',
        value: 1,
    },
    {
        label: '熊猫',
        value: 2,
    }
];

const sortedObjects = list.toSorted((a, b) => {
  return a.value - b.value; 
});
console.log(sortedObjects);

/**
[
    {
        "label": "狮子",
        "value": 1
    },
    {
        "label": "熊猫",
        "value": 2
    },
    {
        "label": "老虎",
        "value": 3
    }
]
*/
  • Array.prototype.toReversedtoReversed()reverse()的复制版。
["a","b","c","d","e"].toReversed(); // ['e', 'd', 'c', 'b', 'a']
  • Array.prototype.withArray.prototype.with新的with()方法允许您根据索引修改单个元素,并返回一个新的数组。
const arr = ["I", "am", "a", "man"];

const newArr = arr.with(3, "superMan");

console.log(newArr); // ['I', 'am', 'a', 'superMan']

结语

在前端开发的道路上,JavaScript 的新特性不断涌现。作为前端开发者,我们应保持学习的热情和敏锐的洞察力,紧跟技术发展的步伐,不断探索和实践这些新特性,为用户带来更加出色的交互体验和高性能的前端应用。同时,积极参与技术社区的交流与分享,共同推动前端技术的进步。

作者:洞窝-明杰