【讀書筆記】《JavaScript權威指南》第3章類型、值和變量

概念(瞭解)

  計算機程序的運行需要對值(value)進行操作。
  在編程語言中,能夠表示並操作的值的類型稱做數據類型(type),編程語言最基本的特性就是能夠支持多種數據類型。
  當程序需要將值保存起來以備將來使用時,便將其賦值給(將值“保存”到)一個變量(variable)。變量是一個值的符號名稱,可以通過名稱來獲得對值的引用。

js數據類型“一覽衆山小”(掌握)

  本書在此章節開頭寫了一個很好的概括,我看過許多博文和視頻,他們都沒有講得如此的清楚,大多數人都是把重心放在了api上面,然而作者在這裏並沒有講api,真正的講了數據類型,讓我對js有了一個整體的認知,在我學習python等其他語言時,我發現它們的數據設計都是大同小異的,讀了這篇文章真的是讓我更容易學習計算機語言,這個概括在後面每一章開頭都不會重現的,有許多人看着冗長就直接跳過了,非常可惜。因此本段不會做太多的刪減。

原始類型

  JavaScript的數據類型分爲兩類:原始類型(primitive type)和對象類型(object type)。JavaScript中的原始類型包括數字、字符串和布爾值。
  JavaScript原始類型中有兩個特殊的原始值:null(空)和undefined(未定義),它們不是數字、字符串和布爾值。它們通常分別代表了各自特殊類型的唯一的成員。

對象類型

  javaScript中除了數字、字符串、布爾值、nu11和undefined之外的就是對象了。對象(object)是屬性(property)的集合,每個屬性都由“名/值對”(值可以是原始值,比如數字、字符串,也可以是對象)構成。

數組

  普通的JavaScript對象是“命名值”的無序集合。JavaScript同樣定義了一種特殊對象一一數組(array),表示帶編號的值的有序集合。JavaScript爲數組定義了專用的語法,使數組擁有一些和普通對象不同的特有行爲特性。

函數

  JavaScript還定義了另一種特殊對象一一函數。函數是具有與它相關聯的可執行代碼的對象,通過調用函數來運行可執行代碼,並返回運算結果。和數組一樣,函數的行爲特徵和其他對象都不一樣。JavaScript爲使用函數定義了專用語法。對於JavaScript函數來講,最重要的是,它們都是真值,並且JavaScript可以將它們當做普通對象來對待。

類對象

  如果函數用來初始化(使用new運算符)一個新建的對象,我們稱之爲構造函數(constructor)。每個構造函數定義了一一類(class)對象一一由構造函數初始化的對象組成的集合。類可以看做是對象類型的子類型。除了數組(Array)類和函數(Function)類之外,JavaScript語言核心定義了其他三種有用的類。日期(Date)類定義了代表日期的對象。正則(RegExp)類定義了表示正則表達式(一種強大的模式匹配工具,在第10章會講到)的對象。錯誤(Error)類定義了那些表示JavaScript程序中運行時錯誤和語法錯誤的對象。可以通過定義自己的構造函數來定義需要的類。

內存管理

  JavaScript解釋器有自己的內存管理機制,可以自動對內存進行垃圾回收(garbage collection)。這意味着程序可以按需創建對象,程序員則不必擔心這些對象的銷燬和內存回收。當不再有任何引用指向一個對象,解釋器就會知道這個對象沒用了,然後自動回收它所佔用的內存資源。

面向對象

  JavaScript是一種面向對象的語言。不嚴格地講,這意味着我們不用全局的定義函數去操作不同類型的值,數據類型本身可以定義方法(method)來使用值。例如,要對數組a中的元素進行排序,不必要將a傳入sort()函數,而是調用a的一個方法sort():

a.sort();//sort(a)的面向對象的版本

  第9章將會講述方法的定義。從技術上講,只有JavaScript對象才能擁有方法。然而,數字、字符串和布爾值也可以擁有自己的方法。在JavaScript中,只有null和undefined是無法擁有方法的值。

可變類型和不可變類型

  JavaScript的類型可以分爲原始類型和對象類型,也可分爲可以擁有方法的類型和不能擁有方法的類型,同樣可分爲可變(mutable)類型和不可變(immutable)類型。可變類型的值是可修改的。對象和數組屬於可變類型:JavaScript程序可以更改對象屬性值和數組元素的值。數字、布爾值、null和undefined屬於不可變類型——比如,修改一個數值的內容本身就說不通。字符串可以看成由字符組成的數組,你可能會認爲它是可變的。然而在JavaScript中,字符串是不可變的:可以訪問字符串任意位置的文本,但JavaScript並未提供修改已知字符串的文本內容的方法。

數據類型轉換

  JavaScript可以自由地進行數據類型轉換。比如,如果在程序期望使用字符串的地方使用了數字,JavaScript會自動將數字轉換爲字符串。如果在期望使用布爾值的地方使用了非布爾值,JavaScript也會進行相應的轉換。JavaScript中靈活的類型轉換規則對“判斷相等”(equality)的定義亦有影響。

變量和作用域

  JavaScript變量是無類型的(untyped),變量可以被賦予任何類型的值,同樣一個變量也可以重新賦予不同類型的值。使用var關鍵字來聲明(declare)變量。JavaScript採用詞法作用域(lexical scoping)。不在任何函數內聲明的變量稱做全局變量(global variable),它在JavaScript程序中的任何地方都是可見的。在函數內聲明的變量具有函數作用域(function scope),並且只在函數內可見。

數字

範圍(瞭解)

  和其他編程語言不同,JavaScript不區分整數值和浮點數值。JavaScript中的所有數字均用浮點數值表示。JavaScript採用IEEE 754標準定義的64位浮點格式表示數字,這意味着它能表示的最大值是+-1.7976931348623157×10308,最小值是+-5×10-324。
  按照JavaScript中的數字格式,能夠表示的整數範圍是從-9007199254740992~
9007 199254740992(即-2^53~2^53),包含邊界值。如果使用了超過此範圍的整數,則無法保證低位數字的精度。然而需要注意的是,JavaScript中實際的操作(比如數組索引,以及第4章講到的位操作符)則是基於32位整數。

整型直接量(瞭解)

  除了十進制的整型直接量,JavaScript同樣能識別十六進制(以16爲基數)值。所謂十六進制的直接量是指以“0x”或“0X”爲前綴,其後跟隨十六進制數串的直接量。下面是十六進制整型直接量的例子:

Oxff//15*16+15=255(十進制)
0XCAFE911

  儘管ECMAScript標準不支持八進制直接量,但JavaScript的某些實現可以允許採用八進制(基數爲8)形式表示整數。八進制直接量以數字0開始,其後跟隨一個由0~7(包括0和7)之間的數字組成的序列,例如:

0377//3*64+7*8+7=255(十進制)

  由於某些JavaScript的實現支持八進制直接量,而有些不支持,因此最好不要使用以0爲前綴的整型直接量,畢竟我們也無法得知當前JavaScript的實現是否支持八進制的解析。
在ECMAScript6的嚴格模式下,八進制直接量是明令禁止的。

浮點型直接量(瞭解)

3.14
2345.789
.333333333333333333
6.02e23//6.02×10^23
1.4738223E-32//1.4738223×10^32

JavaScript中的算術運算(掌握)

  JavaScript程序是使用語言本身提供的算術運算符來進行數字運算的。這些運算符包括加法運算符(+)、減法運算符(一)、乘法運算符(*)、除法運算符(/)和求餘(求整除後的餘數)運算符(%),除了基本的運算符外,JavaScript還支持更加複雜的算術運算,這些複雜運算通過作爲Math對象的屬性定義的函數和常量來實現。

溢出(overflow)

  當數字運算結果超過了JavaScript所能表示的數字上限(溢出),結果爲一個特殊的無窮大(infinity)值,在JavaScript中以Infinity表示。同樣地,當負數的值超過了JavaScript所能表示的負數範圍,結果爲負無窮大,在JavaScript中以-Infinity表示。
無窮大值的行爲特性和我們所期望的是一致的:基於它們的加、減、乘和除運算結果還是無窮大值(當然還保留它們的正負號)。

下溢(underflow)

  下溢(underflow)是當運算結果無限接近於零並比JavaScript能表示的最小值還小的時候發生的一種情形。這種情況下,JavaScript將會返回0。當一個負數發生下溢時,JavaScript返回一個特殊的值“負零”。這個值(負零)幾乎和正常的零完全一樣,JavaScript程序員很少用到負零。

被零整除與NaN

  被零整除在JavaScript並不報錯:它只是簡單的返回無窮大(Infinity)或負無窮大(-Infinity)。但有一個例外,零除以零是沒有意義的,這種整除運算結果也是一個非數字(not-a-number)值,用NaN表示。無窮大除以無窮大、給任意負數作開方運算或者算術運算符與不是數字或無法轉換爲數字的操作數一起使用時都將返回NaN。
  JavaScript中的非數字值有一點特殊:它和任何值都不相等,包括自身。也就是說,沒辦法通過x==NaN來判斷變量x是否是NaN。相反,應當使用x!=x來判斷,當且僅當x爲NaN的時候,表達式的結果才爲true。函數isNaN()的作用與此類似,如果參數是NaN或者是一個非數字值(比如字符串和對象),則返回true。JavaScript中有一個類似的函數isFinite(),在參數不是NaN、Infinity或-Infinity的時候返回true。

負零值

負零值同樣有些特殊,它和正零值是相等的(甚至使用JavaScript的嚴格相等測試來判斷)。這意味着這兩個值幾乎一模一樣,除了作爲除數之外:

var zero=0;//正常的零值
zero === negz; //=>true:正零值和負零值相等
1/zero ===1/negz; //=>false:正無窮大和負無窮大不等

二進制浮點數和四捨五入錯誤(掌握)

  實數有無數個,但JavaScript通過浮點數的形式只能表示其中有限的個數(確切地說是18437736874454810627個)。也就是說,當在JavaScript中使用實數的時候,常常只是真實值的一個近似表示。
  JavaScript採用了IEEE-754浮點數表示法(幾乎所有現代編程語言所採用),這是一種二進制表示法,可以精確地表示分數,比如1/2、1/8和1/1024。遺憾的是,我們常用的分數(特別是在金融計算方面)都是十進制分數1/10、1/100等。二進制浮點數表示法並不能精確表示類似0.1這樣簡單的數字。
  JavaScript中的數字具有足夠的精度,並可以極其近似於0.1。但事實是,數字不能精確表述的確帶來了一些問題。看下這段代碼:

var x=.3-.2; //30美分減去20美分
var y=.2-.1; //20美分減去10美分
x==y; //=>false:兩值不相等!
x==.1; //=>false:.3-.2不等於.1
y==.1; //=>true:.2-.1等於.1

  JavaScript的未來版本或許會支持十進制數字類型以避免這些舍入問題。在這之前你可能更願意使用大整數進行重要的金融計算,例如,要使用整數“分”而不要使用小數“元”進行基於貨幣單位的運算。

日期和時間(瞭解)

  JavaScript語言核心包括Date()構造函數,用來創建表示日期和時間的對象。這些日期對象的方法爲日期計算提供了簡單的API。日期對象不像數字那樣是基本數據類型。詳細細節在後面章節會說到。

文本

UTF-16編碼(瞭解)

  JavaScript採用UTF-16編碼的Unicode字符集,JavaScript字符串是由一組無符號的16位值組成的序列。最常用的Unicode字符都是通過16位的內碼錶示,並代表字符串中的單個字符。
  那些不能表示爲16位的Unicode字符則遵貓UTF-16編碼規則——用兩個16位值組成的一個序列(亦稱做“代理項對”)表示。這意味着一個長度爲2的JavaScript字符串(兩個16位值)有可能表示一個Unicode字符。

var p="π";      //π由16位內碼錶示0x03c0
var e="𝑒;     //𝑒17位內碼錶示0x1d45;
p.length;       //1
e.length;       //2
//在ECMAScript 6中,有一個新的Unicode轉義符,能讓你指定任意的碼位(不用再管是否是16位)
console.log('\u{1d452}'); //𝑒

字符串拆分成數行(掌握)

  在ECMAScript3中,字符串直接量必須寫在一行中,而在ECMAScript5中,字符串直接量可以拆分成數行,每行必須以反斜線()結束,反斜線和行結束符都不算是字符串直接量的內容。如果希望在字符串直接量中另起一行,可以使用轉義字符n(後續會有介紹)。

"two\nlines"//這裏定義了一個顯示爲兩行的字符串
"one\ 
1ong\
line" //用三行代碼定義了顯示爲單行的字符串,只在ECMAScript5中可用

引號嵌套(瞭解)

  需要注意的是,當使用單引號來定界字符串時,需要格外小心英文中的縮寫和所有格寫法,比如can't和O'Reily's。因爲撤號和單引號是同一個字符,所以必須使用反斜線(\)來轉義(轉義符將在下一章講解)所有的撤號。
  當JavaScript代碼和HTML代碼混雜在一起的時候,最好在JavaScript和HTML代碼中各自使用獨立的引號風格。

<button onclick="alert("Thank you")">Click Me</button>

轉義字符(瞭解)

  在JavaScript字符串中,反斜線()有着特殊的用途,反斜線符號後加一個字符,就不再表示它們的字面含義了,比如,n就是一個轉義字符,它表示的是一個換行符。

\o NUL字符(\u0000)
\b 退格符(\u0008)
\t 水平製表符(\u0009)
\n 換行符(\u000A)
\v 垂直製表符(\u000B)
\f 換頁符(\u000C)
\r 回車符(\u000D)
\" 雙引號(\u0022)
\' 撇號或單引號(\u0027)
\\ 反斜線(\u005C)
\xXX 由兩位十六進制數XX指定的Latin-1字符
\uXXXX 由4位十六進制數XXXX指定的Unicode字符

  "\n"這個轉義字符則常與alert()搭配使用。
  如果一定要在document.write()當中使用“\n”,必須包裹在HTML的<PRE>標記裏纔有作用。(一般用<br>)。

布爾值(瞭解)

  任意JavaScript的值都可以轉換爲布爾值。下面這些值會被轉換成false:

undefined 
null
0
NaN
""//空字符串

  所有其他值,包括所有對象(數組)都會轉換成true。false和上面6個可以轉換成false的值有時稱做“假值”(falsy value),其他值稱做“真值”(truthy value)。
  JavaScript期望使用一個布爾值的時候,假值會被當成false,真值會被當成true。

null和undefined(掌握)

null

  對null執行typeof預算,結果返回字符串“object”,也就是說,可以將nu11認爲是一個特殊的對象值,含義是“非對象”。但實際上,通常認爲nu11是它自有類型的唯一一個成員,它可以表示數字、字符串和對象是“無值”的。

undefined

  ECMAScript5中做了修正;undefined在該版本中是隻讀的。實際上,使用“.”和“[0”來存取這兩個值的成員或方法都會產生一個類型錯誤。nul1和undefined都不包含任何屬性和方法。
  你或許認爲undefined是表示系統級的、出乎意料的或類似錯誤的值的空缺,而nu11是表示程序級的、正常的或在意料之中的值的空缺。如果你想將它們賦值給變量或者屬性,或將它們作爲參數傳入函數,最佳選擇是使用null。
  聯想延伸因此Object.prototype._proto_ 是null,而不是undefined

全局對象(掌握)

  當JavaScript解釋器啓動時(或者任何Web瀏覽器加載新頁面的時候),它將創建一個新的全局對象,並給它一組定義的初始屬性,全局對象的屬性是全局定義的符號,JavaScript程序可以直接使用。

  • 全局屬性,比如undefined、Infinity和NaN
  • 全局函數,比如isNaN()、parseInt()和eval()
  • 構造函數,比如Date()、RegExp()、String()、Object()和Array()
  • 全局對象,比如Math和JSON
var global = this;//定義一個引用全局對象的全局變量

包裝對象(掌握)

概念

  存取字符串、數字或布爾值的屬性時創建的臨時對象稱做包裝對象。

讀取屬性或方法

  字符串既然不是對象,爲什麼它會有屬性呢?只要引用了字符串s的屬性,JavaScript就會將字符串值通過調用new String(s)的方式轉換成對象,這個對象繼承了字符串的方法並被用來處理屬性的引用。一旦屬性引用結束,這個新創建的對象就會銷燬(其實在實現上並不一定創建或銷燬這個臨時對象,然而整個過程看起來是這樣)。
  同字符串一樣,數字和布爾值也具有各自的方法:通過Number()和Boolean()構造函數創建一個臨時對象,這些方法的調用均是來自於這個臨時對象。nul1和undefined沒有包裝對象:訪問它們的屬性會造成一個類型錯誤。

設置屬性或方法

  在讀取字符串、數字和布爾值的屬性值(或方法)的時候,表現的像對象一樣。但如果你試圖給其屬性賦值,則會忽略這個操作:修改只是發生在臨時對象身上,而這個臨時對象並未繼續保留下來。

var s="test";//創建一個字符串
s.len=4;//給它設置一個屬性
var t=s.len;//查詢這個屬性undefined

原始值和包裝對象

  需要注意的是,可通過String(),Number()或Boolean()構造函數來顯式創建包裝對象:

vars="test",n=1,b=true;//一個字符串、數字和布爾值
var S=new String(s);//一個字符串對象
var N=new Number(n);//一個數值對象
var B=new Boolean(b);//一個布爾對象

  JavaScript會在必要時將包裝對象轉換成原始值,因此上段代碼中的對象S、N和B常常,但不總是表現的和值s、n和b一樣。“= =”等於運算符將原始值和其包裝對象視爲相等,但“===”全等運算符將它們視爲不等。通過typeof運算符可以看到原始值和其包裝對象的不同。

不可變的原始值和可變的對象引用(掌握)

  JavaScript中的原始值(undefined、null、布爾值、數字和字符串)與對象(包括數組和函數)有着根本區別。原始值是不可更改的:任何方法都無法更改(或“突變”)一個原始值。

值比較

  原始值的比較是值的比較:只有在它們的值相等時它們才相等。對象值都是引用(reference),對象的比較均是引用的比較:當且僅當它們引用同一個基對象時,它們才相等。

類型轉換(掌握)

image

顯式類型轉換

  做顯式類型轉換最簡單的方法就是使用Boolean()、Number()、String()或Object()函數。當不通過new運算符調用這些函數時,它們會作爲類型轉換函數並按照上表所描述的規則做類型轉換:

Number("3") //=>3
String(false)//=>"false"或使用false.toString()
Boolean([])//=>true
Object(3)//=>new Number(3)

  需要注意的是,除了null或undefined之外的任何值都具有tostring()方法,這個方法的執行結果通常和String()方法的返回結果一致。
  如果試圖把null或undefined轉換爲對象,則會像上表所描述的那樣拋出一個類型錯誤(TypeError)。Object()函數在這種情況下不會拋出異常:它僅簡單地返回一個新創建的空對象。

隱式類型轉換

JavaScript中的某些運算符會做隱式的類型轉換。如果“+”運算符的一個操作數是字符串,它將會把另外一個操作數轉換爲字符串。一元“+”運算符將其操作數轉換爲數字。同樣,一元“!”運算符將其操作數轉換爲布爾值並取反。

x+""//等價於String(x)
+x//等價於Number(x).也可以寫成x-0
!!x//.等價於Boolean(x).注意是雙歎號

對象轉換爲原始值

  new Boolean(false)是一個對象而不是原始值,它將轉換爲true。
所有的對象繼承了兩個轉換方法。第一個是toString(),第二個是valueOf()。

[1,2,3].tostring()//=>“1,2,3"
(function(x){f(x);}).tostring()//=>"function(x){\n f(x);\n}"
/\d+/g.toString()//=>"/\\d+/g"
new Date(2010,0,1).toString()//=>"Fri Jan 01 201000:00:00 CMT-0800(PST)"
var d=new Date(2010,0,1);//2010年1月1日(太平洋時間)
d.valueOf()//=>1262332800000

對象到字符串的轉換

  如果對象具有toString()方法,則調用這個方法。如果對象沒有tostring()方法,或者這個方法並不返回一個原始值,那麼JavaScript會調用valueOf()方法。

對象到數字的轉換

  如果對象具有valueof()方法,後者返回一個原始值,則JavaScript將這個原始值轉換爲數字(如果需要的話)並返回這個數字。否則,如果對象具有toString()方法,後者返回一個原始值,則JavaScript將其轉換並返回。
  對象轉換爲數字的細節解釋了爲什麼空數組會被轉換爲數字0以及爲什麼具有單個元素的數組同樣會轉換成一個數字。數組繼承了默認的value0f()方法,這個方法返回一個對象而不是一個原始值,因此,數組到數字的轉換則調用tostring()方法。空數組轉換成爲空字符串,空字符串轉換成爲數字0。含有一個元素的數組轉換爲字符串的結果和這個元素轉換字符串的結果一樣。如果數組只包含一個數字元素,這個數字轉換爲字符串,再轉換回數字。

作爲屬性的變量(掌握)

  當聲明一個JavaScript全局變量時,實際上是定義了全局對象的一個屬性。
  當使用var聲明一個變量時,創建的這個屬性是不可配置的,也就是說這個變量無法通過delete運算符刪除。
  如果你沒有使用嚴格模式並給一個未聲明的變量賦值的話,JavaScript會自動創建一個全局變量。以這種方式創建的變最是全局對象的正常的可配值屬性,並可以刪除它們:

var true
var=1;//聲明一個不可刪除的全局變量
fakevar=2;//創建全局對象的一個可刪除的屬性
this.fakevar2=3;//同上delete truevar//=>false:變量並沒有被刪除
delete fakevar//=>true:變量被刪除
delete this.fakevar2//=>true:變量被刪除

聲明上下文對象

  JavaScript全局變量是全局對象的屬性,這是在ECMAScript規範中強制規定的。對於局部變量則沒有如此規定,但我們可以想象得到,局部變量當做跟函數調用相關的某個對象的屬性。ECMAScript 5規範稱爲“聲明上下文對象”(declarative environment record)。

作用域鏈(掌握)

作用域鏈含義

  如果將一個局部變量看做是自定義實現的對象的屬性的話,那麼可以換個角度來解讀變量作用域。
  每一段JavaScript代碼(全局代碼或函數)都有一個與之關聯的作用域鏈(scope chain)。這個作用域鏈是一個對象列表或者鏈表,這組對象定義了這段代碼
“作用域中”的變量。

變量解析規則

  當JavaScript需要查找變量x的值的時候(這個過程稱做“變量解析”(variable resolution)),它會從鏈中的第一個對象開始查找,如果這個對象有一個名爲x的屬性,則會直接使用這個屬性的值,如果第一個對象中不存在名爲x的屬性,JavaScript會繼續查找鏈上的下一個對象。如果第二個對象依然沒有名爲x的屬性,則會繼續查找下一個對象,以此類推。如果作用域鏈上沒有任何一個對象含有屬性x,那麼就認爲這段代碼的作用域鏈上不存在x,並最終拋出一個引用錯誤(ReferenceError)異常。

作用域鏈組成

  在JavaScript的最頂層代碼中(也就是不包含在任何函數定義內的代碼),作用域鏈由一個全局對象組成。
  在不包含嵌套的函數體內,作用域鏈上有兩個對象,第一個是定義函數參數和局部變量的對象,第二個是全局對象。
  在一個嵌套的函數體內,作用域鏈上至少有三個對象。

函數定義和調用對於作用域鏈

  當定義一個函數時,它實際上保存一個作用域鏈。當調用這個函數時,它創建一個新的對象來存儲它的局部變量,並將這個對象添加至保存的那個作用域鏈上,同時創建一個新的更長的表示函數調用作用域的“鏈”。
  對於嵌套函數來講,事情變得更加有趣,每次調用外部函數時,內部函數又會重新定義一遍。因爲每次調用外部函數的時候,作用域鏈都是不同的。內部函數在每次定義的時候都有微妙的差別一一在每次調用外部函數時,內部函數的代碼都是相同的,而且關聯這段代碼的作用域鏈也不相同。
  作用域鏈的概念對於理解with語句是非常有幫助的,同樣對理解閉包的概念也至關重要。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章