本文論述的12個概念,對於 JavaScript 開發者來說都是非常重要的。
作者 | Nick Scialli
譯者 | 譚開朗
責編 | 屠敏
以下為譯文:
JavaScript 是一種複雜的程式語言。無論你的開發水平如何,理解 JavaScript 的基礎概念都尤為重要。
本文將與大家分享 12 個實用的 JavaScript 技能,未來我也將在 Github 的 JS Tips&Tidbits 倉庫(https://github.com/nas5w/javascript-tips-and-tidbits)中持續更新此概念列表。
值和引用變量的賦值
理解 JavaScript 的賦值原理是正確編碼的基礎,否則,很容易在編碼過程中無意篡改值。
JavaScript 通常是通過值來賦值變量。但需要注意一點:JavaScript 基本數據類型(布爾值、null、undefined、字符串和數字)的賦值是拷貝賦值,而數組,函數或對象的賦值是引用賦值。
舉個例子。下面的代碼,把 var1 賦值給 var2。因為 var1 是基本數據類型(字符串),所以 var2 等於 var1 的字符串值,或者說 var2 全等於 var1。因此,給 var2 重新賦值不會影響到 var1。
let var1 = 'My string';let var2 = var1;var2 = 'My new string';console.log(var1);// 'My string'console.log(var2);// 'My new string'
讓我們對比一下給對象賦值。
let var1 = { name: 'Jim' }let var2 = var1;var2.name = 'John';console.log(var1);// { name: 'John' }console.log(var2);// { name: 'John' }
如果還像上面那樣原始賦值,就出現問題了。如果創建了一個會篡改值的函數,那情況將會更糟糕。
閉包
閉包是一種重要的 JavaScript 模式,用於對變量進行私有訪問。如下例子中,createGreeter 函數返回一個能訪問 greeting 值為「Hello」的匿名函數。往後每引用一次 sayHello 變量都會訪問該 greeting 值。
functioncreateGreeter(greeting) {returnfunction(name) {console.log(greeting + ', ' + name); }}const sayHello = createGreeter('Hello');sayHello('Joe');// Hello, Joe
在更真實的案例場景中,假設一個初始函數 apiConnect(apiKey) 返回一些引用 API key 的方法。在這種情況下,初始函數的內部參數 apiKey 只需賦值一次,往後無需重新賦值。
functionapiConnect(apiKey) {functionget(route) {return fetch(`${route}?key=${apiKey}`); }functionpost(route, params) {return fetch(route, {method: 'POST',body: JSON.stringify(params),headers: {'Authorization': `Bearer ${apiKey}` } }) }return { get, post }}const api = apiConnect('my-secret-key');// No need to include the apiKey anymoreapi.get('http://www.example.com/get-endpoint');api.post('http://www.example.com/post-endpoint', { name: 'Joe' });
解構
別忘了學習 JavaScript 參數的解構賦值。這是一種簡潔地提取對象屬性的通用方法。
const obj = {name: 'Joe',food: 'cake'}const { name, food } = obj;console.log(name, food);// 'Joe' 'cake'
要想提取不同名的屬性,可以使用以下格式來指定它們。
const obj = {name: 'Joe',food: 'cake'}const { name: myName, food: myFood } = obj;console.log(myName, myFood);// 'Joe' 'cake'
在下面的例子中,解構簡潔地向 introduce 函數傳遞 person 對象參數。換言之,解構可以(通常用來)直接提取參數同時傳遞給一個函數。如果你熟悉 React,那很可能在之前就見到過此用法。
const person = {name: 'Eddie',age: 24}functionintroduce({ name, age }) {console.log(`I'm ${name} and I'm ${age} years old!`);}console.log(introduce(person));// "I'm Eddie and I'm 24 years old!"
展開語法
展開語法是個比較難理解的 JavaScript 概念,叫擴展運算符會相對容易理解。在下面的例子中,Math.max 方法不會作用於數組 arr,因為 Math.max 方法的參數只能是單個元素而不能是一個數組。擴展運算符可以從數組中提取出單個元素。
const arr = [4, 6, -1, 3, 10, 4];const max = Math.max(...arr);console.log(max);// 10
剩餘語法
讓我們來談談 JavaScript 的剩餘語法。剩餘語法可以通過一個函數將任意數量的參數收集到一個數組中。
functionmyFunc(...args) {console.log(args[0] + args[1]);}myFunc(1, 2, 3, 4);// 3
數組方法
JavaScript 數組方法可以神奇且優雅的進行數據轉換。作為 StackOverflow 的貢獻者之一,我經常看到關於如何用某種方法操作數組對象的問題。這往往是數組方法的最佳用例。
我將在這裡用類似的表達方式(某些已合併在內)來介紹多種不同的數組方法。以下羅列的方法並不全面:你也可以參考 MDN(我喜愛的 JavaScript 參考資料:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#)回顧和實踐所有方法。
map, filter, reduce
JavaScript 的 map、filter、reduce 數組方法很容易被混淆。這些方法是用來轉換數組或者返回值的集合。
map:將調用的數組的每個元素傳遞給指定的函數,並返回一個數組。const arr = [1, 2, 3, 4, 5, 6];const mapped = arr.map(el => el + 20);console.log(mapped);// [21, 22, 23, 24, 25, 26]
filter:返回的數組元素是函數邏輯為真的一個子集。const arr = [1, 2, 3, 4, 5, 6];const filtered = arr.filter(el => el === 2 || el === 4);console.log(filtered);// [2, 4]
reduce:按函數方法計算值。const arr = [1, 2, 3, 4, 5, 6];const reduced = arr.reduce((total, current) => total + current);console.log(reduced);// 21
find, findIndex, indexOf
JavaScript 的 find, findIndex, indexOf 數組方法通常被合併。用法如下。
find:返回與指定條件匹配的第一個元素,不再往後匹配。const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];const found = arr.find(el => el > 5);console.log(found);// 6
再次注意,(上面的例子中)雖然5以後的所有值都符合條件,但也只返回第一個匹配的元素。這在 for 循環中匹配某個條件後便跳出循環的情況下非常有用!
findIndex:和 find 用法相同,不過不是返回第一個匹配的元素,而是返回該匹配元素的索引值。為清晰可見,以下面的名字數組為例,而不是數字數組。const arr = ['Nick', 'Frank', 'Joe', 'Frank'];const foundIndex = arr.findIndex(el => el === 'Frank');console.log(foundIndex);// 1
indexOf:和 findIdex 用法相同,但它不以函數作為參數,它的參數是一個簡單的值。適用於簡單邏輯或不需要要函數進行判定的情況。const arr = ['Nick', 'Frank', 'Joe', 'Frank'];const foundIndex = arr.indexOf('Frank');console.log(foundIndex);// 1
push, pop, shift, unshift
有很多很棒的數組方法可以給目標數組添加或刪除元素。
push:這是一種將元素添加至數組末尾的相對簡單的方法。它修改數組值,函數本身返回壓入數組的值。let arr = [1, 2, 3, 4];const pushed = arr.push(5);console.log(arr);// [1, 2, 3, 4, 5]console.log(pushed);// 5
pop:刪除數組的最後一個元素。同樣,它改變了原數組。函數本身返回被刪除元素的值。let arr = [1, 2, 3, 4];const popped = arr.pop();console.log(arr);// [1, 2, 3]console.log(popped);// 4
shift:從數組頭部刪除一個元素。同樣,它改變了原數組。函數本身返回被刪除元素的值。let arr = [1, 2, 3, 4];const shifted = arr.shift();console.log(arr);// [2, 3, 4]console.log(shifted);// 1
unshift:在數組頭部添加一個或多個元素。同樣,它改變了原數組。與其他很多方法不同的是,函數本身返回數組新的長度。let arr = [1, 2, 3, 4];const unshifted = arr.unshift(5, 6, 7);console.log(arr);// [5, 6, 7, 1, 2, 3, 4]console.log(unshifted);// 7
splice, slice
這兩個方法都是修改數組子集或返回數組子集。
splice:通過刪除或替換現有元素和/或添加新元素改變數組的值。此方法會改變原數組。下面的示例代碼解讀為:從第1位數組元素開始,往後移除0個元素,同時插入 b 值。
let arr = ['a', 'c', 'd', 'e'];arr.splice(1, 0, 'b')
slice:返回從指定的起始位置到結束位置的淺拷貝數組。如果沒有指定結束位置,則會返回到原數組的最後部分。重要的是,該方法不會改變原數組的值,而是返回對應的子集。let arr = ['a', 'b', 'c', 'd', 'e'];const sliced = arr.slice(2, 4);console.log(sliced);// ['c', 'd']console.log(arr);// ['a', 'b', 'c', 'd', 'e']
sort
sort:根據帶兩個參數的函數對數組進行排序。改變原數組的值。當函數返回負數或0,則數組元素順序不改變。當函數返回正數,數組元素會重新排序。let arr = [1, 7, 3, -1, 5, 7, 2];const sorter = (firstEl, secondEl) => firstEl - secondEl;arr.sort(sorter);console.log(arr);// [-1, 1, 2, 3, 5, 7, 7]
嘿,以上方法你都掌握了嗎?或許我們都沒有。實際上,在我寫本篇文章時夜不得不時常查閱 MDN 文檔,這很正常。只要知道有什麼數組方法就達到 95% 的效果啦。
Generators 函數
不要害怕使用 * 函數。Generator 函數指定了下一次調用 next() 生成的 value 值。在有限個生成值或無限次循環下使用,最後 next() 會返回一個 undefined 值。
function* greeter() {yield'Hi';yield'How are you?';yield'Bye';}const greet = greeter();console.log(greet.next().value);// 'Hi'console.log(greet.next().value);// 'How are you?'console.log(greet.next().value);// 'Bye'console.log(greet.next().value);// undefined
下面是一個無限值的 Generator 函數:
function* idCreator() {let i = 0;while (true)yield i++;}const ids = idCreator();console.log(ids.next().value);// 0console.log(ids.next().value);// 1console.log(ids.next().value);// 2// etc...
恆等運算符和相等運算符
一定要知道 JavaScript 中恆等運算符和相等運算符之間的區別!相等運算符在比較值前允許進行類型轉換,而恆等運算符則不允許類型轉換。
console.log(0 == '0');//trueconsole.log(0 === '0');//false
比較對象
JavaScript 新手常犯的錯誤之一是對對象直接進行比較。變量是指向內存中的引用對象,而不是對象本身!實際比較它們的一種方法是將對象轉換成 JSON 字符串。這種方法有個缺點:不能保證對象屬性的順序!比較對象更安全的方法是使用專門用於深度對象比較的庫(比如 lodash 庫的 isEqual 方法)。
下面這兩個對象看起來相等,但實際上它們指向不同的引用對象。
const joe1 = { name: 'Joe' };const joe2 = { name: 'Joe' };console.log(joe1 === joe2);// false
相反地,下面的計算結果返回 true,因為把一個對象賦值給另一個對象,它們的指向相同(內存中只有一個對象)。
const joe1 = { name: 'Joe' };const joe2 = joe1;console.log(joe1 === joe2);// true
請回顧上面的第一個概念:值和引用變量的賦值,將有助於更系統的理解將指向內存的一個對象變量賦值給另一個變量的結果。
回調函數
太多人被 JavaScript 的回調函數嚇倒了。回調函數很簡單,舉下面這個例子。將 console.log 作為回調函數傳給 myFunc 函數,它在計時器 setTimeout 完成時執行。這就是回調函數的全部!
functionmyFunc(text, callback) { setTimeout(function() { callback(text); }, 2000);}myFunc('Hello world!', console.log);// 'Hello world!'
Promises
一旦你理解了 JavaScript 的回調函數,你很快就發現自己陷入了嵌套的「回調地獄」。這就是 Promises 的用途。將異步邏輯封裝在 Promise 中,異步操作執行成功則運行 resolve 回調函數,否則則運行 reject 回調函數。Promise 運行成功進入 then 回調函數,失敗則進入 catch 回調。
const myPromise = newPromise(function(res, rej) { setTimeout(function(){if (Math.random() < 0.9) {return res('Hooray!'); }return rej('Oh no!'); }, 1000);});myPromise .then(function(data) {console.log('Success: ' + data); }) .catch(function(err) {console.log('Error: ' + err); });// If Math.random() returns less than 0.9 the following is logged:// "Success: Hooray!"// If Math.random() returns 0.9 or greater the following is logged:// "Error: On no!"
Async Await
一旦掌握了 JavaScript Promises 的秘訣,你可能會喜歡 async await,這是基於 Promises 的「語法糖」。在下面的例子中,我們創建一個異步函數 myFunc,並在函數中等待執行 greeter 這一 promise。
const greeter = newPromise((res, rej) => { setTimeout(() => res('Hello world!'), 2000);})asyncfunctionmyFunc() {const greeting = await greeter;console.log(greeting);}myFunc();// 'Hello world!'
結論
如果你以前完全不知道以上這 12 個概念,那麼現在,你的 JavaScript 知識多少有些增長了!如果你知道所有這些,那麼希望這是鞏固和增長知識的機會。
原文:https://hackernoon.com/12-javascript-concepts-that-will-level-up-your-development-skills-b37d16ad7104本文為 CSDN 翻譯,如需轉載,請註明來源出處。