詞法結構
類型、值和變量
詞法結構
- JS是一門高端的、動態的、弱類型的編程語言,非常適合面向對象和函數式的編程風格
- 火狐瀏覽器下使用Firebug進行調試,F12用來喚醒/關閉Firebug操作面板,Cmd+Shift+J用來喚醒錯誤控制臺
- JS程序是用Unicode字符集編寫的,<strong>區分大小寫</strong>
類型、值和變量
- 在編程語言中,能夠表示并操作的值的類型稱作數據類型。JS的數據類型分為兩類:
1.原始類型(primitive type)
數字、字符串、布爾值、null(空)、undefined(未定義)
2.對象類型(object type)
對象、數組、函數、日期類、正則類、錯誤類
- JS有自己的內存管理機制,可以自動對內存進行垃圾回收
- 從技術上講,只有JS對象才能擁有方法,然而數字、字符串和布爾值也可以擁有自己的方法(原理將在后文講述)。在JS中只有null和undefined是無法擁有方法的值
- JS類型可分為原始類型和對象類型,也可以分為擁有方法的類型和不能擁有方法的類型,同樣可分為可變類型(mutable)和不可變類型(immutable)。可變類型的值是可以修改的,對象和數組屬于可變類型;數字、布爾值、null和undefined屬于不可變類型。字符串可以看成由字符組成的數組,然而在JS中,字符串數不可變的:可以訪問字符串任意位置的文本,但JS并未提供修改已知字符串的文本內容的方法
- JS可自由的進行數據類型轉換。
- JS變量是無類型的(untyped),變量可以被賦予任何類型的值,同樣一個變量也可以重新賦予不同類型的值,使用var關鍵字來聲明declare變量
- JS采用詞法作用域(lexical scoping),不在任何函數內聲明的變量稱作全局變量(global variable),它在JS程序中的任何地方都是可見的;在函數內聲明的變量具有函數作用域(function scope),并且只在函數內可見。
- JS不區分整數值和浮點數值,在JS中所有數字軍用浮點數值表示。JS采用IEEE 754標準定義的64位浮點格式表示數字(雙精度double)。整數的范圍(-2的53次方到2的53次方),需要注意的是,JS實際的操作(比如數組索引、位操作符)則是基于32位整數
- JS支持十進制和十六進制,但有些不支持八進制(ECMAScript標準不支持),因此最好不要使用以0位前綴的整型直接量。
- JS中的算術運算,除了基本運算+、-、*、/、%之外,JS還支持更加復雜的算術運算,這些復雜運算通過作為Math對象的屬性定義的函數和常量來實現。
Math.pow(2,53) // =>9007199254740992:2的53次冪
Math.round(.6) // =>1.0:四舍五入
Math.ceil(.6) // =>1.0:向上取整
Math.floor(.6) //=>0: 向下取整
Math.abs(-5) //=>5:求絕對值
Math.max(x,y,z) // 返回最大值
Math..min(x,y,z) // 返回最小值
Math.random() //生成一個大于0小于1.0的偽隨機數
Math.PI//π:圓周率
Math.E // e:自然對數的底數
Math.sqrt(3) // 3的平方根
Math.pow(3,1/3) // 3的立方根
Math.sin(o) // 三角函數:還有Math.cos,Math.atan等
Math.log(10) // 10的自然對數
Math.log(100)/Math.LN10 // 以10為底100的對數
Math.log(512)/Math.LN2 // 以2為底512的對數
Math.exp(3) // e的3次冪
- 算數運算在溢出(overflow)、下溢(underflow)或被0整除時不會報錯。當數字運算結果超過了JS所能表示的數字上限(溢出),結果為一個特殊的無窮大(infinity)值,在JS中以Infinity表示。同樣的,當負數超過了JS所能表示的負數范圍,結果為負無窮大,在JS中以-Infinity表示。它們的加減乘除運算結果還是無窮大值。
- 下溢是當運算結果無限接近于0并比JS所能表示的最小值還小的時候發生的一種情況。這種情況下,JS會返回0.當一個負數發生下溢時,JS返回一個特殊的值“負零”。這個負零幾乎和正常的零完全一樣,JS程序員很少用到負零。
- 被零整除在JS中并不報錯:它只是簡單的返回無窮大(Infinity)或負無窮大(-Infinity)。但有一個例外,零除以零是沒有意義的,這種整除運算結果也是一個非數字(not-a-number)值,用NaN表示。無窮大除以無窮大,給任意負數作開方運算或者算術運算符與不是數字或無法轉換為數字的操作數一起使用時都會返回NaN。
- JS預定義了全局變量Infinity和NaN,用來表示正無窮大和非數字值,在ECMAScript 5中,定義為只讀。
- JS中的非數字值有一點特殊:它和任何值都不相等,包括自身。也就是說沒有辦法通過x==NaN來判斷變量x是否是NaN,相反,應該使用x!=x來判斷,當且僅當x為NaN的時候,表達式的結果才是true。函數isNaN()的作用與此類似,如果參數是NaN或者一個非數字值的時候,返回true。JS中有一個類似的函數isFinite(),在參數不是NaN、Infinity、-Infinity的時候返回true。負零值同樣有些特殊,它和正零值是相等的,這兩個值幾乎是一模一樣的,除了作為除數以外。
var zero = 0; // 正常的零值
var negz = -0; // 負零值
zero === negz; // => true: 正零值和負零值相等
1/zero === 1/negz // =>false: 正無窮大和負無窮大不等
- 二進制浮點數和四舍五入的錯誤
// 數字具有足夠的精度,并可以極其近似于0.1.但事實上,數字不能精確表述帶來了一些問題
var x = .3 -.2;
var y = .2 -.1;
console.log(x); // 0.9999999999998
console.log(y); // 0.1
console.log(x==y); // false
console.log(x==.1); // false
console.log(y==.1); //true
- 字符串string是一組由16位值組成的不可變的有序序列,JS中并沒有單個字符的“字符型”。記住JS中字符串是固定不變的,因此有關字符串的方法比如replace()、toUpperCase()都是返回新字符串,元字符串本身并沒有改變。
- undefined、null、0、-0、NaN、""(空字符串)都會被轉換成false,所有其他值包括所有對象(數組)都會轉換成true
- null和undefined的區別
1.null是JS的關鍵字,表示一個特殊值,常用來描述“空值”,對null執行typeof預算,結果返回
“object”,也就是說,可以將null認為是一個特殊的對象值,含義是“非對象”。但實際上,通常
認為null是它自有類型的唯一一個成員,它可以表示數字、字符串和對象是“無值”的。
2.undefined未定義來表示更深層次的“空值”。它是變量的一種取值,表明變量沒有初始化,如果
要查詢對象屬性或數組元素的值時返回undefined則說明這個屬性或者元素不存在。如果函數沒有
返回值,則返回undefined。引用沒有提供實參的函數形參的值也只會得到undefined。
undefined是預定義的全局變量(它和null不同,它不是關鍵字),它的值就是“未定義”。
在ECMAScript 5中是只讀的。使用typeof云素服得到undefined的類型,返回的是
“undefined”,表明這個值是這個類型的唯一成員。
3.盡管null和undefined是不同的,但它們都表示“值的空缺”,兩者往往是可以互換的。判斷相等
運算符“==”認為兩者是相等的(要使用嚴格相等運算符“===”來區分它們)。在希望是布爾類型的地
方它們的值都是假值,和false類似。null和undefined都不包含任何屬性和方法。實際上,使用
“.”和“[]”來存取這兩個值的成員和方法都會產生一個類型錯誤。如果想將它們賦值給變量或者屬性
,或將它們作為參數傳入函數,最佳選擇是使用null。
- 全局對象(global object)的屬性時全局定義的符號,JS程序可以直接使用。當JS解釋器啟動時(或者web瀏覽器在加載新頁面的時候),它將創建一個新的全局對象,并給它一組定義的初始屬性:
全局屬性,比如undefined、Infinity和NaN
全局函數,比如isNaN()、parseInt()、eval()
構造函數,比如Date()、RegExp()、String()、Object()和Array()
全局對象,比如Math和JSON
- 包裝對象,現在回到前邊遺留的一個問題:數字、字符串和布爾值既然不是對象為什么可以擁有自己的屬性和方法呢?原因是只要引用了字符串s的屬性,JS就會將字符串值通過new String(s)的方式轉換成對象,這個對象繼承了字符串的方法,并被用來處理屬性的引用。一旦引用結束,這個新創建的對象就會銷毀(其實在實現上并不一定創建或銷毀這個臨時對象,然而整個過程看起來是這樣的)。同字符串一樣,數字和布爾值也具有各自的方法:通過Number()、Boolean()構造函數創建一個臨時對象,這些方法的調用均來自于這個臨時對象。null和undefined沒有包裝對象,訪問它們會造成一個類型錯誤
- 看如下代碼,分析其執行結果
var s = "test";
s.len = 4;
var t = s.len;
當運行這段代碼時,t的值是undefined。第二行代碼創建了一個臨時字符串對象,并給其len屬性賦值為4,隨即銷毀這個對象。第三行通過原始的(沒有修改過)字符串值創建一個新字符串對象,嘗試讀取len屬性,這個屬性自然是不存在的,表達式求值結果是undefined。這段代碼說明了在讀取字符串、數字和布爾值的屬性值(或方法)的時候,表現的像對象一樣。但如果你試圖給其屬性賦值,則會忽略這個操作:修改只是發生在臨時對象身上,而這個臨時對象并未繼續保留下來。
- 存取字符串、數字或布爾值的屬性時創建的臨時對象稱作包裝對象,它只是偶爾用來區分字符串值和字符串對象、數字和數值對象以及布爾值和布爾對象。這三者的屬性都是只讀的,不能給他們定義新屬性,因此它們是有別于對象的。可通過String()、Number()、Boolean()構造函數來顯示創建包裝對象:
var s = "test", n = 1, b = true; // 一個字符串、數字和布爾值
var S = new String(s); // 一個字符串對象
var N = new Number(n); // 一個數值對象
var B = new Boolean(b); // 一個布爾對象
JS會在必要時將包裝對象轉換成原始值,因此上段代碼中的對象S、N和B常常但又不總是表現的和s、n和b一樣。“==”等于運算符將原始值和其包裝對象視為相等,但“===”全等運算符將它們視為不等。通過typeof運算符可以看到原始值和其包裝對象的不同。
- 不可變的原始值和可變的對象引用:原始值(undefined、null、布爾值、數字和字符串)是不可更改的,任何方法都不可更改。字符串中的所有方法看上去返回了一個修改后的字符串,實際上返回的是一個新的字符串。
- 原始值的比較是值的比較:只有在它們的值相等時它們才相等,比較兩個單獨的字符串,當且僅當它們的長度相等且每個索引的字符都相等時,JS才認為它們相等。
var string1 = "hehe";
var string2 = "hehe";
string1 == string2; // => true
string1 === string2; // => true
- 對象和原始值不同,對象是可變可修改的,對象的比較并非值的比較:即使兩個對象包含同樣的屬性及相同的值,它們也是不相等的。各個索引元素完全相等的兩個數組也是不相等的
// 具有相同屬性的兩個對象
var object1 = {x:1};
var object2 = {x:1};
object1 == object2; // false: 兩個單獨的對象永不相等
object1 === object2; // false: 兩個單獨的對象永不相等
alert(object1 === object2);
// 具有相同元素的兩個數組
var array1 = [1,2];
var array2 = [1,2];
array1 == array2; // false: 兩個單獨的數組永不相等
array1 === array2; // false: 兩個單獨的數組永不相等
我們通常將對象稱為引用類型(reference type),以此來和JS的基本類型區分開來。對象值都是引用,對象的比較均是引用的比較;當且僅當它們引用同一個基對象時,它們才相等。
var a= []; // 頂一個引用空數組的變量a
var b = a; // 變量b引用同一個數組
b[0] = 1; // 通過變量b來修改引用的數組
a[0] // => 1: 變量a也會修改
a === b // => true: a和b引用同一個數組,因此它們相等
上邊代碼中,將對象(數組)賦值給一個變量,僅僅是賦值的引用值:對象本身并沒有復制一次。如果想得到一個對象或者數組的副本,則必須顯示復制對象的每個屬性或數組的每個元素。下面例子則是通過循環來完成數組的復制
var a = ['a', 'b', 'c']; // 待復制的數組
var b = []; // 復制到的目標空數組
for (var i = 0; i < a.length; i++) {
b[i] = a[i];
}
alert(b); // => ['a', 'b', 'c']
同樣的如果相比較兩個單獨的對象或者數組,則必須比較他們的屬性或元素。下邊代碼比較兩個數組的函數
function equalArrays(a, b) {
if (a.length != b.length) return false;
for (var i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
- 類型轉換
10 + "objects" // =>"10 objects" 數字10轉換成字符串
"7" * "4" // =>28: 兩個字符串均轉換成數字
var n = 1 - "x" // =>NaN:字符串“x”無法轉換為數字
n + "objects" // =>"NaN objects": NaN轉換成字符串“NaN”
- JS類型轉換
值 | 轉字符串 | 轉數字 | 轉布爾值 | 轉對象 |
---|---|---|---|---|
undefined | "undefined" | NaN | false | throws TypeErrow |
null | "null" | 0 | false | throws TypeErrow |
true | "true" | 1 | new Boolean(true) | |
false | "false" | 0 | new Boolean(false) | |
''"(空字符串) | 0 | false | new String("") | |
"1.2"(非空,數字) | 1.2 | true | new String("1.2") | |
"one"(非空,非數字) | NaN | true | new String("one") | |
0 | "0" | false | new Number(0) | |
-0 | "0" | false | new Number(-0) | |
NaN | "NaN" | false | new Number(NaN) | |
Infinity | "Infinity" | true | new Number(Infinity) | |
-Infinity | "-Infinity" | true | new Number(-Infinity) | |
1(無窮大,非零) | "1" | true | new Number(1) | |
{}(任意對象) | 下文有解釋 | 下文有解釋 | true | |
[]'(任意數組) | "" | 0 | true | |
[9]'(1個數字元素) | "9" | 9 | true | |
['a']'(其他數組) | 使用join()方法 | NaN | true | |
function(){}(任意函數) | 下文有解釋 | NaN | true |
原始值到對象的的轉換很簡單,原始值通過調用String()、Number()或Boolean()構造函數,轉換為它們各自的包裝對象。null和undefined屬于例外,當將它們用在期望是一個對象的地方都會造成一個類型錯誤(TypeError)異常,而不會執行正常的轉換
- 轉換和相等性
由于JS可以做靈活的類型轉換,因此其“==”相等運算符也隨相等的含義靈活多變
下面的比較結果都是true
null == undefined // 這兩值被認為相等
"0" == 0 // 在比較之前字符串轉換成數字
0 == false // 在比較之前布爾值轉換成數字
"0" == false // 在比較之前字符串和布爾值都轉換成數字
注:"=="等于運算符在判斷兩個值是否相等時做了類型轉換,而"==="恒等運算符在判斷相等時并未做任何類型
轉化
需要注意的是,一個值轉換成另外一個值并不意味著兩個值相等。比如如果在期望使用布爾值的地方使用了undefined,它將轉換成false,但這并不意味著undefined==false
- 顯示類型轉化
最簡單的方法就是使用Boolean()、Number()、String()或Object()函數
除了null或undefined之外的任何值都具有toString()方法,這個方法執行的結果通常是和String()方法返回的結果一致。 - Number類定義的toString()類型,在做數字到字符串的轉換過程中,可以接受表示轉換基數的可選參數,如不指定,默認是十進制。例子如下:
var n = 17;
binary_string = n.toString(2); // 轉換為"10001"
octal_string = n.toString(8); // 轉換為"021"
hex_string = n.toString(16); // 轉換為"0x11"
- 控制小數點位置和有效數字位數的方法
var n = 123456.789;
// toFixed()根據小數點后指定位數將數字轉換成字符串
n.toFixed(0); // "123457"
n.toFixed(2); // "123456.79"
n.toFixed(5); // "123456.78900"
// toExponential()使用指數計數法將數字轉換為指數形式的字符串,其中小數點前只有一位,小數點后的位數
由參數決定
n.toExponential(1); // "1.2e+5"
n.toExponential(3); // "1.235e+5"
// toPrecision()根據指定有效數字位數將數字轉換成字符串
n.toPrecision(4); // "1.235e+5"
n.toPrecision(7); // "123456.8"
n.toPrecision(10); // "123456.7890"
- parseInt()只解析整數,parseFloat()可以解析整數和浮點數。如果字符串前綴是“0x”或者“0X”,parseInt()將其解釋為十六進制數。這兩個函數都會跳過任意數量的前導空格,盡可能解析更多數值字符,并忽略后面的內容。但是如果第一個非空字符串是非法的數字直接量,將最終返回NaN
parseInt("3 blind mice") // 3
parseFloat(" 3.14 meters") // 3.14
parseInt("-12.34") // -12
parseInt("0xFF") // 255
parseInt("0xff") // 255
parseInt("-0xFF") // -255
parseFloat(".1") // 0.1
parseInt("0.1") // 0
parseInt(".1") // NaN 整數不能以“.”開頭
parseFloat("$72.67") // NaN 數字不能以"$"開始
parseInt()可以接收第二個參數,這個參數指定數字轉換的基數
parseInt("11", 2) // 3(1*2+1)
parseInt("ff", 16) // 255(15*16+15)
parseInt("zz", 36) // 1295(35*36+35)
parseInt("077", 8) // 63(7*8+7)
parseInt("077", 10) // 77(7*10+7)
- 對象轉換成原始值
對象到布爾值的轉換很簡單:所有對象(包括數組和函數)都轉換成true。對于包裝對象亦是如此: new Boolean(false)是一個對象而不是原始值,將轉換成true - 所有的對象繼承了兩個轉換方法:第一個是toString(),它的作用是返回一個反映這個對象的字符串
數組類:[1,2,3].toString() // => "1,2,3"
數組類的toString()方法將每個數組元素轉換成一個字符串,并在元素之間添加逗號后合并成結果字符串
函數類:(function(x) { f(x); }).toString() // =>"function(x) {\n f(x); \n}"
函數類的toString()方法返回這個函數的實現定義的表示方式,轉換成JS源代碼字符串
日期類:new Date(2010,0,1).toString() // =>"Fri Jan 01 2010 00:00:00 GMT-0800(PST)"
日期類的toString()方法返回一個可讀的日期和時間字符串
RegExp類:/\d+/g.toString() // => "/\\d+/g"
RegExp類的t0String()方法將RegExp對象轉換成表示正則表達式直接量的字符串
- 另一個轉換對象的函數式valueOf()。如果存在任意原始值,它就默認將對象轉換成它的原始值。日期類的valueOf()方法返回它的一個內部表示:1970年1月1日以來的毫秒數
var d = new Date(2010, 0, 1); // 2010年1月1日(太平洋時間)
d.valueOf() // => 1262332800000
- JS中對象到字符串的轉換經過了以下過程
(1)如果對象具有toString()方法,則調用這個方法。如果它返回一個原始值,JS將這個值轉換成字符串(如果本身不是字符串的話),并返回這個字符串結果
(2)如果對象沒有toString()方法,或者這個方法并不返回一個原始值,那么JS會調用valueOf()方法。如果存在這個方法,則JS調用它。如果返回值是原始值,JS將這個值轉換成字符串(如果本身不是字符串的話),并返回這個字符串結果。
(3)否則JS無法從toString()或valueOf()獲得一個原始值,因此這時它將拋出一個錯誤類型異常 - JS中對象到數字的轉換過程,JS做了同樣的事情,只是它會首先嘗試使用valueOf()方法
(1)如果對象具有valueOf()方法,后者返回一個原始值,則JS將這個原始值轉換成數字(如果需要的話)返回這個數字
(2)否則,如果對象具有toString()方法,后者返回一個原始值,則JS將其轉換并返回
(3)否則,JS跑出一個錯誤類型異常
對象轉換為數字的細節揭示了為什么空數組會被轉為數字0以及為什么具有單個元素的數組同樣會轉換為一個數字。數組繼承了默認的valueOf()方法,這個方法返回一個對象而不是一個原始值,因此,數組到數字的轉換則調用了toString()方法,空數組轉換成空字符串,空字符串轉換成數字0 - 聲明提前:JS的函數作用域是指在函數內聲明的所有變量在函數體內都是可見的。有意思的是,這意味著變量在聲明之前甚至已經可用。JS的這個特性被非正式的稱為聲明提前,即JS函數里聲明的所有變量都被“提前”至函數體的頂部
- 作為屬性的變量。當聲明一個JS全局變量時,實際上是定義了全局對象的一個屬性。當使用var聲明一個變量時,創建的這個屬性是不可配置的,也就是說這個變量無法通過delete運算符刪除。如果沒有使用嚴格模式并給一個未聲明的變量賦值的話,JS會自動創建一個全局變量,以這種方式創建的變量是全局對象的正常的可配置屬性,并可以刪除它們:
var truevar = 1; // 聲明一個不可刪除的全局變量
fakevar = 2; // 創建全局對象的一個可刪除的屬性
this.fakevar2 = 3; // 同上
delete truevar; // =>false:變量并沒有被刪除
delete fakevar; // =>true:變量被刪除
delete this.fakevar2; // =>true:變量被刪除