typeof和instanceof原理

目錄

  • JavaScript數據類型
  • typeof
  • JavaScript原型鏈
  • instanceof

JavaScript數據類型

JavaScript有八種內置類型

  • 空值(null)
  • 未定義(undefined)
  • 布爾值(boolean)
  • 數字(number)
  • 字符串(string)
  • 對象 (object)
  • 符號(symbol, ES6中新增)
  • 大整數(BigInt, ES2020 引入)

除對象外,其他統稱為“基本類型”。

typeof null // 'object'
typeof undefined; // "undefined"
typeof false; // "boolean"
typeof 1; // "number"
typeof '1'; // "string"
typeof {}; // "object" 
typeof []; // "object" 
typeof new Date(); // "object"

typeof Symbol; // "Symbol"
typeof 123n // 'bigint'

這里的類型值的是值,變量是沒有類型的,變量可以隨時持有任何類型的值。JavaScript中變量是“弱類型”的,一個變量可以現在被賦值為 字符串類型,隨后又被賦值為數字類型。

typeof是一個操作符而不是函數,用來檢測給定變量的數據類型。

Symbol 是ES6中引入的一種原始數據類型,表示獨一無二的值。BigInt(大整數)是 ES2020 引入的一種新的數據類型,用來解決 JavaScript中數字只能到 53 個二進制位(JavaScript 所有數字都保存成 64 位浮點數,大于這個范圍的整數,無法精確表示的問題。(在平常的開發中,數據的id 一般用 string 表示的原因)。為了與 Number 類型區別,BigInt 類型的數據必須添加后綴n。 1234為普通整數,1234nBigInt。了解更多可以看 《ES6 入門教程》

typeof null 為什么返回 'object',稍后會從JavaScript數據底層存儲機制來解釋。

還有一種情況

function foo() {};
typeof foo; // 'function'

這樣看來,function 也是JavaScript的一個內置類型。然而查閱規范,就會知道,它實際上是 object 的一個"子類型"。具體來說,函數是“可調用對象”,它有一個內部屬性[[call]],該屬性使其可以被調用。typeof 可以用來區分函數其他對象。

但是使用 typeof不能 判斷對象具體是哪種類型。所有typeof 返回值為 "object" 的對象(如數組,正則等)都包含一個內部屬性 [[class]](我們可以把它看做一個內部的分類)。這個屬性無法直接訪問,一般通過 Object.prototype.toString(...)來查看。

Object.prototype.toString.call(new Date); // "[object Date]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(/reg/ig); // "[object RegExp]"

instanceof 運算符也常常用來判斷對象類型。用法: 左邊的運算數是一個object,右邊運算數是對象類的名字或者構造函數; 返回truefalse

[] instanceof Array; // true
[] instanceof Object; // true
[] instanceof RegExp; // false
new Date instanceof Date; // true

instanceof 的內部機制是:檢測構造函數的 prototype 屬性是否出現在某個實例對象的原型鏈上。下面會詳解介紹該部分。

typeof 原理

typeof原理: 不同的對象在底層都表示為二進制,在Javascript中二進制前(低)三位存儲其類型信息

  • 000: 對象
  • 010: 浮點數
  • 100:字符串
  • 110: 布爾
  • 1: 整數

typeof null 為"object", 原因是因為 不同的對象在底層都表示為二進制,在Javascript中二進制前(低)三位都為0的話會被判斷為Object類型,null的二進制表示全為0,自然前三位也是0,所以執行typeof時會返回"object"。
一個不恰當的例子,假設所有的Javascript對象都是16位的,也就是有16個0或1組成的序列,猜想如下:

Array: 1000100010001000
null:  0000000000000000

typeof []  // "object"
typeof null // "object"

因為Array和null的前三位都是000。為什么Array的前三位不是100?因為二進制中的“前”一般代表低位, 比如二進制00000011對應十進制數是3,它的前三位是011。

instanceof

要想從根本上理解,需要從兩個方面入手:

  • 語言規范中是如何定義這個運算符的
  • JavaScript原型繼承機制

通俗一些講,instanceof 用來比較一個對象是否為某一個構造函數的實例。注意,instanceof運算符只能用于對象,不適用原始類型的值。

  1. 判斷某個實例是否屬于某種類型
function Foo() {};
Foo.prototype.message = ...;
const a = new Foo();
  1. 也可以判斷一個實例是否是其父類型或者祖先類型的實例。
function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);

console.log(auto instanceof Car);
// expected output: true

console.log(auto instanceof Object);
// expected output: true

JavaScript原型鏈

理解原型

我們創建的每個函數都有一個 [prototype])屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。那么 prototype 就是調用 構造函數 而創建的那個對象實例的原型對象。使用原型對象的好處是可以讓所有對象實例共享它所包含的屬性和方法。

function Person() {};
Person.prototype.name = 'kangkang';
Person.prototype.sayName = function() {
    console.log(this.name);
}

const person1 = new Person();
person1.sayName(); // 'kangkang'

const person2 = new Person();
person2.sayName(); // 'kangkang'

console.log(person1.sayName === person2.sayName);
// true

構造函數,原型和實例的關系

  • 每個構造函數都有一個原型對象
  • 原型對象都包含一個指向構造函數指針
  • 實例都包含一個指向原型對象指針

那么,假如我們讓原型對象等于另一個類型實例,結果會怎么樣?
顯然,此時的原型對象將包含一個指向另一個原型指針,相應地,另一個原型中也包含著一個指向指向另一個構造函數指針。假如另一個原型又是另一個類型實例,那么上述關系依然成立,如此層層遞進,就構成了實例與原型的鏈條。這就是所謂原型鏈的基本概念。

上面這段話有點繞,如果想不明白的話,這里可以停一下,讀三篇,再結合我們平常寫代碼使用過程中的實際場景。

[[prototype]]機制

[[prototype]]機制就是存在與對象中的一個內部鏈接,它會引用其他對象。
通常來說,這個鏈接的作用是:如果在對象上沒有找到需要的屬性或者方法引用,引擎就會繼續在 [[ptototype]]關聯的對象上進行查找,同理,如果在后者中也沒有找到需要的引用就會繼續查找它的[[prototype]],以此類推。這一系列對象的鏈接被稱為“原型鏈”。

但是哪里是 [[prototype]]的 ”盡頭“呢?

所有普通的 [[prototype]]鏈最終都會執行內置的 Object.prototype。由于所有的"普通"(內置,不是特定主機的擴展)對象都”源于“(或者說把[[prototype]] 鏈頂端設置為)這個Object.prototype對象,所以說它包含JavaScript中許多通用的功能。比如說.toString().valueOf()等等

Object.prototype是js原型鏈的最頂端,它的__proto__null(有proto屬性,但值是 null,因為這是原型鏈的最頂端);

為什么要這么設計?

最主要的就是節省內存,如果屬性和方法定義在原型上,那么所有的實例對象就能共享。

__proto__

絕大多數(不是所有)瀏覽器也支持一種非標準的方法來訪問內部的 [[prototype]]屬性。

function Foo() {};
const a = new Foo();

a.__proto__ === Foo.prototype; // true

這個奇怪的.__proto__屬性“神奇地”引用了內部的[[prototype]]對象。如果你想直接查找(甚至可以直接通過.proto.proto ...來遍歷)原型鏈的話,這個方法非常有用。

.construtor一樣,__proto__實際上并不存在于你正在使用的對象(本例中是a)。實際上,它和其他的常用函數(.toString()、.isPrototypeOf(...),等等 一樣,存在于內置的Object.prototype中。(它們是不可枚舉的;

此外,.__proto__看起來很像一個屬性,但是實際上它更像一個 getter/setter
.__proto__的實現大致是這樣的

Object.defineProperty(Object.prototype, "__proto__", {
    get: function() {
        return Object.getPrototypeOf(this);
    },
    // ES6中的Object.setPrototypeOf
    set: function(o) {
        Object.setPrototypeOf(this, o);
        return o;
    }
})

因此,訪問(獲取值) a.__proto__時,實際上是調用了 a.__proto__()(調用getter函數)。雖然getter函數存在于Object.prototype對象中,但是 它的 this 指向對象a,所以和object.getPrototypeOf(a)結果相同。

.__proto__是可設置屬性,之前的代碼中使用ES6的Object.setPrototypeOf(...)進行設置。然而,通常來說你不需要修改已有對象的[[prototype]]

原型鏈

JavaScript原型鏈
    1. function Foo 就是一個方法,比如內置的 Array,String,或者自定義方法。
    1. function Object就是 Object
    1. function Function就是 Function
    1. 以上三個其實都是 function,所以他們的 __proto__都是 Function.prototype
    1. 記住 String, Array, Number,Object, Function這些其實都是 function
function Foo() {};

console.log(Object instanceof Object); // true
console.log(Function instanceof Function); // true
console.log(Function instanceof Object); // true

console.log(Foo instanceof Foo); // false
console.log(Foo instanceof Object); // true
console.log(Foo instanceof Function); // true

大家可以在控制臺輸出,可以直觀的看到每個步驟的輸出,結合instanceof 的規范跟js原型鏈 加深理解。

回過頭來再看instanceof

instanceof的語法:

object instanceof constructor
// 等同于
constructor.prototype.isPrototypeOf(object)
  • object: 要檢測的對象
  • constructor:某個構造函數

instanceof的代碼實現。

function instanceof(L, R) { //L是表達式左邊,R是表達式右邊
    const O = R.prototype;
    L = L.__proto__;
    while(true) {
        if (L === null)
            return false;
        if (L === O) // 這里重點:當 L 嚴格等于 0 時,返回 true 
            return true;
        L = L.__proto__;
    }
}

instanceof原理: 檢測 constructor.prototype是否存在于參數 object的 原型鏈上。instanceof 查找的過程中會遍歷object的原型鏈,直到找到 constructorprototype ,如果查找失敗,則會返回false,告訴我們,object 并非是 constructor 的實例。

原型鏈這部分很不好理解,我基本上都是看完過幾天就忘,所以要多看幾遍多理解,花些時間搞明白,搞明白這部分。之后再看相關的東西,就很簡單易懂。這部分是JavaScript很重要的核心。花幾天時間反復看,弄明白了,以后理解很多問題都是簡單的多。如果你發現我上面哪部分表述的不太準確,記得給我指出來,互相學習。這部分推薦好好看看 《JavaScript高級程序設計(第3版)》第六章的這部分,還有 《你不知道的JavaScript(上卷)》第五章關于這部分內容的講解。

Symbol.hasInstance

對象的Symbol.hasInstance屬性,指向一個內部方法。當其他對象使用instanceof運算符,判斷是否為該對象的實例時,會調用這個方法。比如,foo instanceof Foo在語言內部,實際調用的是Foo[Symbol.hasInstance](foo)

class MyClass {
  [Symbol.hasInstance](foo) {
    return foo instanceof Array;
  }
}

[1, 2, 3] instanceof new MyClass() // true

總結

看完之后,腦子里可以把上面的內容串一下;看看下面的幾個問題你是否可以立刻想出來

  • JavaScript有哪幾種數據類型,都有哪些判斷數據類型的操作,返回值是什么,原理是什么
  • typeof null 為什么是 ”object“
  • 什么是原型,哪里是 [[prototype]]的 ”盡頭“,為什么要這么設計
  • JavaScript原型鏈的核心是什么
  • instanceof的原理是什么
  • Symbol.hasInstance又是什么(或者你自己實現一個instanceof
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容