摘要:檢測函數(shù)從技術(shù)上講,中的函數(shù)是引用類型,同樣存在構(gòu)造函數(shù),每個函數(shù)都是其實例,比如不好的寫法然而,這個方法亦不能跨幀使用,因為每個幀都有各自的構(gòu)造函數(shù),好在運算符也是可以用于函數(shù)的,返回。
上周寫過一篇讀書筆記《編寫可維護的JavaScript》之編程實踐,其中 第8章 避免『空比較』是博主在工作中遇坑較多的雷區(qū),所以特此把該章節(jié)重新整理分享,希望大家不再坑隊友(>﹏<)。
在 JavaScript 中,我們常常會看到這樣的代碼:變量與null的比較(這種用法很有問題),用來判斷變量是否被賦予了一個合理的值。比如:
var Controller = { process: function(items) { if (items !== null) { // 不好的寫法 items.sort(); items.forEach(function(item) { // 執(zhí)行一些邏輯 }); } } }
在這段代碼中,process()方法顯然希望items是一個數(shù)組,因為我們看到items擁有sort()和forEach()。這段代碼的意圖非常明顯:如果參數(shù)items不是一個組數(shù),則停止接下來的操作。這種寫法的問題在于,和null的比較并不能真正避免錯誤的發(fā)生。items的值可以是1,也可以是是字符串,甚至可以是任意對象。這些值都和null不相等,進而會導致process()方法一旦執(zhí)行到sort()時就會出錯。
僅僅和null比較并不能提供足夠的信息來判斷后續(xù)代碼的執(zhí)行是否真的安全。好在 JavaScript 為我們提供了很多種方法來檢測變量的真實值。
檢測原始值在 JavaScript 中有5種原始類型(也稱為簡單數(shù)據(jù)類型): String、Number、Boolean、Undefined和Null。如果你希望一個值是String、Number、Boolean或Undefined,最佳選擇是使用typeof運算符,它會返回一個表示類型的字符串。
對于字符串,typeof返回"string"。
對于數(shù)字,typeof返回"number"。
對于布爾值,typeof返回"boolean"。
對于undefined,typeof返回"undefined"。
typeof的基本語法是:typeof variable,你還可以這樣用:typeof(variable),盡管這是合法的 JavaScript 語法,這種用法讓typeof看起來像一個函數(shù)而非運算符。鑒于此,我們更推薦無括號的寫法。
使用typeof來檢測這4種原始類型是非常安全的做法。來看下面這些例子。
// 檢測"String" if (typeof name === "string") { anotherName = name.substring(3); } // 檢測"Number" if (typeof count === "number") { updateCount(count); } // 檢測"Boolean" if (typeof found === "boolean" && found) { message("Found!"); } // 檢測"Undefined" if (typeof MyApp === "undefined") { MyApp = { // 其他代碼 }; }
typeof運算符的獨特之處在于,將其用于一個未聲明的變量也不會報錯。未定義的變量和值為undefined的變量通過typeof都將返回"undefined"。
最后一個原始類型null,通過typeof將返回"object",這看上去很怪異,被認為是標準規(guī)范的嚴重 bug,因此在編程時要 杜絕使用typeof來檢測null的類型。
console.log(typeof null); // "object"
簡單地和null進行比較通常不會包含足夠的信息以判斷值的類型是否合法,所以null一般不應(yīng)用于檢測語句。
但有一個例外,如果所期望的值真的是null,則可以直接和null進行比較。例如:
// 如果你需要檢測 null,則使用這種方法 var element = document.getElementById("my-div"); if (element !== null) { element.className = "found"; }
如果 DOM 元素不存在,則通過document.getElementById()得到的值為null。這個方法要么返回一個節(jié)點,要么返回null。由于這時null是可預見的一種輸出,則可以用恒等運算符===或非恒等運算符!==來檢測返回結(jié)果。
檢測引用值typeof運算符的返回值除了上述提到的string、number、boolean、undefined和object之外,還有function。從技術(shù)的角度來講,函數(shù)在 JavaScript 中也是對象,不是一種數(shù)據(jù)類型。然而,函數(shù)也確實有一些特殊的屬性,因此通過typeof運算符來區(qū)分函數(shù)和其他對象是有必要的。這一特性將在后面 檢測函數(shù) 中用到。
在 JavaScript 中除了原始值之外的都是引用值(也稱為對象),常用的引用類型有:Object、Array、Date和RegExp,這些引用類型都是 JavaScript 的內(nèi)置對象。typeof運算符在判斷這些引用類型時全都返回"object"。
console.log(typeof {}); // "object" console.log(typeof []); // "object" console.log(typeof new Date()); // "object" console.log(typeof new RegExp()); // "object"
檢測某個引用值類型的最好方法是使用instanceof運算符,instanceof的基本語法是:
value instanceof constructor // 檢測日期 if (value instanceof Date) { console.log(value.getFullYear); } // 檢測 Error if (value instanceof Error) { throw value; } // 檢測正則表達式 if (value instanceof RegExp) { if (value.test(anotherValue)) { console.log("Matches"); } }
instanceof的一個有意思的特性是它不僅檢測構(gòu)造這個對象的構(gòu)造器,還檢測原型鏈。原型鏈包含了很多信息,包括定義對象所采用的繼承模式。比如,默認情況下,每個對象都繼承自Object,因此每個對象的value instanceof Object都會返回ture。比如:
var now = new Date(); console.log(now instanceof Object); // ture console.log(now instanceof Date); // ture
instanceof運算符也可以檢測自定義的類型,比如:
function Person(name){ this.name = name; } var me = new Person("Nicholas"); console.log(me instanceof Object); // ture console.log(me instanceof Person); // ture
這段示例代碼中創(chuàng)建了Person類型。變量me是Person的實例,因此me instanceof Person是true。上文也提到,所有的對象都被認為是Object的實例,因此me instanceof Object也是ture。
在 JavaScript 中檢測 內(nèi)置類型 和 自定義類型 時,最好的做法就是使用instanceof運算符,這也是唯一的方法。
但有一個嚴重的限制,假設(shè)兩個瀏覽器幀(frame)里都有構(gòu)造函數(shù)Person,幀A中的Person實例frameAPersonInstance傳入到幀B中,則會有如下結(jié)果:
console.log(frameAPersonInstance instanceof frameAPerson) // ture console.log(frameAPersonInstance instanceof frameBPerson) // false
盡管兩個Person的定義是完全一樣的,但在不同幀(frame)里,他們被認為是不同類型。有兩個非常重要的內(nèi)置類型也有這個問題:Array和Function,所以檢測它們一般不使用instanceof。
檢測函數(shù)從技術(shù)上講,JavaScript 中的函數(shù)是引用類型,同樣存在Function構(gòu)造函數(shù),每個函數(shù)都是其實例,比如:
function myFunc() {} // 不好的寫法 console.log(myFunc instanceof Function); // true
然而,這個方法亦不能跨幀(frame)使用,因為每個幀都有各自的Function構(gòu)造函數(shù),好在typeof運算符也是可以用于函數(shù)的,返回"function"。
function myFunc() {} // 好的寫法 console.log(typeof myFunc === "function"); // true
檢測函數(shù)最好的方法是使用typeof,因為他可以跨幀(frame)使用。
用typeof來檢測函數(shù)有一個限制。在 IE 8 和更早版本的 IE 瀏覽器中,使用typeof來檢測 DOM 節(jié)點中的函數(shù)都返回"object"而不是"function"。比如:
// IE8 及更早版本的IE console.log(typeof document.createElement); // "object" console.log(typeof document.getElementById); // "object" console.log(typeof document.getElementByTagName); // "object"
之所以出現(xiàn)這種怪異的現(xiàn)象是因為瀏覽器對 DOM 的實現(xiàn)有差異。簡言之,這些早版本的 IE 并沒有將 DOM 實現(xiàn)為內(nèi)置的 JavaScript 方法,導致內(nèi)置typeof運算符將這些函數(shù)識別為對象。因為 DOM 是有明確定義的,了解到對象成員如果存在則意味著它是一個方法,開發(fā)者往往通過in運算符來檢測 DOM 的方法,比如:
// 檢測 DOM 方法 if ("querySelectorAll" in document) { var images = document.querySelectorAll("img"); }
這段代碼檢查querySelectorAll是否定義在document中,如果是,則使用這個方法。盡管不是最理想的方法,如果想在 IE 8 及更早瀏覽器中檢測 DOM 方法是否存在,這是最安全的做法。在其他所有的情形中,typeof運算符是檢測 JavaScript 函數(shù)的最佳選擇。
檢測數(shù)組JavaScript 中最古老的跨域問題之一就是在幀(frame)之間來回傳遞數(shù)組。開發(fā)者很快發(fā)現(xiàn)instanceof Array在此場景中不能返回正確的結(jié)果。正如上文提到的,每個幀都有各自的Array構(gòu)造函數(shù),因此一個幀中的實例在另外一個幀里不會被識別。
關(guān)于如何在 JavaScript 中檢測數(shù)組類型已經(jīng)有狠多研究了,最終 Kangax 給出了一種優(yōu)雅的解決方案:
function isArray(value) { return Object.prototype.toString.call(value) === "[object Array]"; }
Kangax 發(fā)現(xiàn)調(diào)用某個值的內(nèi)置toString()方法在所有瀏覽器中都會返回標準的字符串結(jié)果。對于數(shù)組來說,返回的字符串為"[object Array]",也不用考慮數(shù)組實例實在哪個幀(frame)中被構(gòu)造出來的。這種方法在識別內(nèi)置對象時往往十分有用,但對于自定義對象請不要用這種方法。
ECMAScript5 將Array.isArray()正式引入 JavaScript。唯一的目的就是準確地檢測一個值是否為數(shù)組。同 Kangax 的函數(shù)一樣,Array.isArray()也可以檢測跨幀(frame)傳遞的值,因此很多 JavaScript 類庫目前都類似地實現(xiàn)了這個方法。
function isArray(value) { if (typeof Array.isArray === "function") { return Array.isArray(value); } else { return Object.prototype.toString.call(value) === "[object Array]"; } }
檢測屬性IE 9+、FireFox 4+、Safari 5+、Opera 10.5+、Chrome 都實現(xiàn)了Array.isArray()方法。
另外一種用到null(以及undefined)的場景是當檢測一個屬性是否在對象中存在時,比如:
// 不好的寫法:檢測假值 if (object[propertyName]) { // 一些代碼 } // 不好的寫法:和null相比較 if (object[propertyName] != null) { // 一些代碼 } // 不好的寫法:和undefined相比較 if (object[propertyName] != undefined) { // 一些代碼 }
上面這段代碼里的每個判斷,實際上是通過給定的名字來檢查屬性的值,而并非判斷給定的名字所指的屬性是否存在。在第一個判斷中,當屬性值為假值時結(jié)果會出錯,比如:0、""(空字符串)、false、null和undefined,畢竟這些都是屬性的合法值。
判斷屬性是否存在的最好的方法是使用in運算符。in運算符僅僅會簡單地判斷屬性是否存在,而不去讀屬性的值,如果實例對象的屬性存在、或者繼承自對象的原型,in運算符都會返回true。比如:
var object = { count: 0, related: null }; // 好的寫法 if ("count" in object) { // 這里的代碼會執(zhí)行 } // 不好的寫法:檢測假值 if (object["count"]) { // 這里的代碼不會執(zhí)行 } // 好的寫法 if ("related" in object) { // 這里的代碼會執(zhí)行 } // 不好的寫法,檢測是否為 if (object["related"] != null) { // 這里的代碼不會執(zhí)行 }
如果你只想檢查實例對象的某個屬性是否存在,則使用hasOwnProperty()方法。所有繼承自Object的 JavaScript 對象都有這個方法,如果實例中存在這個屬性則返回true(如果這個屬性只存在于原型里,則返回false)。需要注意的是,在 IE 8 以及更早版本的 IE 中,DOM 對象并非繼承自 Object,因此也不包含這個方法。也就是說,你在調(diào)用 DOM 對象的hasOwnProperty()方法之前應(yīng)當先檢測其是否存在。
// 對于所有非 DOM 對象來說,這是好的寫法 if (object.hasOwnProperty("related")) { // 執(zhí)行這里的代碼會 } // 如果你不確定是否為 DOM 對象,則這樣來寫 if ("hasOwnProperty" in object && object.hasOwnProperty("related")) { // 執(zhí)行這里的代碼會 }
因為存在 IE 8 以及更早版本的 IE 的情形,在判斷實例對象的屬性是否存在時,我更傾向于使用in運算符,只有在需要判斷實例屬性時才會用到hasOwnProperty()。
擴展閱讀不管你什么時候需要檢測屬性的存在性,請使用in運算符或者hasOwnProperty()。這樣做可以避免很多 bug。
《編寫可維護的JavaScript》
《JavaScript權(quán)威指南(第6版)》
《JavaScript高級程序設(shè)計(第3版)》
歡迎來到 石佳劼的博客,如有疑問,請在「原文」評論區(qū) 留言,我會盡量為您解答。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/79703.html
摘要:字面形式允許你在不需要使用操作符和構(gòu)造函數(shù)顯式創(chuàng)建對象的情況下生成引用值。操作符以一個對象和一個構(gòu)造函數(shù)作為參數(shù)鑒別數(shù)組有前一小結(jié)可以知道鑒別數(shù)組類型可以使用。屬性是函數(shù)獨有的,表明該對象可以被執(zhí)行。這種函數(shù)被稱為匿名函數(shù)。 引子: 1.JavaScript 中的變量類型和類型檢測 1.1原始類型 1.2引用類型 1.3內(nèi)建類型的實例化 1.4函數(shù)的字面形式 1.5正則表達式的字...
摘要:返回布爾值,表示參數(shù)字符串是否在原字符串的頭部。布爾值中布爾值有兩個和和都表示值的空缺,但事從背后更深遠的角度考慮,他們的還是有差別的。首先我們來看一下類型轉(zhuǎn)化表任意的值都可以轉(zhuǎn)換為布爾值,只有會被轉(zhuǎn)換為,其他所有值都會被轉(zhuǎn)換成。 開辟了一個關(guān)于javascript的基礎(chǔ)系列,更加深入、細致的了解這門語言。今天分享的是js的數(shù)據(jù)類型。 javascript的數(shù)據(jù)類型可以分為兩類:原始類...
摘要:使函數(shù)不同于其他對象的決定性特性是函數(shù)存在一個被稱為的內(nèi)部屬性。其中,是一個布爾值,指明改對象本身是否可以被修改值為。注意凍結(jié)對象和封印對象均要在嚴格模式下使用。 數(shù)據(jù)類型 在JavaScript中,數(shù)據(jù)類型分為兩類: 原始類型:保存一些簡單數(shù)據(jù),如true,5等。JavaScript共有5中原始類型: boolean:布爾,值為true或false number:數(shù)字,值...
摘要:一個表示編譯器檢測到一個無效的引用值。在實際情況中,往往是在獲取一個未被賦值的引用時被拋出。任何一個函數(shù)上下文都有一個被稱為活動對象的變量對象。沒有找到的話,就會認為引用名沒有基礎(chǔ)值并拋出的錯誤。下沒有下的屬性僅存在于被啟動的情況下。 和其他語言相比,javascript中的對于undefined的理解還是有點讓人困惑的。特別是試著理解ReferenceErrors錯誤(x is no...
摘要:中常常會看到這種代碼變量與的比較這種用法很有問題用來判斷變量是否被賦予了一個合理的值比如不好的寫法執(zhí)行一些邏輯這段代碼中方法顯然是希望是一個數(shù)組因為我們看到的擁有和這段代碼的意圖非常明顯如果參數(shù)不是一個數(shù)組則停止接下來的操作這種寫法的問題在 js中, 常常會看到這種代碼: 變量與null的比較(這種用法很有問題), 用來判斷變量是否被賦予了一個合理的值. 比如: const Contr...
閱讀 1240·2021-11-15 11:37
閱讀 2252·2021-09-30 09:55
閱讀 4516·2021-09-22 15:51
閱讀 3749·2021-09-22 15:46
閱讀 2772·2019-08-30 15:52
閱讀 428·2019-08-29 16:20
閱讀 2895·2019-08-29 15:12
閱讀 1134·2019-08-26 18:27