11.29 三個值得期待的 JavaScript 新特性

一起來來看看JavaScript中一些新特性,本文將介紹它們的語法和相關鏈接,幫助讀者及時瞭解它們的進度,我們將通過編寫一個小型測試項目,展示如何快速上手使用這些新特性!

關於提案

提案有五個階段,詳看介紹文檔 https://tc39.github.io/process-document/ 。每個提案最初提出的都是“strawman”或stage 0階段,在這個階段,它們要麼尚未提交給技術委員會,要麼尚未被拒絕,但仍未達到進入下一階段。

作為個人建議,讀者應避免生產環境中使用stage 0提案,它們處於不穩定的階段。

下文的提案都不屬於第0階段

創建測試項目

新建一個目錄並運行運行以下命令:

npm init -f

npm i [email protected] @babel/[email protected] @babel/[email protected] @babel/[email protected] @babel/[email protected] @babel/[email protected] @babel/[email protected] --save-dev`

然後將以下內容添加到package.json文件中:

{

"scripts": {

"test": "ava"

},

"ava": {

"require": [

"@babel/register",

"@babel/polyfill"

]

}

}

最後創建一個.babelrc文件:

{

"presets": [

[

"@babel/preset-env",

{

"targets": {

"node": "current"

}

}

],

"@babel/preset-stage-0"

],

"plugins": [

"@babel/plugin-transform-runtime"

]

}

現在可以開始寫一些測試用例了

1.可選運算符

在JavaScript中,我們一直在使用對象,但有時候對象裡並不是我們期望的數據結構。假設下面是我們期望得到的數據,可能是通過調用API查詢數據庫得到的。

const data = {

user: {

address: {

street: "Pennsylvania Avenue"

}

}

}

如果該用戶沒有完成註冊,可能得到下面的數據:

const data = {

user: {}

};

當嘗試按下面的方式訪問street時,會得到報錯:

console.log(data.user.address.street);

// Uncaught TypeError: Cannot read property 'street' of undefined

為避免這種情況,需要按如下方式訪問“street”屬性:

const street = data && data.user && data.user.address && data.user.address.street;

console.log(street); // undefined`

在我看來,這種方法:

  1. 不美觀
  2. 繁重
  3. 囉嗦

如果使用可選運算符,可以這樣編碼:

console.log(data.user?.address?.street);

// undefined

這樣看起來更簡單了,現在我們已經看到了這個功能的用處,現在來寫一個測試!

import test from 'ava';


const valid = {

user: {

address: {

street: 'main street',

},

},

};


function getAddress(data) {

return data?.user?.address?.street;

}


test('Optional Chaining returns real values', (t) => {

const result = getAddress(valid);

t.is(result, 'main street');

});

我們看到了可選符號的正常使用,接下來是一些不規範數據的測試用例:

test('Optional chaining returns undefined for nullish properties.', (t) => {

t.is(getAddress(), undefined);

t.is(getAddress(null), undefined);

t.is(getAddress({}), undefined);

});

用於訪問數組元素的用例:

const valid = {

user: {

address: {

street: 'main street',

neighbors: [

'john doe',

'jane doe',

],

},

},

};


function getNeighbor(data, number) {

return data?.user?.address?.neighbors?.[number];

}


test('Optional chaining works for array properties', (t) => {

t.is(getNeighbor(valid, 0), 'john doe');

});


test('Optional chaining returns undefined for invalid array properties', (t) => {

t.is(getNeighbor({}, 0), undefined);

});

有時我們不知道某個函數是否在對象中實現,一個常見的場景是,某些舊版瀏覽器可能沒有某些功能,我們可以使用可選運算符接來檢測函數是否已實現。看如下代碼:

const data = {

user: {

address: {

street: 'main street',

neighbors: [

'john doe',

'jane doe',

],

},

getNeighbors() {

return data.user.address.neighbors;

}

},

};


function getNeighbors(data) {

return data?.user?.getNeighbors?.();

}


test('Optional chaining also works with functions', (t) => {

const neighbors = getNeighbors(data);

t.is(neighbors.length, 2);

t.is(neighbors[0], 'john doe');

});


test('Optional chaining returns undefined if a function does not exist', (t) => {

const neighbors = getNeighbors({});

t.is(neighbors, undefined);

});

如果調用鏈不完整,函數將不會執行,它背後的邏輯應該是這樣的:

value == null ? value[some expression here]: undefined;

如果在可選鏈操作符之後是 undefined 或者 null則什麼都不會執行,我們可以在以下測試中看到該規則的實際應用:

let neighborCount = 0;


function getNextNeighbor(neighbors) {

return neighbors?.[++neighborCount];

}


test('It short circuits expressions', (t) => {

const neighbors = getNeighbors(data);

t.is(getNextNeighbor(neighbors), 'jane doe');

t.is(getNextNeighbor(undefined), undefined);

t.is(neighborCount, 1);

});

有了可選運運算符,我們的代碼中可以減少if語句、lodash等庫以及&&進行鏈式調用的使用。

2.空值合併

以下是我們在JavaScript中看到的一些常見操作:

  1. 檢查 null 或 undefined
  2. 給變量設置默認值
  3. 確保0,false和''不設置默認值

像這樣:

value != null ? value : 'default value';

或者這樣:

value || 'default value'

問題是,對於第二個實現,在值為0、false和''時都被視為false,所以我們必須明確檢查null和undefined。

value != null

和上面相同:

value !== null && value !== undefined

這就是新提案的用武之地,現在我們可以這樣做:

value ?? 'default value';

這可以保護我們不會為0、false和''設置默認值,在不使用三元運算符和!= null檢查的情況下捕獲null和undefined。

接下來編寫一個簡單的測試來驗證它是如何工作的:

import test from 'ava';


test('Nullish coalescing defaults null', (t) => {

t.is(null ?? 'default', 'default');

});


test('Nullish coalescing defaults undefined', (t) => {

t.is(undefined ?? 'default', 'default');

});


test('Nullish coalescing defaults void 0', (t) => {

t.is(void 0 ?? 'default', 'default');

});


test('Nullish coalescing does not default 0', (t) => {

t.is(0 ?? 'default', 0);

});


test('Nullish coalescing does not default empty strings', (t) => {

t.is('' ?? 'default', '');

});


test('Nullish coalescing does not default false', (t) => {

t.is(false ?? 'default', false);

});

在測試中看到,??為null,undefined和void 0設置了默認值,沒有為0,''和false設置默認值。

3.管道運算符

在函數式編程中,我們有一個概念叫compose,它多個函數調用合併在一起,調用時從右到左執行每個函數,函數接收前一個函數的輸出作為其輸入,以下是我們在純JavaScript中討論的一個示例:

function doubleSay (str) {

return str + ", " + str;

}


function capitalize (str) {

return str[0].toUpperCase() + str.substring(1);

}


function exclaim (str) {

return str + '!';

}


let result = exclaim(capitalize(doubleSay("hello")));

result //=> "Hello, hello!"

這種合併使用函數的方式很常見常見,以至於在於大多數功能庫中,如lodash和ramda都有實現。

使用新的管道運算符,可以不使用第三方庫並按如下所示編寫上述內容:

let result = "hello"

|> doubleSay

|> capitalize

|> exclaim;

result //=> "Hello, hello!"`

這個提案目的是使鏈式調用函數更具可讀性,在未來結合函數部分應用也可以很好的工作,類似下面這種使用方式:

let result = 1

|> (_ => Math.max(0, _));

result //=> 1

let result = -5

|> (_ => Math.max(0, _));

result //=> 0

編寫如下測試用例:

import test from 'ava';


function doubleSay (str) {

return str + ", " + str;

}


function capitalize (str) {

return str[0].toUpperCase() + str.substring(1);

}


function exclaim (str) {

return str + '!';

}


test('Simple pipeline usage', (t) => {

let result = "hello"

|> doubleSay

|> capitalize

|> exclaim;


t.is(result, 'Hello, hello!');

});


test('Partial application pipeline', (t) => {

let result = -5

|> (_ => Math.max(0, _));


t.is(result, 0);

});


test('Async pipeline', async (t) => {

const asyncAdd = (number) => Promise.resolve(number + 5);

const subtractOne = (num1) => num1 - 1;

const result = 10

|> asyncAdd

|> (async (num) => subtractOne(await num));


t.is(await result, 14);

});

需要注意,一旦將async函數添加到管道,必須await該返回值,因為此時返回值是promise。有一提案開始支持|> await asyncFunction,但尚未實現。

最後,既然你已經看到了這些提案的實際應用,我希望你能夠嘗試一下這些提案!


分享到:


相關文章: