朝花夕拾,從新介紹javascript類型

任何編程語言都不可缺乏的組成部分——「類型」
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, -InfinityNaN則返回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_VALUENumber.MIN_VALUE

另外在 ECMAScript 6 中,你也能夠經過 Number.isSafeInteger() 方法還有 Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER 來檢查值是否在雙精度浮點數的取值範圍內。 超出這個範圍,JavaScript 中的數字再也不安全了,也就是隻有 second mathematical interger 能夠在 JavaScript 數字類型中正確表現。

BigInt 類型

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().

布爾類型

布爾表示一個邏輯實體,能夠有兩個值:truefalse

根據具體須要,JavaScript 按照以下規則將變量轉換成布爾類型:

  1. false0、空字符串("")、NaNnullundefined 被轉換爲 false
  2. 全部其餘值被轉換爲 true

小提示:在作雙等==邏輯時,2==true 是會返回false的,由於boolean類型被轉換爲數字1,而後再作比較,詳情移步初學者不容錯過的雙等(==)小知識

Undefined 類型

一個沒有被賦值的變量會有個默認值 undefined。undefined是一個不能被配置(non-configurable),不能被重寫(non-writable)的屬性。

  • 一個沒有被賦值的變量的類型是undefined。
  • 一個函數若是沒有使用return語句指定返回值,就會返回一個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 類型只有一個值: null。它是 JavaScript 基本類型 之一

null 是一個字面量,特指對象的值未設置。把 null 做爲還沒有建立的對象,也許更好理解。

注意:typeof null == "object"

null 常在返回類型應是一個對象但沒有關聯的值的地方使用。

// foo 不存在,它歷來沒有被定義過或者是初始化過:
foo;
"ReferenceError: foo is not defined"

// foo 如今已是知存在的,可是它沒有類型或者是值:
var foo = null; 
foo;
null
複製代碼

nullundefined 的不一樣點:

當檢測 nullundefined 時,注意相等(==)與全等(===)兩個操做符的區別 ,前者會執行類型轉換:

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()」 的返回數組裏。
  • 這個屬性能夠經過建立時的原始 symbol 值訪問到,或者經過遍歷 「Object.getOwnPropertySymbols()」 返回的數組。
  • 經過保存在變量 myPrivateMethod的值能夠訪問到對象屬性。
  • 當使用 JSON.stringify() 時,以 symbol 值做爲鍵的屬性會被徹底忽略:

typeof運算符能幫助你識別 symbol 類型

typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
複製代碼

方法:Symbol.for(key)

會根據給定的鍵 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;
}
複製代碼
  • 一個 JavaScript 函數能夠包含 0 個或多個已命名的變量。
  • 函數體中的表達式數量也沒有限制。你能夠聲明函數本身的局部變量。
  • 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/…

相關文章
相關標籤/搜索