在过去五年里,影响最大的JavaScript 特性!!

4,314 阅读13分钟

介绍

技术总是在不断发展,JavaScript 自 1995 年提出诞生以来发生了很多的变化,从那时起到现在它添加了许多新功能。本文讨论了过去 5 年来 JavaScript 中新增的一些超级有用(但可能不太为人所知)的特性!但是本文可能囊括的并不全。如有遗漏欢迎在评论区指出!

String.prototype.padStart() 和 String.prototype.padEnd()

因为padStart 和 padEnd 不是挂载到string的原型上的方法,所以我们可以通过String.padStart()String.padEnd() 直接调用。这两个字符串方法都是将字符串插入到其他字符串的方法。顾名思义,String.padStart()将一个新字符串添加到给定字符串的开头,并将String.padEnd()一个字符串添加到给定字符串的末尾。

注意:这些方法不会改变原来的字符串。

String.padStart(desiredStringLength, stringToAdd)

  • desiredStringLength:你希望得到新字符串的长度。
  • stringToAdd:这是将要添加到原始字符串开头的字符串

让我们看一个例子:

代码示例:

//最初的字符串
let originalString = 'Script';

//对原始的字符串添加字符串
let paddedString = originalString.padStart(10, 'Java');

console.log(paddedString);

// 输出 -->
// 'JavaScript'

如果“我们希望得到的新字符串的长度原始字符串的长度+要添加的字符串” 。会发生什么情况呢?

在这种情况下,String.padStart会把添加到原始字符串开头的字符串多余的部分截掉

例子:

let originalString = 'Script';

let paddedString = originalString.padStart(7, 'Java');

console.log(paddedString);

// 输出 -->
// 'JScript'
// 把将要添加到原始字符串开头的字符串从“Java”截断为“J”

如果我们希望得到的新字符串长度“原始字符串的长度+要添加的字符串”会发生什么情况呢

这可能会导致结果不符合我们的预期!因为String.padStart会把将要添加到原始字符串开头的字符串进行重复插入,直到长度等于我们所希望得到新字符串长度

代码示例:

let originalString = 'Script';

let paddedString = originalString.padStart( 15, 'Java');

console.log(paddedString);

// 输出 -->
// 'JavaJavaJScript'

如果我们没有传入 "将要添加到原始字符串开头的字符串" 又会发生什么呢?

String.padStart会在原始字符串前面添加空格,直到字符串长度等于 我们希望的新字符串长度

代码示例:

let originalString = 'Script';

let paddedString = originalString.padStart(15);

console.log(paddedString);

// 输出 -->
// "         Script"

最后,如果我们没有传入 "我们希望的新字符串长度" 参会发生什么情况呢?

String.padStart会把原始字符串的副本被原封不动地返回:

代码示例:

let originalString = 'Script';

let paddedString = originalString.padStart('Java');

console.log(paddedString);

// 输出 --> 
// 'Script'

String.padEnd(desiredStringLength, stringToAppend)

  • desiredStringLength:你希望得到新字符串的长度。
  • stringToAdd:这是将要添加到原始字符串开头的字符串

String.padEnd字符串方法的工作方式与 String.padStart()相同,但不同的是 String.padEnd会将字符串被添加到给定字符串的末尾。

代码示例:

let originalString = 'Web';

let paddedString = originalString.padEnd(6, 'Dev');

console.log(paddedString);

// 输出 -->
// 'WebDev

相同的规则适用于参数使用:

  • desiredStringLength < 原始字符串 + stringToAppend? 添加到原始字符串末尾的stringToAdd将被截断。
  • desiredStringLength > 原始字符串 + stringToAppend? 将重复添加stringToAdd 到原始字符串末尾的,直到达到 满足我们期待的字符串长度。
  • 没有传递 stringToAppend 参数? 会将空格添加到原始字符串的尾部,直到达到我们期望的长度。
  • 没有传递 desiredStringLength 参数? 原始字符串的副本将原封不动地返回。

String.replaceAll(pattern,replacement)

  • pattern:我们将要被替换的字符串

  • replacement:我们希望替换成的字符串

    你之前可能使用过String.replace(),它接受一个pattern参数和一个replacement参数,并且它会替换掉字符串中匹配上的第一个匹配项。pattern 参数可以是字符串也可以是正则表达式

String.replaceAll()顾名思义,它允许我们替换字符串中所有能匹配上的匹配项,而不仅仅是第一个匹配项。

代码示例:

// 使用示例 String.replace() 
const aString = 'My name is z. z is my name.';

const replaceString = aString.replace('z', 'zayyo');

console.log(replaceString);

// 输出 -->
// "My name is zayyo. z is my name."
// 仅仅吧第一个“z”被替换为“zayyo”
// 使用示例 String.replaceAll() 利用正则表达式
const  regex = /z/ig;

const anotherString = 'My name is z. z is my name.';

const replaceAllString = anotherString.replaceAll(regex, 'zayyo');

console.log(replaceAllString);

// 输出 -->
// ""My name is zayyo. zayyo is my name."."
// 把所有的z都替换成zayyo了

Object.entries()、Object.keys()、Object.values() 和 Object.fromEntries()

上面这些方法对于转换一些JSON数据的结构很有用。。

Object.entries(originalObject)

此对象方法接收一个对象并返回一个全新的二维数组,每个嵌套数组里都包含原始对象的键和值作为元素。

代码示例:

const fruitObject = {
  'banana': 'yellow',
  'strawberry': 'red',
  'tangerine': 'orange' 
};

const fruitArray = Object.entries(fruitObject);

console.log(fruitArray);

// 输出 -->
// [["banana", "yellow"], ["strawberry", "red"], ["tangerine", "orange"]]

在转换我们的JSON数据时,这是一种超级好用的方法。下面这个示例是访问对象中的特定键值对的用法:

代码示例:

const fruitObject = {
  'banana': 'yellow',
  'strawberry': 'red',
  'tangerine': 'orange' 
};

const firstFruit = Object.entries(fruitObject)[0];

console.log(firstFruit);

// 输出 -->
// ['banana', 'yellow']

在JavaScript 中的很多东西都是以数组和字符串的形式保存的。因此,我们还可以将数组和字符串作为参数传入给Object.entries()它会强制把数组和字符串转换为数组。

代码示例:

const string = 'Hello'

const stringAsArgument = Object.entries(string);

console.log(stringAsArgument);

// 输出 --> 
// [["0", "H"], ["1", "e"], ["2", "l"], ["3", "l"], ["4", "o"]]

字符串中的每个字符都被插入到一个单独的数组中,并将其索引设置为数组的第一个元素。当您将数组作为参数传递时,也会发生一样的操作:


const array = [1,2,3]

const formattedArray = Object.entries(array);

console.log(formattedArray);

// 输出 --> 
// [["0", 1], ["1", 2], ["2", 3]]

注意: 对于上面两种情况,得到的嵌套数组里的数组的第一个元素(索引)都是一个字符串。

Object.keys(anObject)

Object.keys方法接受一个对象作为参数,并且返回一个以对象的键作为元素的数组。

代码示例:

const programmingLangs = {
  'JavaScript': 'Brendan Eich', 
  'C': 'Dennis Ritchie',
  'Python': 'Guido van Rossum'
};

const langs = Object.keys(programmingLangs);

console.log(langs);

// 输出 -->
// ["JavaScript", "C", "Python"]

如果我们尝试传递一个字符串作为参数呢?会是什么结果呢?

代码示例:

const string = 'Hallo';

const stringArray = Object.keys(string);

console.log(stringArray);

// 输出 -->
// ["0", "1", "2", "3", "4"]

在这种情况下,字符串也会被强制转换为一个对象。每个字母代表值,它的索引代表键,所以我们返回的数组,就变成了包含字符串中每个字母的索引。

Object.values(anObject)

Object.values()方法的功能和我们刚刚学习的方法类似,但它不是返回数组中的对象键,而是返回数组中的对象值。

代码示例:

const programmingLangs = {
  'JavaScript': 'Brendan Eich', 
  'C': 'Dennis Ritchie',
  'Python': 'Guido van Rossum'
};

const creators = Object.values(programmingLangs);

console.log(creators);

// 输出 -->
// ["Brendan Eich", "Dennis Ritchie", "Guido van Rossum"]

Object.entries()和我们在之前学习Object.keys()一样,我们也可以传入其他数据类型,例如字符串。

代码示例:

const string = 'Bonjour'

const stringArray = Object.values(string);

console.log(stringArray) 

// 输出 -->
// ["B", "o", "n", "j", "o", "u", "r"]

Object.fromEntries(anIterable)

Object.fromEntries()Object.entries()相反。它接受一个可迭代对象作为参数,例如数组或映射,并返回一个对象。让我们来看看:

代码示例:

const arrayTranslations = [
   ['french', 'bonjour'], 
   ['spanish', 'buenos dias'], 
   ['czech', 'dobry den']
];

const objectTranslations = Object.fromEntries(arrayTranslations);

console.log(objectTranslations);

// 输出 --> 
/*
[object Object] {
  czech: "dobry den",
  french: "bonjour",
  spanish: "buenos dias"
} */

因此,我们的可迭代对象(在示例中的嵌套数组)被迭代,并且每个子数组都转换为一个对象,其中索引 0 处的元素作为键,索引 1 处的元素作为值。

Array.flat(optionalDepthArgument)

Array.flatflat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。。

当没有传入 optionalDepthArgument 时,默认深度为 1:


const numArray = [1, 2, [3,4]];

const flatArray = numArray.flat();

console.log(flatArray);

// 输出 -->
// [1, 2, 3, 4]

以下是一个示例,其中 optionalDepthArgument 已有指定的维数:


const numArray = [1, 2, [[[3,4]]]];

const depthOf2Array = numArray.flat(2);

console.log(depthOf2Array);

// 输出 -->
// [1, 2, [3, 4]];

在上面的代码片段中,我们将 optionalDepthArgument 指定为 2,因此我们得到的新数组depthOf2Array是一个 二维的数组。如果你有一个嵌套很深的数组并且想返回一个一维数组但你不确定数组的深度时,你使用 Infinity,可展开任意深度的嵌套数组,帮我们转化成为一个一维数组。


const nestedArray = [1, 2, [3, 4, [5, [6, 7]]]];

const oneDimensionalArray = nestedArray.flat(Infinity);

console.log(oneDimensionalArray); 

// 输出-->
// [1, 2, 3, 4, 5, 6, 7, 8, 9]

Object Spread Operator(对象展开运算符)

我们可能在平常的代码中经常会看见展开运算符 (...),但是大部分都是把它与数组结合一起使用。因为这是克隆和合并数组的好方法。从 ES2018 开始,我们可以利用展开运算符来处理对象。对象的展开运算符的用法与数组展开运算符一样都是克隆和合并。

首先让我们看一下怎么使用展开运算符来克隆我们的对象并添加额外的键值对:


const bookAndAuthor = {
  'Gabriel Garcia Marquez': '100 Years of Solitude'
}

const moreBooksAndAuthors = {
  ...bookAndAuthor, 
  'Paolo Coelho' : 'The Alchemist'
}; 

console.log(moreBooksAndAuthors)

// 输出 -->
/* [object Object] {
  Gabriel Garcia Marquez: "100 Years of Solitude",
  Paolo Coelho: "The Alchemist"
}
*/

在这里,我们可以很轻松地克隆对象并且添加更多属性。

重要的是,使用对象展开运算符复制不会形成对象嵌套 而且它会在内存中创建一个新对象。这意味着我们可以克隆一个对象,为新创建的对象更改或添加属性,但不会改变原始对象。但是,如果我们克隆一个包含嵌套数据的对象,克隆出来的对象会进行浅拷贝,对象里嵌套的对象在克隆时只会进行引用地址的克隆,并不会创建一个新的对象,这意味着如果我们要更改一个对象中的任何嵌套数据,则原来对象中的嵌套对象也会被修改

接下来,让我们看看当我们使用对象展开运算符合并对象时会发生什么:


const book1 = {
  'Milan Kundera': 'The Unbearable Lightness of Being'
}

const book2 = {
  'Bohumil Hrabal': 'I Served the King of England'
}

const books = {...book1, ...book2 }

console.log(books);

// 输出 --> 
/*
  [object Object] {
  Bohumil Hrabal: "I Served the King of England",
  Milan Kundera: "The Unbearable Lightness of Being"
}
*/

这里我们合并了两个对象后创建了一个新对象。

Promise.finally() 和 Promise.allSettled()

Promise.finally(回调函数)

finally()  方法返回一个 Promise。在 promise 结束时,无论结果是 fulfilled 或者是 rejected,都会执行指定的回调函数。这为在 Promise 是否成功完成后都需要执行的代码提供了一种方式。这避免了同样的语句需要在 then() 和 catch() 中各写一次的情况。finally是运行与任何清理任务相关的代码的好时机。


const promise = new Promise((resolve, reject) => {
  let num = Math.floor(Math.random());

  if (num >= 0.5) {
    resolve('promise resolved')
  } else {
    reject('promise rejected')
  }
})

promise
  .then(value => console.log(value))
  .catch(error => console.log(error))
  .finally(() => console.log('promise has been settled'));

// 输出 -->
// 'promise resolved' / 'promise rejected' (depending on value of num)
// 'promise has been settled'

不管promise是resolved还是rejected,方法中的回调函数Promise.finally()总会被执行。

Promise.allSettled([promises])

作为 ES2020 对 JavaScript 的补充,Promise.allSettled() 方法接受一组promise数组并返回一个新的promise数组,只有在数组中的所有的promise都改变状态了(resolve或reject)后才会resolve。一旦resolve,返回值将是一个对象数组,每个对象描述数组中传递的promise的结果。


const promise1 = new Promise((resolve, reject) => {
  resolve('I have been resolved')
}); 

const promise2 = new Promise((resolve, reject) => {
  reject('I have been rejected')
});

Promise.allSettled([promise1, promise2])
  .then(result => console.log(result))

// 输出 --> 
/*
[[object Object] {
  status: "fulfilled",
  value: "I have been resolved"
}, [object Object] {
  reason: "I have been rejected",
  status: "rejected"
}]
*/

在上面的示例中,我们在第 9 行声明Promise.allSettled()并向Promise.allSettled()方法传递一个包含promise1promise2的数组。在第 10 行,我们链接了一个.then()方法,Promise.allSettled().then中打印出我们resolve的返回值。输出显示已返回对象数组。如果我们传入的promise是resolve,我们最终的返回值是一个对象里面包含status和value/reason,status表示我们promise的最终状态,要么是 "fulfilled",要么是 "rejected"。只有当 status 为 "fulfilled",才存在。在 promise 兑现时才有 value。z只有当 status 为 "rejected",才存在,在 promsie 拒绝时才有 reason。

BigInt

igInt 是一种内置对象,它提供了一种方法来表示大于 2^53 - 1 的整数。这原本是 Javascript 中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。

使用 BigInt 的一些注意事项:

  • 在 JavaScript 中使用 Number 数据类型的算术运算符也可以与 BigInt 一起使用,例如 +、*、/ 等。
  • BigInt不能与小数一起使用。
  • 您不能在 BigInt 数据类型和 Number 数据类型之间执行算术运算。

有两种方法可以将变量声明为 BigInt 数据类型,使用BigInt(number)或添加n到数字后面:


let hugeNumber = BigInt(9999999999999999);
let anotherHugeNumber = 7777777777777777n;

console.log(typeof hugeNumber);
console.log(typeof anotherHugeNumber);

// 输出 -->
// 'bigint'
// 'bigint'

逻辑或(||)???.

这几个运算符是我最常用的运算符

||和 ( ??)

这个逻辑运算符有两个操作数。如果左侧操作数是nullorundefined它将返回右侧操作数。另一方面,如果左侧操作数不是nullor undefined,它将返回左侧操作数。

它类似于逻辑 Or 运算符 ( ||),除了||运算符根据左侧是否为假值(与nullor相对undefined)返回右侧。??和之间会有一些行为重叠,||因为 JavaScript 中的假值包括nullundefined,但也包括0“”(空字符串)Nan,当然还有false.

让我们看看实际效果:


const usingOr = undefined || 'Return me because undefined is a falsy value';

const usingOrAgain = 'Return me because I am NOT falsy' || 'I will not be returned'


const usingNullishCoalescing = undefined ?? 'Return me!';

const usingNullishCoalescingAgain = 'I will return because I am NOT null/undefined ' ?? 'I will not be returned';


console.log(usingOr)
console.log(usingOrAgain)
console.log(usingNullishCoalescing)
console.log(usingNullishCoalescingAgain)

// 输出 -->
// "Return me because undefined is a falsy value"
// "Return me because I am NOT falsy"
// "Return me!"
// "I will return because I am NOT null/undefined " 

在运算符优先级方面,逻辑或(||) 运算符的优先级排到倒数第五,因此在组合多个运算符时请记住这一点。有关运算符优先级的更多深入信息,您可以查看此页面

可选链运算符(?.)

在访问对象的属性或方法时使用此运算符。如果属性/方法不存在,它可以帮助我们避免抛出错误。如果没有找到相应的属性/方法,我们会收到undefined,而不是抛出错误。

  • 当与对象属性一起使用,我们可以先查询属性而Object?.property再执行,不仅仅是直接执行Object.property.
  • 当与对象方法一起使用,我们调用方法时我们可以先查询Object.method?.()再执行,不仅仅是直接执行Object.method().

const person = {
  name: 'Pippa',
  favouriteColour: 'green',

  sayHello() {
    return `${this.name} says hello`;
  }
}

// ?. 判断对象的属性
const color = person?.favouriteColour;
const age = person?.age;

console.log(color);
console.log(age);

// ?. w判断对象的方法
console.log(person.sayHello?.());
console.log(person.sayGoodby?.());

// 输出 --> 
// "green"
// undefined
// "Pippa says hello"
// undefined

从输出中我们可以看见,person 对象上不存在person.age属性,我们的时候用了?所以我们没有收到错误,而是返回了 undefined 给我们。如果我们直接调用我们的JavaScript将会报错

数字分隔符( _)

让我们用一个简单的例子来展示。JvaScript 中引入了数字分隔符,以便在处理较大数字时提高可读性。它们允许您将数字“分解”为更容易理解的块,和你使用逗号 (,) 或点 (.) 一样。_我们可以使用字符(下划线)分隔较大的数字。


const harderToReadNumber = 100000000
const easierToReadNumber = 100_000_000

// 可读性比原来好太多了

因为内容太多后续会继续补全,也欢迎大家在评论区补充..