过去10年里,JavaScript取得了长足进步,每年都有全新的功能升级

今天,我们来看看早期ES9中引入的5个最重要的特性,看看你是否错过了其中一些。

1. 异步生成器和迭代

异步生成器是ES9中一个强大的特性。

就像普通的生成器,但现在它可以在异步工作(如网络请求)后弹出值:

function* asyncGenerator() {
  yield new Promise((resolve) =>
    setTimeout(() => resolve('done this ✅'), 2000)
  );
  yield new Promise((resolve) =>
    setTimeout(() => resolve('done that ✅'), 3000)
  );
}

当我们调用.next()时,我们会得到一个Promise

const asyncGen = asyncGenerator();

asyncGen.next().value.then(console.log);
asyncGen.next().value.then(console.log);

这是一个强大的工具,可以在web应用中以结构化+可读的方式流式传输数据 — 看看这个为类似YouTube的视频分享应用缓冲和流式传输数据的函数:

async function* streamVideo({ id }) {
  let endOfVideo = false;
  const downloadChunk = async (sizeInBytes) => {
    const response = await fetch(
      `api.example.com/videos/${id}`
    );
    const { chunk, done } = await response.json();
    if (done) endOfVideo = true;
    return chunk;
  };
  while (!endOfVideo) {
    const bufferSize = 500 * 1024 * 1024;
    yield await downloadChunk(bufferSize);
  }
}

现在要消费这个生成器,我们将使用for await of — 异步迭代:

for await (const chunk of streamVideo({ id: 2341 })) {
  // process video chunk
}

我想知道实际的YouTube JavaScript代码是否使用了这样的生成器?

2. 对象的剩余/展开运算符

毫无疑问,你在某处遇到过现代的展开语法。

这是一种快速且不可变地克隆数组的天才方法:

const colors = ['🔴', '🔵', '🟡'];

console.log([...colors, '🟢']); 
// ['🔴', '🔵', '🟡', '🟢']

在ES6之前我们从未有过它,现在它无处不在。

Redux就是一个重要的例子:

export default function userState(state = initialUserState, action) {
  console.log(arr); 
  switch (action.type) {
    case ADD_ITEM:
      return {
        ...state,
        arr: [...state.arr, action.newItem]
      };
    default: 
      return state;
  }
}

从ES9开始,它也适用于对象:

const info = {
  name: 'Coding Beauty',
  site: 'codingbeautydev.com',
};

console.log({ ...info, theme: '🔵' });

/* Output:
{
  name: 'Coding Beauty',
  site: 'codingbeautydev.com',
  theme: '🔵'
}
*/

覆盖属性:

const langs = {
  j: 'java',
  c: 'c++',
};

console.log({ ...langs, j: 'javascript' });

// Output: { j: 'javascript', c: 'c++' }

这使得它特别适合在默认值的基础上构建,尤其是在制作公共实用程序时。

或者像我用Material UI定制默认主题那样:

image.png

使用展开语法,你甚至可以去掉不想在副本中出现的对象属性。

const colors = {
  yellow: '🟡',
  blue: '🔵',
  red: '🔴',
};

const { yellow, ...withoutYellow } = colors;

console.log(withoutYellow);

// Output: { blue: '🔵', red: '🔴' }

这就是如何以不可变的方式从对象中移除属性。

3. String.raw

当我使用String.raw时,我是在说:只给我我给你的东西。不要处理任何东西。
不要动那些转义字符:

不再需要转义反斜杠,我们不用写:

const filePath = 'C:\\Code\\JavaScript\\tests\\index.js';

console.log(`The file path is ${filePath}`);

// Output: The file path is C:\Code\JavaScript\tests\index.js

而是写:

const filePath = String.raw`C:\Code\JavaScript\tests\index.js`;

console.log(`The file path is ${filePath}`);

// Output: The file path is C:\Code\JavaScript\tests\index.js

非常适合编写带有大量这些反斜杠的正则表达式:

像这样但更糟:

从这个✅:

const patternString = 'The (\\w+) is (\\d+)';
const pattern = new RegExp(patternString);

const message = 'The number is 100';

console.log(pattern.exec(message));
// ['The number is 100', 'number', '100']

到这个✅:

const patternString = String.raw`The (\w+) is (\d+)`;
const pattern = new RegExp(patternString);

const message = 'The number is 100';

console.log(pattern.exec(message));
// ['The number is 100', 'number', '100']

所以"raw"意味着未处理的。

image.png

这就是为什么我们有String.raw()但没有String.cooked()

4. 复杂的正则表达式特性

说到正则表达式,ES9并没有让人失望。

它完全装载了最先进的正则表达式特性,用于高级字符串搜索和替换。

向后查找断言

这是一个新特性,用于确保只有某个特定模式出现在你要搜索的内容之前:

  • 正向后查找:白名单 ?<=pattern
  • 负向后查找:黑名单 ?<!pattern
const str = "It's just $5, and I have €20 and £50";

// Only match number sequence if $ comes first
const regexPos = /(?<=\$)\d+/g;

console.log(str.match(regexPos)); // ['5']

const regexNeg = /(?<!\$)\d+/g;

console.log(str.match(regexNeg)); // ['20', '50']

image.png

命名捕获组

捕获组一直是正则表达式中最宝贵的特性之一,用于以复杂的方式转换字符串。

const str = 'The cat sat on a map';

// $1 -> [a-z]
// $2 -> a
// $3 -> t

// () indicates group
str.replace(/([a-z])(a)(t)/g, '$1*$3');
// -> The c*t s*t on a map

通常,这些组按照它们在正则表达式中的相对位置命名:1, 2, 3...

但这使得理解和更改那些愚蠢的长正则表达式变得更加困难。

所以ES9通过?<name>来命名捕获组解决了这个问题:

const str = 'The cat sat on a map';

// left & right
console.log(str.replace(/(?<left>[a-z])(a)(?<right>t)/g, '$<left>*$<right>'));

// -> The c*t s*t on a map

image.png

你知道当VS Code中出现错误时,你可以快速Alt + 点击跳转到错误发生的确切位置吗?👇

image.png

VS Code使用捕获组使文件名可点击,从而实现这种快速导航。

我想它大概是这样的:

// The stupidly long regex
const regex = /(?<path>[a-z]:[a-z].(?:?:\\/|(?:\\/?)))[\w \-]+):(?<line>\d+):(?<char>\d+)/gi;

// ✅ String.raw!
const filePoint = String.raw`C:\coding-beauty\coding-beauty-javascript\index.js:3:5`;

const extractor = /(?<path>[a-z]:[a-z].(?:?:\\/|(?:\\/?)))[\w \-]+):(?<line>\d+):(?<char>\d+)/i;
const [path, lineStr, charStr] = filePoint
  .match(regex)[0]
  .match(extractor)
  .slice(1, 4);

const line = Number(lineStr);

const char = Number(charStr);

console.log({ path, line, char });

// Replace all filePoint with <button> tag
// <button onclick="navigateWithButtonFilepointInnerText">filePoint</button>

5. Promise.finally

最后我们有了Promise.finally 😉。

你知道finally总是会运行一些代码,无论是否有错误吗?

function startBodyBuilding() {
  if (Math.random() > 0.5) {
    throw new Error("I'm tired😩");
  }
  console.log('Off to the gym 🏋️‍♂️💪');
}

try {
  startBodyBuilding();
} catch {
  console.log('Stopped excuse🛑');
} finally {
  console.log("I'm going!🏃‍♂️");
}

所以Promise.finally就像那样,但是用于异步任务:

async function startBodyBuilding() {
  await think();
  if (Math.random() > 0.5) {
    throw new Error("I'm tired😩");
  }
  console.log('Off to the gym 🏋️‍♂️💪');
}

startBodyBuilding()
  .then(() => {
    console.log('Started ✅');
  })
  .catch(() => {
    console.log('No excuses');
  })
  .finally(() => {
    console.log("I'm going!🏃‍♂️");
  });

Promise.finally()最大的优点是当你链接许多Promise时:

它也能很好地与Promise链一起工作:

getFruitApiUrl().then((url) => {
  return fetch(url)
    .then((res) => res.json())
    .then((data) => {
      fruits.push(data);
    })
    .catch((err) => {
      console.error(err);
    })
    .finally(() => {
      console.log(fruits);
    });
});

这是由ES9带来的。

最后的思考

ES9标志着JavaScript的一个重大飞跃,引入了几个对现代开发至关重要的特性。
使你能够快速编写更清晰、更简洁、更富表现力的代码。

首发于公众号 大迁世界,欢迎关注。📝 每周一篇实用的前端文章 🛠️ 分享值得关注的开发工具 ❓ 有疑问?我来回答

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试完整考点、资料以及我的系列文章。


王大冶
68k 声望104.9k 粉丝