最近在回頭看看js基礎,正好對判斷數據類型有些不懂的地方,或者說不太明白typeof和instanceof原理,因此準備研究研究🤭javascript
涉及到原型的能夠看看這篇文章🤭html
淺談JavaScript原型java
最新的 ECMAScript 標準定義了 8 種數據類型:c#
可能你們對BigInt原始數據類型比較陌生,它的提出解決了一部分問題,好比大於253 - 1
的整數。這本來是 Javascript中能夠用 Number
表示的最大數字。BigInt 能夠表示任意大的整數。post
瞭解了數據類型後,咱們接下來就來看看如何檢測數據類型吧。性能
typeof 操做符返回一個字符串,表示未經計算的操做數的類型。ui
typeof null === 'object';
複製代碼
這可能說是一個JavaScript設計的Bug吧。MDN規範是這麼解釋的:
在 JavaScript 最初的實現中,JavaScript 中的值是由一個表示類型的標籤和實際數據值表示的。對象的類型標籤是 0。因爲 null
表明的是空指針(大多數平臺下值爲 0x00),所以,null 的類型標籤是 0,typeof null
也所以返回 "object"
。
typeof在判斷object類型的數據的時候,不能準確的告知咱們具體是哪種Object,並且在判斷null的時候,也會上述的附加信息。對於判斷是哪種object的時候,咱們須要用到instanceof這個操做符來判斷,咱們後面會說到。
說一說typeof的原理吧,說到這裏,咱們應該考慮一下,JavaScript是怎麼存儲數據的呢,又或者說,對於一個變量,它的數據類型權衡的標準是什麼呢?
查閱了相關的資料,其實這個是一個歷史遺留的bug,在 javascript 的最第一版本中,使用的 32 位系統,爲了性能考慮使用低位存儲了變量的類型信息:
but, 對於 undefined
和 null
來講,這兩個值的信息存儲是有點特殊的。
null
:對應機器碼的 NULL 指針,通常是全零。
undefined
:用 −2^30 整數來表示!
因此,typeof
在判斷 null
的時候就出現問題了,因爲 null
的全部機器碼均爲0,所以直接被當作了對象來看待。
因此,彷佛懂了一點皮毛了(●'◡'●)
instanceof 運算符用於檢測構造函數的 prototype
屬性是否出如今某個實例對象的原型鏈上。
object instanceof constructor
object 某個實例對象
construtor 某個構造函數
複製代碼
// 定義構造函數
function C(){}
function D(){}
var o = new C();
o instanceof C; // true,由於 Object.getPrototypeOf(o) === C.prototype
o instanceof D; // false,由於 D.prototype 不在 o 的原型鏈上
o instanceof Object; // true,由於 Object.prototype.isPrototypeOf(o) 返回 true
C.prototype instanceof Object // true,同上
C.prototype = {};
var o2 = new C();
o2 instanceof C; // true
o instanceof C; // false,C.prototype 指向了一個空對象,這個空對象不在 o 的原型鏈上.
D.prototype = new C(); // 繼承
var o3 = new D();
o3 instanceof D; // true
o3 instanceof C; // true 由於 C.prototype 如今在 o3 的原型鏈上
複製代碼
須要注意的是,若是表達式 obj instanceof Foo
返回 true
,則並不意味着該表達式會永遠返回 true
,由於 Foo.prototype
屬性的值有可能會改變,改變以後的值頗有可能不存在於 obj
的原型鏈上,這時原表達式的值就會成爲 false
。另一種狀況下,原表達式的值也會改變,就是改變對象 obj
的原型鏈的狀況,雖然在目前的ES規範中,咱們只能讀取對象的原型而不能改變它,但藉助於非標準的 __proto__
僞屬性,是能夠實現的。好比執行 obj.__proto__ = {}
以後,obj instanceof Foo
就會返回 false
了。
要想理解instanceof原理的話,咱們須要從兩個方面去了解:
這裏,我直接將規範定義翻譯爲 JavaScript 代碼以下:
function new_instance_of(leftVaule, rightVaule) {
let rightProto = rightVaule.prototype,
leftVaule = leftVaule.__proto__;
while (true) {
if (leftVaule === null) {
return false;
}
if (leftVaule === rightProto) {
return true;
}
leftVaule = leftVaule.__proto__
}
}
複製代碼
從上面的代碼看得出來,instanceof主要的原理就是:
只要右邊的prototype在左邊的原型鏈上及可,也就是返回true。所以,instanceof在查詢的過程當中會遍歷左邊變量的原型鏈,直到找到右邊變量的prototype,若是查找失敗的話,返回false,告訴咱們左邊的變量並不是是右邊變量的實例。
接下來咱們看一看有趣的例子:
function Foo() {}
console.log(Object instanceof Object)
console.log(Function instanceof Function)
console.log(Function instanceof Object)
console.log(Foo instanceof Object)
console.log(Foo instanceof Function)
console.log(Foo instanceof Foo)
複製代碼
關於原型繼承的原理,我簡單用一張圖來表示
這個圖很重要,對於原型鏈不理解的,能夠看看這篇文章:
Object instanceof Object
由圖可知,Object 的 prototype 屬性是 Object.prototype, 而因爲 Object 自己是一個函數,由 Function 所建立,因此 Object.__proto__ 的值是 Function.prototype,而 Function.prototype 的 __proto__ 屬性是 Object.prototype,因此咱們能夠判斷出,Object instanceof Object 的結果是 true 。用代碼簡單的表示一下
複製代碼
leftValue = Object.__proto__ = Function.prototype;
rightValue = Object.prototype;
// 第一次判斷
leftValue != rightValue
leftValue = Function.prototype.__proto__ = Object.prototype
// 第二次判斷
leftValue === rightValue
// 返回 true
複製代碼
剩下的Function instanceof Object等有趣的例子能夠本身手動去實現一下🤭
能夠知道,Object.prototype.toString 最終會返回形式如 [object,class] 的字符串,class 指代的是其檢測出的數據類型,這個是咱們判斷數據類型的關鍵。
var toString=Object.prototype.toString;
console.log(toString.call(und)); // [object Undefined]
console.log(toString.call(nul)); // [object Null]
console.log(toString.call(boo)); // [object Boolean]
console.log(toString.call(num)); // [object Number]
console.log(toString.call(str)); // [object String]
console.log(toString.call(obj)); // [object Object]
console.log(toString.call(arr)); // [object Array]
console.log(toString.call(fun)); // [object Function]
console.log(toString.call(date)); // [object Date]
console.log(toString.call(reg)); // [object RegExp]
console.log(toString.call(err)); // [object Error]
console.log(toString.call(arg)); // [object Arguments]
複製代碼
/** * @desc 數據類型檢測 * @param obj 待檢測的數據 * @return {String} 類型字符串 */
let type = (obj) => typeof obj !== 'object' ? typeof obj : Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();
複製代碼
/** * @desc 是不是 Undefined 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isUndefined = obj => obj === void 0
/** * @desc 是不是 Null 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isNull = obj => obj === Null
/** * @desc 是不是 Boolean 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isBoolean = obj => typeof(obj) === 'boolean'
/** * @desc 是不是 Number 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isNumber = obj => typeof(obj) === 'number'
/** * @desc 是不是 String 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isString = obj => typeof(obj) === 'string'
/** * @desc 是不是 Object 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isObject = obj => Object.prototype.toString.call(obj) === '[object Object]'
/** * @desc 是不是 Array 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isArray = obj => Object.prototype.toString.call(obj) === '[object Array]'
/** * @desc 是不是 Function 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isFunction = obj => typeof obj === 'function'
/** * @desc 是不是 Date 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isDate = obj => Object.prototype.toString.call(obj) === '[object Date]'
/** * @desc 是不是 RegExp 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isRegExp = obj => Object.prototype.toString.call(obj) === '[object RegExp]'
/** * @desc 是不是 Error 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isError = obj => Object.prototype.toString.call(obj) === '[object Error]'
/** * @desc 是不是 Arguments 類型檢測 * @param obj 待檢測的數據 * @return {Boolean} 布爾值 */
let isArguments = obj => Object.prototype.toString.call(obj) === '[object Arguments]'
複製代碼