計算機程序的運行需要對值(value)比如數字3.14或者文本"hello world"進行操作,在編程語言中,能夠表示並操作的值的類型叫做數據類型(type),編程語言最基本的特性就是主持多種數據類型。當程序需要將值保持起來以備將來使用時,便將其賦值給(將值“保存”到)一個變量(variable)。變量是一個值的符號名稱,可以通過名稱獲得對值的引用。變量的工作機制是編程語言的令一個基本特性。本章將參照上節幫助理解本章內容,後續將更深入的講解。
javascript的數據分為兩類:原始類(primitive type)和對象類型(object type)
javascript中的原始類包括數字,字符串,布爾值,本章會有單獨的章節專門講述javascript的數字、字符串、布爾值。javascript還有兩個特殊的原始值,null(空)和Undefined(未定義),他們不是數字、字符串、布爾值。它們分別代表了各自特殊類型的唯一成員。
javascript除了數字、字符串、布爾值、null、undefined之外就是對象了。對象 (object)是屬性(property)的集合。每個屬性都由"名/值對"(值可以是原始值,比如數字,字符串,也可以是對象)構成。其中一個比較特殊的對象(全局對象(global object)會在第五小姐介紹,第六小節將更詳細的描述)
普通的javascript對象是“命名值”的無需集合。javascript同樣定義了一種特殊對象--數組(array),表示帶編號的值的有序集合。javascript為數組定義了專用的語法。使數組擁有一些和普通對象不同的特有的行為屬性。
javascript還定義了一種特殊的對象--函數。函數是具有與它想關聯的可執行代碼的對象,通過調用函數來運行科執行代碼,並返還運算結果。和數組一樣,函數行為特征和其它對象都不一樣。javascript為使用函數定義了專用語法。對javascript函數來講。最重要的是,他們都是真值,並且javascript可以講他們當做普通對象來對待。
如果函數初始化(使用new運算符)一個新建對象,我們稱之為構造函數(constructor)。每個構造函數定義了一類(class)對象--構造函數初始化對象組成的集合。類可以看做對象類型的子類型。除了數組(array)類和函數(function)類之外,javascript還定義了其它三種由用的類。日期(date)定義了代表日期的對象。正則(regExp)定義了正則表達式的對象。錯誤(error)類定義了那行表示javascript程序中運行時錯誤和語法錯誤對象。可以通過定義自己的構造函數來定義需要的類。
javascript解釋器有自己的內存管理機制,可以自動對內存進行垃圾回收(garbagecollection)。這意味著程序程序可以按需創建對象,程序員則不必擔心這些對象的銷毀了內存回收。當不再有任何一個引用指向一個對象,解釋器就知道這個對象沒有用了,然後會自動回收它所占用的內存資源。
javascript是一種面向對象的語言。不嚴格的講,這意味著我們不用全局的定義函數去操作不同類型的值,數據類型本身可以定義方法(method)來使用值,例如要對數組a中的元素進行排序,不必要將a傳入sort()函數,而是調運a的一個方法sort()
a.sort(); //sort(a)面向對象的版本
從技術上來將,只有javascript對象才能擁有方法。然而,數字,字符串,布爾值也擁有自己的方法。在javascript中,只有null和undefined是無法擁有方法的值。
javascript的類型可以分為原始類型和對象類型,可分為可以擁有方法的類型和不能擁有方法的類型。同樣可分為可變(mutable)和不可變(immutable)類型。可變類型的值是可以修改的,對象和數組屬於可變類型:javascript程序可以改變對象的屬性值和數組元素的值。
數字、布爾值、null和undefined屬於不可改變的類型。比如,修改一個數組的內容本身就說不通。字符串可以看做是字符組成的數組,你可以認為它是可以變的。然而在javascript中,字符串是不可變的。可以訪問字符串任意位置的文本,但javascript並未提供修改一直字符串文本內容的方法。
javascript可以自由地進行數據類型轉換。比如,如果在程序期望使用字符串的地方使用了數字,javascript會自動將數字轉換為字符串。如果期望在使用布爾值的地方使用了非布爾值,javascript也會相應的轉換。javascript中對靈活的類型抓換規則對“判斷相等”(equality)
javascript的變量是無類型的(untyped),變量可以被賦予人和類型的值,使用var關鍵字來聲明(declare)變量。javascript采用語法作用域,不在任何函數內聲明的變量稱為全局變量(global variable),它在javascript的程序 中任何地方都是可見的。
1.數字
和其它編程語言不同,javascript不區分整數數值和浮點數數值。javascript中的數值均用浮點數數值來表示。當一個數字直接出現在javascript程序中,我們陳之為數字直接量(numeric literal),javascript支持多種格式的數字直接量。(注意:在任何數字前直接添加負號(-)可以得到它們的負值)但負號是一元求反運算符。,並不是數字直接量語法的組成部分。)
i整數型直接量
javascript中用一個數組序列表示一個十進制的整數
除了十進制的整數直接量,javascript同樣識別十六機制(16)為基數的的值。所謂十六進制是以“0X”或者"0x"為前綴,其後緊跟十六進制數串的直接量。十六進制數值是0-9的數字和a(A)-f(F)之間的字母構成。a-f的字母對於的表述數字10-15下面是十六進制整型直接量的例子
代碼如下:
0xff //15*16+15=255
0xCAFE911
盡管ECMAScript不支持八進制直接量,但javascript的某些實現可以允許采用八進制(基數為8)形式表示整數。八進制直接量以數字0開始,其後跟隨著一個0-7之間數字組成的序列。
代碼如下:
0377 // 3*64 +7*8 +7 =255(十進制)
由於某些javascript的實現支持八進制的之間量,而有些不支持,因此最好不要使用以0為前綴的整數之間量,畢竟我們也無法得知當前javascript的實現是否支持八進制的解析。在ECMAScript6的嚴格模式下,八進制的直接量是明令禁止的。
ii.浮點型直接量
浮點型直接量可以含有小數點,它們采用的是傳統的實數寫法。一個實數由整數部分,小數點和小數部分組成。
此外,還可以使用指數計數法表示浮點型直接量。即在實數後跟字母E或e,後面再跟正負號,其後再加一個整型的指數。這種計數方法表示的數值,是有前面的實數乘以10的指數冪。
可以使用更簡潔的語法來表示
代碼如下:
[digits][.digits][(E|e)[(+|-)]digits]
3.14
2345.455
.33333333333333333
6.02e23 //6.02*10的23次方
1.255454E-23 //1.255454*10的負23次方
iii.javascript中的算術運算
javascript程序是使用語言本省提供的算術運算符來進行數字運算的的。這些運算符包含+ - * /和求余(整除後的余數)運算符%
除了基本的運算符之外,javascript還支持更加復雜的算術運算,這個線復雜的運算通過作為Math對象的屬性定義的函數和常量實現。
代碼如下:
Math.pow(2, 53) //=>9007199254740992 document.write(Math.pow(2,53) )
Math.round(.6) //=>1.0 四捨五入
Math.ceil(.6) //=>1.0向上求整
Math.floor(.6) //=>0.0向下求整
Math.abs(-5) //=>5 求絕對值
Math.max(x, y, z) //返回最大值
Math.min(x, y, z) //返回最小值
Math.random() //生成一個大於0小於1的偽隨機數
Math.PI //圓周率π
Math.E //e:自然對數的底數
Math.sqrt(3) //3的平方根
Math.pow(3, 1 / 3) //3的立方根
Math.sin(0) //三角函數,還有Math.cos,Math.atan等
Math.log(10) //=>2.302585092994046 以10為底的自然對數
Math.log(512) / Math.LN2 //以2為底的512的對數
Math.log(100) / Math.LN10 //以10為底的100的對數
Math.exp(3) //e的三次冪
javascript中的算術運算在溢出(overflow)、下溢(underflow)或被零整除時不會報錯。但數字運算結果超過了javascript中所能表示的數字上線(溢出),結果為一個特殊的無窮大的值(infinty)值,在javascript中以infinty表示。同樣地,當負數的值超過了javascript所能表達的負數范圍,結果為負無窮大,在javascript中以-Infinty表示。無窮大值的行為特性和我們所期望的是一致的:基於它們的加減乘除運算結果是無窮大(保留正負號)
下溢(underflow)是當運算結果無線接近於零並比 javascript能表示的最小值還小的時候發生的一種情形。當一個負數發生下溢時,javascript返回一個特殊的值,“負零”,這個(負零)幾乎和正常的零完全一樣。javascript程序員很少用到負零。
javascript預定義了全局變量Infinaty和NaN,用來表達正無窮大河非數字值,在ECMAScipt3中,這兩個值是可以讀寫的。ECMAScript5修正了這個問題,將他們定義為只讀的。ECMAScipt3中的Number對象定義的屬性值也是只讀的,這裡有一些例子:
代碼如下:
Infinity //將一個可讀/寫的變量初始化為infinty
Number.POSITIVE_INFINITY //同樣的值,只讀
1 / 0 //這也是同樣的值
Number.MAX_VALUE + 1 //計算結果還是Infinity
Number.NEGATIVE_INFINITY //表示了負無窮大
-Infinity
-1/0
-Number.MAX_VALUE -1
NaN //將一個可讀/寫的變量初始化為NaN
Number.NaN //同樣的值,但是只讀
0/0 //計算結果還是NaN
Number.MIN_VALUE/2 //發生下溢。計算結果為0
-Number.MIN_VALUE/2 //負零
-1/Infinity //負零
-0 //負零
javascript中的非數字值有一點特殊,它和人和值都不相等,包括自身。也就是說沒法通過x==NaN來判斷x是否為NaN。相反,應當使用x!=x來判斷,當且僅當x為NaN的時候,表達式的結果為true.函數isNaN()作用與此相似,如果參數是NaN或者是一個非數字值(比如字符串和對象),則返回true。javascript中有一個類似的函數isFinite(),在參數不是NaN、Infinty或-Infinity的時候返回true.
負零值同樣有些特殊,它和正負零是相等的(甚至使用javascript的嚴格相等測試來判斷)、這意味這兩個值幾乎是一模一樣的,除了作為除數之外:
代碼如下:
var zero = 0;
var negz = -0;
zero === negz //=>true 正負零值相等
1/zero === 1/negz //false 正無窮大和負無窮大不等
iiii.二進制浮點數和四捨五入錯誤
實數有無數個,但javascript通過浮點數的形式只能表示有限的個數(確切的說有18 437 736 874 454 810 627個),也就是說,當javascript中使用實數的時候,常常只是真實值的一個近似的表示。
javascript采用了IEEE-754浮點數表示法(幾乎所有的現代編程語言采用)。這是一種二進制表示法,可以精確的表示分數,比如1/2 1/8 和1/1024,遺憾的是,我們常采用的分數,特別是金融計算方面,都是以十進制分數1/10 ,1/100等。二進制表示法並不能表示類似0.1這樣簡單的數字。
javascript中的數字具有足夠的精度。並可以接近0.1.但事實上,數字不能精確表述帶來了一些問題。
代碼如下:
var x = .3 - .2;
var y = .2 - .1;
alert(x == y) //=>false 兩值不相等
x == .1 //=>false .3-.2 不等於.1
y == .1 //=>true .2-.1等於1
由於捨入誤差,0.3和0.2之間的近似差值實際上並不等於0.2和0.1之間的近似差值(在真實模擬環境中,0.3-0.2=0.099 999 999 999 999 98).這個問題不只在javascript中存在,理解這一點十分重要:在任何使用二進制浮點數的編程語言中都會有這個問題。同樣需要注意的是,上述代碼中x和y的值非常接近彼此和最終的正確值。這種計算結果可以勝任大多數的計算任務。這個問題也是只有比較兩個值是否相等的時候才才會出現。
javascript的未來版或許支持十進制數字類型以避免這個問題,在這之前你可能更願意使用大整數進行重要的金融計算。例如,要使用整數“分”,而不要使用小數“元”進行基於貨幣單位的運算。
iiiii.日期和時間
javascript語言核心包含Date()構造函數,原來創建日期和時間的對象,這些日期對象的方法為日期計算提供了簡單的API,日期對象不能像數字那樣是基本數據類型。
代碼如下:
var zhen = new Date(2011, 0, 1); //2011年1月1日
var later = new Date(2011, 0, 1, 17, 10, 30); //同一天
var now = new Date(); //當前的日期和時間
var elapsed = now - zhen; //日期減法。計算時間間隔的毫秒數
later.getFullYear(); //=>2011
later.getMonth(); //=>0 從0開始計數的月份
later.getDate(); //=>1 從1開始計數的天數
later.getDay(); //=>5 得到星期幾。 0代表星期日,5代表星期1
later.getHours() //=>當地時間
later.getUTCHours() //使用UTC表示小時的時間,基於時區。
2.文本
字符串(string)是一組16位值組成的不可變的有序序列,每個字符通常來自於Unicode字符集。javascript通過字符串類型來表示文本。字符串的長度(length)是其所含的16位值的個數。javascript字符串(和其數組)的索引從0開始。空字符串的(empty string)長度為0,javascript中並沒有表示單個字符的“字符型”。要表示一個16位值,只需要將其賦值給字符串變量即可。這個字符串的長度為1。
字符集,內碼和javascript字符串
javascript采用UTF-16編碼的Unicode字符集,javascript字符串是由一組無序號的16位值組成的序列。最常用的Unicode字符都是通過16位的內碼表示,並代表字符串中的單個字符,那行不能表示為16位的Unicode字符則遵循UTF-16編碼規則----用兩個16位值組成一個序列(亦稱為“代理項對”)表示。這意味著一個長度為2的javascript字符串(兩個16位值)可能表示一個Unicode字符。
代碼如下:
var p ="π" ; //π由16位內碼表示0x03c0
var e = "e"; //e由17位內碼表示0x1d452
p.length // =>1 p包含一個16位的值
e.length // =>2 e通過UTF-16編碼後包含兩個值:"\ud835\udc52"
javascript定義的各式字符串操作方法均作用於16位值,而非字符,且不會對代理項對做單獨處理。同樣javascript不會對字符串做標准化的加工。甚至不能保證字符串是合法的UTF-16格式
i字符串直接量
在javascript程序中的字符串直接量,是由單引號或雙引號括起來的字符序列,由單引號定界的字符串中可以包含雙引號,由雙引號定界的字符串中也可以包含單引號。這裡有幾個字符串直接量的例子。
代碼如下:
"" //空字符串,0個字符
'testing'
"3.14"
'name="myform"'
"wouldn't you prefer O'Reily's book?"
ECMAScript3中,字符串直接量必須寫在一行中,而在ECMAScript5中,字符串的直接量可以拆分為數行,每行必須以反斜線(\)結束,反斜線和行結束符都不是字符串直接量的內容。如果希望在一起,則可以使用\n轉義字符。
需要注意的是,當使用單引號定界字符串時,需要格外小心英文中的縮寫和所有格式寫法,英文撇號和單引號是同一個字符,所以必須使用反斜線(\)來轉義。
ii轉義字符
在javascript字符串中,反斜線(\)有著特殊的用途,反斜線後加一個字符,就不再表示他們的字面含義了,比如\n 就是一個轉義字符,它表示一個換行符。
代碼如下:
\o //NUL字符
\b //退格符
\t //水平制表符
\n //換行符
\v //垂直制表符
\f //換頁符
\r //回車符
\" //雙引號
\\ 反斜線
\xXX 由兩位十六進制指定的Latin-1字符
\xXXXX 由四位十六進制XXXX指定的Unicode字符
iii字符串的使用
javascript的內置功能之一就是字符串連接。將運算符+用於字符串,表示字符串連接。例如
代碼如下:
var msg = "hello" + "world"; //生成字符串hello world
要確定一個字符串的長度——其所包含的16位值的個數,可以使用length屬性,比如字符串s的長度。
s.length
除了length屬性,字符串還提供很多可以調用的方法。
代碼如下:
var s = "hello,world";
s.charAt(0); //"h"第一個字符
s.charAt(s.length - 1) //"d"最後一個字符
s.substring(1, 4) //"ell" 2-4個字符
s.slice(1, 4) //ell 同上
s.slice(-3) // 最後出現的3個字符
s.indexOf(l ")//2字符l 第一次出現的位置
s.lastIndexOf("l") //10 字符l最後一次出現的位置
s.indexOf("l",3)//在位置3之後,l字符首次出現的位置
s.split(",") //=> ["hello","world"]分隔成子串
s.replace("h","H")// =>"Hllo,world"全文字符替換
s.toUpperCase() //=>"HELLO,WORLD"
在javascript中,字符串是固定不變的,類似replace()和toUpperCase()方法都返回了新的字符串,原來的字符本身沒有發生變化。
在ECMAScript中,字符可以當做只讀數組,除了使用charAt()方法,也可以使用方括弧來訪問字符串中的單個字符。(16位值)
代碼如下:
s = "hello,world"
s[0] //=>"h"
s[s.length-1] //=>"d"
Foxfire很久之前就支持這樣方法的字符串索引,多數現代浏覽器(IE除外)也緊跟Mozailla的腳步,在ECMAScript成型之前就完成了這一特性
iiii模式匹配
javascript定義了RegExp()構造函數,用來創建表示文本模式匹配的對象,這些模式被稱為“正則表達式”(regular expression),javascript彩陽Perl中的正則表達語法。String和RegExp對象均定義了利用正則表達式進行模式匹配和查找與替換的函數。
RegExp對象並不是語言中的基本數據類型,和Date一樣,它只是一種具有實用API的特殊對象。正則表達式的語法很復雜,API也很豐富。在第10章節會詳細介紹。RegExp是一種強大和常用的文本處理工具,此處只是一個概述。
盡管RegExp並不是語言中的基本數據類型,但是他們依然具有直接量的寫法,可以直接在javascript中使用。在兩條斜線之間的文本構成了一個正則表達式的直接量。第二條斜線之後也可以跟隨一個或多個字母。用來修飾匹配模式的含義。例如:
代碼如下:
/^HTML/ //匹配以HTML開始的字符串
/[1-9][0-9]*/ //匹配一個非零數字,後面是任意個數字
/\bjavascript\b/i/ //匹配單詞javascript,並忽略大小寫
RegExp對象定義了很多有用的方法,字符串同樣具有可以接受RegExp參數的方法。例如:
代碼如下:
var text = "testing:1,2,3"; //文本示例
var pattern = /\d+/g / //匹配所有包含一個或多個數字的實例
pattern.test(text) // =>true:匹配成功
text.search(pattern) //=>9 :首次匹配成功的位置
text.match(pattern) //=> ["1","2","3"]所有匹配組成數組
text.repeat(pattern,"#"); //=>"testing:#,#,#"
text.split(/\D+/); //=>["","1","2","3"]:用非數字字符截取字符串
3.布爾值
布爾值指代真或假,開或關,這個類型只有兩個值,保留字true或false
javascript中的比較語句的結果通常都是布爾值。例如
a==4
這段代碼用來檢測變量的a的值是否等於4.如果等於,則值為true,如果不等值為false
布爾值通常用於javascript的控制語句中,例如javascript中的if/else語句,如果布爾值為true執行第一段邏輯,如果為false執行另一段代碼,例如
代碼如下:
if (a == 4)
b = b + 1;
else
a = a + 1;
任意javascript的值都可以轉化為布爾值,下面這些值都被轉化為false
代碼如下:
undefined
null
0
-0
NaN
""//空字符串
所有其它值,包括所有對象(數組)都會被轉換為true,false和上面6個可以轉化為false的值有時候稱為“假值”,javascript期望使用一個布爾值時,假值會被當做false,真值會被當做true
來看一個例子,加上變量o是一個對象或是null,可以通過一條if語句來檢測o是否是非null值。
if(o!==null)...
不等操作符“!==”將o和null比較,並得出結果為 true或false。可以先忽略這裡的比較語句,null是一個假值,對象是一個真值。
if(o)...
對於第一種情況,只要當o不是null時才會執行if後的代碼,第二種情況的限制沒有那麼嚴格。只有o不是false或任何假值(比如null或unfined)時才執行這個if。
布爾值包含toString()方法,因此可以使用這個方法將字符串轉換為 “true”或"false",但它不包含其他有用的方法,除了這個不重要的API,還有三個重要的布爾值運算符。
&&運算符,||運算符和一元操作符“!”執行了布爾非(NOT)操作,如果真值返回false,假值返回true,比如
代碼如下:
if ((x == 0 && y == 0) || !(z == 0)) {
//x和y都是零或z是非零
}
4.null和undefined
null是javascript語言的關鍵字,它表示一個特殊值“空值”,對於null執行typeof()運算,返回object.也就是說,可以將null認為是一個特殊的對象值,含義是"非對象"。但實際上,通常認為null是它自由類型的唯一一個成員。它可以表示數字,字符串,和對象是“無值”的。大多數編程語言和javascript一樣含有null,你可以對null或者nil很熟。
javascript還有第二個值表示值的空缺。用來表示更深層次的“空值”。它是一種變量的一種取值。表示變量的沒有初始化。如果要查詢對象屬性或數組元素的值是返回undefined則表明這個屬性或者元素不存在。undefined是預定義的全局變量(它和null不一樣,它不是關鍵字),它的值就是未定義。如果使用typeof來測試undefined類型,則返回“undefined”,表明這個值是這個類型的唯一成員。
盡管null和undefined是不同的,但它們都表示“值的空缺”,兩者往往可以互換。判斷相等的運算符“==”認為兩者是相等的(要使用嚴格相等運算符"==="來區分它們)。在希望值是布爾類型的地方它們的值都是假值。和false類似。null和undefined都是不包含任何屬性和方法。實際上,使用"."和"[]"來存取這兩個值的成員或方法,都會產生一個類型錯誤。
你或許認為undefined是表示系統級的,出乎意料的活類似錯誤的值的空缺,而null是表示程序級的,正常或在意料之中的值的空缺,如果你想將它們復制變量或者屬性,或將它們作為參數傳入函數,null是最佳的選擇。
5.全局對象
前幾節討論了javascript的元素類型和原始值。對象類型——對象、數組和函數/但有一類非常重要的對象,不得現在就必須將清楚:全局對象
全局對象(global object)在javascript中有著重要的用途。全局對象的屬性是全局定義的符號。javascript程序可以直接使用。當javascript解釋器啟動時,它將新建一個新的全局對象,並給它一組定義的初始屬性。
全局屬性 比如undefined Infinty和NaN
全局函數 比如isNaN()、parseInt()和eval()
構造函數,比如Date()、RegExp()、String()、Object()和Array()
全局對象,比如Math 和JSON
全局對象的初始屬性並不是保留字,但他們應當當做保留字來對待。
在代碼的最頂級——不在任何函數內的javascript代碼,可以通過javascript關鍵字來引用全局對象。
var global = this; //定義一個引用全局對象的全局變量。
在客戶端javascript中,window對象充當了全局對象,這個全局window對象有一個熟悉window引用其本身,它可以代替this來引用全局對象,window定義了全局核心屬性。但也征對web浏覽器和和互動javascript定義了一部分其他全局屬性。
當初次創建時,全局對象定義了javascript中所有的預定義全局值,這個特殊對象同樣包含了為程序定義的全局值。如果代碼聲明了一個全局變量。這個全局變量就是全局對象的一個屬性。
6.包裝對象
javascript對象是一種復合值:它是屬性或已命名值的集合。通過"."來引用屬性值,當屬性值是一個函數的時候,陳其為方法,通過o.m()來調運對象o中的方法。
我們看到字符串也同樣具有屬性和方法。
代碼如下:
var s ="hello world";
var word = s.substring(s.indexOf("")+1,s.length);//使用字符串的屬性。
document.write(word) //"ello world"
字符串既然不是對象,為什麼它有屬性呢?只要引用了字符串s的屬性,javascript就會將字符串的值通過調用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);
javascript會在必要的時候將包裝轉換為原始值,因此上段代碼中的對象S N B常常——但不總是——表現的值和s n b一樣,"=="等於運算符將原始值和其包裝對象視為相等。
但"==="全籌運算符將它們視為不等,通過typeof運算符可以看到原始值和其包裝的對象的不同。
7.不可變的原始值和可變的對象引用。
javascript的原始值(undefined null 布爾值 數字和字符串)與對象(包括數組和函數)有著根本的區別,原始值是不可更改的;任何方法都無法(或突變)一個原始值。對數字和布爾值來說顯然如此———改變數字的值本身就說不通,而對字符串來說就不那麼明顯,因為字符串看起來由字符組成的數組。我們期望可以通過指定的索引來修改字符串中的字符。實際上javascript是禁止這樣做的。字符串中所有的方法看上去返回了一個修改後的字符串,實際上是返回一個新的字符串。
代碼如下:
var s = "hello world";
s.toUpperCase(); //返回"HELLO WORLD"並沒更改s的值
s //=> "hello world" 原始的字符串並未改變
原始值的比較是值的比較,只有在他們的值相當時它們在才相等。這對數字、布爾值、null和undefined來說聽起來有點難,並沒有其他辦法來比較他們。同樣,對於字符串來說則不那麼明顯;如果比較兩個單獨的字符串,當且僅當他們的長度相等且每個索引的字符都相等時,javascript的才認為相等。
代碼如下:
var o = {x:1} //定義一個對象
o.x = 2 //通過修改對象的屬性來改變對象
o.y = 3 //再次更改這個對象,給它增加一個新屬性
var a =[1,2,3] //數組也是可以修改的
a[0]=0; //更改數組中的一個元素
a[3]=4; 給數組增加一個新元素
對象的比較並非值的比較:即使兩個對象包含同樣的屬性及相同的值,他們也是不相等的,各個索引元素完全相等的兩個數組也不相等
代碼如下:
var o ={x:1}, p={x:1}//兩個具有相同屬性的兩個對象
o === p ;//=>false 兩個單獨的對象永不相等( o == p ; =>false)
var a =[],b=[]; //兩個單獨的空數組
a === b ; //=>false兩個單獨的數組永不相等
我們通常將對象稱為引用類型(reference type),以此來和javascript的基本類型區分開來。依照術語的叫法,對象都是引用(reference),對象的比較均是引用的比較;當且當它們應用同一個基對象時,它們才相等。
代碼如下:
var a = []; //定義一個引用空數組的變量a
var b = a; //變量b引用同一個數組
b[0] = 1;
a[0] //=>1 變量a也會修改
a === b //=>true a和b引用同一個數組,因此他們相等。
就像你剛才看到的如上代碼,將對象(或數組)賦值給一個變量,僅僅是賦值的引用值:對象本身並沒有復制一次。
如果你想得到一個對象或數組的副本,則必須顯式復制對象的每個屬性或數組的每個元素。下面的這個例子則是通過循環來完成對數組的復制。
代碼如下:
var a = ['a', 'b', 'c']; //待復制的數組
var b = []; //復制到目標的空數組
for (var i = 0; i < a.length; i++) { //遍歷a[]中的每個元素
b[i] = a[i]; //將元素復制到b中。
}
同樣的,如果我們想比較兩個單獨或者數組,則必須比較他們的屬性或元素。下面這段代碼定義了一個比較練個數組的函數。
代碼如下:
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; // 否則他們相等
}
8.類型轉化
javascript中的取值型非常靈活,我們已經從布爾值看到了這一點:當javascript期望使用一個布爾值時候,你可以提供任意類型值。javascript將根據需要自行轉換類型。一些值(真值)為true,其它值(假值)轉化為false.這在其它類型中同樣適用。如果javascript期望使用一個字符串,它把給定的值轉換為字符串。如果javascript期望使用一個數組,它把給定的值轉換為數字(如果轉化結果無意義的話將返回NaN),一些例子如下:
代碼如下:
10 + "object" //=> "10object";
"7" * "4" // =>28 兩個字符串均轉化為數字
var n = 1 - "x" // =>NaN字符串x無法轉換為數字
n + " objects" // =>"NaN objects":NaN轉換為字符串"NaN"
下表說明了在javascript中如何進行類型轉化。粗體突出了那些讓你倍感意外的類型轉化。空單元格表示不必要也沒有執行的轉換。
值
轉換為字符串
數字
布爾值
對象
undefined
null
"undefined"
"null"
NaN
0
false
false
throws TypeError
throws TypeError
true
false
"ture"
"false"
1
0
new Boolean(true)
new Boolean(false)
""(空字符串)
"1.2"(非空,數字)
"one"(非空,非數字)
0
1.2
NaN
false
true
true
new String("")
new String("1.2")
new String("one")
0
-0
NaN
Infinty
-Infinty
1(無窮大,非零)
"0"
"0"
"NaN"
"Infinity"
"-Infinity" "1"
false
false
false
true
true
true
new Number(0);
new Number(-0);
new Number(NaN)
new Number(Infinty)
new Number(-Infinty)
new Number(1)
{}(任意對象)
[](任意數組)
[9](1個數字元素)
['a'](其它數組)
function(){}(任意函數)
參考本小節第三節內容
""
"9"
使用join()方法
參考本小節第三節內容
參考本小節第三節內容
0
9
NaN
NaN
true
true
true
true
true
上表提到的原始值到原始值的轉換行對簡單,我們已經在第本文第三小節討論過轉換為布爾值的情況了。所有原始值轉換為字符串的情形也已經明確定義。轉換為數字的情形比較微妙。那些以數字表示的字符串可以直接轉化為數字,也允許在開始和結尾處帶有空格。但在開始和結尾處的任意非空字符都不會被當成數字量的一部分,進而造成字符串為數字的結果為NaN。有一些數字轉換看起來讓人奇怪:true轉換為1,false、空字符串""轉換為0.
原始值到對象的轉換也非常簡單,原始值通過調用String(),Number()或Boolean()構造函數,轉化為它們各自的包裝對象。見本文第6節。
null和undefined屬於例外,當將它們用在期望是一個對象的地方都會造成一個類型錯誤(TypeError)異常。而不會執行正常的轉換。
對象到原始值的轉換多少有些復雜,本小節第三小節有專門描述。
i.轉換和相等性
由於javascript可以做靈活的類型轉換,因此其“==”相等運算符也隨相等的含義靈活多變。例如:如下這些比較結果均是true;
null == undefined //這兩值被認為相等
"0" == 0 //在比較之前,字符串轉換成數字。
0 = false //在這之前布爾值轉換成數字。
"0" ==false //在比較之前字符串和布爾值都轉換成數字
在第四章9節第一小節相信講解了“==”等於運算符在判斷兩個值是否相等時做了那些類型轉換,並同樣介紹了“===”恆等運算符在判斷相等時並未做任何的類型轉換。
需要特別注意的是:一個值轉換為另一個值並不意味著兩個值相等。比如在期望使用布爾值的地方使用了undefined,將會轉換為false,但這不表明undefined==false。javascript運算符和語句期望使用多樣化的數據類型,並可以互相轉換。if語句將undefined轉化為false,但“==”運算符從不試圖將其轉化為布爾值。
ii.顯式類型轉化
盡管javascript可以做做很多類型轉換,但有時仍需要做顯式轉換,或者為了使代碼變得清晰易讀而做顯式轉換。
做顯式轉換最重簡單的方法就是使用Boolean()、Number()、String()或Object函數。我們在本文第6節已經介紹過了. 當不通過new運算符調運這些函數時,他們會作為類型轉換函數並按照上邊表格所描述的規則做類型轉換。
代碼如下:
Number("3") //=>3
String(false) //=>"false"或使用false.toString()
Boolean([]) //=>true
Object(3) // =>new Number(3)
需要注意的是,除了null或undefined之外的任何值都具有toString()方法,在這個方法的執行結果通常和String()方法返回的結果一致。同樣需要注意的話,如果試圖把null或undefined轉化為對象。則會拋出一個類型錯誤typeerro。Object()函數在這種情況下不會拋出異常:它僅簡單返回一個新創建的空對象。
javascript中的某些運算符會做隱式的類型轉換,有時用於類型轉換。如果“+”運算符的一個操作數是字符串,它將會把令一個操作數轉換為字符串。一元“+”運算符將其操作數轉換為數字。同樣,一元“!”運算符將其操作數轉換為布爾值取反,在代碼中常會看到這種類型轉換的慣用法。
代碼如下:
x + "" // 等於字符串String(x)
+x //等價於Number(x),也可以寫成x-0
!!x //等價於Boolean(x)
在計算機中數字的解析和格式化代碼是非常普通的工作。javascript中提供了專門的函數和方法用來更加精確的數字到字符串(number-to-string)和字符串到數字(string-to-number)的抓換。
Nmuber類定義的toString()方法可以接收表示基數(二進制,八進制,十六進制等)的可選參數,如果不指定該參數,轉化規則將是十進制。同樣也可以將數字轉換為其它進制數。(范圍在2-36之間)
代碼如下:
var n = 17;
b_string = n.toString(2); //轉化為10001
o_string = "0" + n.toString(8); //轉化為八進制 021
hex_string = "0x" + n.toString(16); //轉化為16進制 0x11
javascript為控制輸出中小數點位置和有效數字位數,或者決定是否需要指定指數計數法。Number類為這種數字到字符串定義了三個方法。
toFixed()根據小數點後指定位數,將數字轉換為字符串,它從不使用指數計數法。toExponential()使用指數計數法,將數字轉換為指數形式的字符串,其中小數點前只有一位,小數點後的位置則由參數指定(也就是說有效數字位數要比指定的位數多一位)。toPrecision()根據指定的有效數字位數,將數字轉換為字符串。如果有效數字的位數小於數字整數部分的位數,則轉換成指數形式。我們注意到,三個方法都會適當的進行四捨五入或填充0,
代碼如下:
var n = 123456.789;
n.toFixed(0); //"123457"
n.toFixed(2); //"123456.79"
n.toFixed(5); //"123456.78900"
n.toExponential(1); //"1.2e+5"
n.toExponential(3); //"1.235e+5"
n.toPrecision(4); // "1.235e+5"
n.toPrecision(7); //"123456.8"
n.toPrecision(10); //"123456.7890"
如果通過Number()轉換函數傳入一個字符串,它會試圖將其轉化為一個整數或浮點數直接量,這個方法只能基於十進制進行轉換,並且不能出現非法的尾隨字符。parseInt()和parseFloat()函數(它們是全局函數,不屬於人和類的方法),更加靈活。parseInt()只解析整數。而parseFloat()則可以解析整數和浮點數。如果字符串前邊是0x或0X,parseInt()將其解析為16進制數。兩個方法都會跳過任意量的前導空格,盡可能解析更多數值字符。並忽略後邊的內容。如果第一個是非法的數字直接量,則返回NaN
代碼如下:
parseInt("3many nice") //=>3;
parseFloat("3.14meters") //=>3.14
parseInt("-12.34") //=>-12
parseInt("0xff") //=>255
parseInt("-0XFF") //=>-255
parseFloat(".1") // =>0.1
parseInt("0.1") //=> 0
parseInt(".1") //=>NaN 不能以.開始
parseInt("$112") //=>NaN 不能以$開頭
parseInt()可以接收第二個可選參數。這個參數指定數字轉換的基數。合法的取值范圍是2-36
代碼如下:
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)
iii.對象轉化為原始值。
對象到布爾值的轉換非常簡單:所有的對象(包括數組和函數)都轉換為true。對於包裝對象亦是如此,new Boolean(false)是一個對象而不是原始值,它將轉換為true。 對象到字符串(object-to-String)和對象到數字(object-to-number)的轉換是通過調用帶轉換對象的一個方法來完成的。一個麻煩的事實是,javascript對象有兩個不同的方法來執行轉換,並且接下來要討論並且接下來要討論的場景更加復雜。值得注意的是,這裡提到的字符串和數字的轉換規則只適用於本地對象(native fangf object).宿主對象(例如:由web浏覽器定義的對象),根據各自的算法可以轉換成字符串和數字。
所有的對象繼承了兩個轉換方法。第一個是toString(), 它的作用是返回一個反映這個對象的字符串。默認的toString()方法並不會返回一個有趣的值。
({x:1,y:2}).toString() //=>"[object object]"
很多類定義了更多特定版本的toString()方法.
例如:數組類(Array class)的toString()方法將每個數組元素轉換為一個字符串,並在元素之間添加逗號後並合成結果字符串。
函數類(Function class)的toString()方法返回這個函數的實現定義的表示方式。實際上,這裡的實現方式是通常是將用戶定義函數轉換為javascript源代碼字符串。
日期類(Date class)定義toString()方法返回一個可讀的(可被javascript-parsable解析的)日期和事件字符串
RegExp class定義的toString()方法將RegExp對象轉換為正則表達式直接量字符串。
代碼如下:
[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(2015, 0, 1).toString() //=>Thu Jan 01 2015 00:00:00 GMT+0800 (中國標准時間)
另外一個函數是valueOf(),這個方法的任務並未詳細定義:如果存在任意原始值,它就默認將對象轉換為表示它的原始值。對象是復合值,而且大多數對象無法真正表示一個原始值,數組、函數和正則表達式簡單地繼承了這個默認方法,調用這些類型的實例的的valueOf()方法簡單地返回對象本身。日期類定義的valueOf()方法返回它的一個內部表示:1970年1月1日以來的毫秒數。
代碼如下:
var d = new Date(2015, 0, 1); //=>Thu Jan 01 2015 00:00:00 GMT+0800 (中國標准時間)
d.valueOf() //=>1420041600000
通過是用我們剛才講解過的toString()和valueOf()方法,就可以做到對象到字符串和對象到數字的轉換了。但在某些場景中,javascript執行了完全不同的對象到原始值的轉換。這些特殊的場景在本節的最後會講到。
javascript對象到字符串的轉換經過了如下這些步奏
如果對象具有toString()方法,則調用這個方法。如果它返回一個原始值,javascript將這個值轉換為字符串(如果本身不是字符串的話),並返回這個字符串結果。
如果對象沒toString()方法,或者這個方法並不返回一個原始值,那麼javascript會調用valueOf()方法。如果存在這個方法,則javascript調用它。如果返回值是原始值,javascript將責怪值轉換為字符串。
9.變量聲明。
在javascript程序中,使用一個變量之前應該先聲明,變量是通過var來聲明的,如下所示:
var i;
var sum;
也可以通過一個var關鍵字聲明多個變量
var i,sun;
而且還可以將變量的初始值和變量聲明和寫在一起;
var message = "hello";
var i=0 ,j=0,k=0;
如果在var聲明語句中給變量指定初始值,那麼雖然聲明了這個變量,但在給它存入一個值前,它的初始值是undefined. 我們注意到,在for和fo/in循環中同樣可以使用var語句,這樣可以更加簡潔地聲明在循環體語法中內使用的循環變量。例如:
代碼如下:
for (var i = 0; i < 10; i++) log(i);
for (var i = 0, j = 10; i < 10, j = 100; i++, j--) console.log(i * j)
for (var p in o) console.log(p);
如果在var聲明語句中給變量指定初始值,那麼雖然聲明了這個變量,但在給它存入一個值前,它的初始值是undefined. 我們注意到,在for和fo/in循環中同樣可以使用var語句,這樣可以更加簡潔地聲明在循環體語法中內使用的循環變量。例如:
代碼如下:
var i=10;
i="ten";
10.變量作用域
一個變量的左右域(scope)是程序源代碼中定義這個變量的區域,全局變量擁有全局作用域,在javascript代碼中的任何地方都是定義。然而在函數內部聲明變量只在函數體內有定義。他們是局部變量,作用是局部性的。函數參數也是局部變量,它們只在函數體內有定義。
在函數體內,局部變量的優先級高於同名的全局變量。如果在函數內聲明一個局部變量或者函數參數中帶有的變量和全局變量重名,那麼全局變量就被局部變量所遮蓋。
代碼如下:
var scope = "global"; //聲明一個全局變量
function checkscope() {
var scope = "local"; //聲明一個同名的局部變量
return scope;
}
checkscope(); //=>"local"
盡管在全局作用域編寫代碼時可以不寫var語句,但聲明局部變量時則必須使用var語句。
代碼如下:
scope = "global"; //聲明一個全局變量,甚至不使用var來聲明
function checkscope2() {
scope = "local"; //修改了全局變量
myscope = "local"; //這裡顯示式得聲明了一個新的全局變量
return [scope, myscope]; //
}
checkscope2(); //=> ["local","local"]:產生了副作用
scope // =>"local"全局變量修改了
myscope //=> "local"全局命名空間搞亂了。
函數定義是可以嵌套的。由於每個函數都有它直接的作用域,因此會出現幾個局部作用域嵌套的情況。
代碼如下:
var scope = "global scope"; //全局變量
function checkscope() {
var scope = "local scope"; //局部變量
function nested() {
var scope = "sested scope"; //嵌套作用域內的局部變量
return scope;
}
return nested();
}
checkscope() //=>"嵌套作用域" sested scope
i.函數作用域和聲明提前
在一些類似c語言的編程語言中,花括號內的每一段代碼都具有各自的左右域,而且變量在聲明他們的代碼之外是不可見的我們稱之為塊級作用域(block scope),而javascript中沒有塊級作用域,javascript取而代之的使用了函數作用域(function scope);變量在聲明它們的函數體以及這個函數體嵌套的任意函數體內都是有意義的。
如下代碼,在不同的位置定義了i j k,他們都在同一個作用域內,這三個變量在函數體內均有定義的。
代碼如下:
function test(o) {
var i = 0; //i在整個函數體內均是定義的
if (typeif o == "object") {
var j = 0; //j在函數體內是有定義的,不僅僅是在這個代碼段內
for (var k = 0; k < 10; k++) { //k在函數體內是有定義的,不僅僅是在循環內
console.log(k); //輸出數字0-9
}
console.log(k); //k已經定義,輸出10
}
console.log(j); //j已經定義了,但可能沒有初始化。
}
javascript的函數作用域是指在函數內聲明的所有變量在函數體內始終是可見的。有意思的是,這意味這變量在聲明之前甚至已經可用。javascript的這個特性被非正式的稱為聲明提前(hoisting),即javascript函數裡聲明的所有變量(但不涉及賦值)都被提前至函數整體的頂部。如下代碼:
代碼如下:
var scope = "global";
function f() {
console.log(scope); //輸出"undefined",而不是"global"
var scope = "local"; //變量在這裡賦初始值,但變量本身在函數體內任何地方都是有定義的
console.log(scope); //輸出"local"
你可能誤以為函數的第一行會輸出"global",因為代碼還沒有執行到var語句聲明局部變量的地方。其實不然,由於函數作用域的特性模具部變量在整個函數體內始終有定義的,也就是說,在函數體內局部變量覆蓋了同名全局變量。盡管如此,只有在程序執行到var語句的時候,局部變量才能正真的被賦值。
因此,上述的過程等價於:將函數內的變量聲明"提前"至函數頂部,同時變量初始化留在原來的位置:
代碼如下:
function f() {
var scope; //在函數的頂部聲明了局部變量
console.log(scope); //變量存在,但其值是"undefined"
scope = "local"; //在這裡將其初始化,並賦值
console.log(scope); //這裡它具有了我們所期望的值
}
在具有塊級作用域的編程語言中,在狹小的作用域裡讓變量聲明和使用變量的代碼盡可能靠近彼此,通常來說,這是一個非常不錯的編程習慣。由於在javascript中沒有塊級作用域,因此一些程序員特意將變量聲明放在函數體頂部,而不是將聲明放在靠近使用變量之處。這種做法使得他們的源代碼非常清晰地反映了真實的變量作用域。
ii作為屬性的變量
當聲明一個javascript全局變量時面試及上是定義了全局對象的一個屬性。見本文第三節。
當使用var聲明一個變量時,創建的這個屬性是不可配置的。見第六章第7節。也就是說這個變量無法通過delete運算符刪除。可能你已經注意到了,如果你沒有使用嚴格模式並給一個未聲明的變量賦值的話。javascript會自動創建一個全局變量。以這種方式創建變量是全局對象正常的可配置屬性。可以刪除它們。
代碼如下:
var truevar = 1; //聲明一耳光不可刪除的全局變量
fakevar = 2; //創建全局對象的一個可刪除的屬性
this.fakevar2 = 3; //同上
delete truevar // =>false 變量並沒有刪除
delete fakevar //=>true 變量被刪除
delete this.fakevar2 //=>true 變量被刪除
javascript全局變量是全局對象的屬性,這是在ECMAScript規范中強制規定的。對於局部變量則沒有此規定,但我們可以想象得到,局部變量當做跟函數調用相關的某個對象的屬性。ECMAScript3規范稱對象為“調用對象”(call object),ECMAScript5規定范稱為“聲明上下文對象”(declarative environment record)。javascript可以允許使用this關鍵字引用全局對象,卻沒有方法可以引用局部變量中存放的對象。這種存放局部變量的對象的特有性質,是一種對我們不可見的內部實現。然而,這些局部變量對象存在的觀念是非常重要的。
iii作用域鏈
javascript是基於詞法作用域的語言:通過閱讀包含變量定義在內的舒航源碼就能知道變量的作用域。
全局變量在程序中始終是都是有定義的。局部變量在聲明它的函數體內以及其所嵌套的函數內始終是有定義的。