文共5530字,預計學習時長11分鐘
延展操作符首次于ES6中引入,并很快成為最受歡迎的功能之一。盡管事實上延展操作符只適用于數組,但仍有建議提出可以將其功能擴展到對象。最終ES9中引入了此功能。
本教程將說明為什么應該使用擴展運算符,以及它如何運作。
目錄
1.為什么要使用延展操作符
2.克隆數組/對象
3.將類數組結構轉換為數組
4.延展操作符作為參數
5.將元素添加到數組/對象
6.合并數組/對象
為什么要使用延展操作符
閱讀了以上列表之后,你可能會想:“JavaScript就已經能夠滿足需求了,為什么還要使用延展操作符?”我們先來介紹下不變性。
牛津詞典:不變性 - 隨著時間的推移不變或無法改變。
作為軟件開發的術語,不可變指狀態不能隨時間變化的值。實際上,通常使用的大多數值(原始值,如字符串,整數等)都是不可變的。
然而,JavaScript中非常特殊的一點是,其中的數組和對象實際上是可變的。這可能成為一個大問題。以下實例闡明了其中原因:
const mySquirtle={ name: 'Squirtle', type: 'Water', hp: 100 }; const anotherSquirtle = mySquirtle; anotherSquirtle.hp = 0; console.log(mySquirtle); //Result: { name: 'Squirtle', type: 'Water', hp: 0 }
從上述代碼中可以看到,我們有一個變量Squirtle(杰尼龜)。因為剛剛訪問了神奇寶貝中心,這只杰尼龜的HP值為100。
由于還想要另一只杰尼龜,因此聲明變量為anotherSquirtle,將初始Squirtle指定為它的值。一場苦戰后,另一只杰尼龜被擊敗了。因此,訪問另一只杰尼龜的HP值并將其更改為0。下一步,檢查初始Squirtle,輸入console.log和...
等等,什么?初始Squirtle的HP降至0。這怎么可能?可憐的杰尼龜遭遇了什么?原來是發生了JavaScript變異。接下來將為你解釋其中緣由。
當創建anotherSquirtle變量并將初始Squirtle指定為其值時,實際是給初始Squirtle對象的內存位置分配了一個引用。這是因為JavaScript數組和對象是引用數據類型。與基本數據類型不同,引用數據類型指向存儲實際對象/數組的內存地址。
為了便于理解,可以將引用數據類型想象為全局變量的指針。更改引用數據類型的值實際上是在更改全局變量的值。
這意味著當將anotherSquirtle的HP值更改為0時,實際是將存儲在內存中的Squirtle對象的HP值更改為0。這就是為什么mySquirtle的HP值為0 - 因為mySquirtle是對存儲在內存中的對象的引用,可以通過anotherSquirtle變量被改變。謝謝JavaScript。
如何解決這個問題?
為了避免變量的變異,需要在要復制數組/對象時,創建數組/對象實例。如何實現這一操作?
使用延展操作符。
延展操作符如何運作
從MDN文檔中可以查到:展開語法(spread syntax),可以在函數調用或數組構造時,將數組表達式或string等iterable在語法層面展開,還可以在構造字面量對象時,將對象表達式按鍵-值方式展開。
簡而言之,延展操作符......延展iterable中的項(iterable指receiver中任何可循環的項,如字符串,數組,集等)。(receiver用于接收展開值。)為便于理解,以下是數組的簡單示例:
const numbers = [1, 2, 3]; console.log(...numbers); //Result: 1 2 3 const pokemon = ['Squirtle', 'Bulbasur', 'Charmander']; console.log(...pokemon); //Squirtle Bulbasur Charmander const pokedex = [ { name: 'Squirtle', type: 'Water' }, { name: 'Bulbasur', type: 'Plant' }, { name: 'Charmander', type: 'Fire' } ]; console.log(...pokedex); //{ name: 'Squirtle', type: 'Water' } { name: 'Bulbasur', type: 'Plant' } { name: 'Charmander', type: 'Fire' } import pandas as pd
數組中使用延展操作符的三個示例
如上所示,當在數組上使用延展操作符時,可以獲取數組中所含的每個單獨的項。在上述所有示例中,receiver都是一個函數,即console.log函數。夠簡單吧?
克隆數組/對象
現在已經知道了延展操作符的工作原理,可以利用它復制數組和對象而不改變其值。怎么做呢?延展內容然后使用數組[]或對象文字{}來生成數組/對象實例。
仍然以上文的杰尼龜為例,通過克隆mySquirtle變量解決上文中的問題:
const mySquirtle = { name: 'Squirtle', type: 'Water', hp: 100 }; const anotherSquirtle = { ...mySquirtle }; anotherSquirtle.hp = 0; console.log(anotherSquirtle); //Result: { name: 'Squirtle', type: 'Water', hp: 0 } console.log(mySquirtle); //Result: { name: 'Squirtle', type: 'Water', hp: 100 }
使用延展操作符復制對象
通過使用解延展操作符解構mySquirtle變量內容并使用對象字面量,創建了Squirtle對象的新實例。這樣,就防止變量突然變異。
使用相同的語法復制數組:
const pokemon = ['Squirtle', 'Bulbasur', 'Charmander']; const pokedex = [...pokemon]; pokedex.push('Cyndaquil'); console.log(pokemon); //[ 'Squirtle', 'Bulbasur', 'Charmander' ] console.log(pokedex); //[ 'Squirtle', 'Bulbasur', 'Charmander', 'Cyndaquil' ]
使用延展操作符復制數組
注意:延展操作符只執行淺拷貝。這意味著若在數組/對象中存儲了引用數據類型,則在使用延展操作符進行復制時,嵌套數組/對象將包含對原始的引用,因此其數值將是可變的。
將類數組對象轉換為數組
類數組對象與數組非常相似。它們通常都有編號元素和長度屬性。但是,兩者有一個至關重要的區別:類數組對象沒有任何數組函數。
類數組對象包含主要由DOM方法返回的HTML節點列表,和每個JS函數和少部分其他函數自動生成的參數變量。
使用與克隆數組相同的語法,可以使用延展操作符將類數組結構轉換為數組,這可以代替使用Array.from的方法。以下是將nodeList轉換為數組的示例:
const nodeList = document.getElementsByClassName("pokemon"); const array = [...nodeList]; console.log(nodeList); //Result: HTMLCollection [ div.pokemon, div.pokemon ] console.log(array); //Result: Array [ div.pokemon, div.pokemon ]
將nodelist轉換為數組
使用這種技術,可以將任何類數組結構轉換為數組,從而訪問所有數組函數。
延展操作符用作參數
某些函數接受可變數量的參數。其中一個典型列子就是Math集合中的函數。以Math.max()函數為例。它接受n個數字參數,并返回最大的參數。假設需要將一個數字數組傳遞給Math.max()函數。該怎么做呢?
可以這樣做:
const numbers = [1, 4, 5]; const max = Math.max(numbers[0], numbers[1], numbers[2]); console.log(max); //Result: 5
但是,這樣做無疑是自尋死路。若是有20個值怎么辦?1000個值呢?真的要通過索引訪問每個值嗎?當然不是。我們可以通過使用延展操作符提取數組中每個單獨的值,如下所示:
const numbers = [1, 4, 5, 6, 9, 2, 3, 4, 5, 6]; const max = Math.max(...numbers); console.log(max); //Result: 9
大救星:延展操作符。
添加新元素
將項添加到數組
向數組添加新元素,首先需要延展數組的內容并使用數字字面量[]創建數組實例,需要包含原始數組的內容以及要添加的值:
const pokemon = ['Squirtle', 'Bulbasur']; const charmander = 'Charmander'; const cyndaquil = 'Cyndaquil'; const pokedex = [...pokemon, charmander, cyndaquil]; console.log(pokedex); //Result: [ 'Squirtle', 'Bulbasur', 'Charmander', 'Cyndaquil' ]
使用延展操作符將項添加到數組中
如你所見,可以任意添加新項。
向對象添加屬性
通過使用與數組相同的語法,可以在克隆對象時輕松添加新屬性。稍微轉變一下,就有一個不同的語法來向對象添加屬性(也可以用于數組):
const basicSquirtle = { name: 'Squirtle', type: 'Water' }; const fullSquirtle = { ...basicSquirtle, species: 'Tiny Turtle Pokemon', evolution: 'Wartortle' }; console.log(fullSquirtle); //Result: { name: 'Squirtle', type: 'Water', species: 'Tiny Turtle Pokemon', evolution: 'Wartortle' }
如你所見,可以在對象字面量中而不是在外部直接聲明和初始化新變量。
合并數組/對象
數組
如上述例子所示,可以通過延展數組并使用數組字面量來合并兩個數組。但是,這一部分要講的不是簡單地添加新元素,而是添加另一個(延展)數組:
const pokemon = ['Squirtle', 'Bulbasur', 'Charmander']; const morePokemon = ['Totodile', 'Chikorita', 'Cyndaquil']; const pokedex = [...pokemon, ...morePokemon]; console.log(pokedex); //Result: [ 'Squirtle', 'Bulbasur', 'Charmander', 'Totodile', 'Chikorita', 'Cyndaquil' ]
使用延展操作符合并數組
這也適用于數組對象:
const pokemon = [ { name: 'Squirtle', type: 'Water' }, { name: 'Bulbasur', type: 'Plant' }];const morePokemon = [{ name: 'Charmander', type: 'Fire' }]; const pokedex = [...pokemon, ...morePokemon]; console.log(pokedex); //Result: [ { name: 'Squirtle', type: 'Water' }, { name: 'Bulbasur', type: 'Plant' }, { name: 'Charmander', type: 'Fire' } ]Merging two arrays of objects with the spread operator
對象
可以使用與之前相同的語法將兩個(或更多)對象合并到一個對象中(你可能已經留意到,擴展運算符在數組和對象中的使用方式非常相似):
const baseSquirtle = { name: 'Squirtle', type: 'Water' }; const squirtleDetails = { species: 'Tiny Turtle Pokemon', evolution: 'Wartortle' }; const squirtle = { ...baseSquirtle, ...squirtleDetails }; console.log(squirtle); //Result: { name: 'Squirtle', type: 'Water', species: 'Tiny Turtle Pokemon', evolution: 'Wartortle' }
使用延展操作符合并對象
本教程說明了為什么應該使用擴展運算符(重點強調不變性!),它是如何工作的以及幾個基本用法。
留言 點贊 關注
我們一起分享AI學習與發展的干貨
如需轉載,請后臺留言,遵守轉載規范
篇文章JavaScript基礎——你真的清楚JavaScript是什么嗎?,我們明白了JavaScript是一個單線程、非阻塞、異步、解釋性語言,清楚了什么是單線程、進程、阻塞、調用堆棧、異步回調、任務循環等感念,沒看的或者不清楚的建議點擊《JavaScript基礎——你真的清楚JavaScript是什么嗎?再看一遍,只有理解了,才能輕松閱讀理解本篇文章內容。
什么是callback?
JavaScript 是單線程工作,這意味著兩段腳本不能同時運行,而是必須一個接一個地運行。我們人類是多線程工作。您可以使用多個手指打字,可以一邊開車一邊與人交談。唯一一個會妨礙我們的是打噴嚏,因為當我們打噴嚏的時候,所有當前進行的活動都必須暫停。這真是非常討厭,尤其是當您在開車并想與人交談時。您可不想編寫像打噴嚏似的代碼。JavaScript由于單線程限制,防止阻塞,只能通過異步函數的調用方式,把需要延遲處理的事件放入事件循環隊列。到目前為止,回調是編寫和處理JavaScript程序異步邏輯的最常用方式。說了這么多,既然回調這么重要,到底什么是回調(callback)呢?
簡單的定義:回調就是一個在另外一個函數執行完后要執行的函數
復雜的定義:在JavaScript中,函數是對象。因此函數可以將函數作為參數,并且可以由其他函數進行返回。執行此操作的函數稱為高階函數。任何作為參數傳遞的函數都稱為回調函數。
為什么需要回調?
開篇已經介紹了JavaScript是單線程的,需要通回調函數處理異步相關的邏輯,理論還是過于生硬,我們還是來看段代碼吧:
function first(){ console.log(1); } function second(){ console.log(2); } first(); second();
正如你所料,先執行first函數,再執行second函數,控制臺將輸出以下內容:
1 2
目前看來沒什么問題,如果first()函數中含有某種無法立即執行的函數呢?例如,我們必須發送請求然后等待結果響應的API請求?為了模擬API請求,我們可以使用setTimeout函數模擬。我們將函數延遲500毫秒來模擬請求,我們更改后代碼如下:
function first(){ // Simulate a code delay setTimeout( function(){ console.log(1); }, 500 ); } function second(){ console.log(2); } first(); second();
我們將 console.log(1) 延遲500毫秒輸出,這段代碼會怎么輸出呢?
2 1
我們希望的順序先執行first,再執行second,但是由于JavaScript是異步的,所有的延遲處理都要放入循環隊列里,因此事與愿違,不能按照我們的希望順序輸出。如果希望這段代碼按照我們的意愿輸出,我們可以使用回調函數,確保某些代碼執行完了,在循序執行另外一段代碼。
創建回調
說了這么多,讓我們創建一個簡單的回調!
我們打開編輯器,先輸入如下代碼:
function doHomework(subject) { alert(`Starting my ${subject} homework.`); }
上面我們創建了doHomeWork的函數,我們接受一個變量,通過控制臺調用,將得到下面的提示:
doHomework('math'); // Alerts: Starting my math homework.
接著,我們開始添加回調,在doHomework函數中添加一個參數callback,然后在第二個參數中回調我們定義的函數。代碼如下:
function doHomework(subject, callback) { alert(`Starting my ${subject} homework.`); callback(); } doHomework('math', function() { alert('Finished my homework'); });
正如你希望的,我們在控制臺里運行上述代碼,將會受到兩個連續的alert,Starting my math homework,然后彈出 Finished my homework。
但是回調函數并不是非得在調用函數中定義,我們可以單獨定義,修改后的代碼如下:
function doHomework(subject, callback) { alert(`Starting my ${subject} homework.`); callback(); } function alertFinished(){ alert('Finished my homework'); } doHomework('math', alertFinished);
此示例的輸出結果和上段代碼的結果一致,我們實現了在doHomework函數中調用alertFinished,實現了函數作為參數進行傳遞,實現了回調函數的創建。
用回調寫一段真實業務場景的代碼!
例如我們有一個需求,用NodeJs實現從論壇帖子列表中顯示其中的一個帖子的信息及留言列表信息,代碼如下:
DB/posts.json(帖子列表數據)
[ { "id": "001", "title": "Greeting", "text": "Hello World", "author": "Jane Doe" }, { "id": "002", "title": "JavaScript 101", "text": "The fundamentals of programming.", "author": "Alberta Williams" }, { "id": "003", "title": "Async Programming", "text": "Callbacks, Promises and Async/Await.", "author": "Alberta Williams" } ]
DB/comments.json(評論列表)
[ { "id": "phx732", "postId": "003", "text": "I don't get this callback stuff." }, { "id": "avj9438", "postId": "003", "text": "This is really useful info." }, { "id": "gnk368", "postId": "001", "text": "This is a test comment." } ]
Index.js
const fs = require('fs'); const path = require('path'); const postsUrl = path.join(__dirname, 'db/posts.json'); const commentsUrl = path.join(__dirname, 'db/comments.json'); //return the data from our file function loadCollection(url, callback) { fs.readFile(url, 'utf8', function(error, data) { if (error) { console.log(error); } else { return callback(JSON.parse(data)); } }); } //return an object by id function getRecord(collection, id, callback) { var collectobj=collection.find(function(element){ return element.id == id; }); callback(collectobj); return collectobj; } //return an array of comments for a post function getCommentsByPost(comments, postId) { return comments.filter(function(comment){ return comment.postId == postId; }); } loadCollection(postsUrl, function(posts){ loadCollection(commentsUrl, function(comments){ getRecord(posts, "001", function(post){ const postComments = getCommentsByPost(comments, post.id); console.log(post); console.log(postComments); }); }); });
大家請注意,我們在loadCollection函數中我們沒有使用try/catch,使用的是if/else,因為catch無法從readFile方法中獲取錯誤。上述代碼還需要完善,我沒有包含任何錯誤處理。如果在任何步驟中發生錯誤,程序將無法繼續。
錯誤處理是很重要的事情,我們寫代碼時要嚴格對待,比如我們要編寫一個用戶登錄的功能。涉及從網頁表單里獲取用戶名和密碼,查詢我們的數據庫,確認用戶信息是否正確,驗證通過后,將用戶引導到用戶中心頁面。如果用戶名密碼格式不正確,用戶名密碼不正確,我們應該將錯誤信息返回給用戶,并引導用戶重新登錄。
總結
很好!我們一起把回調的內容學完了,理解了什么是回調,異步編程是我們的代碼中使用的一種方法,用于推遲事件以便以后執行。當您處理異步任務時,回調是一種解決方案,以便它們按順序執行。
如果我們有多個任務依賴于前幾個任務的結果,那我們就要使用多個嵌套回調,但是就會引發“回調地域”(過多的回調嵌套會使得代碼變得難以理解與維護),還好Promise解決了“回調地獄”的問題,讓我們以同步的方式編寫代碼,小編將會再下篇文章里進行詳細介紹,敬請期待!
更多精彩內容,請微信關注”前端達人”公眾號!
JavaScript、CSS 相比,HTML 經過三十多年的發展,似乎逐漸走進無人問津的角落,如何才能讓 HTML 再次回到人們視野的中心。
作者 | Yaser Adel Mehraban
譯者 | 譚開朗,責編 | 屠敏
出品 | CSDN(ID:CSDNnews)
以下為譯文:
有多少次,身為開發者的你編寫了一個HTML塊而沒有意識到可能編碼得并不理想?
為什么
HTML一直處于無人問津的角落,因為JavaScript和CSS總是吸引人們的注意力。
請在腦海里先保留這種印象,因為我要用一些簡單的技巧來發揮作用,讓HTML再次回到人們視野的中心。
以下是創建一目了然、可維護和可擴展的代碼的一些方法,其很好的應用了HTML5的語義標記元素,并將在支持的瀏覽器中正確呈現。
其緣由就不贅述了,讓我們來看看具體是什么吧。
文檔類型
在index.html的頂部位置,請確保聲明了DOCTYPE。這將在所有瀏覽器中激活標準模式,并告知瀏覽器該如何編譯文檔。請記住DOCTYPE不是HTML元素。
HTML5是這樣的:
<!DOCTYPE html>
注意:如果應用了框架,這已預先寫好。如果沒有,我強烈建議使用像Emmet這樣的代碼片段,它在VS代碼中可用。
想了解更多關于其他文檔類型的信息嗎?點擊這里查看參考文件:https://html.com/tags/doctype/。
可選標簽
有些標簽在HTML5中是可選的,主要是因為元素是隱式呈現的。信不信由你,你可以省略<html>標簽,而頁面呈現得也很好。
<!DOCTYPE HTML>
<head>
<title>Hello</title>
</head>
<body>
<p>Welcome to this example.</p>
</body>
</html>
上面是一個有效的HTML,但在某些情況下就不能這樣做了。例如標簽后面跟著注釋:
<!DOCTYPE HTML>
<!-- where is this comment in the DOM? -->
<head>
<title>Hello</title>
</head>
<body>
<p>Welcome to this example.</p>
</body>
</html>
上面是無效的,因為注釋位于<thml>標簽之外,解析樹發生了更改。
結束標簽
應始終記得結束標簽,否則某些瀏覽器在呈現頁面時會出現問題。出于可讀性和其他原因,建議保留這些內容,稍后我會詳細介紹。
<div id="example">
<img src="example.jpg" alt="example" />
<a href="#" title="test">example</a>
<p>example</p>
</div>
以上都是有效的標簽,但也有一些特例,如下。
自閉合標簽是有效的,但不是必需的。這些元素包括:
<br>, <hr>, <img>, <input>, <link>, <meta>,
<area>, <base>, <col>, <command>, <embed>, <keygen>, <param>, <source>, <track>, <wbr>
注意:普通元素永遠不能有自閉合標簽。
<title />
上面顯然是無效的。
字符集
預先定義字符集。最好是將它放在頂部元素中。
<head>
<title>This is a super duper cool title, right ?</title>
<meta charset="utf-8">
</head>
上面是無效的,標題無法正確呈現。正確寫法是將字符集移到頂部位置。
<head>
<meta charset="utf-8">
<title>This is a super duper cool title, right ?</title>
</head>
語言
不忽略可選標簽的另一個原因是在使用屬性時。在這種情況下,我們應該定義web頁面的語言,這對于可訪問性和搜索非常重要。
<html lang="fr-CA">
...
</html>
標題
永遠不要忽略標題標簽,否則可訪問性太差了。我個人就永遠不會使用這樣的網站,因為我剛打開它即刻在20多個頁面后就找不到了(瀏覽器選項卡不會有任何顯示)。
base標簽
這是一個非常有用的標簽,應該謹慎使用。它將設置應用程序的基本URL。一旦設置好,所有鏈接都將相對于這個基本URL,這可能會導致一些不必要的行為:
<base href="http://www.example.com/" />
通過以上設置,href="#internal"將被編譯為href=http://www.example.com/#internal。或者href="example.org"將被編譯為href="http://www.example.com/example.org"。
描述
這個meta標簽非常有用,盡管嚴格來說它不是最佳寫法。但在搜索引擎時,這是超級有用的。
<meta name="description" content="HTML best practices">
這有一個帖子“搜索引擎優化正盛行”:https://yashints.dev/blog/2019/06/11/seo-tips。
語義標簽
雖然可以使用div創建UX工程師的線框,但這并不意味著必須這樣做。語義HTML為頁面提供了意義,而不單純是內容顯示。像p、section、h{1-6}、main、nav等標簽都是語義標簽。如果使用p標簽,用戶將知道這表示一段文本,瀏覽器也知道如何展示它們。
語義HTML超出了本文的范圍。但是我們應該進行檢查,就好比寫作所用的筆,而我們有鼠標。
hr不應該用于格式化
<hr>不是格式化元素,所以不要用它來格式化內容。在HTML5中,這個標簽代表了內容的主題分離。正確的用法是這樣的:
<p>Paragraph about puppies</p>
<p>Paragraph about puppies' favourite foods</p>
<p>Paragraph about puppies' breeds</p>
<hr>
<p>Paragraph about why I am shaving my head </p>
使用title屬性時要小心
title屬性是一個功能強大的工具,它可以幫助闡明頁面上元素的操作或目的,比如工具提示。但是,它不能與圖像上的alt等其他屬性互換。
HTML 5 規范道:
目前不鼓勵依賴title屬性,因為很多用戶代理不按照規范的訪問方式來暴露該屬性(例如,使用鼠標等設備來喚出提示框,而不包括只用鍵盤或觸控鍵盤的用戶,或者現代手機或平板電腦)。
請閱讀有關如何正確使用此屬性的更多信息:https://html.spec.whatwg.org/multipage/dom.html#the-title-attribute。
單引號或雙引號
我見過的許多代碼庫,他們的標記中混合了這兩種形式。這并不好,特別是當你使用一個依賴于單引號的框架時,比如php,當你在一個句子中使用單引號時,就像我現在做的一樣。另一個原因是保持一致,這總是好的。不要這樣寫:
<img alt="super funny meme" src='/img/meme.jpg'>
而寫為:
<img alt="super funny meme" src="/img/meme.jpg">
省略布爾值
當涉及到屬性的布爾值時,建議省略,因為它們不添加任何值,還會增加標記的權重。
public class MyActivity extends AppCompatActivity {
<audio autoplay="autoplay" src="podcast.mp3">
<!-- instead -->
<audio autoplay src="podcast.mp3">
省略類型屬性
不需要向scriptand樣式標簽添加type屬性。某些服務(如W3C的標記驗證工具)還會出現驗證錯誤。
驗證標記
可以使用W3C的標記驗證等服務以確保有效的標記。
拒絕內聯樣式
HTML中寫的是內容,其如何展示取決于樣式。將展示形式留給CSS吧,不要使用內聯樣式,這將有利于開發人員和瀏覽器理解你的標記。
總結
這些只是編寫標簽時要記住的冰山一角。還有很多很好的資源可以讓你深入了解,強烈建議你重復閱讀。
《GitHub HTML最佳實踐》:https://github.com/hail2u/html-best-practices
《W3C School HTML樣式指南》:https://www.w3schools.com/html/html5_syntax.asp
希望你喜歡本文,并能寫出優雅的標簽。
原文:https://dev.to/yashints/let-s-write-html-like-a-pro-28h5
本文為 CSDN 翻譯,轉載請注明來源出處。
【END】
*請認真填寫需求信息,我們會在24小時內與您取得聯系。