主要結合最近的實際開發經驗,以及著名的紅寶書,在加上本身的理解,若有錯誤或補充,可在評論中指出,歡迎討論。
本文主要介紹js變量中一些不爲人知或者容易混淆的點,對常規信息採用略過形式,
因爲ES5在各大瀏覽器中的實現標準略有差別,故而測試環境主要採用最爲經常使用的Chrome,版本信息以下
相關信息:java
數據類型共分爲兩種,簡單數據類型(也叫作基本數據類型)和複雜數據類型,前者主要包括:Number、String、Boolean以及並不經常使用的Undefined和Null,後者則只有Object。
堆棧問題:基本數據的保存在棧內存中,而複雜數據也就是引用類型則保存在堆內存中,在對前者複製時將會開闢一個新的內存,而對後者的複製僅僅只是複製了堆內存的指針,而對於這個存儲了指針的值則是存儲在了棧內存中。設計模式
這裏沒有對指針作過多的解釋,具體定義能夠查看c或者java中對指針的介紹。
不過在JavaScript中也能夠認爲,萬物基於Object,也是本文的重點介紹對象。
至於檢測相關類型,簡單歸納就是:基本數據類型用typeof,複雜數據類型用instanceof,這二者將會在後面頻繁見到,這裏不作過多介紹。數組
Number在ES中分爲整數與浮點數,也可以使用八進制(0)或十六進制(0x)等,若是小數點後爲0,則默認解析爲整數型。
因爲內存限制,Number存在一個極限,若是運算結果超過了這個極限,數值就會變爲無窮,即Infinity(使用isFinite()判斷),在大多數瀏覽器中規則:瀏覽器
數值 | 調用方式 | |
---|---|---|
最大值 | 1.7976931348623157e+308 | Number.MAX_VALUE |
最小值 | 5e-324 | Number.MAX_VALUE |
正無窮 | Infinity | Number.NEGATIVE_INFINITY |
負無窮 | -Infinity | Number.POSITIVE_INFINITY |
此處須要介紹一個特殊的數值NaN(Not a NUmber),即非數值(沒錯,他叫非數值,倒是一個特殊的數值),他一般用來表示,本來要返回一個數值,可是沒有返回數值的狀況,不會報錯,常見狀況有:數據結構
最特殊的點是,他不等於任何值,包括他本身,NaN == NaN //false
因此爲了斷定NaN數值,ES定義了isNaN()app
isNaN(NaN); //true isNaN(10); //false isNaN('10'); //false isNaN('blue'); //true,沒法轉化爲數值 isNaN(true); //true,轉換爲1,
那麼問題又來了,爲何字符串返回也是true了呢?
上面有寫到,NaN是在本來要返回一個數值卻不能返回數值的狀況下返回的特殊值,在此處將字符串轉換爲Number過程當中,沒法轉化,故而返回NaN。
關於將非數值轉換爲數值的方法:Number(),parseInt(),parseFloat(),
這裏略過常見的轉換規則,下面寫出幾個較爲少見的規則:函數
Number('10abc') //NaN parseInt('10ab20c') //10,就近原則, parseInt('1azzz2b',16) //26 parseFloat('0xA2') //0
在String中,'和"沒有區別,可是要先後匹配,也能夠包含一些相似n,r,'的特殊字符字面量。
對於內存而言,字符串實際上是不可變的,對一個字符串進行的更改其實是進行了銷燬再重建的流程。
講一個值轉換爲字符串,用的最多的就是toString(),能夠傳參改變進制,null和undefined沒有這個方法,爲了解決這個問題,ES定義了轉型函數String(),能夠將任何類型轉換爲字符串。測試
var num=10; num.toString(2); //'1010' String(null); //'null' String(undefined); //'undefined;
在ES中,不少對字符串操做的方法,一樣也能對Array操做,並且用法上一般都是相同的,如concat,splice,slice等,甚至對一個字符串使用下標同樣可以訪問對應的字符,同時,字符串也有一些訪問指定字符的方法this
var str='hello world'; str[2] //'l'; str.charAt(1); //'e' str.charCodeAt(1) //'101'返回的是字符編碼,
因爲字符串相關方法其實重合度挺高的,若是一一介紹過去本文會顯得斑駁,因此這裏再也不用大篇幅去描述和貼代碼,如下使用關鍵字+核心描述的形式介紹,編碼
此處略過一些你們都熟用的方法如concat,splice,indexof,split等
複雜數據類型只有一種,那就是object,可是由其派生的類型則有許多,例如:Function、Array等,這些都是在開發中經常使用的數據結構,其實,萬物基於Object,
String,Number這些基本的數據類型,實際上也是一個funciton,這一點,當你在實例化一個字符串的時候就能發現
var str1=new String('dutny'); //{'dutny'} var str2='dutny'; //'dutny' str1==str2; //true str1===str2; //false
如上也是能夠實例化字符串的,而且在非全等條件下兩種聲明方式獲得的字符是相等的,而在全等條件下則是表示爲false,這是由於非全等條件下的比較,是通過轉換後的比較,即
12=='12'; //true 12==='12' //false
實際上在上述實例化過程後,str1表現爲一個object,而str2表現爲一個String,由此在非全等條件下類型轉換後的表現形式是相等的,即兩個都是'dutny',
typeof str1; //object typeof str2 //string
這裏有點扯遠了,綜合上述,乍一看,彷佛沒有經過new操做符實例化的str2沒有表現爲對象,實際上並不是如此,由於在js中,new操做符實例化的變量會爲變量在堆內存開闢一塊新的內存,而直接賦值則直接將變量保存在棧內存,故而str1表現爲{'dutny'},而str2表現爲'dutny'。
str1.__proto__===String.prototype; //true str2.__proto__===String.prototype //true
這也就解釋了,當你使用了直接賦值的方法,對多個變量直接賦值爲同一個內容物,實際上這幾個被直接賦值的變量是指向爲同一塊內存,由於你沒有new出一塊新的內存,雖然如此,但在開發中依舊不建議是用new操做符爲變量賦值,一則是在實際開發中並不會出現同一個內容物屢次賦值的重複性工做,二則是對內存的佔用較大,即便js中有垃圾回收也難保不會出現問題。
扯了上面那麼多,不過只是證實了String實際上是objec的實現,其實仍是那句話,在js中,萬物基於Object,甚至連null也是基於Object。
typeof null //object
其實對於萬物基於object這個說法,目前並非一個官方的說法,或者說沒有權威性的官方說明,只是各人對於各人的理解不一樣,例如說字符串的對象屬性能夠說成是原型設計模式而共享的來自構造函數的原型屬性,可能對於我來講,這個說法或多或少仍是受到了java的影響,因此比較偏向這個方向,你們不用刻意的去追求哪個說法。
回到正題,簡述了一下object,首要即是介紹Function了,因爲堆棧內存的特性,因此實例化一個函數的時候,其實能夠將函數名看成這個指針,也就可以理解函數的重載問題,以及函數也能夠賦值。
接下來講到function的內部屬性arguments和this,其實函數對於傳參並無嚴格的規定,你能夠違反函數定義的參數數量,傳遞超出數量的參數,也能夠少傳.
function say(){ console.log('i am dutny') }; say('tom'); //i am dutny
在這個基礎上,若是咱們超量了,那麼怎麼在函數中訪問超出的那個變量呢?這就要引入arguments概念.
function say(){ console.log('i am '+arguments[0]) }; say('dutny'); //i am dutny
能夠看到,arguments也是按照下標順序訪問,初次以外,arguments還有callee屬性,它指向所在函數的自己,有了這個屬性能夠解決不少問題,例如在當即執行的匿名函數能夠進行遞歸或其餘調用本身的操做,在函數名字發生改變的狀況下依舊可以正確的調用本身。除此以外,函數內部還定義了caller屬性,這個屬性保存着調用當前函數的函數的引用,在全局做用域中則是null,他是函數自己的屬性,能夠經過函數名調用,再結合上述的callee使用。
function say(){ console.log(arguments.callee.caller); } function person(){ let name='dutny'; say(); } person(); //顯示person的源代碼,
再說this,這是一個比較複雜的點,各類詳解博客也解釋的很是詳細,通俗點說,他指向調用當前函數的做用域。
var name='dutny_a'; function say(){ console.log(this.name); }; var sco={name:'dutny_b'}; sco.say=say; say(); //dutny_a sco.say(); //dutny_b
從上能夠看出:在全局做用域環境下調用say,this指向的是全局做用域,在特定的局部做用域下調用say,this指向的則是局部做用於。
更多this相關的詳細信息有挺多博客詳盡的描述了,這裏再也不贅述,有興趣的朋友能夠本身去看看博客或者相關書籍
說道this,就要引入兩個由ES定義的兩個非繼承而來的函數的方法:apply()和call(),這兩個方法都是爲了去指定函數調用的做用域而出現的,不一樣的是,在使用call()時,須要一一指定須要傳入的參數,而apply()則能夠選擇傳入參數數組或是arguments。
var name='dutny_a'; var _this=this; function say(age){ console.log('name:'+this.name+',age:'+age); }; function reSay(age){ var sco={name:'dutny_b'}; say.apply(sco,arguments); // say.call(_this,age) // name:dutny_a,age:25 } reSay(25); //name:dutny_b,age:25
其實除了上述兩個方法,ES還定義了bind()方法,也是函數的方法,只有一個參數,指定當前調用函數的做用域,這裏不作贅述。
再說一說Array,
方法 | 做用 | 方法 | 做用 | |
---|---|---|---|---|
棧方法 | push() | 尾部推入 | pop() | 尾部彈出 |
隊列方法 | push() | 尾部推入 | shift() | 頭部彈出 |
位置方法 | indexOf() | 從頭查找 | lastIndexOf() | 從尾查找 |
歸併方法 | reduce() | 從頭歸併 | reduceRight() | 從尾歸併 |
其餘 | unshift() | 頭部推入 |
還有經常使用的迭代方法,每一個迭代方法接受兩個參數,在每一項上運行的函數和運行該函數的做用域(可選),傳入這些方法的函數接受三個參數:數組項的值、該項在數組中的位置和數組自己:
除此以外,還要注意的一點是,數組的排序方法sort(),默認是按照字符串比較排序,因此會出現以下狀況:
var arr=[24,1,3]; console.log(arr.sort()); //[1,24,3]
因爲在字符串的比較中,'24'中的'2'是小於'3'的,因此出現瞭如上狀況,若是想要避免這種狀況,sort()能夠接受一個比較函數做爲參數:
var arr=[24,1,3]; function compare(value1,value2){ return value2-value1; }; console.log(arr.sort(compare)); //[24, 3, 1]
若是想要反序能夠。使用reverse()反轉數組。
以上差很少就是這篇博客的內容了,原本重點在object這邊,結果發現隔天再寫的時候有點沒有思路了,因此暫時就寫這麼多吧,若是有補充或者糾錯的能夠在評論區討論,