這篇文章,來聊聊 JS 中的數據類型與變量。這是在學習 JS 時最基礎的一類問題,但卻很重要。但願個人分享有幫助到你。html
文章開頭,我先提幾個面試中遇到的問題:前端
好比:如何理解參數的按值傳遞?面試
什麼是暫時性死區?segmentfault
什麼是變量提高?數組
全局變量和 window 的屬性有什麼區別?爲何?安全
... ...微信
這篇文章的風格,在分析知識點的同時,插入一些我經歷過的面試題。異步
在 JS 中,基本數據類型有 6 種,即數值、字符串、布爾值、null、undefined、Symbol。函數
對於基本數據類型,咱們須要明白的是:基本類型在內存中的存儲方式是棧。每個值都是單獨存放,互不影響。學習
基本類型都是按值訪問的。在比較時,按值進行比較:
1 === 1 // true
引用類型的值保存在堆中,而引用是保存在棧中。
引用類型按引用訪問。在比較時,也比較的引用:
{} === {} // => false
在 JS 中,參數能夠是任何類型的值,甚至能夠是函數。
這裏要分析的是參數是以哪一種類型傳遞的?引用類型仍是基本類型?
先看一個基礎的例子:
var out_num = 1; function addOne(in_num) { in_num += 1; return in_num; } addOne(out_num); // => 2 out_num // => 1
這個例子中,咱們給 addOne() 函數傳遞一個實參 out_num,這個時 out_num 會傳遞給 in_num,即內部存在着 in_num = out_num
的過程。最後咱們看到的結果是 out_num 並無被函數改變,說明 in_num 和 out_num 是兩個在內存中獨立存放的值,即按值傳遞。
再來看一個變形:
var out_obj = { value: 1 }; function addOne(in_obj) { in_obj.value += 1; return in_obj; } addOne(out_obj); // => { value: 2 } out_obj // => { value: 2 }
問題來了?函數參數不是按值傳遞嗎?爲何這裏函數內部的處理反映到外部了?這是一個超級超級超級的理解誤區。
首先,咱們仍是得擺正觀點,即函數參數是按值傳遞的。那這裏怎麼理解呢?對於引用類型而言,前面說引用類型分爲引用和實際的內存空間。在這裏 out_obj 依舊傳遞給 in_obj,即 in_obj = out_obj
,out_obj 和 in_obj 是兩個引用,它們在內存中的存儲方式是獨立的,可是它們卻指向同一塊內存。
而 in_obj.value = 1
則是直接操做的實際對象。實際對象的改變,會同步到全部引用這個實際對象的引用。
你再來看這個例子,或許就會更清晰一些。
var out_obj = { value: 1 }; function addOne(in_obj) { in_obj = { value: 2 }; return in_obj; } addOne(out_obj); // => { value: 2 } out_obj // => { value: 1 }
你只要抓住一點:對象的賦值就會形成引用指向的實際對象發生改變。
判斷數據類型,一般有三種具體的方法:
一、typeof 操做符
typeof 操做符返回一個表示數據類型的字符串。它存在如下明顯的缺陷:
typeof null // => 'object' typeof [] // => 'object'
這是由於在 JS 語言設計之初遺留的 bug。能夠閱讀這篇文章 http://2ality.com/2013/10/typ... 瞭解更多關於 typeof 處理 null 的問題。
因此 typeof 最好用於判斷一些基本類型,好比數值、字符串、布爾值、undefined、Symbol。
二、instanceof 操做符
typeof 的背後是經過判斷 type tags 來判斷數據類型,而 instanceof 則是經過判斷構造函數的 prototype 是否出如今對象原型鏈上的任何位置。
舉個例子:
{} instanceof Object // => true [] instanceof Array // => true [] instanceof Object // => true
也判斷自定義類型:
function Car(make, model, year) { this.make = make; this.model = model; this.year = year; } var auto = new Car('Honda', 'Accord', 1998); console.log(auto instanceof Car); // => true console.log(auto instanceof Object); // => true
因此,對於字面量形式的基本數據類型,不能經過 instanceof 判斷:
1 instanceof Number // => false Number(1) instanceof Number // => false new Number(1) instanceof Number // => true
三、Object.prototype.toString()
這是目前最爲推薦的一種方法,能夠更加精細且準確的判斷任何數據類型,甚至是 JSON、正則、日期、錯誤等等。在 Lodash 中,其判斷數據類型的核心也是 Object.prototype.toString() 方法。
Object.prototype.toString.call(JSON) // => "[object JSON]"
關於這背後的原理,你能夠閱讀這篇文章 http://www.cnblogs.com/ziyunf...
四、其餘
上面三種是通用的判斷數據類型的方法。面試中還會出現如何判斷一個數組、如何判斷 NaN、如何判斷類數組對象、如何判斷一個空對象等問題。這一類問題比較開放,解決思路一般是抓住判斷數據的核心特色。
舉個例子:判斷類數組對象。
你先要知道 JS 中類數組對象是什麼樣子的,並尋求一個實際的參照物,好比 arguments 就是類數組對象。那麼類數組對象具備的特色是:真值 & 對象 & 具備 length 屬性 & length 爲整數 & length 的範圍大於等於 0,小於等於最大安全正整數(Number.MAX_SAFE_INTEGER)。
在你分析特色的時候,答案就呼之欲出了。【注意全面性】
JS 數據類型的動態性將貫穿整個 JS 的學習,這是 JS 很是重要的特性,不少現象就是由於動態性的存在而成爲 JS 獨有。
正是因爲動態性,JS 的數據類型可能在你毫無察覺的狀況下,就發生了改變,直到運行時報錯。
這裏主要分析下面 8 種轉換規則。
一、if 語句
if 語句中的類型轉換是最多見的。
if (isTrue) { // ... } else {}
在 if 語句中,會自動調用 Boolean() 轉型函數對變量 isTrue 進行轉換。
當 isTrue 的值是 null, undefined, 0, NaN, '' 時,都會轉爲 false。其他值除 false 自己外都會轉爲 true。
二、Number() 轉型函數
咱們重點關注 null undefined 以及字符串在 Number() 下的轉換:
Number(null) // => 0 Number(undefined) // => NaN Number('') // => 0 Number('123') // => 123 Number('123abc') // => NaN
注意和 parseInt() 對比。
三、parseInt()
parseInt(null) // => NaN parseInt(undefined) // => NaN parseInt('') // => NaN parseInt('123') // => 123 parseInt('123abc') // => 123
四、==
這裏須要注意的是:
null == undefined // => true null == 0 // => false undefined == false // => false
null 與 undefined 的相等性是由 ECMA-262 規定的,而且 null 與 undefined 在比較相等性時不能轉換爲其餘任何值。
五、關係操做符
對於兩個字符串的比較,是比較的字符編碼值:
'B' < 'a' // => true
一個數值,另外一個其餘類型,都將轉爲數字進行比較。
兩個布爾值轉爲數值進行比較。
對象,先調用 valueOf(),若不存在該方法,則調用 toString()。
六、加法
加法中特別注意的是,數字和字符串相加,將數字轉爲字符串。
'1' + 2 => // '12' 1 + 2 => // 3
對於對象和布爾值,調用它們的 toString() 方法獲得對應的字符串值,而後進行字符串相加。對於 undefined 和 null 調用 String() 取得字符串 'undeifned' 和 'null'。
{ value: 1 } + true // => "[object Object]true"
七、減法
對於字符串、布爾值、null 或者 undefined,自動調用 Number(),轉換結果若爲 NaN,那麼最終結果爲 NaN。
對於對象,先調用 valueOf(),若是獲得 NaN,結果爲 NaN。若是沒有 valueOf(),則調用 toString()。
八、乘法、除法
對於非數值,都會調用 Number() 轉型函數。
JS 中有三種聲明變量的方式:var, let, const。
var 聲明變量最大的一個特色是存在變量提高。
console.log(a); // undefined var a = 1; console.log(a); // 1
第一個打印結果表示,在聲明變量 a 以前,a 就已經能夠訪問了,只不過並未賦值。這就是變量提高現象。(具體緣由,我放在後面分析做用域的時候來寫)
let 和 const 就不存在這個問題,可是又引入了暫時性死區這樣的概念。
/** * 這上面都屬於變量 a 的暫時性死區 * console.log(a) // => Reference Error */ let a = 1; console.log(a); // => 1
即聲明 a 以前,不可以訪問 a,而直接報錯。
而暫時性死區的出現又引出另一個問題,即 typeof 再也不安全。你能夠參考這篇文章 http://es-discourse.com/t/why...
補充:一個經典面試題
for (var i = 0; i < 4; i++) { setTimeout(function(){ console.log(i); }, i * 1000); }
我先再也不這裏展開分析,我打算放到異步與事件循環機制中去分析。不過這裏將 var 替換成 let 能夠做爲一種解決方案。若是你有興趣,也能夠先去分析。
對於 const,這裏再補充一點,用於加深對基本類型和引用類型的理解。
const a = 1; const b = { value: 1 }; a = 2; // => Error b.value = 2; // => 2 b = { value: 2 }; // => Error
本質上,const 並非保證變量的值不得改動,而是變量指向的內存地址不得改動。
直接經過 var 聲明全局變量,這個全局變量會做爲 window 對象的一個屬性。
var a = 1; window.a // => 1
在這裏提出兩個問題,一是 let 聲明的全局變量會成爲 window 的屬性嗎?二是 var 聲明的全局變量和直接在 window 建立屬性有沒有區別?
先來回答第一問題。let 聲明的全局變量不會成爲 window 的屬性。用什麼來支撐這樣的結論呢?在 ES6 中,對於 let 和 const 聲明的變量從一開始就造成封閉做用域。想一想以前的暫時性死區。
第二個問題,var 聲明的全局變量和直接在 window 建立屬性存在着本質的區別。先看下面的代碼:
var a = 1; window.a // => 1 window.b = 2; delete window.a delete window.b window.a // => 1 window.b // => undefined
咱們能夠看到,直接建立在 window 上的屬性能夠被 delete 刪除,而 var 建立的全局屬性則不會。這是現象,經過現象看本質,兩者本質上的區別在於:
使用 var 聲明的全局變量的 [[configurable]] 數據屬性的值爲 false,不能經過 delete 刪除。而直接在對象上建立的屬性默認 [[configurable]] 的值爲 true,便可以被 delete 刪除。(關於 [[configurable]] 屬性,在後面的文章中分析對象的時候還會提到)
在這篇「數據類型與變量」文章中,分析了 7 個大類。再來回顧一下:
基本類型、引用類型、參數傳遞方式、如何判斷數據類型、數據類型如何轉換、變量提高與暫時性死區、聲明全局變量。
這些不只是校招面試中的高頻考點,也是學習 JS 必不可少的知識點。
Tip1:《JavaScript 高級程序設計》這本書被稱做「前端的聖經」是有緣由的。對於正在準備校園招聘的你,很是有必要!書讀百遍,其義自見。你會發現你在面試中遇到的絕大部分 JS 相關的知識點都能在這本書中找到「答案」!
Tip2:在準備複習的過程當中,注意知識的模塊性與相關性。你得有本身劃分知識模塊的能力,好比今天的「數據類型與變量」模塊。相關性是指,任何的知識都是由聯繫的,好比這裏牽涉到做用域、內存等模塊。
這篇文章會不斷更新,若有出入,也但願你在後臺留言,或者 Email 給我:
swpu.leo@gmail.com
文章首發在 cameraee 微信公衆號
並同步更新至如下平臺:
CSDN https://blog.csdn.net/swpu_leo
Segment fault https://segmentfault.com/u/sw...
掘金 https://juejin.im/user/58bd20...