1. 程式人生 > >處理 JS中 undefined 的 7 個技巧

處理 JS中 undefined 的 7 個技巧

摘要: JS的大部分報錯都是undefined...

Fundebug經授權轉載,版權歸原作者所有。

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

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

對於JavaScript,直譯器在訪問尚未初始化的變數或物件屬性時返回undefined

。例如:

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

另一方面,null表示缺少的物件引用,JS本身不會將變數或物件屬性設定為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

由於 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 及其對程式碼安全性的影響。

1. 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
  • 或者不存在陣列元素movies[3]

都會被定義為undefined

ECMAScript規範定義了undefined 值的型別

Undefined type是其唯一值為undefined 值的型別。

在這個意義上,typeof undefined返回“undefined”字串

typeof undefined === "undefined"; // => true

當然typeof可以很好地驗證變數是否包含undefined的值

let nothing;
typeof nothing === "undefined"; // => true

2. 導致undefined的常見場景

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
  • 使用預設值來表示無關緊要的函式引數
  • 驗證屬性是否存在或使用預設屬性填充不安全物件
  • 避免使用稀疏陣列

關於Fundebug

Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有陽光保險、核桃程式設計、荔枝FM、掌門1對1、微脈、青團社等眾多品牌企業。歡迎