es javascript_构建使用所有7个JavaScript ES2020新功能的应用

es javascript

Web开发的世界发展Swift,尤其是在JavaScript生态系统中。 新功能,框架和库不断涌现,而您停止学习的那一刻就是您的技能开始变得过时的那一刻。

使您JavaScript技能保持精明的重要部分是紧跟JavaScript的最新功能。 因此,我认为构建一个包含JavaScript ES2020中所有七个新功能的应用程序会很有趣。

最近,我在好市多(Costco)进行了一些大宗购物,以购买一些食品必需品。 像大多数商店一样,它们的价格标签显示每件商品的单价,因此您可以评估和比较每笔交易的质量。 您会选择小袋子还是大袋子? (我在跟谁开玩笑?是Costco。放开!)

但是,如果不显示单价怎么办?

在本文中,我将使用香草JavaScript作为前端,使用Node.js和Express.js作为后端来构建单价计算器应用程序。 我将在Heroku上部署该应用程序,这是快速部署node.js应用程序的简便位置。

JavaScript ES2020的新增功能?

JavaScript编程语言符合称为ECMAScript的规范。 从ES2015(或ES6)的发布开始,每年都会发布一个新版本JavaScript。 截至目前,最新版本为ES2020(ES11)。 ES2020包含七个令人兴奋的新功能,JavaScript开发人员一直在等待很长一段时间才能看到。 新功能包括:

1. Promise.allSettled()

2.可选链接

3.空位合并

4. globalThis

5.动态导入

6. String.prototype.matchAll()

7. BigInt

您应该注意,并非所有浏览器都支持这些功能。 如果要立即开始使用这些功能,请确保提供适当的polyfill或使用Babel这样的编译器,以确保您的代码与旧版浏览器兼容。

入门

如果要遵循自己的代码副本,请首先创建一个Heroku帐户,然后在计算机上安装Heroku CLI。 有关安装说明,请参见此Heroku指南 。

完成此操作后,您可以使用CLI轻松创建和部署项目。 运行此示例应用程序所需的所有源代码都可以在GitHub上找到 。

以下是有关如何克隆存储库并将其部署到Heroku的分步说明:

gitclone https://github.com/thawkin3/unit-price-calculator.git
cd unit-price-calculator
heroku create
git push heroku master
heroku open

系统总览

我的单价计算器应用程序非常简单:它可以让您比较虚构产品的各种价格和重量选项,然后计算单价。 页面加载后,它将通过点击两个API端点从服务器获取产品数据。

然后,您可以选择产品,首选的度量单位以及价格/重量组合。 点击提交按钮后,即完成单价计算。

es javascript_构建使用所有7个JavaScript ES2020新功能的应用_第1张图片

单价计算器应用

既然您已经看到了该应用程序,那么让我们看一下我如何使用所有这七个ES2020功能。 对于每个功能,我将确切讨论它的含义,它的有用性以及使用方式。

1. Promise.allSettled()

当用户首次访问计算器应用程序时,将启动三个API请求,以从服务器获取产品数据。 我们使用Promise.allSettled()等待所有三个请求完成:

const fetchProductsPromise = fetch( '/api/products' )
  .then( response => response.json())

const fetchPricesPromise = fetch( '/api/prices' )
  .then( response => response.json())

const fetchDescriptionsPromise = fetch( '/api/descriptions' )
  .then( response => response.json())

Promise .allSettled([fetchProductsPromise, fetchPricesPromise, fetchDescriptionsPromise])
  .then( data => {
    // handle the response
  })
  .catch( err => {
    // handle any errors
  })

Promise.allSettled()是一项新功能,对现有Promise.all()功能进行了改进。 这两种方法都允许您提供一个promise数组作为参数,并且这两种方法都返回一个promise。

区别在于Promise.all()将短路并在任何承诺被拒绝的情况下尽早拒绝自己。 另一方面, Promise.allSettled()等待所有诺言得到解决,而不管它们是被解决还是被拒绝,然后再自行解决。

因此,如果您希望获得所有承诺的结果,即使某些承诺被拒绝,也可以开始使用Promise.allSettled()

让我们看一下Promise.all()另一个示例:

// promises 1-3 all will be resolved
const promise1 = new Promise ( ( resolve, reject ) => setTimeout( () => resolve( 'promise 1 resolved!' ), 100 ))
const promise2 = new Promise ( ( resolve, reject ) => setTimeout( () => resolve( 'promise 2 resolved!' ), 200 ))
const promise3 = new Promise ( ( resolve, reject ) => setTimeout( () => resolve( 'promise 3 resolved!' ), 300 ))

// promise 4 and 6 will be resolved, but promise 5 will be rejected
const promise4 = new Promise ( ( resolve, reject ) => setTimeout( () => resolve( 'promise 4 resolved!' ), 1100 ))
const promise5 = new Promise ( ( resolve, reject ) => setTimeout( () => reject( 'promise 5 rejected!' ), 1200 ))
const promise6 = new Promise ( ( resolve, reject ) => setTimeout( () => resolve( 'promise 6 resolved!' ), 1300 ))

// Promise.all() with no rejections
Promise .all([promise1, promise2, promise3])
  .then( data => console .log( 'all resolved! here are the resolve values:' , data))
  .catch( err => console .log( 'got rejected! reason:' , err))
// all resolved! here are the resolve values: ["promise 1 resolved!", "promise 2 resolved!", "promise 3 resolved!"]

// Promise.all() with a rejection
Promise .all([promise4, promise5, promise6])
  .then( data => console .log( 'all resolved! here are the resolve values:' , data))
  .catch( err => console .log( 'got rejected! reason:' , err))
// got rejected! reason: promise 5 rejected!

现在,让我们看一下Promise.allSettled()另一个示例,以记录诺言被拒绝时行为的不同:

// promises 1-3 all will be resolved
const promise1 = new Promise ( ( resolve, reject ) => setTimeout( () => resolve( 'promise 1 resolved!' ), 100 ))
const promise2 = new Promise ( ( resolve, reject ) => setTimeout( () => resolve( 'promise 2 resolved!' ), 200 ))
const promise3 = new Promise ( ( resolve, reject ) => setTimeout( () => resolve( 'promise 3 resolved!' ), 300 ))

// promise 4 and 6 will be resolved, but promise 5 will be rejected
const promise4 = new Promise ( ( resolve, reject ) => setTimeout( () => resolve( 'promise 4 resolved!' ), 1100 ))
const promise5 = new Promise ( ( resolve, reject ) => setTimeout( () => reject( 'promise 5 rejected!' ), 1200 ))
const promise6 = new Promise ( ( resolve, reject ) => setTimeout( () => resolve( 'promise 6 resolved!' ), 1300 ))

// Promise.allSettled() with no rejections
Promise .allSettled([promise1, promise2, promise3])
  .then( data => console .log( 'all settled! here are the results:' , data))
  .catch( err => console .log( 'oh no, error! reason:' , err))
// all settled! here are the results: [
//   { status: "fulfilled", value: "promise 1 resolved!" },
//   { status: "fulfilled", value: "promise 2 resolved!" },
//   { status: "fulfilled", value: "promise 3 resolved!" },
// ]

// Promise.allSettled() with a rejection
Promise .allSettled([promise4, promise5, promise6])
  .then( data => console .log( 'all settled! here are the results:' , data))
  .catch( err => console .log( 'oh no, error! reason:' , err))
// all settled! here are the results: [
//   { status: "fulfilled", value: "promise 4 resolved!" },
//   { status: "rejected", reason: "promise 5 rejected!" },
//   { status: "fulfilled", value: "promise 6 resolved!" },
// ]

2.可选链接

提取产品数据后,我们将处理响应。 从服务器返回的数据包含具有深层嵌套属性的对象数组。 为了安全地访问这些属性,我们使用了新的可选链接运算符:

if (data?.[ 0 ]?.status === 'fulfilled' && data?.[ 1 ]?.status === 'fulfilled' ) {
  const products = data[ 0 ].value?.products
  const prices = data[ 1 ].value?.prices
  const descriptions = data[ 2 ].value?.descriptions
  populateProductDropdown(products, descriptions)
  saveDataToAppState(products, prices, descriptions)
  return
}

可选链接是我在ES2020中最兴奋的功能。 可选的链接运算符- ?. —使您可以安全地访问对象的深层属性,而无需检查每个属性的存在。

例如,在ES2020之前,您可以编写如下所示的代码以访问某些user对象的street属性:

const user = {
  firstName : 'John' ,
  lastName : 'Doe' ,
  address : {
    street : '123 Anywhere Lane' ,
    city : 'Some Town' ,
    state : 'NY' ,
    zip : 12345 ,
  },
}

const street = user && user.address && user.address.street
// '123 Anywhere Lane'

const badProp = user && user.fakeProp && user.fakeProp.fakePropChild
// undefined

为了安全地访问street属性,首先必须确保user对象存在并且address属性存在,然后才能尝试访问street属性。

使用可选的链接,访问嵌套属性的代码要短得多:

const user = {
  firstName : 'John' ,
  lastName : 'Doe' ,
  address : {
    street : '123 Anywhere Lane' ,
    city : 'Some Town' ,
    state : 'NY' ,
    zip : 12345 ,
  },
}

const street = user?.address?.street
// '123 Anywhere Lane'

const badProp = user?.fakeProp?.fakePropChild
// undefined

如果链中的任何点不存在值,则将返回undefined 否则,返回值将是您想要访问的属性的值,这与预期的一样。

3.空位合并

应用加载时,我们还将获取用户的计量单位首选项:千克或磅。 偏好设置存储在本地存储中,因此首次访问者尚不存在该偏好设置。 要使用本地存储中的值或默认使用千克来处理,我们使用无效的合并运算符:

appState.doesPreferKilograms =JSON .parse(doesPreferKilograms ?? 'true' )

空合并运算符- ?? —是方便的运算符,用于当您明确想要使用变量的值(只要它不是undefinednull 您应该使用此运算符,而不是简单的OR- || —运算符,如果指定的变量是布尔值,并且即使它为false也要使用其值。

例如,假设您有一个用于某些功能设置的开关。 如果用户专门为该功能设置设置了一个值,则您要尊重他或她的选择。 如果他们没有指定设置,那么您想默认为他们的帐户启用该功能。

在ES2020之前,您可能会这样写:

const useCoolFeature1 = true
const useCoolFeature2 = false
const useCoolFeature3 = undefined
const useCoolFeature4 = null

const getUserFeaturePreference = ( featurePreference ) => {
  if (featurePreference || featurePreference === false ) {
    return featurePreference
  }
  return true
}

getUserFeaturePreference(useCoolFeature1) // true
getUserFeaturePreference(useCoolFeature2) // false
getUserFeaturePreference(useCoolFeature3) // true
getUserFeaturePreference(useCoolFeature4) // true

使用无效的合并运算符,您的代码将更短,更容易理解:

const useCoolFeature1 = true
const useCoolFeature2 = false
const useCoolFeature3 = undefined
const useCoolFeature4 = null

const getUserFeaturePreference = ( featurePreference ) => {
  return featurePreference ?? true
}

getUserFeaturePreference(useCoolFeature1) // true
getUserFeaturePreference(useCoolFeature2) // false
getUserFeaturePreference(useCoolFeature3) // true
getUserFeaturePreference(useCoolFeature4) // true

4. globalThis

如上所述,为了获取和设置用户对度量单位的偏好,我们使用本地存储。 对于浏览器,本地存储对象是window对象的属性。 虽然您可以直接调用localStorage ,但是也可以使用window.localStorage来调用它。 在ES2020中,我们还可以通过globalThis对象访问它(还请注意,再次使用可选链接来进行某些功能检测,以确保浏览器支持本地存储):

const doesPreferKilograms = globalThis.localStorage?.getItem?.( 'prefersKg' )

globalThis功能非常简单,但是它解决了许多有时会咬你的矛盾之处。 简而言之, globalThis包含对全局对象的引用。 在浏览器中,全局对象是window对象。

在节点环境中,全局对象从字面上称为global 使用globalThis可以确保无论代码运行在什么环境下,您始终对全局对象始终具有有效的引用。

这样,您可以编写可移植JavaScript模块,这些模块将在浏览器的主线程,Web Worker或节点环境中正确运行。

// assuming you're in a browser
window .alert( 'Hi from the window!' )
globalThis.alert( 'Hi from globalThis, which is also the window!' )

5.动态导入

一旦用户选择了产品,计量单位以及重量和价格组合,他或她就可以单击“提交”按钮来查找单位价格。 单击该按钮时,将延迟加载用于计算单价JavaScript模块。

您可以在浏览器的开发工具中检查网络请求,以确保在您单击按钮之前未加载第二个文件:

import ( './calculate.js' )
  .then( module => {
    // use a method exported by the module
  })
  .catch( err => {
    // handle any errors loading the module or any subsequent errors
  })

在ES2020之前,在JavaScript中使用import语句意味着在请求父文件时,导入的文件会自动包含在父文件中。

诸如webpack的捆绑软件已经普及了“代码拆分”的概念,该功能使您可以将JavaScript捆绑软件拆分为多个文件,然后按需加载。 React还通过其React.lazy()方法实现了此功能。

代码拆分对于单页应用程序(SPA)极为有用。 您可以将代码分为每个页面单独的捆绑包,因此仅下载当前视图所需的代码。 这显着加快了初始页面加载时间,从而使最终用户不必预先下载整个应用程序。

代码拆分对于大部分很少使用的代码也很有帮助。 例如,假设您在应用程序的页面上有一个“导出PDF”按钮。 PDF下载代码很大,在页面加载时包含它可以减少总体加载时间。

但是,并非每个访问此页面的用户都需要或想要导出PDF。 为了提高性能,您可以延迟加载PDF下载代码,以便仅在用户单击“导出PDF”按钮时才下载其他JavaScript捆绑包。

在ES2020中,将动态导入直接纳入JavaScript规范中!

让我们看一下没有动态导入的“导出PDF”功能的示例设置:

import { exportPdf } from './pdf-download.js'

const exportPdfButton = document .querySelector( '.exportPdfButton' )
exportPdfButton.addEventListener( 'click' , exportPdf)

// this code is short, but the 'pdf-download.js' module is loaded on page load rather than when the button is clicked

现在让我们看看如何使用动态导入来延迟加载大型PDF下载模块:

const exportPdfButton = document .querySelector( '.exportPdfButton' )

exportPdfButton.addEventListener( 'click' , () => {
  import ( './pdf-download.js' )
    .then( module => {
      // call some exported method in the module
      module .exportPdf()
    })
    .catch( err => {
      // handle the error if the module fails to load
    })
})

// the 'pdf-download.js' module is only imported once the user click the "Export PDF" button

6. String.prototype.matchAll()

当调用calculateUnitPrice方法时,我们传递产品名称和价格/重量组合。 价格/重量组合是一个字符串,看起来像“ 10公斤200美元”。 我们需要解析该字符串以获得价格,重量和度量单位。 (肯定有一种更好的方法来构造此应用程序,以避免解析这样的字符串,但是为了演示此下一个功能,我将以此方式进行设置。)要提取必要的数据,可以使用String.prototype.matchAll()

const matchResults = [...weightAndPrice.matchAll( /\d+|lb|kg/g )]

一行代码中发生了很多事情。 我们基于正则表达式在字符串中寻找匹配项,该正则表达式搜索数字和字符串“ lb”或“ kg”。 它返回一个迭代器,然后我们可以将其扩展为一个数组。 该数组最后包含三个元素,每个匹配项一个元素(200、10和“ kg”)。

此功能可能是最难理解的,特别是如果您不熟悉正则表达式。 String.prototype.matchAll()的简短解释是,它对String.prototype.match()RegExp.prototype.exec()的功能进行了改进。 此新方法使您可以将字符串与正则表达式进行匹配,并返回所有匹配结果(包括捕获组)的迭代器。

你明白了吗? 让我们看另一个有助于巩固概念的示例:

const regexp = /t(e)(st(\d?))/
const regexpWithGlobalFlag = /t(e)(st(\d?))/g
const str = 'test1test2'

// Using `RegExp.prototype.exec()`
const matchFromExec = regexp.exec(str)
console .log(matchFromExec)
// ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined]

// Using `String.prototype.match()` on a regular expression WITHOUT a global flag returns the capture groups
const matchFromMatch = str.match(regexp)
console .log(matchFromMatch)
// ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined]

// Using `String.prototype.match()` on a regular expression WITH a global flag does NOT return the capture groups :(
const matchesFromMatchWithGlobalFlag = str.match(regexpWithGlobalFlag)
for ( const match of matchesFromMatchWithGlobalFlag) {
  console .log(match)
}
// test1
// test2

// Using `String.prototype.matchAll()` correctly returns even the capture groups when the global flag is used :)
const matchesFromMatchAll = str.matchAll(regexpWithGlobalFlag)
for ( const match of matchesFromMatchAll) {
  console .log(match)
}
// ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]

7. BigInt

最后,我们将价格除以权重就可以得出单价。 您可以使用普通数来执行此操作,但是在处理大数时,ES2020引入了BigInt ,它使您可以在不损失精度的情况下对大整数进行计算。 对于我们的应用程序,使用BigInt是过大的,但是谁知道,也许我们的API端点会发生变化,包括一些疯狂的批量交易!

const price = BigInt(matchResults[ 0 ][ 0 ])
const priceInPennies = BigInt(matchResults[ 0 ][ 0 ] * 100 )
const weight = BigInt(matchResults[ 1 ][ 0 ])
const unit = matchResults[ 2 ][ 0 ]

const unitPriceInPennies = Number (priceInPennies / weight)
const unitPriceInDollars = unitPriceInPennies / 100
const unitPriceFormatted = unitPriceInDollars.toFixed( 2 )

如果您曾经使用过包含非常大数字的数据,那么您将知道在执行JavaScript数学运算时确保数字数据的完整性可能会很痛苦。 在ES2020之前,可以安全存储的最大整数由Number.MAX_SAFE_INTEGER表示,即2 ^ Number.MAX_SAFE_INTEGER

如果您尝试将大于该值的数字存储在变量中,则有时该数字将无法正确存储:

const biggestNumber = Number .MAX_SAFE_INTEGER // 9007199254740991

const incorrectLargerNumber = biggestNumber + 10
// should be: 9007199254741001
// actually stored as: 9007199254741000

新的BigInt数据类型有助于解决此问题,并允许您使用大得多的整数。 要使整数成为BigInt ,您只需将字母n附加到整数的末尾或在整数上调用函数BigInt()

const biggestNumber = BigInt( Number .MAX_SAFE_INTEGER) // 9007199254740991n

const correctLargerNumber = biggestNumber + 10 n
// should be: 9007199254741001n
// actually stored as: 9007199254741001n

结论

而已! 现在您已经了解了ES2020的新功能,您还等什么呢? 赶快去那里,立即开始编写新JavaScript!

翻译自: https://hackernoon.com/building-an-app-that-uses-all-7-new-javascript-es2020-features-6c6t3232

es javascript

你可能感兴趣的:(es javascript_构建使用所有7个JavaScript ES2020新功能的应用)