處理 JS 中 undefined 的 7 個技巧

原文:dmitripavlutin.com/7-tips-to-h…javascript

譯者:前端小智html


阿里雲最近在作活動,低至2折,有興趣能夠看看promotion.aliyun.com/ntms/yunpar…前端


爲了保證的可讀性,本文采用意譯而非直譯。java

大約8年前,當原做者開始學習JS時,遇到了一個奇怪的狀況,既存在undefined 的值,也存在表示空值的null。它們之間的明顯區別是什麼?它們彷佛都定義了空值,並且,比較null == undefined的計算結果爲truegit

大多數現代語言,如Ruby、Python或Java都有一個空值(nilnull),這彷佛是一種合理的方式。es6

對於JavaScript,解釋器在訪問還沒有初始化的變量或對象屬性時返回undefined。例如:github

let company;
company;    // => undefined
let person = { name: 'John Smith' };
person.age; // => undefined
複製代碼

另外一方面,null表示缺乏的對象引用,JS自己不會將變量或對象屬性設置爲nullexpress

一些原生方法,好比String.prototype.match(),能夠返回null來表示丟失的對象。看看下面的示例:數組

let array = null;  
array;                // => null
let movie = { name: 'Starship Troopers',  musicBy: null };
movie.musicBy;        // => null
'abc'.match(/[0-9]/); // => null 
複製代碼

因爲 JS 的寬容特性,開發人員很容易訪問未初始化的值,我也犯了這樣的錯誤。安全

一般,這種危險的操做會生成undefined 的相關錯誤,從而快速地結束腳本。相關的常見錯誤消息有:

  • TypeError: 'undefined' is not a function
  • TypeError: Cannot read property '<prop-name>' of undefined
  • type errors

JS 開發人員能夠理解這個笑話的諷刺:

function undefined() {
  // problem solved
}
複製代碼

爲了下降此類錯誤的風險,必須理解生成undefined的狀況。更重要的是抑制它的出現並阻止在應用程序中傳播,從而提升代碼的持久性。

讓我們詳細討論undefined 及其對代碼安全性的影響。

undefined 是什麼鬼

JS 有6種基本類型

  • Boolean: truefalse
  • Number: 1, 6.7, 0xFF
  • String: "Gorilla and banana"
  • Symbol: Symbol("name") (starting ES2015)
  • Null: null
  • Undefined: undefined.

和一個單獨的Object 類型:{name: "Dmitri"}, ["apple", "orange"]

根據ECMAScript規範,從6種原始類型中,undefined是一個特殊的值,它有本身的Undefined類型。

未爲變量賦值時默認值爲undefined

該標準明肯定義,當訪問未初始化的變量、不存在的對象屬性、不存在的數組元素等時,將接收到一個undefined 的值。例如

let number;
number;     // => undefined

let movie = { name: 'Interstellar' };
movie.year; // => undefined

let movies = ['Interstellar', 'Alexander'];
movies[3];  // => undefined
複製代碼

上述代碼大體流程:

  • 未初始化的變量number
  • 一個不存在的對象屬性movie.year
  • 或者不存在數組元素movies3

都會被定義爲undefined

ECMAScript規範定義了undefined 值的類型

Undefined type是其惟一值爲undefined 值的類型。

在這個意義上,typeof undefined返回「undefined」字符串

typeof undefined === 'undefined'; // => true    
複製代碼

固然typeof能夠很好地驗證變量是否包含undefined的值

let nothing;
typeof nothing === 'undefined';   // => true  
複製代碼

2. 建立未定義的常見場景

2.1未初始化變量

還沒有賦值(未初始化)的聲明變量默認爲undefined

let myVariable;
myVariable; // => undefined    
複製代碼

myVariable已聲明,但還沒有賦值,默認值爲undefined

解決未初始化變量問題的有效方法是儘量分配初始值。 變量在未初始化狀態中越少越好。 理想狀況下,你能夠在聲明const myVariable ='Initial value'以後當即指定一個值,但這並不老是可行的。

技巧1:使用 let 和 const 來代替 var

在我看來,ES6 最好的特性之一是使用constlet聲明變量的新方法。constlet具備塊做用域(與舊的函數做用域var相反),在聲明行以前都存在於暫時性死區

當變量一次性且永久地接收到一個值時,建議使用const聲明,它建立一個不可變的綁定。

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    
複製代碼

lengthhalf 變量被賦值一次。將它們聲明爲const彷佛是合理的,由於這些變量不會改變。

若是須要從新綁定變量(即屢次賦值),請應用let聲明。只要可能,當即爲它賦一個初值,例如,let index = 0

那麼使用 var 聲明呢,相對於ES6,建議是徹底中止使用它。

var 聲明的變量提會被提高到整個函數做用域頂部。能夠在函數做用域末尾的某個地方聲明var變量,可是仍然能夠在聲明以前訪問它:對應變量的值是 undefined

相反,用let 或者 const 聲明的變量以前不能訪問該變量。之因此會發生這種狀況,是由於變量在聲明以前處於暫時死區。這很好,由於這樣就不多有機會訪問到 undefined 值。

使用let(而不是var)更新的上述示例會引起ReferenceError 錯誤,由於沒法訪問暫時死區中的變量。

function bigFunction() {
  // code...
  myVariable; // => Throws 'ReferenceError: myVariable is not defined'
  // code...
  let myVariable = 'Initial value';
  // code...
  myVariable; // => 'Initial value'
}
bigFunction();
複製代碼

技巧2:增長內聚性

內聚描述模塊的元素(命名空間、類、方法、代碼塊)內聚在一塊兒的程度。凝聚力的測量一般被稱爲高凝聚力或低內聚。

高內聚是優選的​​,由於它建議設計模塊的元素以僅關注單個任務,它構成了一個模塊。

  • 專一且易懂:更容易理解模塊的功能

  • 可維護且更容易重構:模塊中的更改會影響更少的模塊

  • 可重用:專一於單個任務,使模塊更易於重用

  • 可測試:能夠更輕鬆地測試專一於單個任務的模塊

高內聚和低耦合是一個設計良好的系統的特徵。

代碼塊自己可能被視爲一個小模塊,爲了儘量實現高內聚,須要使變量儘量接近使用它們代碼塊位置。

例如,若是一個變量僅存在以造成塊做用域內,不要將此變量公開給外部塊做用域,由於外部塊不該該關心此變量。

沒必要要地延長變量生命週期的一個典型例子是函數中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';
}
複製代碼

indexitemlength變量在函數體的開頭聲明,可是,它們僅在最後使用,那麼這種方式有什麼問題呢?

從頂部的聲明到for語句中變量 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';
}
複製代碼

indexitem變量僅存在於for語句的做用域內,for 以外沒有任何意義。length變量也被聲明爲接近其使用它的位置。

爲何修改後的版本優於初始版本? 主要有幾點:

  • 變量未暴露undefined狀態,所以沒有訪問undefined的風險

  • 將變量儘量地移動到它們的使用位置會增長代碼的可讀性

  • 高內聚的代碼塊在必要時更容易重構並提取到單獨的函數中

2.2 訪問不存在的屬性

訪問不存在的對象屬性時,JS 返回undefined

我們用一個例子來講明這一點:

let favoriteMovie = {
  title: 'Blade Runner'
};
favoriteMovie.actors; // => undefined
複製代碼

favoriteMovie是一個具備單個屬性 title 的對象。 使用屬性訪問器favoriteMovie.actors訪問不存在的屬性actors將被計算爲undefined

自己訪問不存在的屬性不會引起錯誤, 但嘗試從不存在的屬性值中獲取數據時就會出現問題。 常見的的錯誤是 TypeError: Cannot read property <prop> of undefined

稍微修改前面的代碼片斷來講明TypeError throw

let favoriteMovie = {
  title: 'Blade Runner'
};
favoriteMovie.actors[0];
// TypeError: Cannot read property '0' of undefined
複製代碼

favoriteMovie沒有屬性actors,因此favoriteMovie.actors的值 undefined。所以,使用表達式favoriteMovie.actors[0]訪問undefined值的第一項會引起TypeError

JS 容許訪問不存在的屬性,這種容許訪問的特性容易引發混淆:可能設置了屬性,也可能沒有設置屬性,繞過這個問題的理想方法是限制對象始終定義它所持有的屬性。

不幸的是,我們經常沒法控制對象。在不一樣的場景中,這些對象可能具備不一樣的屬性集,所以,必須手動處理全部這些場景:

接着咱們實現一個函數append(array, toAppend),它的主要功能在數組的開頭和/或末尾添加新的元素。 toAppend參數接受具備屬性的對象:

  • first:元素插入數組的開頭

  • last:元素在數組末尾插入。

函數返回一個新的數組實例,而不改變原始數組(即它是一個純函數)。

append()的第一個版本看起來比較簡單,以下所示:

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對象能夠省略firstlast屬性,所以必須驗證toAppend中是否存在這些屬性。若是屬性不存在,則屬性訪問器值爲undefined

檢查firstlast屬性是不是undefined,在條件爲 if(toappendix .first){}和if(toappendix .last){}中進行驗證:

這種方法有一個缺點, undefinedfalsenull0NaN''是虛值。

append() 的當前實現中,該函數不容許插入虛值元素:

append([10], { first: 0, last: false }); // => [10]
複製代碼

0false是虛值的。 由於 if(toAppend.first){}if(toAppend.last){}實際上與falsy進行比較,因此這些元素不會插入到數組中,該函數返回初始數組[10]而不會進行任何修改。

如下技巧解釋瞭如何正確檢查屬性的存在。

技巧3:檢查屬性是否存在

JS 提供了許多方法來肯定對象是否具備特定屬性:

  • obj.prop!== undefined:直接與undefined進行比較

  • typeof obj.prop!=='undefined':驗證屬性值類型

  • obj.hasOwnProperty('prop'):驗證對象是否具備本身的屬性

  • 'prop' in obj:驗證對象是否具備本身的屬性或繼承屬性

個人建議是使用 in 操做符,它的語法短小精悍。in操做符的存在代表一個明確的意圖,即檢查對象是否具備特定的屬性,而不訪問實際的屬性值。

obj.hasOwnProperty('prop')也是一個很好的解決方案,它比 in 操做符稍長,僅在對象本身的屬性中進行驗證。

涉及與undefined進行比較剩下的兩種方式可能有效,但在我看來,obj.prop!== undefinedtypeof obj.prop!=='undefined'看起來冗長而怪異,並暴露出直接處理undefined的可疑路徑。。

讓我們使用in操做符改進append(array, toAppend) 函數:

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)在對應屬性存在時爲true,不然爲falsein操做符的使用解決了插入虛值元素0false的問題。如今,在[10]的開頭和結尾添加這些元素將產生預期的結果[0,10,false]

技巧4:解構訪問對象屬性

在訪問對象屬性時,若是屬性不存在,有時須要指示默認值。可使用in和三元運算符來實現這一點。

const object = { };
const prop = 'prop' in object ? object.prop : 'default';
prop; // => 'default'
複製代碼

當要檢查的屬性數量增長時,三元運算符語法的使用變得使人生畏。對於每一個屬性,都必須建立新的代碼行來處理默認值,這就增長了一堵難看的牆,裏面都是外觀類似的三元運算符。

爲了使用更優雅的方法,可使用 ES6 對象的解構。

對象解構容許將對象屬性值直接提取到變量中,並在屬性不存在時設置默認值,避免直接處理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 = '", skipifquote = true} = config解構賦值在一行中從config對象中提取charskipifquote屬性。若是config對象中有一些屬性不可用,那麼解構賦值將設置默認值:char'"'skipifquotefalse

該功能仍有改進的空間。讓咱們將解構賦值直接移動到參數部分。併爲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()縮短了一行。 ={}在解構賦值的右側,確保在徹底沒有指定第二個參數的狀況下使用空對象。

對象解構是一個強大的功能,能夠有效地處理從對象中提取屬性。 我喜歡在被訪問屬性不存在時指定要返回的默認值的可能性。由於這樣能夠避免undefined以及與處理它相關的問題。

技巧5:用默認屬性填充對象

若是不須要像解構賦值那樣爲每一個屬性建立變量,那麼丟失某些屬性的對象能夠用默認值填充。

ES6 Object.assign(target,source1,source2,...)將全部可枚舉的自有屬性的值從一個或多個源對象複製到目標對象中,該函數返回目標對象。

例如,須要訪問unsafeOptions對象的屬性,該對象並不老是包含其完整的屬性集。

爲了不從unsafeOptions訪問不存在的屬性,讓咱們作一些調整:

  • 定義包含默認屬性值的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對象定義屬性fontSizecolor的默認值。

Object.assign() 將第一個參數做爲目標對象{}。 目標對象從unsafeOptions源對象接收fontSize屬性的值。 而且人defaults對象的獲取color屬性值,由於unsafeOptions不包含color屬性。

枚舉源對象的順序很重要:後面的源對象屬性會覆蓋前面的源對象屬性。

如今能夠安全地訪問options對象的任何屬性,包括options.color在最初的unsafeOptions中是不可用的。

還有一種簡單的方法就是使用ES6中展開運算符:

const unsafeOptions = {
  fontSize: 18
};
const defaults = {
  fontSize: 16,
  color: 'black'
};
const options = {
  ...defaults,
  ...unsafeOptions
};
options.fontSize; // => 18
options.color;    // => 'black'
複製代碼

對象初始值設定項從defaultsunsafeOptions源對象擴展屬性。 指定源對象的順序很重要,後面的源對象屬性會覆蓋前面的源對象。

使用默認屬性值填充不完整的對象是使代碼安全且持久的有效策略。不管哪一種狀況,對象老是包含完整的屬性集:而且沒法生成undefined的屬性。

2.3函數參數

函數參數隱式默認爲undefined

一般,用特定數量的參數定義的函數應該用相同數量的參數調用。在這種狀況下,參數獲得指望的值

function multiply(a, b) {
  a; // => 5
  b; // => 3
  return a * b;
}
multiply(5, 3); // => 15
複製代碼

調用multiply(5,3)使參數ab接收相應的53值,返回結果:5 * 3 = 15

在調用時省略參數會發生什麼?

function multiply(a, b) {
  a; // => 5
  b; // => undefined
  return a * b;
}
multiply(5); // => NaN
複製代碼

函數multiply(a, b){}由兩個參數ab定義。調用multiply(5)用一個參數執行:結果一個參數是5,可是b參數是undefined

技巧6:使用默認參數值

有時函數不須要調用的完整參數集,能夠簡單地爲沒有值的參數設置默認值。

回顧前面的例子,讓咱們作一個改進,若是b參數未定義,則爲其分配默認值2

function multiply(a, b) {
  if (b === undefined) {
    b = 2;
  }
  a; // => 5
  b; // => 2
  return a * b;
}
multiply(5); // => 10
複製代碼

雖然所提供的分配默認值的方法有效,但不建議直接與undefined值進行比較。它很冗長,看起來像一個hack .

這裏可使用 ES6 的默認值:

function multiply(a, b = 2) {
  a; // => 5
  b; // => 2
  return a * b;
}
multiply(5);            // => 10
multiply(5, undefined); // => 10  
複製代碼

2.4 函數返回值

隱式地,沒有return語句,JS 函數返回undefined

在JS中,沒有任何return語句的函數隱式返回undefined

function square(x) {
  const res = x * x;
}
square(2); // => undefined  
複製代碼

square() 函數沒有返回計算結果,函數調用時的結果undefined

return語句後面沒有表達式時,默認返回 undefined

function square(x) {
  const res = x * x;
  return;
}
square(2); // => undefined
複製代碼

return; 語句被執行,但它不返回任何表達式,調用結果也是undefined

function square(x) {
  const res = x * x;
  return res;
}
square(2); // => 4
複製代碼

技巧7:不要相信自動插入分號

JS 中的如下語句列表必須以分號(;)結尾:

  • 空語句

  • let,const,var,import,export聲明

  • 表達語句

  • debugger 語句

  • continue 語句,break 語句

  • throw 語句

  • return 語句

若是使用上述聲明之一,請儘可能務必在結尾處指明分號

function getNum() {
  let num = 1; 
  return num;
}
getNum(); // => 1
複製代碼

let 聲明和return 語句結束時,強制性寫分號

當你不想寫這些分號時會發生什麼? 例如,我們想要減少源文件的大小。

在這種狀況下,ECMAScript 提供自動分號插入(ASI)機制,爲你插入缺乏的分號

ASI 的幫助下,能夠從上一個示例中刪除分號
function getNum() { // Notice that semicolons are missing let num = 1 return num } getNum() // => 1

上面的代碼是有效的JS代碼,缺乏的分號ASI會自動爲咱們插入。

乍一看,它看起來很 nice。 ASI 機制容許你少寫沒必要要的分號,可使JS代碼更小,更易於閱讀。

ASI 建立了一個小而煩人的陷阱。 當換行符位於returnreturn \n expression之間時,ASI 會在換行符以前自動插入分號(return; \n expression)。

函數內部return; ? 即該函數返回undefined。 若是你不詳細瞭解ASI的機制,則意外返回的undefined會產生意想不到的問題。

getPrimeNumbers()調用返回的值:

function getPrimeNumbers() {
  return 
    [ 2, 3, 5, 7, 11, 13, 17 ]
}
getPrimeNumbers() // => undefined
複製代碼

return語句和數組之間存在一個換行,JS 在return後自動插入分號,解釋代碼以下:

function getPrimeNumbers() {
  return; 
  [ 2, 3, 5, 7, 11, 13, 17 ];
}
getPrimeNumbers(); // => undefined
複製代碼

return; 使函數getPrimeNumbers() 返回undefined而不是指望的數組。

這個問題經過刪除return和數組文字之間的換行來解決:

function getPrimeNumbers() {
  return [ 
    2, 3, 5, 7, 11, 13, 17 
  ];
}
getPrimeNumbers(); // => [2, 3, 5, 7, 11, 13, 17]
複製代碼

個人建議是研究自動分號插入的確切方式,以免這種狀況。

固然,永遠不要在return和返回的表達式之間放置換行符。

2.5 void 操做符

void <expression>計算表達式不管計算結果如何都返回undefined

void 1;                    // => undefined
void (false);              // => undefined
void {name: 'John Smith'}; // => undefined
void Math.min(1, 3);       // => undefined
複製代碼

void操做符的一個用例是將表達式求值限制爲undefined,這依賴於求值的一些反作用。

3.未定義的數組

訪問越界索引的數組元素時,會獲得undefined

const colors = ['blue', 'white', 'red'];
colors[5];  // => undefined
colors[-1]; // => undefined
複製代碼

colors數組有3個元素,所以有效索引爲0,12

由於索引5-1沒有數組元素,因此訪問colors[5]colors[-1]值爲undefined

JS 中,可能會遇到所謂的稀疏數組。這些數組是有間隙的數組,也就是說,在某些索引中,沒有定義元素。

當在稀疏數組中訪問間隙(也稱爲空槽)時,也會獲得一個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
複製代碼

使用數組時,爲了不獲取undefined,請確保使用有效的數組索引並避免建立稀疏數組。

4. undefined和null之間的區別

一個合理的問題出現了:undefinednull之間的主要區別是什麼?這兩個特殊值都表示爲空狀態。

主要區別在於undefined表示還沒有初始化的變量的值,null表示故意不存在對象。

讓我們經過一些例子來探討它們之間的區別。

number 定義了但沒有賦值。

let number;
number; // => undefined
複製代碼

number 變量未定義,這清楚地代表未初始化的變量。

當訪問不存在的對象屬性時,也會發生相同的未初始化概念

const obj = { firstName: 'Dmitri' };
obj.lastName; // => undefined
複製代碼

由於obj中不存在lastName屬性,因此JS正確地將obj.lastName計算爲undefined

在其餘狀況下,你知道變量指望保存一個對象或一個函數來返回一個對象。可是因爲某些緣由,你不能實例化該對象。在這種狀況下,null是丟失對象的有意義的指示器。

例如,clone()是一個克隆普通JS對象的函數,函數將返回一個對象

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(): 15null(或者一般是一個原始值,nullundefined)。在這種狀況下,函數不能建立克隆,所以返回null—— 一個缺失對象的指示符。

typeof操做符區分了這兩個值

typeof undefined; // => 'undefined'
typeof null;      // => 'object'
複製代碼

嚴格相等運算符===能夠正確區分undefinednull

let nothing = undefined;
let missingObject = null;
nothing === missingObject; // => false
複製代碼

總結

undefined的存在是JS的容許性質的結果,它容許使用:

  • 未初始化的變量
  • 不存在的對象屬性或方法
  • 訪問越界索引的數組元素
  • 不返回任何結果的函數的調用結果

大多數狀況下直接與undefined進行比較是一種很差的作法。一個有效的策略是減小代碼中undefined關鍵字的出現:

  • 減小未初始化變量的使用

  • 使變量生命週期變短並接近其使用的位置

  • 儘量爲變量分配初始值

  • 多敷衍 const 和 let

  • 使用默認值來表示可有可無的函數參數

  • 驗證屬性是否存在或使用默認屬性填充不安全對象

  • 避免使用稀疏數組

代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

交流(歡迎加入羣,羣工做日都會發紅包,互動討論技術)

爲了回饋讀者,《大遷世界》不按期舉行(每月一到三次),現金抽獎活動,保底200,外加用戶讚揚,但願你能成爲大遷世界的小錦鯉,快來試試吧

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

github.com/qq449245884…

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

每次整理文章,通常都到2點才睡覺,一週4次左右,挺苦的,還望支持,給點鼓勵

相關文章
相關標籤/搜索