目錄
- 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
為普通整數,1234n
為BigInt
。了解更多可以看 《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
,右邊運算數是對象類的名字或者構造函數; 返回true
或false
。
[] 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運算符只能用于對象,不適用原始類型的值。
- 判斷某個
實例
是否屬于某種類型
function Foo() {};
Foo.prototype.message = ...;
const a = new Foo();
- 也可以判斷一個實例是否是其父類型或者祖先類型的實例。
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]]
。
原型鏈
-
function Foo
就是一個方法,比如內置的 Array,String,或者自定義方法。
-
-
function Object
就是Object
-
-
function Function
就是Function
-
- 以上三個其實都是 function,所以他們的
__proto__
都是Function.prototype
- 以上三個其實都是 function,所以他們的
- 記住
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
的原型鏈,直到找到 constructor
的 prototype
,如果查找失敗,則會返回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
)