摘要: JS的大部分報(bào)錯(cuò)都是undefined...
- 作者:前端小智
- 原文:處理 JS中 undefined 的 7 個(gè)技巧
Fundebug經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所有。
大約8年前琐凭,當(dāng)原作者開(kāi)始學(xué)習(xí)JS時(shí)算芯,遇到了一個(gè)奇怪的情況,既存在undefined
的值沾谓,也存在表示空值的null
殴胧。它們之間的明顯區(qū)別是什么?它們似乎都定義了空值渗稍,而且,比較null == undefined
的計(jì)算結(jié)果為true
团滥。
大多數(shù)現(xiàn)代語(yǔ)言竿屹,如Ruby、Python或Java都有一個(gè)空值(nil
或null
)惫撰,這似乎是一種合理的方式羔沙。
對(duì)于JavaScript,解釋器在訪問(wèn)尚未初始化的變量或?qū)ο髮傩詴r(shí)返回undefined
厨钻。例如:
let company;
company; // => undefined
let person = { name: 'John Smith' };
person.age; // => undefined
另一方面,null
表示缺少的對(duì)象引用坚嗜,JS本身不會(huì)將變量或?qū)ο髮傩栽O(shè)置為null
夯膀。
一些原生方法,比如String.prototype.match()
苍蔬,可以返回null
來(lái)表示丟失的對(duì)象诱建。看看下面的示例:
let array = null;
array; // => null
let movie = { name: "Starship Troopers", musicBy: null };
movie.musicBy; // => null
"abc".match(/[0-9]/); // => null
由于 JS 的寬容特性碟绑,開(kāi)發(fā)人員很容易訪問(wèn)未初始化的值俺猿,我也犯了這樣的錯(cuò)誤。
通常格仲,這種危險(xiǎn)的操作會(huì)生成undefined
的相關(guān)錯(cuò)誤押袍,從而快速地結(jié)束腳本。相關(guān)的常見(jiàn)錯(cuò)誤消息有:
TypeError: 'undefined' is not a function
TypeError: Cannot read property '<prop-name>' of undefined
type errors
JS 開(kāi)發(fā)人員可以理解這個(gè)笑話的諷刺:
function undefined() {
// problem solved
}
為了降低此類(lèi)錯(cuò)誤的風(fēng)險(xiǎn)凯肋,必須理解生成undefined
的情況谊惭。更重要的是抑制它的出現(xiàn)并阻止在應(yīng)用程序中傳播,從而提高代碼的持久性。
讓咱們?cè)敿?xì)討論undefined
及其對(duì)代碼安全性的影響圈盔。
1. undefined 是什么鬼
JS 有6種基本類(lèi)型
- Boolean:
true
或false
- Number:
1, 6.7, 0xFF
- String:
"Gorilla and banana"
- Symbol:
Symbol("name")
(starting ES2015) - Null:
null
- Undefined:
undefined
.
和一個(gè)單獨(dú)的Object
類(lèi)型:{name: "Dmitri"}豹芯, ["apple", "orange"]
驱敲。
根據(jù)ECMAScript規(guī)范铁蹈,從6種原始類(lèi)型中,undefined
是一個(gè)特殊的值众眨,它有自己的Undefined
類(lèi)型木缝。
未為變量賦值時(shí)默認(rèn)值為
undefined
。
該標(biāo)準(zhǔn)明確定義围辙,當(dāng)訪問(wèn)未初始化的變量我碟、不存在的對(duì)象屬性、不存在的數(shù)組元素等時(shí)姚建,將接收到一個(gè)undefined
的值矫俺。例如
let number;
number; // => undefined
let movie = { name: "Interstellar" };
movie.year; // => undefined
let movies = ["Interstellar", "Alexander"];
movies[3]; // => undefined
上述代碼大致流程:
- 未初始化的變量
number
- 一個(gè)不存在的對(duì)象屬性
movie.year
- 或者不存在數(shù)組元素movies[3]
都會(huì)被定義為undefined
。
ECMAScript規(guī)范定義了undefined
值的類(lèi)型
Undefined type是其唯一值為
undefined
值的類(lèi)型掸冤。
在這個(gè)意義上厘托,typeof undefined
返回“undefined”字符串
typeof undefined === "undefined"; // => true
當(dāng)然typeof
可以很好地驗(yàn)證變量是否包含undefined
的值
let nothing;
typeof nothing === "undefined"; // => true
2. 導(dǎo)致undefined的常見(jiàn)場(chǎng)景
2.1 未初始化變量
尚未賦值(未初始化)的聲明變量默認(rèn)為undefined
。
let myVariable;
myVariable; // => undefined
myVariable
已聲明稿湿,但尚未賦值,默認(rèn)值為undefined
铅匹。
解決未初始化變量問(wèn)題的有效方法是盡可能分配初始值。 變量在未初始化狀態(tài)中越少越好饺藤。 理想情況下包斑,你可以在聲明const myVariable ='Initial value'
之后立即指定一個(gè)值,但這并不總是可行的涕俗。
技巧1:使用 let 和 const 來(lái)代替 var
在我看來(lái)罗丰,ES6 最好的特性之一是使用const
和let
聲明變量的新方法。const
和let
具有塊作用域(與舊的函數(shù)作用域var
相反)再姑,在聲明行之前都存在于暫時(shí)性死區(qū)萌抵。
當(dāng)變量一次性且永久地接收到一個(gè)值時(shí),建議使用const
聲明元镀,它創(chuàng)建一個(gè)不可變的綁定绍填。
const
的一個(gè)很好的特性是必須為變量const myVariable ='initial'
分配一個(gè)初始值。 變量未暴露給未初始化狀態(tài)栖疑,并且訪問(wèn)undefined
是不可能的讨永。
以下示例檢查驗(yàn)證一個(gè)單詞是否是回文的函數(shù):
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
似乎是合理的蔽挠,因?yàn)檫@些變量不會(huì)改變住闯。
如果需要重新綁定變量(即多次賦值)瓜浸,請(qǐng)應(yīng)用let
聲明。只要可能比原,立即為它賦一個(gè)初值插佛,例如,let index = 0
量窘。
那么使用 var
聲明呢雇寇,相對(duì)于ES6,建議是完全停止使用它蚌铜。
var
聲明的變量提會(huì)被提升到整個(gè)函數(shù)作用域頂部锨侯。可以在函數(shù)作用域末尾的某個(gè)地方聲明var
變量冬殃,但是仍然可以在聲明之前訪問(wèn)它:對(duì)應(yīng)變量的值是 undefined
囚痴。
相反,用let
或者 const
聲明的變量之前不能訪問(wèn)該變量审葬。之所以會(huì)發(fā)生這種情況深滚,是因?yàn)樽兞吭诼暶髦疤幱?a target="_blank">暫時(shí)死區(qū)。這很好涣觉,因?yàn)檫@樣就很少有機(jī)會(huì)訪問(wèn)到 undefined
值痴荐。
使用let
(而不是var)更新的上述示例會(huì)引發(fā)ReferenceError
錯(cuò)誤,因?yàn)闊o(wú)法訪問(wèn)暫時(shí)死區(qū)中的變量官册。
function bigFunction() {
// code...
myVariable; // => Throws 'ReferenceError: myVariable is not defined'
// code...
let myVariable = 'Initial value';
// code...
myVariable; // => 'Initial value'
}
bigFunction();
技巧2:增加內(nèi)聚性
內(nèi)聚描述模塊的元素(命名空間生兆、類(lèi)、方法膝宁、代碼塊)內(nèi)聚在一起的程度鸦难。凝聚力的測(cè)量通常被稱(chēng)為高凝聚力或低內(nèi)聚。
高內(nèi)聚是優(yōu)選的昆汹,因?yàn)樗ㄗh設(shè)計(jì)模塊的元素以?xún)H關(guān)注單個(gè)任務(wù)明刷,它構(gòu)成了一個(gè)模塊。
- 專(zhuān)注且易懂:更容易理解模塊的功能
- 可維護(hù)且更容易重構(gòu):模塊中的更改會(huì)影響更少的模塊
- 可重用:專(zhuān)注于單個(gè)任務(wù)满粗,使模塊更易于重用
- 可測(cè)試:可以更輕松地測(cè)試專(zhuān)注于單個(gè)任務(wù)的模塊
高內(nèi)聚和低耦合是一個(gè)設(shè)計(jì)良好的系統(tǒng)的特征。
代碼塊本身可能被視為一個(gè)小模塊愚争,為了盡可能實(shí)現(xiàn)高內(nèi)聚映皆,需要使變量盡可能接近使用它們代碼塊位置。
例如轰枝,如果一個(gè)變量?jī)H存在以形成塊作用域內(nèi)捅彻,不要將此變量公開(kāi)給外部塊作用域,因?yàn)橥獠繅K不應(yīng)該關(guān)心此變量鞍陨。
不必要地延長(zhǎng)變量生命周期的一個(gè)典型例子是函數(shù)中for
循環(huán)的使用:
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
變量在函數(shù)體的開(kāi)頭聲明从隆,但是,它們僅在最后使用缭裆,那么這種方式有什么問(wèn)題呢键闺?
從頂部的聲明到for
語(yǔ)句中變量 index 和 item 都是未初始化的,值為 undefined
澈驼。它們?cè)谡麄€(gè)函數(shù)作用域內(nèi)具有不合理較長(zhǎng)的生命周期辛燥。
一種更好的方法是將這些變量盡可能地移動(dòng)到使用它們的位置:
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
變量?jī)H存在于for
語(yǔ)句的作用域內(nèi),for
之外沒(méi)有任何意義缝其。length
變量也被聲明為接近其使用它的位置挎塌。
為什么修改后的版本優(yōu)于初始版本? 主要有幾點(diǎn):
- 變量未暴露
undefined
狀態(tài)内边,因此沒(méi)有訪問(wèn)undefined
的風(fēng)險(xiǎn) - 將變量盡可能地移動(dòng)到它們的使用位置會(huì)增加代碼的可讀性
- 高內(nèi)聚的代碼塊在必要時(shí)更容易重構(gòu)并提取到單獨(dú)的函數(shù)中
2.2 訪問(wèn)不存在的屬性
訪問(wèn)不存在的對(duì)象屬性時(shí)榴都,JS 返回
undefined
。
咱們用一個(gè)例子來(lái)說(shuō)明這一點(diǎn):
let favoriteMovie = {
title: 'Blade Runner'
};
favoriteMovie.actors; // => undefined
favoriteMovie
是一個(gè)具有單個(gè)屬性 title
的對(duì)象漠其。 使用屬性訪問(wèn)器favoriteMovie.actors
訪問(wèn)不存在的屬性actors
將被計(jì)算為undefined
嘴高。
本身訪問(wèn)不存在的屬性不會(huì)引發(fā)錯(cuò)誤, 但嘗試從不存在的屬性值中獲取數(shù)據(jù)時(shí)就會(huì)出現(xiàn)問(wèn)題辉懒。 常見(jiàn)的的錯(cuò)誤是 TypeError: Cannot read property <prop> of undefined
阳惹。
稍微修改前面的代碼片段來(lái)說(shuō)明TypeError throw
:
let favoriteMovie = {
title: 'Blade Runner'
};
favoriteMovie.actors[0];
// TypeError: Cannot read property '0' of undefined
favoriteMovie
沒(méi)有屬性actors
,所以favoriteMovie.actors
的值 undefined
眶俩。因此莹汤,使用表達(dá)式favoriteMovie.actors[0]
訪問(wèn)undefined
值的第一項(xiàng)會(huì)引發(fā)TypeError
。
JS 允許訪問(wèn)不存在的屬性颠印,這種允許訪問(wèn)的特性容易引起混淆:可能設(shè)置了屬性纲岭,也可能沒(méi)有設(shè)置屬性,繞過(guò)這個(gè)問(wèn)題的理想方法是限制對(duì)象始終定義它所持有的屬性。
不幸的是线罕,咱們常常無(wú)法控制對(duì)象止潮。在不同的場(chǎng)景中,這些對(duì)象可能具有不同的屬性集钞楼,因此喇闸,必須手動(dòng)處理所有這些場(chǎng)景:
接著我們實(shí)現(xiàn)一個(gè)函數(shù)append(array, toAppend)
,它的主要功能在數(shù)組的開(kāi)頭和/或末尾添加新的元素询件。 toAppend
參數(shù)接受具有屬性的對(duì)象:
- first:元素插入數(shù)組的開(kāi)頭
- last:元素在數(shù)組末尾插入燃乍。
函數(shù)返回一個(gè)新的數(shù)組實(shí)例,而不改變?cè)紨?shù)組(即它是一個(gè)純函數(shù))宛琅。
append()
的第一個(gè)版本看起來(lái)比較簡(jiǎn)單刻蟹,如下所示:
function append(array, toAppend) {
const arrayCopy = array.slice();
if (toAppend.first) {
arrayCopy.unshift(toAppend.first);
}
if (toAppend.last) {
arrayCopy.push(toAppend.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]
由于toAppend
對(duì)象可以省略first
或last
屬性,因此必須驗(yàn)證toAppend
中是否存在這些屬性嘿辟。如果屬性不存在舆瘪,則屬性訪問(wèn)器值為undefined
片效。
檢查first
或last
屬性是否是undefined
,在條件為 if(toappendix .first){}
和if(toappendix .last){}
中進(jìn)行驗(yàn)證:
這種方法有一個(gè)缺點(diǎn), undefined
英古,false
淀衣,null
,0
哺呜,NaN
和''
是虛值舌缤。
在append()
的當(dāng)前實(shí)現(xiàn)中,該函數(shù)不允許插入虛值元素:
append([10], { first: 0, last: false }); // => [10]
0
和false
是虛值的某残。 因?yàn)?if(toAppend.first){}
和if(toAppend.last){}
實(shí)際上與falsy
進(jìn)行比較国撵,所以這些元素不會(huì)插入到數(shù)組中,該函數(shù)返回初始數(shù)組[10]
而不會(huì)進(jìn)行任何修改玻墅。
以下技巧解釋了如何正確檢查屬性的存在介牙。
技巧3: 檢查屬性是否存在
JS 提供了許多方法來(lái)確定對(duì)象是否具有特定屬性:
-
obj.prop!== undefined
:直接與undefined
進(jìn)行比較 -
typeof obj.prop澳厢!=='undefined'
:驗(yàn)證屬性值類(lèi)型 -
obj.hasOwnProperty('prop')
:驗(yàn)證對(duì)象是否具有自己的屬性 -
'prop' in obj
:驗(yàn)證對(duì)象是否具有自己的屬性或繼承屬性
我的建議是使用 in
操作符环础,它的語(yǔ)法短小精悍。in
操作符的存在表明一個(gè)明確的意圖剩拢,即檢查對(duì)象是否具有特定的屬性线得,而不訪問(wèn)實(shí)際的屬性值。
obj.hasOwnProperty('prop')
也是一個(gè)很好的解決方案徐伐,它比 in
操作符稍長(zhǎng)贯钩,僅在對(duì)象自己的屬性中進(jìn)行驗(yàn)證。
涉及與undefined
進(jìn)行比較剩下的兩種方式可能有效办素,但在我看來(lái)角雷,obj.prop!== undefined
和typeof obj.prop性穿!=='undefined'
看起來(lái)冗長(zhǎng)而怪異勺三,并暴露出直接處理undefined
的可疑路徑。需曾。
讓咱們使用in
操作符改進(jìn)append(array, toAppend)
函數(shù):
function append(array, toAppend) {
const arrayCopy = array.slice();
if ('first' in toAppend) {
arrayCopy.unshift(toAppend.first);
}
if ('last' in toAppend) {
arrayCopy.push(toAppend.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 toAppend
(和'last' in toAppend
)在對(duì)應(yīng)屬性存在時(shí)為true
吗坚,否則為false
。in
操作符的使用解決了插入虛值元素0
和false
的問(wèn)題〈敉颍現(xiàn)在刻蚯,在[10]
的開(kāi)頭和結(jié)尾添加這些元素將產(chǎn)生預(yù)期的結(jié)果[0,10,false]
。
技巧4:解構(gòu)訪問(wèn)對(duì)象屬性
在訪問(wèn)對(duì)象屬性時(shí)桑嘶,如果屬性不存在,有時(shí)需要指示默認(rèn)值躬充√佣ィ可以使用in
和三元運(yùn)算符來(lái)實(shí)現(xiàn)這一點(diǎn)讨便。
const object = { };
const prop = 'prop' in object ? object.prop : 'default';
prop; // => 'default'
當(dāng)要檢查的屬性數(shù)量增加時(shí),三元運(yùn)算符語(yǔ)法的使用變得令人生畏以政。對(duì)于每個(gè)屬性霸褒,都必須創(chuàng)建新的代碼行來(lái)處理默認(rèn)值,這就增加了一堵難看的墻盈蛮,里面都是外觀相似的三元運(yùn)算符废菱。
為了使用更優(yōu)雅的方法,可以使用 ES6 對(duì)象的解構(gòu)抖誉。
對(duì)象解構(gòu)允許將對(duì)象屬性值直接提取到變量中殊轴,并在屬性不存在時(shí)設(shè)置默認(rèn)值,避免直接處理undefined
的方便語(yǔ)法袒炉。
實(shí)際上旁理,屬性提取現(xiàn)在看起來(lái)簡(jiǎn)短而有意義:
const object = { };
const { prop = 'default' } = object;
prop; // => 'default'
要查看實(shí)際操作中的內(nèi)容,讓我們定義一個(gè)將字符串包裝在引號(hào)中的有用函數(shù)我磁。quote(subject, config)
接受第一個(gè)參數(shù)作為要包裝的字符串孽文。 第二個(gè)參數(shù)config
是一個(gè)具有以下屬性的對(duì)象:
- char:包裝的字符,例如
'
(單引號(hào))或“
(雙引號(hào))夺艰,默認(rèn)為”
芋哭。 -
skipIfQuoted
:如果字符串已被引用則跳過(guò)引用的布爾值,默認(rèn)為true
郁副。
使用對(duì)象析構(gòu)的優(yōu)點(diǎn)减牺,讓咱們實(shí)現(xiàn)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 = '", skipifquote = true} = config
解構(gòu)賦值在一行中從config
對(duì)象中提取char
和skipifquote
屬性霞势。如果config
對(duì)象中有一些屬性不可用烹植,那么解構(gòu)賦值將設(shè)置默認(rèn)值:char
為'"'
,skipifquote
為false
愕贡。
該功能仍有改進(jìn)的空間草雕。讓我們將解構(gòu)賦值直接移動(dòng)到參數(shù)部分。并為config
參數(shù)設(shè)置一個(gè)默認(rèn)值(空對(duì)象{}
)固以,以便在默認(rèn)設(shè)置足夠時(shí)跳過(guò)第二個(gè)參數(shù)墩虹。
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"'
注意,解構(gòu)賦值替換了函數(shù) config
參數(shù)憨琳。我喜歡這樣:quote()
縮短了一行诫钓。
={}
在解構(gòu)賦值的右側(cè),確保在完全沒(méi)有指定第二個(gè)參數(shù)的情況下使用空對(duì)象篙螟。
對(duì)象解構(gòu)是一個(gè)強(qiáng)大的功能菌湃,可以有效地處理從對(duì)象中提取屬性。 我喜歡在被訪問(wèn)屬性不存在時(shí)指定要返回的默認(rèn)值的可能性遍略。因?yàn)檫@樣可以避免undefined
以及與處理它相關(guān)的問(wèn)題惧所。
技巧5: 用默認(rèn)屬性填充對(duì)象
如果不需要像解構(gòu)賦值那樣為每個(gè)屬性創(chuàng)建變量骤坐,那么丟失某些屬性的對(duì)象可以用默認(rèn)值填充。
ES6 Object.assign(target下愈,source1纽绍,source2,...)
將所有可枚舉的自有屬性的值從一個(gè)或多個(gè)源對(duì)象復(fù)制到目標(biāo)對(duì)象中,該函數(shù)返回目標(biāo)對(duì)象势似。
例如拌夏,需要訪問(wèn)unsafeOptions
對(duì)象的屬性,該對(duì)象并不總是包含其完整的屬性集履因。
為了避免從unsafeOptions
訪問(wèn)不存在的屬性障簿,讓我們做一些調(diào)整:
- 定義包含默認(rèn)屬性值的
defaults
對(duì)象 - 調(diào)用
Object.assign({},defaults搓逾,unsafeOptions)
來(lái)構(gòu)建新的對(duì)象options
卷谈。 新對(duì)象從unsafeOptions
接收所有屬性,但缺少的屬性從defaults
對(duì)象獲取霞篡。
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
對(duì)象定義屬性fontSize
和color
的默認(rèn)值。
Object.assign()
將第一個(gè)參數(shù)作為目標(biāo)對(duì)象{}
朗兵。 目標(biāo)對(duì)象從unsafeOptions
源對(duì)象接收fontSize
屬性的值污淋。 并且人defaults
對(duì)象的獲取color
屬性值,因?yàn)?code>unsafeOptions不包含color
屬性余掖。
枚舉源對(duì)象的順序很重要:后面的源對(duì)象屬性會(huì)覆蓋前面的源對(duì)象屬性寸爆。
現(xiàn)在可以安全地訪問(wèn)options
對(duì)象的任何屬性,包括options.color
在最初的unsafeOptions
中是不可用的盐欺。
還有一種簡(jiǎn)單的方法就是使用ES6中展開(kāi)運(yùn)算符:
const unsafeOptions = {
fontSize: 18
};
const defaults = {
fontSize: 16,
color: 'black'
};
const options = {
...defaults,
...unsafeOptions
};
options.fontSize; // => 18
options.color; // => 'black'
對(duì)象初始值設(shè)定項(xiàng)從defaults
和unsafeOptions
源對(duì)象擴(kuò)展屬性赁豆。 指定源對(duì)象的順序很重要,后面的源對(duì)象屬性會(huì)覆蓋前面的源對(duì)象冗美。
使用默認(rèn)屬性值填充不完整的對(duì)象是使代碼安全且持久的有效策略魔种。無(wú)論哪種情況,對(duì)象總是包含完整的屬性集:并且無(wú)法生成undefined
的屬性粉洼。
2.3 函數(shù)參數(shù)
函數(shù)參數(shù)隱式默認(rèn)為
undefined
节预。
通常,用特定數(shù)量的參數(shù)定義的函數(shù)應(yīng)該用相同數(shù)量的參數(shù)調(diào)用属韧。在這種情況下安拟,參數(shù)得到期望的值
function multiply(a, b) {
a; // => 5
b; // => 3
return a * b;
}
multiply(5, 3); // => 15
調(diào)用multiply(5,3)
使參數(shù)a
和b
接收相應(yīng)的5
和3
值,返回結(jié)果:5 * 3 = 15
宵喂。
在調(diào)用時(shí)省略參數(shù)會(huì)發(fā)生什么?
function multiply(a, b) {
a; // => 5
b; // => undefined
return a * b;
}
multiply(5); // => NaN
函數(shù)multiply(a, b){}
由兩個(gè)參數(shù)a
和b
定義糠赦。調(diào)用multiply(5)
用一個(gè)參數(shù)執(zhí)行:結(jié)果一個(gè)參數(shù)是5
,但是b
參數(shù)是undefined
。
技巧6: 使用默認(rèn)參數(shù)值
有時(shí)函數(shù)不需要調(diào)用的完整參數(shù)集愉棱,可以簡(jiǎn)單地為沒(méi)有值的參數(shù)設(shè)置默認(rèn)值唆铐。
回顧前面的例子,讓我們做一個(gè)改進(jìn)奔滑,如果b
參數(shù)未定義,則為其分配默認(rèn)值2
:
function multiply(a, b) {
if (b === undefined) {
b = 2;
}
a; // => 5
b; // => 2
return a * b;
}
multiply(5); // => 10
雖然所提供的分配默認(rèn)值的方法有效顺少,但不建議直接與undefined
值進(jìn)行比較朋其。它很冗長(zhǎng),看起來(lái)像一個(gè)hack .
這里可以使用 ES6 的默認(rèn)值:
function multiply(a, b = 2) {
a; // => 5
b; // => 2
return a * b;
}
multiply(5); // => 10
multiply(5, undefined); // => 10
2.4 函數(shù)返回值
隱式地脆炎,沒(méi)有
return
語(yǔ)句梅猿,JS 函數(shù)返回undefined
。
在JS中秒裕,沒(méi)有任何return
語(yǔ)句的函數(shù)隱式返回undefined
:
function square(x) {
const res = x * x;
}
square(2); // => undefined
square()
函數(shù)沒(méi)有返回計(jì)算結(jié)果袱蚓,函數(shù)調(diào)用時(shí)的結(jié)果undefined
。
當(dāng)return
語(yǔ)句后面沒(méi)有表達(dá)式時(shí)几蜻,默認(rèn)返回 undefined
喇潘。
function square(x) {
const res = x * x;
return;
}
square(2); // => undefined
return;
語(yǔ)句被執(zhí)行,但它不返回任何表達(dá)式梭稚,調(diào)用結(jié)果也是undefined
颖低。
function square(x) {
const res = x * x;
return res;
}
square(2); // => 4
技巧7: 不要相信自動(dòng)插入分號(hào)
JS 中的以下語(yǔ)句列表必須以分號(hào)(;)
結(jié)尾:
- 空語(yǔ)句
-
let,const,var,import,export
聲明 - 表達(dá)語(yǔ)句
-
debugger
語(yǔ)句 -
continue
語(yǔ)句,break
語(yǔ)句 -
throw
語(yǔ)句 -
return
語(yǔ)句
如果使用上述聲明之一弧烤,請(qǐng)盡量務(wù)必在結(jié)尾處指明分號(hào):
function getNum() {
let num = 1;
return num;
}
getNum(); // => 1
let
聲明和return
語(yǔ)句結(jié)束時(shí)忱屑,強(qiáng)制性寫(xiě)分號(hào)。
當(dāng)你不想寫(xiě)這些分號(hào)時(shí)會(huì)發(fā)生什么暇昂? 例如莺戒,咱們想要減小源文件的大小。
在這種情況下急波,ECMAScript 提供自動(dòng)分號(hào)插入(ASI)機(jī)制从铲,為你插入缺少的分號(hào)。
ASI 的幫助下幔崖,可以從上一個(gè)示例中刪除分號(hào):
function getNum() {
// Notice that semicolons are missing
let num = 1
return num
}
getNum() // => 1
上面的代碼是有效的JS代碼,缺少的分號(hào)ASI會(huì)自動(dòng)為我們插入食店。
乍一看,它看起來(lái)很 nice赏寇。 ASI 機(jī)制允許你少寫(xiě)不必要的分號(hào)吉嫩,可以使JS代碼更小,更易于閱讀嗅定。
ASI 創(chuàng)建了一個(gè)小而煩人的陷阱自娩。 當(dāng)換行符位于return
和return \n expression
之間時(shí),ASI 會(huì)在換行符之前自動(dòng)插入分號(hào)(return; \n expression
)。
函數(shù)內(nèi)部return;
忙迁? 即該函數(shù)返回undefined
脐彩。 如果你不詳細(xì)了解ASI的機(jī)制,則意外返回的undefined
會(huì)產(chǎn)生意想不到的問(wèn)題姊扔。
來(lái) getPrimeNumbers()
調(diào)用返回的值:
function getPrimeNumbers() {
return
[ 2, 3, 5, 7, 11, 13, 17 ]
}
getPrimeNumbers() // => undefined
在return
語(yǔ)句和數(shù)組之間存在一個(gè)換行,JS 在return
后自動(dòng)插入分號(hào)惠奸,解釋代碼如下:
function getPrimeNumbers() {
return;
[ 2, 3, 5, 7, 11, 13, 17 ];
}
getPrimeNumbers(); // => undefined
return;
使函數(shù)getPrimeNumbers()
返回undefined
而不是期望的數(shù)組。
這個(gè)問(wèn)題通過(guò)刪除return
和數(shù)組文字之間的換行來(lái)解決:
function getPrimeNumbers() {
return [
2, 3, 5, 7, 11, 13, 17
];
}
getPrimeNumbers(); // => [2, 3, 5, 7, 11, 13, 17]
我的建議是研究自動(dòng)分號(hào)插入的確切方式恰梢,以避免這種情況佛南。
當(dāng)然,永遠(yuǎn)不要在return
和返回的表達(dá)式之間放置換行符嵌言。
2.5 void 操作符
void <expression>
計(jì)算表達(dá)式無(wú)論計(jì)算結(jié)果如何都返回undefined
嗅回。
void 1; // => undefined
void (false); // => undefined
void {name: 'John Smith'}; // => undefined
void Math.min(1, 3); // => undefined
void
操作符的一個(gè)用例是將表達(dá)式求值限制為undefined
,這依賴(lài)于求值的一些副作用摧茴。
3. 未定義的數(shù)組
訪問(wèn)越界索引的數(shù)組元素時(shí)绵载,會(huì)得到undefined
。
const colors = ['blue', 'white', 'red'];
colors[5]; // => undefined
colors[-1]; // => undefined
colors
數(shù)組有3個(gè)元素苛白,因此有效索引為0,1
和2
娃豹。
因?yàn)樗饕?code>5和-1
沒(méi)有數(shù)組元素,所以訪問(wèn)colors[5]
和colors[-1]
值為undefined
丸氛。
JS 中培愁,可能會(huì)遇到所謂的稀疏數(shù)組。這些數(shù)組是有間隙的數(shù)組缓窜,也就是說(shuō)定续,在某些索引中,沒(méi)有定義元素禾锤。
當(dāng)在稀疏數(shù)組中訪問(wèn)間隙(也稱(chēng)為空槽)時(shí)私股,也會(huì)得到一個(gè)undefined
。
下面的示例生成稀疏數(shù)組并嘗試訪問(wèn)它們的空槽
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
使用數(shù)組時(shí)恩掷,為了避免獲取undefined
倡鲸,請(qǐng)確保使用有效的數(shù)組索引并避免創(chuàng)建稀疏數(shù)組。
4. undefined和null之間的區(qū)別
一個(gè)合理的問(wèn)題出現(xiàn)了:undefined
和null
之間的主要區(qū)別是什么?這兩個(gè)特殊值都表示為空狀態(tài)黄娘。
主要區(qū)別在于undefined
表示尚未初始化的變量的值峭状,null
表示故意不存在對(duì)象。
讓咱們通過(guò)一些例子來(lái)探討它們之間的區(qū)別逼争。
number 定義了但沒(méi)有賦值优床。
let number;
number; // => undefined
number
變量未定義,這清楚地表明未初始化的變量誓焦。
當(dāng)訪問(wèn)不存在的對(duì)象屬性時(shí)胆敞,也會(huì)發(fā)生相同的未初始化概念
const obj = { firstName: 'Dmitri' };
obj.lastName; // => undefined
因?yàn)?code>obj中不存在lastName
屬性,所以JS正確地將obj.lastName
計(jì)算為undefined
。
在其他情況下移层,你知道變量期望保存一個(gè)對(duì)象或一個(gè)函數(shù)來(lái)返回一個(gè)對(duì)象仍翰。但是由于某些原因,你不能實(shí)例化該對(duì)象观话。在這種情況下予借,null
是丟失對(duì)象的有意義的指示器。
例如匪燕,clone()
是一個(gè)克隆普通JS對(duì)象的函數(shù)蕾羊,函數(shù)將返回一個(gè)對(duì)象
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
但是,可以使用非對(duì)象參數(shù)調(diào)用clone()
: 15
或null
(或者通常是一個(gè)原始值帽驯,null
或undefined
)。在這種情況下书闸,函數(shù)不能創(chuàng)建克隆尼变,因此返回null
—— 一個(gè)缺失對(duì)象的指示符。
typeof
操作符區(qū)分了這兩個(gè)值
typeof undefined; // => 'undefined'
typeof null; // => 'object'
嚴(yán)格相等運(yùn)算符===
可以正確區(qū)分undefined
和null
:
let nothing = undefined;
let missingObject = null;
nothing === missingObject; // => false
總結(jié)
undefined
的存在是JS的允許性質(zhì)的結(jié)果浆劲,它允許使用:
- 未初始化的變量
- 不存在的對(duì)象屬性或方法
- 訪問(wèn)越界索引的數(shù)組元素
- 不返回任何結(jié)果的函數(shù)的調(diào)用結(jié)果
大多數(shù)情況下直接與undefined
進(jìn)行比較是一種不好的做法嫌术。一個(gè)有效的策略是減少代碼中undefined
關(guān)鍵字的出現(xiàn):
- 減少未初始化變量的使用
- 使變量生命周期變短并接近其使用的位置
- 盡可能為變量分配初始值
- 多敷衍 const 和 let
- 使用默認(rèn)值來(lái)表示無(wú)關(guān)緊要的函數(shù)參數(shù)
- 驗(yàn)證屬性是否存在或使用默認(rèn)屬性填充不安全對(duì)象
- 避免使用稀疏數(shù)組
關(guān)于Fundebug
Fundebug專(zhuān)注于JavaScript、微信小程序牌借、微信小游戲度气、支付寶小程序、React Native膨报、Node.js和Java線上應(yīng)用實(shí)時(shí)BUG監(jiān)控磷籍。 自從2016年雙十一正式上線,F(xiàn)undebug累計(jì)處理了10億+錯(cuò)誤事件现柠,付費(fèi)客戶(hù)有陽(yáng)光保險(xiǎn)院领、核桃編程、荔枝FM够吩、掌門(mén)1對(duì)1比然、微脈、青團(tuán)社等眾多品牌企業(yè)周循。歡迎大家免費(fèi)試用强法!