翻译:JavaScript 中处理 undefined 的 7 个技巧
7 tips to handle undefined in JavaScript
上面是原文鏈接。今天想嘗試來翻譯下這篇文章。
------------- 我是正文如下的分割線 ----------------
大約八年前,我剛開始學習JavaScript,我覺得很奇怪的是,undefined和null都代表空值。那么它們之間有什么明顯的區別?它們似乎都定義空值,而且在控制臺比較null == undefined輸出為true。
大多數的現代語言如Ruby,Python或Java都只有一個空值(nil或null),看上去很合理的樣子。
而對于JavaScript,當訪問尚未初始化的變量或對象屬性時,解釋器將返回undefined。舉個栗子:
let company;
company; // => undefined
let person = { name: 'John Smith' };
person.age; // => undefined
null則表示不存在的對象引用。JavaScript本身并不將變量或對象屬性設置為null。
一些內部對象的方法比如String.prototype.match()可以通過返回null來表示一個丟失的對象。具體看下面的例子:
let array = null;
array; // => null
let movie = { name: 'Starship Troopers', musicBy: null };
movie.musicBy; // => null
'abc'.match(/[0-9]/); // => null
由于javascript是松散型語言,開發者很容易被訪問未初始化的值誘惑。我也曾犯過這樣低級的錯誤。
通常這樣的冒險行為會引發undefined的相關錯誤,并迅速停止腳本運行。相關的常見錯誤信息是:
TypeError: 'undefined' is not a function
TypeError: Cannot read property '<prop-name>' of undefined
其他類似的類型錯誤
一個JavaScript開發者都懂的笑話:
function undefined() {
// problem solved
}
為了減少此類錯誤的風險,您必須了解可能引發undefined的場景。更重要的是避免它在你的應用程序中出現并引發其他錯誤,這增加了代碼的耐用性。
接下來,讓我們詳細了解undefined以及其對代碼安全的影響。
1. undefined 是什么
JavaScript有6種基本數據類型:
Boolean 布爾值:trueorfalse
Number 數值:1,6.7,0xFF
String 字符串:"Gorilla and banana"
Symbol 獨一無二的值:Symbol("name")(ES6 引入)
Null:null
Undefined:undefined.
另外還有一種Object類型:{name: "Dmitri"},["apple", "orange"].(由鍵值對組成)
在這6種基本類型中,undefined是undefined類型的唯一的值。
根據ECMAScript標準:
Undefined valueprimitive value is used when a variable has not been assigned a value.
當一個變量(聲明后)沒有被賦值時,這個變量的值會被默認為undefined。
標準明確規定,當您訪問未初始化的變量,或者不存在的對象屬性、數組元素等等,您會得到一個值undefined。例如:
let number;
number; // => undefined
let movie = { name: 'Interstellar' };
movie.year; // => undefined
let movies = ['Interstellar', 'Alexander'];
movies[3]; // => undefined
正如上面的例子所示,訪問:
一個未初始化的變量number
對象未定義的屬性movie.year
或者數組中不存在的元素movies[3]
均會得到一個值:undefined
ECMAScript規范規定了undefined值的類型:
Undefined type is a type whose sole value is the undefined value.
undefined類型只有一個唯一的值:undefined
從這個意義上,typeof運算符為undefined值返回一個字符串undefined:
typeof undefined === 'undefined'; // => true
當然,typeof可以很好驗證一個變量是否包含了一個未定義的值:
let nothing; typeof nothing === 'undefined'; // => true
2. 引發undefined的常見場景
2.1 未初始化變量
A declared variable that is not yet assigned with a value (uninitialized) is by default
undefined.
“聲明一個變量,未賦值(未初始化),變量的值默認為undefined。”
舉個顯而易見的例子:
let myVariable; myVariable; // => undefined
聲明了變量myVariable,未賦值。那么訪問這個變量,返回undefined
解決未初始化變量問題的一個有效方法是,盡可能分配一個初始值。變量未初始化情況越少越好。理想情況下,您在聲明一個變量后立刻賦值const myVariable = 'Initial value',但這并不總是如您所愿。
Tip 1:使用const,或者let,不使用var
在我看來,ECMAScript 2015的最佳特色之一,便是提供了聲明變量的新方法:const和let。這是一個很大的進步,這些聲明的作用域在其代碼所在的代碼塊以內(相反,var聲明的作用域在該語句所在的函數體內),并且保存在一個“暫存死區”內直到變量被聲明。
當要給一個變量賦一個值并且不修改值的時候,我建議用const聲明變量。它創建了一個不可變的綁定。
<---------- 插入非翻譯文原文的題外話的分割線 START ------>
在查資料的時候發現,有些人認為const聲明的是不可變的常量。這是不完全正確的(譯者注:作為一個菜鳥,說這句話總有點底氣不足)??丛模?/p>
ES6
constdoesnotindicate that a value is ‘constant’ or immutable. Aconstvalue can definitely change. The following is perfectly valid ES6 code that does not throw an exception:
const foo = {};
foo.bar = 42;
console.log(foo.bar);
// → 42
這個代碼并未拋出異常,說明const聲明的變量是可變的。不可變的只是const聲明的變量所創建的綁定。(這里就不展開敘述)
<---------- 插入非翻譯文原文的題外話的分割線 END ------>
const的一個美妙特性是,你必須給變量賦值一個初始值const myVariable = 'initial'.變量不會暴露在初始化狀態,也不可能訪問到undefined。
下面這個函數,讓我們來驗證一個詞是否一個回文:
function isPalindrome(word) {
const length = word.length;
const half = Math.floor(length / 2);
for (let index = 0; index < half; index++) {
if (word[index] !== word[length - index - 1]) {
return false;
}
}
return true;
}
isPalindrome('madam'); // => true
isPalindrome('hello'); // => false
length和half兩個變量被一次性賦值,值也不會被修改,因此用const來聲明看上去很合理。
如果您需要重新綁定變量(即多次賦值),那么用let來聲明變量。只要有可能,立即給它分配一個初始值,例如let index = 0.
那么老家伙var怎么辦?基于ES2015,我的建議是把它掃進歷史垃圾堆吧。
使用var來聲明變量的一個問題是,發生在整個函數作用域變量提升。您可以在函數作用域底部聲明一個 var 變量,就可以在函數頂部訪問到這個聲明的變量,然后得到一個值:undefined。
function bigFunction() {
// code...
myVariable; // => undefined
// code...
var myVariable = 'Initial value';
// code...
myVariable; // => 'Initial value'
}
bigFunction();
在這個語句var myVariable = 'Initial value'之前,變量myVariable就可以訪問,并且含有一個undefined的值。
相反的,let(包括const)聲明的變量在聲明之前無法訪問。因為在聲明之前,變量保存在一個暫存死區(TDZ = temporl dead zone)內。這很愉快,因為您沒有多少機會獲取到一個undefined的值。
上訴例子,用let來代替var,會拋出一個ReferenceError異常,因為您無法訪問在TDZ里的變量。
function bigFunction() {
// code...
myVariable; // => 拋出異常 'ReferenceError: myVariable is not defined'
// code...
let myVariable = 'Initial value';
// code...
myVariable; // => 'Initial value'
}
bigFunction();
給不可變的綁定使用const或者 let,盡量避免您的代碼暴露給未初始化的變量。
Tip 2:增加聚合度
聚合度是指一個模塊內部(命名空間、類、方法、代碼塊)承擔職責之間的相關程度。評估聚合度強度,我們通常稱為高內聚或者低內聚。
高內聚略勝一籌,因為高內聚意味著一個模塊僅完成一個獨立的功能(譯者注:模塊內部不存在與該功能無關的操作或狀態。),它的優點是:
專一性和易于理解性: 更容易理解一個模塊的功能。
可維護性和容易重構:減少模塊對其他模塊內部實現的依賴。
可重用:專注于一個單一的任務,它使模塊更容易重用。
測試性:可以更容易地測試集中在單個任務上的模塊。
(a. 低耦合高內聚 b. 高耦合低內聚)
好的設計的一個特征,就是高內聚低耦合。
一個代碼塊本身可以看作是一個小模塊。為了獲得高內聚的好處,您需要將變量盡可能地靠近使用它們的代碼塊。
例如,如果一個變量的功能只是用在塊作用域內,則聲明變量并只允許變量在該塊中生存(通過使用const或let聲明)。不要將這個變量暴露在這個塊作用域外,因為這個變量和外部無關
一個典型例子是函數內使用for循環導致變量壽命過長:
function someFunc(array) {
var index, item, length = array.length;
// some code...
// some code...
for (index = 0; index < length; index++) {
item = array[index];
// some code...
}
return 'some result';
}
變量index,item和length在函數體頂部聲明,卻在底部才被引用。那么這種方法有什么問題呢?
所有在頂部聲明變量,在for循環內使用變量的方式,變量item,index,item未被初始化并且面臨(返回)一個 undefined。它們的生命周期很不講道理地,長達整個函數作用域。
一個更好的方法是在靠近第一次使用的位置初始化變量,
function someFunc(array) {
// some code...
// some code...
const length = array.length;
for (let index = 0; index < length; index++) {
const item = array[index];
// some
}
return 'some result';
}
變量index和item只生存在for循環體內。在for循環外,它們沒有任何意義。
變量length也是在引用它的位置附近聲明。
為什么修改后的版本比上一個版本更好一些呢。讓我們來看看它的優勢:
變量并未暴露在未初始化狀態,減少您讀取到undefined的風險。
盡可能的把變量定義在靠近使用它的地方,增加代碼可讀性。
高內聚的代碼更容易重構、在必要時更容易提取分離功能。
2.1 訪問非現有屬性
When accessing anon-existing object property, JavaScript returns
undefined.讀取不存在的對象屬性時JavaScript會返回 undefined。
下面用一個例子來論證:
let favoriteMovie = {
title: 'Blade Runner'
};
favoriteMovie.actors; // => undefined
對象favoriteMovie只有一個屬性title,使用屬性訪問器favoriteMovie.actors讀取不存在的屬性actors返回undefined。
讀取不存在的屬性并不會報錯。真正的問題出現在試圖從非現有屬性值獲取數據時。這是undefined引發的普遍陷阱,比如一個眾所周知的報錯信息:TypeError: Cannot read property <prop> of undefined.
讓我們稍微修改前面的代碼片段來表明一個TypeError異常:
let favoriteMovie = {
title: 'Blade Runner'
};
favoriteMovie.actors[0];
// TypeError: Cannot read property '0' of undefined
favoriteMovie沒有actors這個屬性,因此這個屬性是undefined。
結果就是,用favoriteMovie.actors[0]讀取一個未定義值的第一個元素,拋出一個類型異常:TypeError。
JavaScript的允許訪問非現有屬性的這個特性是造成這個混淆的來源。這個屬性設置了嗎,還是未設置。避開這個問題的理想方法是約束對象始終定義它所持有的屬性。
然而,您并不總是能控制你所使用的對象。這些對象在不同的場景中可能有不同的屬性集。所以你必須手動處理所有這些場景。
假設現在要實現一個append(array, config)函數,它可以在數組的開始和/或結束時添加新元素。參數config接受具有以下屬性的對象:
first: 在數組前插入元素
last: 在數組結尾插入元素.
這個函數返回一個新的數組,不會更改原數組(也就是說,它是一個純函數)。(譯注:純函數指不依賴于且不改變它作用域之外的變量狀態的函數。返回值只由它調用時的參數決定。)
下面看append()函數的一個簡單粗略的例子。
function append(array, config) {
const arrayCopy = array.slice();
if (config.first) {
arrayCopy.unshift(config.first);
}
if (config.last) {
arrayCopy.push(config.last);
}
return arrayCopy;
}
append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]
append(['Hello'], { last: 'World' }); // => ['Hello', 'World']
append([8, 16], { first: 4 }); // => [4, 8, 16]
因為對象config可以省略第一個或最后一個屬性,所以必須驗證這些屬性是否存在于對象config中。
屬性如果不存在,則返回undefined。條件語句if(config.first){}和if(config.last){},用來驗證first或last屬性是否未定義,檢查屬性是否存在。
先不忙下定論。這個方法有一個嚴重的缺點。undefined,還有false,null,0,NaN和" "都是falsy值(譯者注:當進行邏輯判斷時均為false)。
在這種情況下,參數的屬性值為falsy的函數被拒絕執行。
append([10], { first: 0, last: false }); // => [10]
由于0和false都是falsy,if(config.first){}和if(config.last){}對falsy進行了對比,這兩個元素并不會被插入到數組,函數返回了一個未修改的數組[10]。
下面的提示說明如何正確檢查屬性是否存在。
Tip 3:檢查屬性是否存在
幸運的是,JavaScript提供了一系列方法來確定對象是否具有某種屬性:
obj.prop !== undefined: 直接和undefined 對比
typeof obj.prop !== 'undefined': 驗證屬性的值的類型
obj.hasOwnProperty('prop'): (接收一個字符串參數)驗證對象是否具有自己的(不是在原型鏈中的)某個(這個參數名字的)屬性。
'prop' in obj: 驗證對象是否擁有或者繼承某個屬性。
我的建議是使用in操作符,它是一個語法糖,目的很明確,只檢查對象是否具有特定屬性,而不訪問實際的屬性值。
obj.hasOwnProperty('prop')也是一個比較好的解決辦法。它比in操作符稍長,只驗證對象本身的屬性。
以上提到的兩個方式,在和 undefined 比較時有用。但是在我看來,obj.prop !== undefined和typeof obj.prop !== 'undefined'顯得冗長怪異,并且暴露了一個直接處理 undefined的環境變量(譯者注:這句不太理解)。
我們用操作符 in 來改進代碼:
function append(array, config) {
const arrayCopy = array.slice();
if ('first' in config) {
arrayCopy.unshift(config.first);
}
if ('last' in config) {
arrayCopy.push(config.last);
}
return arrayCopy;
}
append([2, 3, 4], { first: 1, last: 5 }); // => [1, 2, 3, 4, 5]
append([10], { first: 0, last: false }); // => [0, 10, false]
相應的屬性只要存在,'first' in config(和 'last' in config) 就是true,否則就是false。
操作符in解決了屬性值為0和false的問題,函數執行得到了我們想要的結果:[0, 10, false].
Tip 4:解構對象屬性
讀取對象屬性時,如果屬性不存在,則需要指示默認值。
結合三元運算符和 in 操作符來完成這個目的:
const object = { };
const prop = 'prop' in object ? object.prop : 'default';
prop; // => 'default'
需要檢查的屬性越多,三元運算符的語法就越難用。對于每一個屬性,您必須創建一行新的代碼來處理默認值,就像壘砌一堵三元運算符的丑陋的墻。
為了讓我們的代碼更優雅一些,我們來了解下ES2015的這個超贊的新語法:解構。
對象解構允許直接將對象屬性值直接插入變量中,如果屬性不存在,則設置默認值(譯者注:解構可以用很簡潔的方式為未定義屬性或值設置默認值)。這個語法避免直接處理undefined。
真正地實現了簡短并且意義明確地獲取屬性:
const object = { };
const { prop = 'default' } = object;
prop; // => 'default'
為了查看它如何工作,讓我們定義一個函數,用引號包一個字符串。quote(subject, config)的第一個參數作為要包裝的字符串,第二個參數 config 是一個具有以下屬性的對象:
char: 符號, 例如 '(單引號) 或者 "(雙引號)。 默認為".
skipIfQuoted: 字符串如果已有引號,則跳過這個字符串。返回一個布爾值。默認為true。
應用對象解構的優勢,我們來實現這個函數 quote():
function quote(str, config) {
const { char = '"', skipIfQuoted = true } = config;
const length = str.length;
if (skipIfQuoted
&& str[0] === char
&& str[length - 1] === char) {
return str;
}
return char + str + char;
}
quote('Hello World', { char: '*' }); // => '*Hello World*'
quote('"Welcome"', { skipIfQuoted: true }); // => '"Welcome"'
const { char = '"', skipIfQuoted = true } = config一行代碼解構賦值,從對象config提取屬性char和skipIfQuoted
如果config對象中的個別屬性未定義,解構賦值也可以為char設置默認值為"", 為skipIfQuoted設置默認值為true(譯者注:原文為false,但我覺得這里應該是true)。
幸運的是,這個函數還有改進空間。
直接把解構賦值作為參數,并把參數config設置默認值為一個空的對象{},當有足夠的默認設置時,省略第二個參數(??)。
function quote(str, { char = '"', skipIfQuoted = true } = {}) {
const length = str.length;
if (skipIfQuoted
&& str[0] === char
&& str[length - 1] === char) {
return str;
}
return char + str + char;
}
quote('Hello World', { char: '*' }); // => '*Hello World*'
quote('Sunny day'); // => '"Sunny day"'
注意,這里用解構賦值代替了參數config來作為函數簽名。我更喜歡這樣:quote()少了一行。
= {}在解構賦值表達式的右側,確保如果沒有指定第二個參數,則使用空對象。quote('Sunny day').
對象解構是一個強大的功能,更直觀清晰地提取對象的屬性。我喜歡在訪問未定義的屬性時指定要返回的默認值。
因為這樣可以避免undefined和undefined帶來的問題。
Tip 5: 用默認屬性填充對象
如果不需要像解構賦值那樣給每個屬性創建變量,可以用默認值來填充缺失一些屬性的對象。
ES2015中,Object.assign(target, source1, source2, ...)將源對象(source)的所有可枚舉屬性,復制到目標對象(target)。函數返回目標對象。
(譯者注:Object.assign方法實行的是淺拷貝,而不是深拷貝。也就是說,如果源對象某個屬性的值是對象,那么目標對象拷貝得到的是這個對象的引用。)
例如,你需要訪問unsafeoptions對象的屬性,它并不總是包含了完整的屬性。
為避免訪問不存在的屬性時獲取undefined,我們來做一些調整:
定義一個對象defaults用來保存默認屬性值。
調用Object.assign({ }, defaults, unsafeOptions)創建新對象options. 新對象從unsafeOptions接收所有屬性, 而缺失的部分則從defaults 獲取.
const unsafeOptions = {
fontSize: 18
};
const defaults = {
fontSize: 16,
color: 'black'
};
const options = Object.assign({}, defaults, unsafeOptions);
options.fontSize; // => 18
options.color; // => 'black'
unsafeOptions 只有一個屬性 fontSize。對象 defaults 為屬性值 fontSize 和 color 定義了默認值。
Object.assign()第一個參數作為目標對象 {}. 目標對象從源對象unsafeOptions獲取屬性fontSize的值,由于unsafeOptions不具有屬性color, 從源對象 defaults 獲取屬性 color 的值。
所枚舉的源對象的位置很重要:后面的屬性會覆蓋前面的屬性。
現在您可以很安全的訪問option對象的任何屬性,包括并未在unsafeOptions對象中的options.color。
其實還有一個更容易和更簡潔的方法來填充對象的默認屬性。
我推薦使用一個新的JavaScript語法(現在3級),對象字面量的擴展特性。
不調用Object.assign(),而是使用對象的擴展語法從源對象復制可枚舉的屬性到目標對象。
(譯者注:spread syntax擴展語法:可以使用三個點作為前綴,即...應用于可遍歷對象上,訪問每個元素。)
const unsafeOptions = {
fontSize: 18
};
const defaults = {
fontSize: 16,
color: 'black'
};
const options = {
...defaults,
...unsafeOptions
};
options.fontSize; // => 18
options.color; // => 'black'
(譯者注:如果您的瀏覽器在這個例子上報錯了,您可以下一個babel插件:babel-plugin-transform-object-rest-spread,也可以看另外幾個簡單例子:擴展語法復制一個數組,復制的是引用。這里就不多做展開)
1. 復制數組:
const names = ['Luke','Eva','Phil']; const copiedList = [...names] console.log(copiedList);// ['Luke','Eva','Phil']
2. 連接數組: const concatinated = [...names, ...names]; console.log(concatinated); // ['Luke','Eva','Phil', 'Luke','Eva','Phil']
對象字面量可以把兩個源對象的屬性擴展到一起。所枚舉的源對象的位置很重要:后面的屬性會覆蓋前面的屬性。
使用默認屬性值填充不完整的對象是使代碼安全持久的有效策略。無論情況如何,對象始終包含完整的屬性集:undefined也不會再出現。
2.3 函數參數
The function parameters implicitly default to
undefined.函數參數默認為未定義。
通常,具有特定參數個數的函數調用時,應該具備相同數量的參數。在這種情況下,參數得到你期望的值:
function multiply(a, b) {
a; // => 5
b; // => 3
return a * b;
}
multiply(5, 3); // => 15
multiply(5, 3)調用時,參數a,b接收相應的值 5 和 3。并按照預期執行:5 * 3 = 15.
當你忽略了調用的參數時會發生什么?函數參數變成 undefined。讓我們稍微修改前面的例子,調用函數只有一個參數:
function multiply(a, b) {
a; // => 5
b; // => undefined
return a * b;
}
multiply(5); // => NaN
函數function multiply(a, b) { }在參數完整的情況下正常執行。
multiply(5)函數調用只有一個參數,參數 a 是5,而 b 則為undefined。
Tip 6: 利用默認參數值
有時函數在調用時不需要全部參數集。您可以簡單地為沒有值的參數設置默認值。
回顧上面的例子,我們來做一些改進。如果參數 b 未定義,那么給它賦值默認為2.
function multiply(a, b) {
if (b === undefined) {
b = 2;
}
a; // => 5
b; // => 2
return a * b;
}
multiply(5); // => 10
multiply(5)調用時候只有一個參數。參數 a 是 5,b 為 undefined。
條件語句驗證 b 是否未定義,如果未定義,則設定默認值為2。
雖然這個驗證辦法有效,但我不推薦使用。冗長并且雜亂無章。
更好的辦法是使用 ES6的默認參數 特性(譯者注:可以指定任意參數的默認值。)。它簡潔、直觀,并且無需和undefined直接比較。
繼續修改前面的例子,設置默認參數b,看起來更好一些:
function multiply(a, b = 2) {
a; // => 5
b; // => 2
return a * b;
}
multiply(5); // => 10
multiply(5, undefined); // => 10
b = 2作為函數簽名,確保參數 b 如果未提供,參數默認值為 2。
ES2015的默認參數 簡潔、直觀,接下來都使用它為可選參數設置默認值吧。
2.4 函數返回值
Implicitly, without
returnstatement, a JavaScript function returnsundefined.
函數內沒有執行return語句,則把未定義值賦給當前函數。
function square(x) {
const res = x * x;
}
square(2); // => undefined
square()函數不返回任何計算結果。函數調用結果未定義undefined。
return;語句執行,但表達式被省略,調用函數的表達式結果依舊是未定義 undefined。
當然,(下面的例子)闡釋了return語句在返回函數的用法:
function square(x) {
const res = x * x;
return res;
}
square(2); // => 4
現在函數調用后求值為4,它是2的平方。
Tip 7: 不要相信分號自動插入
JavaScript中,下面這些語句,必需用分號(;)結尾:
空語句
let,const,var,import,export聲明
表達式
debugger
continue語句,break語句
throw語句
return語句
以上任意一條語句,都要用分號結尾:
function getNum() {
// 注意結尾有分號
let num = 1;
return num;
}
getNum(); // => 1
let 和 return 語句都用分號結尾。
不用分號結尾有什么后果?例如,您要壓縮源文件。
ECMAScript提供了一個Automatic Semicolon Insertion(ASI) 機制,這個機制會為您插入缺失的分號。
于是上一個例子,您可以省略分號:
function getNum() {
// 注意分號不見了。
let num = 1
return num
}
getNum() // => 1
這個代碼有效。缺失的分號自動補全。
乍一看,它看起來相當不錯。ASI機制讓您省略不必要的分號。您的JavaScript代碼更簡潔易讀。
但是ASI依舊有一個惱人的坑。return 和 return 后面的表達式,如果中間換行了,比如這樣:return, ASI機制會自動插入分號,變成這樣:
expressionreturn;.
expression
函數內有一個return;語句,意味著什么?函數返回 undefined。如果您并不知道ASI機制的細節,它會被誤導,返回一個意外的undefined。
舉個栗子,讓我們來調用getPrimeNumbers() 函數,學習它的返回值:
function getPrimeNumbers() {
return
[ 2, 3, 5, 7, 11, 13, 17 ]
}
getPrimeNumbers() // => undefined
在return語句和 數組表達式之間換行,JS自動插入分號,解釋器解析代碼為:
function getPrimeNumbers() {
return;
[ 2, 3, 5, 7, 11, 13, 17 ];
}
getPrimeNumbers(); // => undefined
表達式return;導致函數并未按照預期執行,而是返回未定義。
刪除新行可以解決這個問題:
function getPrimeNumbers() {
return [
2, 3, 5, 7, 11, 13, 17
];
}
getPrimeNumbers(); // => [2, 3, 5, 7, 11, 13, 17]
我的建議是避免依賴ASI機制,自己加上分號。
EsLint規則的一個小功能就是可檢查識別語句結束時需要分號的地方。
2.5void操作符
void 表達式會被計算但是返回值永遠為undefined。
void 1; // => undefined
void (false); // => undefined
void {name: 'John Smith'}; // => undefined
void Math.min(1, 3); // => undefined
void的一個用法是執行表達式但不返回值,這個表達式的執行結果會有副作用。
3. 數組中的undefined
讀取數組界外索引值返回undefined。
const colors = ['blue', 'white', 'red']; colors[5]; // => undefined colors[-1]; // => undefined
數組 colors 有3個元素,索引值為 0, 1, 2。
索引 5 和 -1 位置并無元素,colors[5]和colors[-1]返回undefined.
JavaScript,有一個概念叫所謂的稀疏數組。數組中的元素之間可以有空隙,例如一些索引位置上未定義值。
讀取稀疏數組中的空隙(也叫空的內存槽位),返回undefined。
來看下面生成稀疏數組并試圖讀取空槽數據的例子:
const sparse1 = new Array(3); sparse1; // => [<empty slot>, <empty slot>, <empty slot>] sparse1[0]; // => undefined sparse1[1]; // => undefined const sparse2 = ['white', ,'blue'] sparse2; // => ['white', <empty slot>, 'blue'] sparse2[1]; // => undefined
用構造函數創建一個長度為3的數組 sparse1,擁有3個空槽(預分配一個數組空間)。
用字面量創建一個數組 sparse2,省略了第二個元素。(省略的元素在數組中是不存在的,是沒有值的。)
讀取以上任意稀疏數組的空值,均返回undefined。
當使用數組時,為了避免捕獲undefined,請確保使用有效的數組索引并避免創建稀疏數組。
4.undefined和null 的區別
undefined 和 null 之間的主要區別是什么?這兩個特殊值都意味著“無”。
主要區別在于,undefined 表示一個未初始化的變量的值,而 null 表示不應該有值的不存在的對象。
舉幾個例子仔細探究下。
定義一個變量 number,但尚未賦值。
let number; number; // => undefined
變量 number 為undefined,表明自身就是一個未初始化的變量。
讀取一個不存在的對象屬性,也會產生同樣的未初始化概念。
const obj = { firstName: 'Dmitri' };
obj.lastName; // => undefined
屬性lastName 不在對象 obj 內,JavaScript正確地解析為undefined。
在其他情況下,對象或者函數可以賦值給一個變量,來返回一個對象。但是您無法實例化這個對象。在這種情況下,null 就是判斷一個缺失對象的明確指標。
例如,clone() 函數 用來克隆一個普通的JavaScript對象,并返回一個對象:
function clone(obj) {
if (typeof obj === 'object' && obj !== null) {
return Object.assign({}, obj);
}
return null;
}
clone({name: 'John'}); // => {name: 'John'}
clone(15); // => null
clone(null); // => null
函數clone()的參數如果不是對象,比如 15 或者 null,(或者一個原始值 null 或者 undefined),函數就不會執行克隆任務,因為看起來很合理,返回null代表一個丟失的對象。
(譯者注:null 作為函數的參數,表示該函數的參數不是對象。)
typeof 操作符區分二者如下:
typeof undefined; // => 'undefined' typeof null; // => 'object'
嚴格運算符 === 正確區分 undefined 和 null
let nothing = undefined; let missingObject = null; nothing === missingObject; // => false
5. 總結
JavaScript作為松散型語言,它可以用undefined來:
未初始化變量
不存在的對象屬性或者方法
讀取數組界外元素
調用無返回值函數
像本文提到的那些雖然可行的方法一樣,大多數直接比較undefined不是一個好辦法。
有效的策略是在代碼中盡可能減少出現關鍵字undefined。同時,記住并極力避免那些可能出現的意外情況,養成以下這些好習慣:
減少未初始化變量的使用
縮短變量生命周期并接近引用位置
盡可能給變量賦值
使用const,或者 let
對于不重要的函數參數使用默認值
驗證屬性存在或填充不安全對象的默認屬性
避免使用稀疏數組
(譯者注:終于翻譯完了。感覺有些地方有點啰嗦啊。同樣的意思來來去去的講。)
總結
以上是生活随笔為你收集整理的翻译:JavaScript 中处理 undefined 的 7 个技巧的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 要怎么才能读取到layui的版本号
- 下一篇: Android自动化测试环境部署