任何編程語言都不可缺乏的組成部分——「類型」
ps: 這篇文章大概會浪費你10-15分鐘的時間~javascript
JavaScript 是一種弱類型或者說動態語言。這意味着你不用提早聲明變量的類型,在程序運行過程當中,類型會被自動肯定。這也意味着你可使用同一個變量保存不一樣類型的數據:前端
var foo = 42; // foo is a Number now
foo = "bar"; // foo is a String now
foo = true; // foo is a Boolean now
複製代碼
JavaScript 中的類型包括:java
你可能還常常見到並使用 undefined
(未定義)類型和 null
(空)類型。此外還有Array
(數組)類型,以及分別用於表示日期和正則表達式的 Date
(日期)和 RegExp
(正則表達式),這三種類型都是特殊的對象。嚴格意義上說,Function(函數)也是一種特殊的對象。因此準確來講,JavaScript 中的類型應該包括這些:node
7 種原始類型:正則表達式
和Object
(對象)編程
原始值( primitive values )數組
除 Object 之外的全部類型都是不可變的(值自己沒法被改變),所以稱這些類型的值爲「原始值」。瀏覽器
JavaScript 採用「遵循 IEEE 754 標準的雙精度 64 位格式」表示數字(-(2^53 -1) 到 2^53 -1)。和其餘編程語言(如 C 和 Java)不一樣,JavaScript 不區分整數值和浮點數值,全部數字在 JavaScript 中均用浮點數值表示,因此在進行數字運算的時候要特別注意。看看下面的例子:安全
0.1 + 0.2 = 0.30000000000000004
複製代碼
注意,除了上述例子以外,還有
0.1+0.7 = 0.7999999999999999
數據結構而除此以外,0.1 加上其餘 0.3, 0.4, ..., 0.9都是正常的
在具體實現時,整數值一般被視爲32位整型變量,在個別實現(如某些瀏覽器)中也以32位整型變量的形式進行存儲,直到它被用於執行某些32位整型不支持的操做,這是爲了便於進行位操做。
數字類型中只有一個整數有兩種表示方法: 0 可表示爲 -0 和 +0("0" 是 +0 的簡寫)。 在實踐中,這也幾乎沒有影響。 例如 +0 === -0
爲真。 可是,你可能要注意除以0的時候:
42 / +0; // Infinity
42 / -0; // -Infinity
複製代碼
JavaScript 支持標準的算術運算符,包括加法、減法、取模(或取餘)等等。還有一個內置對象 Math
(數學對象),用以處理更多的高級數學函數和常數:
Math.sin(3.5);
var circumference = 2 * Math.PI * r;
複製代碼
你可使用內置函數 parseInt()
將字符串轉換爲整型。該函數的第二個可選參數表示字符串所表示數字的基(進制):
parseInt("123", 10); // 123
parseInt("010", 10); // 10
複製代碼
這是由於字符串以數字 0 開頭,parseInt()
函數會把這樣的字符串視做八進制數字;同理,0x開頭的字符串則視爲十六進制數字。
若是想把一個二進制數字字符串轉換成整數值,只要把第二個參數設置爲 2 就能夠了:
parseInt("11", 2); // 3
複製代碼
JavaScript 還有一個相似的內置函數 parseFloat()
,用以解析浮點數字符串,與parseInt()
不一樣的地方是,parseFloat()
只應用於解析十進制數字。
單元運算符 + 也能夠把數字字符串轉換成數值:
+ "42"; // 42
+ "010"; // 10
+ "0x10"; // 16
複製代碼
若是給定的字符串不存在數值形式,函數會返回一個特殊的值 NaN
(Not a Number 的縮寫):
parseInt("hello", 10); // NaN
複製代碼
要當心NaN:若是把 NaN
做爲參數進行任何數學運算,結果也會是 NaN
:
NaN + 5; //NaN
複製代碼
可使用內置函數 isNaN()
來判斷一個變量是否爲 NaN
:
isNaN(NaN); // true
複製代碼
注意: parseInt()
和 parseFloat()
函數會嘗試逐個解析字符串中的字符,直到趕上一個沒法被解析成數字的字符,而後返回該字符前全部數字字符組成的數字。然而若是使用運算符 "+", 只要字符串中含有沒法被解析成數字的字符,該字符串都將被轉換成 NaN
。可分別使用這兩種方法解析「10.2abc」這一字符串,並比較獲得的結果,來理解這兩種方法的區別。
parseInt('10.2abc'); // 10
parseFloat('10.2abc'); // 10.2
+ "10.2abc"; // NaN
複製代碼
JavaScript 還有兩個特殊值:Infinity
(正無窮)和 -Infinity
(負無窮):
1 / 0; // Infinity
-1 / 0; // -Infinity
複製代碼
可使用內置函數 isFinite()
來判斷一個變量是不是一個有窮數, 若是類型爲Infinity
, -Infinity
或 NaN則返回false
:
isFinite(1/0); // false
isFinite(Infinity); // false
isFinite(-Infinity); // false
isFinite(NaN); // false
isFinite(0); // true
isFinite(2e64); // true
isFinite("0"); // true
// 若是是純數值類型的檢測,則返回 false:
Number.isFinite("0"); // false
複製代碼
要檢查值是否大於或小於 +/-Infinity
,你可使用常量 Number.MAX_VALUE
和 Number.MIN_VALUE
。
另外在 ECMAScript 6 中,你也能夠經過 Number.isSafeInteger()
方法還有 Number.MAX_SAFE_INTEGER
和 Number.MIN_SAFE_INTEGER
來檢查值是否在雙精度浮點數的取值範圍內。 超出這個範圍,JavaScript 中的數字再也不安全了,也就是隻有 second mathematical interger 能夠在 JavaScript 數字類型中正確表現。
BigInt
類型是 JavaScript 中的一個基礎的數值類型,能夠用任意精度表示整數。使用 BigInt,能夠安全地存儲和操做大整數,甚至能夠超過數字的安全整數限制。BigInt是經過在整數末尾附加 n
或調用構造函數來建立的。
> const x = 2n ** 53n;
9007199254740992n
> const y = x + 1n;
9007199254740993n
複製代碼
能夠對BigInt
使用運算符+、*、-、**
和%
,就像對數字同樣。BigInt 嚴格來講並不等於一個數字,但它是鬆散的。
在將BigInt
轉換爲Boolean
時,它的行爲相似於一個數字:if、||、&&、Boolean 和!。
警告:BigInt
不能與數字互換操做。不然,將拋出TypeError
。
JavaScript 中的字符串是一串Unicode 字符序列,是一組16位的無符號整數值的「元素」。更準確地說,它們是一串UTF-16編碼單元的序列,每個編碼單元由一個 16 位二進制數表示。每個Unicode字符由一個或兩個編碼單元來表示。
字符串中的每一個元素佔據了字符串的位置。第一個元素的索引爲0,下一個是索引1,依此類推。字符串的長度是它的元素的數量。若是想表示一個單獨的字符,只需使用長度爲 1 的字符串。
經過訪問字符串的 length
(編碼單元的個數)屬性,能夠獲得它的長度。
"hello".length; // 5
複製代碼
String實際上是 JavaScript 對象。你能夠像 object 同樣使用字符串,字符串也有 methods(方法)能讓你操做字符串和獲取字符串的信息。
"hello".charAt(0); // "h"
"hello, world".replace("world", "mars"); // "hello, mars"
"hello".toUpperCase(); // "HELLO"
複製代碼
注意:JavaScript 字符串是不可更改的。這意味着字符串一旦被建立,就不能被修改。可是,能夠基於對原始字符串的操做來建立新的字符串。即JavaScript 中對字符串的操做必定返回了一個新字符串,原始字符串並無被改變
。例如:
String.substr()
.+
) 或者 String.concat()
.布爾表示一個邏輯實體,能夠有兩個值:true
和 false
。
根據具體須要,JavaScript 按照以下規則將變量轉換成布爾類型:
false
、0
、空字符串(""
)、NaN
、null
和 undefined
被轉換爲 false
true
小提示:在作雙等==邏輯時,2==true 是會返回false的,由於boolean類型被轉換爲數字1,而後再作比較,詳情移步初學者不容錯過的雙等(==)小知識
一個沒有被賦值的變量會有個默認值 undefined。
undefined是一個不能被配置(non-configurable),不能被重寫(non-writable)的屬性。
返回
值,就會返回一個undefined值。使用undefined和嚴格相等或不相等操做符來決定一個變量是否擁有值。可是若是你不知道這個值是否聲明過,例如試圖使用全局變量中的屬性,建議使用typeof
,
它不會在一個變量沒有被聲明的時候拋出一個錯誤。
// 這裏沒有聲明y
if(typeof y === 'undefined') { // 沒有錯誤,執行結果爲true
console.log("y is " + typeof y ) // y is undefined
}
if(y === undefined) { // ReferenceError: y is not defined
}
複製代碼
Null 類型只有一個值: null。
它是 JavaScript 基本類型 之一
值 null
是一個字面量,特指對象的值未設置。把 null
做爲還沒有建立的對象,也許更好理解。
注意:
typeof null == "object"
null
常在返回類型應是一個對象但沒有關聯的值的地方使用。
// foo 不存在,它歷來沒有被定義過或者是初始化過:
foo;
"ReferenceError: foo is not defined"
// foo 如今已是知存在的,可是它沒有類型或者是值:
var foo = null;
foo;
null
複製代碼
null
與 undefined
的不一樣點: 當檢測 null
或 undefined
時,注意相等(==)與全等(===)兩個操做符的區別 ,前者會執行類型轉換:
typeof null // "object" (由於一些之前的緣由而不是'null')
typeof undefined // "undefined"
null === undefined // false
null == undefined // true
null === null // true
null == null // true
!null //true
isNaN(1 + null) // false
isNaN(1 + undefined) // true
複製代碼
相關知識點: 初學者不容錯過的雙等(==)小知識
符號(Symbols)是ECMAScript 第6版新定義的。符號類型是惟一的而且是不可修改的。
Symbol()
函數會返回symbol類型的值,該類型具備靜態屬性和靜態方法。每一個從Symbol()
返回的symbol值都是惟一的。一個symbol值能做爲對象屬性的標識符;這是該數據類型僅有的目的。
不支持
var sym = new Symbol(); // TypeError
const symbol1 = Symbol();
const symbol2 = Symbol(42);
const symbol3 = Symbol('foo');
console.log(typeof symbol1);
// expected output: "symbol"
console.log(symbol3.toString());
// expected output: "Symbol(foo)"
console.log(Symbol('foo') === Symbol('foo'));
// expected output: false
複製代碼
Symbol
函數的參數是可選的,字符串類型。是對symbol的描述,可用於調試但不是訪問symbol自己。
再次強調,Symbol類型惟一合理的用法是用變量存儲 symbol的值,而後使用存儲的值建立對象屬性。
var myPrivateMethod = Symbol();
this[myPrivateMethod] = function() {...};
複製代碼
當一個 symbol 類型的值在屬性賦值語句中被用做標識符,
for( ... in ...)
」 中做爲成員出現,也由於這個屬性是匿名的,它一樣不會出如今 「Object.getOwnPropertyNames()
」 的返回數組裏。Object.getOwnPropertySymbols()
」 返回的數組。myPrivateMethod
的值能夠訪問到對象屬性。typeof
運算符能幫助你識別 symbol 類型
typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
複製代碼
會根據給定的鍵 key,來從運行時的 symbol 註冊表中找到對應的 symbol,若是找到了,則返回它,不然,新建一個與該鍵關聯的 symbol,並放入全局 symbol 註冊表中。
JavaScript 中的對象,Object,能夠簡單理解成「名稱-值」對(而不是鍵值對,ES 2015 的映射表(Map),比對象更接近鍵值對))
「名稱」部分是一個 JavaScript 字符串,「值」部分能夠是任何 JavaScript 的數據類型——包括對象。
有兩種簡單方法能夠建立一個空對象:
var obj = new Object();
var obj = {};
複製代碼
這兩種方法在語義上是相同的。第二種更方便的方法叫做「對象字面量(object literal)」法。
「對象字面量」也能夠用來在對象實例中定義一個對象:
var obj = {
name: "Carrot",
"for": "Max",//'for' 是保留字之一,使用'_for'代替
details: {
color: "orange",
size: 12
}
}
複製代碼
對象的屬性能夠經過鏈式(chain)表示方法進行訪問:
obj.details.color; // orange
obj["details"]["size"]; // 12
複製代碼
注意:若'名稱'不包含中劃線之類的字符,推薦使用第一種
object.key
來調用若key未知或須要經過參數傳遞進來,則使用第二種
object[key]
來調用,下面會說明緣由
下面的例子建立了一個對象原型,Person
,和這個原型的實例,You
。
function Person(name, age) {
this.name = name;
this.age = age;
}
// 定義一個對象
var You = new Person("You", 24);
// 咱們建立了一個新的 Person,名稱是 "You"
// ("You" 是第一個參數, 24 是第二個參數..)
複製代碼
完成建立後,對象屬性能夠經過以下兩種方式進行賦值和訪問:
obj.name = "Simon"
var name = obj.name;
複製代碼
和:
// bracket notation
obj['name'] = 'Simon';
var name = obj['name'];
// can use a variable to define a key
var user = prompt('what is your key?')
obj[user] = prompt('what is its value?')
複製代碼
這兩種方法在語義上也是相同的。第二種方法的優勢在於屬性的名稱被看做一個字符串,這就意味着它能夠在運行時被計算,缺點在於這樣的代碼有可能沒法在後期被解釋器優化。它也能夠被用來訪問某些以預留關鍵字做爲名稱的屬性的值:
obj.for = "Simon"; // 語法錯誤,由於 for 是一個預留關鍵字
obj["for"] = "Simon"; // 工做正常
複製代碼
JavaScript 中的數組是一種特殊的對象。它的工做原理與普通對象相似(以數字爲屬性名,但只能經過[]
來訪問),但數組還有一個特殊的屬性——length
(長度)屬性。這個屬性的值一般比數組最大索引大 1。
注意,Array.length
並不老是等於數組中元素的個數,以下所示:
var a = ["dog", "cat", "hen"];
a[100] = "fox";
a.length; // 101
複製代碼
若是試圖訪問一個不存在的數組索引,會獲得 undefined
:
typeof(a[90]); // undefined
複製代碼
ES2015 引入了更加簡潔的 for
...of
循環,能夠用它來遍歷可迭代對象,例如數組:
for (const currentValue of a) {
// Do something with currentValue
}
複製代碼
不推薦使用
for...in
循環,該方法遍歷數組的索引。若是哪一個傢伙直接向Array.prototype
添加了新的屬性,使用這樣的循環這些屬性也一樣會被遍歷
ECMAScript 5 增長了另外一個遍歷數組的方法,forEach()
:
["dog", "cat", "hen"].forEach(function(currentValue, index, array) {
// Do something with currentValue or array[index]
});
複製代碼
若部分遍歷,還能夠考慮
some(), every()
等方法,建議查看 Array 方法的完整文檔。
最簡單的函數就像下面這個這麼簡單:
function add(x, y) {
var total = x + y;
return total;
}
複製代碼
return
語句在返回一個值並結束函數。若是沒有使用 return
語句,或者一個沒有值的 return
語句,JavaScript 會返回 undefined
。你能夠傳入多於函數自己須要參數個數的參數,只是多餘的參數會被忽略。
函數其實是訪問了函數體中一個名爲 arguments
的內部對象,這個對象就如同一個相似於數組的對象同樣,包括了全部被傳入的參數。
function add() {
var sum = 0;
for (var i = 0, j = arguments.length; i < j; i++) {
sum += arguments[i];
}
return sum;
}
add(2, 3, 4, 5); // 14
複製代碼
爲了使代碼變短一些,咱們可使用剩餘參數來替換arguments的使用。
function avg(...args) {
var sum = 0;
for (let value of args) {
sum += value;
}
return sum / args.length;
}
avg(2, 3, 4, 5); // 3.5
複製代碼
不管「剩餘參數操做符」被放置到函數聲明的哪裏,它都會把除了本身以前的全部參數存儲起來。
一般不建議使用該特性,會致使傳參意義不明確
JavaScript 容許以遞歸方式調用函數。遞歸在處理樹形結構(好比瀏覽器 DOM)時很是有用。
function countChars(elm) {
if (elm.nodeType == 3) { // TEXT_NODE 文本節點
return elm.nodeValue.length;
}
var count = 0;
for (var i = 0, child; child = elm.childNodes[i]; i++) {
count += countChars(child);
}
return count;
}
複製代碼
你能夠命名當即調用的函數表達式(IIFE——Immediately Invoked Function Expression),以下所示:
var charsInBody = (function counter(elm) {
if (elm.nodeType == 3) { // 文本節點
return elm.nodeValue.length;
}
var count = 0;
for (var i = 0, child; child = elm.childNodes[i]; i++) {
count += counter(child);
}
return count;
})(document.body);
複製代碼
如上所提供的函數表達式的名稱的做用域僅僅是該函數自身。這容許引擎去作更多的優化,而且這種實現更可讀、友好。
讓咱們來定義一我的名對象,這個對象包括人的姓和名兩個域(field)。名字的表示有兩種方法:「名 姓(First Last)」或「姓, 名(Last, First)」。
function makePerson(first, last) {
return {
first: first,
last: last
}
}
function personFullName(person) {
return person.first + ' ' + person.last;
}
function personFullNameReversed(person) {
return person.last + ', ' + person.first
}
s = makePerson("Simon", "Willison");
personFullName(s); // Simon Willison
personFullNameReversed(s); // Willison, Simon
複製代碼
上面的寫法雖然能夠知足要求,可是看起來很麻煩,由於須要在全局命名空間中寫不少函數。既然函數自己就是對象,若是須要使一個函數隸屬於一個對象,那麼不可貴到:
function makePerson(first, last) {
return {
first: first,
last: last,
fullName: function() {
return this.first + ' ' + this.last;
},
fullNameReversed: function() {
return this.last + ', ' + this.first;
}
}
}
s = makePerson("Simon", "Willison");
s.fullName(); // Simon Willison
s.fullNameReversed(); // Willison, Simon
複製代碼
當使用函數時,函數內this
指代當前的對象,也就是調用了函數的對象。若是在一個對象上使用點或者方括號來訪問屬性或方法,這個對象就成了 this
。若是並無使用「點」運算符調用某個對象,那麼 this
將指向全局對象(global object)。這是一個常常出錯的地方。例如:
s = makePerson("Simon", "Willison");
var fullName = s.fullName;
fullName(); // undefined undefined
複製代碼
下面使用關鍵字 this
改進已有的 makePerson
函數:
function Person(first, last) {
this.first = first;
this.last = last;
this.fullName = function() {
return this.first + ' ' + this.last;
}
this.fullNameReversed = function() {
return this.last + ', ' + this.first;
}
}
var s = new Person("Simon", "Willison");
複製代碼
new
關鍵字將生成的 this
對象返回給調用方,而被 new
調用的函數稱爲構造函數。習慣的作法是將這些函數的首字母大寫,這樣用 new
調用他們的時候就容易識別了。
下面是一個 new
方法的簡單實現:
function trivialNew(constructor, ...args) {
var o = {}; // 建立一個對象
constructor.apply(o, args);
return o;
}
複製代碼
這並非
new
的完整實現,由於它沒有建立原型(prototype)鏈。
每次咱們建立一個 Person 對象的時候,咱們都在其中建立了兩個新的函數對象,下面使用原型鏈進行優化。
Person.prototype
是一個能夠被Person
的全部實例共享的對象。它是一個名叫原型鏈(prototype chain)的查詢鏈的一部分:當你試圖訪問一個 Person
沒有定義的屬性時,解釋器會首先檢查這個 Person.prototype
來判斷是否存在這樣一個屬性。因此,任何分配給 Person.prototype
的東西對經過 this
對象構造的實例都是可用的。
function Person(first, last) {
this.first = first;
this.last = last;
}
Person.prototype.fullName = function() {
return this.first + ' ' + this.last;
}
Person.prototype.fullNameReversed = function() {
return this.last + ', ' + this.first;
}
複製代碼
JavaScript 容許你在程序中的任什麼時候候修改原型(prototype)中的一些東西,也就是說你能夠在運行時(runtime)給已存在的對象添加額外的方法
關於原型鏈更多內容,可參見繼承與原型鏈
最後附上對象及其衍生類型的一些判斷方式
判斷數組
Array.isArray()
[] instanceof Array
判斷是否在Array的原型鏈上[].constructor === Array
經過其構造函數判斷Object.prototype.toString.call([])
判斷值是否爲'[object Array]'const arr = [1, 2, 3];
Array.isArray(arr); // true
arr instanceof Array; // true
arr.constructor === Array; // true
Object.prototype.toString.call(arr); // "[object Array]"
複製代碼
判斷對象
{} instanceof Object
判斷是否在Object的原型鏈上{}.constructor === Object
經過其構造函數判斷Object.prototype.toString.call({}), 值爲
'[object Object]'const obj = {};
obj instanceof Object; // true
obj.constructor === Object; // true
Object.prototype.toString.call(obj); // "[object Object]"
複製代碼
判斷函數
func typeof function
func instanceof Function
判斷是否在Function
的原型鏈上func.constructor === Function
經過構造函數判斷Object.prototype.toString.call(func)
值爲 "[object Function]"function func() {}
typeof(func); // function
func instanceof Function; // true
func.constructor === Function; // true
Object.prototype.toString.call(func); // "[object Function]"
複製代碼
相關係列: 從零開始的前端築基之旅(超級精細,持續更新~)
參考文檔:
從新介紹javascript:developer.mozilla.org/zh-CN/docs/…
JavaScript 數據類型和數據結構: developer.mozilla.org/zh-CN/docs/…