JavaScript學習記錄day9-標準對象
[TOC]
在JavaScript的世界裏,一切都是對象。
但是某些對象還是和其他對象不太一樣。為了區分對象的類型,我們用typeof
操作符獲取對象的類型,它總是返回一個字符串:
typeof 123; // ‘number‘ typeof NaN; // ‘number‘ typeof ‘str‘; // ‘string‘ typeof true; // ‘boolean‘ typeof undefined; // ‘undefined‘ typeof Math.abs; // ‘function‘ typeof null; // ‘object‘ typeof []; // ‘object‘ typeof {}; // ‘object‘
可見,number
、string
、boolean
、function
和undefined
有別於其他類型。特別註意null
的類型是object
,Array
的類型也是object
,如果我們用typeof
將無法區分出null
、Array
和通常意義上的object
——{}
。
1. 包裝對象
除了這些類型外,JavaScript還提供了包裝對象,熟悉Java的小夥伴肯定很清楚int
和Integer
這種曖昧關系。
number
、boolean
和string
都有包裝對象。沒錯,在JavaScript中,字符串也區分string
類型和它的包裝類型。包裝對象用new
創建:
var n = new Number(123); // 123,生成了新的包裝類型 var b = new Boolean(true); // true,生成了新的包裝類型 var s = new String(‘str‘); // ‘str‘,生成了新的包裝類型
雖然包裝對象看上去和原來的值一模一樣,顯示出來也是一模一樣,但他們的類型已經變為object
了!所以,包裝對象和原始值用===
比較會返回false
:
typeof new Number(123); // ‘object‘
new Number(123) === 123; // false
typeof new Boolean(true); // ‘object‘
new Boolean(true) === true; // false
typeof new String(‘str‘); // ‘object‘
new String(‘str‘) === ‘str‘; // false
<font color="red">所以閑的蛋疼也不要使用包裝對象!尤其是針對string類型!!!</font>
如果我們在使用Number
、Boolean
和String
時,沒有寫new
會發生什麽情況?
此時,Number()
、Boolean
和String()
被當做普通函數,把任何類型的數據轉換為number
、boolean
和string
類型(註意不是其包裝類型):
var n = Number(‘123‘); // 123,相當於parseInt()或parseFloat()
typeof n; // ‘number‘
var b = Boolean(‘true‘); // true
typeof b; // ‘boolean‘
var b2 = Boolean(‘false‘); // true! ‘false‘字符串轉換結果為true!因為它是非空字符串!
var b3 = Boolean(‘‘); // false
var s = String(123.45); // ‘123.45‘
typeof s; // ‘string‘
總結一下,有這麽幾條規則需要遵守:
- 不要使用
new Number()
、new Boolean()
、new String()
創建包裝對象; - 用
parseInt()
或parseFloat()
來轉換任意類型到number
; - 用
String()
來轉換任意類型到string
,或者直接調用某個對象的toString()
方法; - 通常不必把任意類型轉換為
boolean
再判斷,因為可以直接寫if (myVar) {...}
; typeof
操作符可以判斷出number
、boolean
、string
、function
和undefined
;- 判斷
Array
要使用Array.isArray(arr)
; - 判斷
null
請使用myVar === null
; - 判斷某個全局變量是否存在用
typeof window.myVar === ‘undefined‘
; - 函數內部判斷某個變量是否存在用
typeof myVar === ‘undefined‘
。
最後有細心的同學指出,任何對象都有toString()
方法嗎?null
和undefined
就沒有!確實如此,這兩個特殊值要除外,雖然null
還偽裝成了object
類型。
更細心的同學指出,number
對象調用toString()
報SyntaxError:
123.toString(); // SyntaxError
遇到這種情況,要特殊處理一下:
123..toString(); // ‘123‘, 註意是兩個點!第一個點解釋為小數點
(123).toString(); // ‘123‘,【推薦使用】
2. Date
在JavaScript中,Date
對象用來表示日期和時間。
要獲取系統當前時間,用:
var now = new Date();
now; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
now.getFullYear(); // 2015, 年份
now.getMonth(); // 5, 月份,註意月份範圍是0~11,5表示六月
now.getDate(); // 24, 表示24號
now.getDay(); // 3, 表示星期三
now.getHours(); // 19, 24小時制
now.getMinutes(); // 49, 分鐘
now.getSeconds(); // 22, 秒
now.getMilliseconds(); // 875, 毫秒數
now.getTime(); // 1435146562875, 以number形式表示的時間戳
註意,當前時間是瀏覽器從本機操作系統獲取的時間,所以不一定準確,因為用戶可以把當前時間設定為任何值。
如果要創建一個指定日期和時間的Date對象,可以用:
var d = new Date(2015, 5, 19, 20, 15, 30, 123);
d; // Fri Jun 19 2015 20:15:30 GMT+0800 (CST)
JavaScript的月份範圍用整數表示是0~11,0
表示一月,1
表示二月……,所以要表示6月,我們傳入的是5
!
JavaScript的Date對象月份值從0開始,牢記0=1月,1=2月,2=3月,……,11=12月。
第二種創建一個指定日期和時間的方法是解析一個符合ISO 8601格式的字符串:
var d = Date.parse(‘2015-06-24T19:49:22.875+08:00‘);
d; // 1435146562875
但它返回的不是Date對象,而是一個時間戳。不過有時間戳就可以很容易地把它轉換為一個Date:
var d = new Date(1435146562875);
d; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
d.getMonth(); // 5
使用
Date.parse()
時傳入的字符串使用實際月份01~12,轉換為Date對象後getMonth()獲取的月份值為0~11。
時區
Date對象表示的時間總是按瀏覽器所在時區顯示的,不過我們既可以顯示本地時間,也可以顯示調整後的UTC時間:
var d = new Date(1435146562875);
d.toLocaleString(); // ‘2015/6/24 下午7:49:22‘,本地時間(北京時區+8:00),顯示的字符串與操作系統設定的格式有關
d.toUTCString(); // ‘Wed, 24 Jun 2015 11:49:22 GMT‘,UTC時間,與本地時間相差8小時
那麽在JavaScript中如何進行時區轉換呢?實際上,只要我們傳遞的是一個number
類型的時間戳,我們就不用關心時區轉換。任何瀏覽器都可以把一個時間戳正確轉換為本地時間。
時間戳是個什麽東西?時間戳是一個自增的整數,它表示從1970年1月1日零時整的GMT時區開始的那一刻,到現在的毫秒數。假設瀏覽器所在電腦的時間是準確的,那麽世界上無論哪個時區的電腦,它們此刻產生的時間戳數字都是一樣的,所以,時間戳可以精確地表示一個時刻,並且與時區無關。
所以,我們只需要傳遞時間戳,或者把時間戳從數據庫裏讀出來,再讓JavaScript自動轉換為當地時間就可以了。
要獲取當前時間戳,可以用:
‘use strict‘;
if (Date.now) {
console.log(Date.now()); // 老版本IE沒有now()方法
} else {
console.log(new Date().getTime());
}
3. 正則表達式
字符串是編程時涉及到的最多的一種數據結構,對字符串進行操作的需求幾乎無處不在。比如判斷一個字符串是否是合法的Email地址,雖然可以編程提取@前後的子串,再分別判斷是否是單詞和域名,但這樣做不但麻煩,而且代碼難以復用。
正則表達式是一種用來匹配字符串的強有力的武器。它的設計思想是用一種描述性的語言來給字符串定義一個規則,凡是符合規則的字符串,我們就認為它“匹配”了,否則,該字符串就是不合法的。
所以我們判斷一個字符串是否是合法的Email的方法是:
創建一個匹配Email的正則表達式;
用該正則表達式去匹配用戶的輸入來判斷是否合法。
因為正則表達式也是用字符串表示的,所以,我們要首先了解如何用字符來描述字符。
在正則表達式中,如果直接給出字符,就是精確匹配。用\d
可以匹配一個數字,\w
可以匹配一個字母或數字,所以:
‘00\d‘
可以匹配‘007‘
,但無法匹配‘00A‘
;
‘\d\d\d‘
可以匹配‘010‘
;
‘\w\w‘
可以匹配‘js‘
;
.
可以匹配任意字符,所以:
‘js.‘
可以匹配‘jsp‘
、‘jss‘
、‘js!‘
等等。
要匹配變長的字符,在正則表達式中,用*
表示任意個字符(包括0個),用+
表示至少一個字符,用?
表示0個或1個字符,用{n}
表示n個字符,用{n,m}
表示n-m個字符:
來看一個復雜的例子:\d{3}\s+\d{3,8}
。
我們來從左到右解讀一下:
\d{3}
表示匹配3個數字,例如‘010‘
;
\s
可以匹配一個空格(也包括Tab等空白符),所以\s+
表示至少有一個空格,例如匹配‘ ‘
,‘\t\t‘
等;
\d{3,8}
表示3-8個數字,例如‘1234567‘
。
綜合起來,上面的正則表達式可以匹配以任意個空格隔開的帶區號的電話號碼。
如果要匹配‘010-12345‘
這樣的號碼呢?由於‘-‘是特殊字符,在正則表達式中,要用‘\‘
轉義,所以,上面的正則是\d{3}\-\d{3,8}
。
但是,仍然無法匹配‘010 - 12345‘
,因為帶有空格。所以我們需要更復雜的匹配方式。
進階
要做更精確地匹配,可以用[]
表示範圍,比如:
[0-9a-zA-Z\_]
可以匹配一個數字、字母或者下劃線;
[0-9a-zA-Z\_]+
可以匹配至少由一個數字、字母或者下劃線組成的字符串,比如‘a100‘
,‘0_Z‘
,‘js2015‘
等等;
[a-zA-Z\_\$][0-9a-zA-Z\_\$]*
可以匹配由字母或下劃線、$
開頭,後接任意個由一個數字、字母或者下劃線、$
組成的字符串,也就是JavaScript允許的變量名;
[a-zA-Z\_\$][0-9a-zA-Z\_\$]{0, 19}
更精確地限制了變量的長度是1-20個字符(前面1個字符+後面最多19個字符)。
A|B
可以匹配A
或B
,所以(J|j)ava(S|s)cript
可以匹配‘JavaScript‘
、‘Javascript‘
、‘javaScript‘
或者‘javascript‘
。
^
表示行的開頭,^\d
表示必須以數字開頭。
$
表示行的結束,\d\$
表示必須以數字結束。
你可能註意到了,js也可以匹配‘jsp‘,但是加上^js$就變成了整行匹配,就只能匹配‘js‘了。
RegExp
有了準備知識,我們就可以在JavaScript中使用正則表達式了。
JavaScript有兩種方式創建一個正則表達式:
第一種方式是直接通過/正則表達式/
寫出來,第二種方式是通過new RegExp(‘正則表達式‘)
創建一個RegExp對象。
兩種寫法是一樣的:
var re1 = /ABC\-001/;
var re2 = new RegExp(‘ABC\\-001‘);
console.log(re1); // /ABC\-001/
console.log(re2); // /ABC\-001/
註意,如果使用第二種寫法,因為字符串的轉義問題,字符串的兩個\\
實際上是一個\
。
先看看如何判斷正則表達式是否匹配:
var re = /^\d{3}\-\d{3,8}$/;
console.log(re.test(‘010-12345‘)); // true
console.log(re.test(‘010-1234x‘)); // false
console.log(re.test(‘010 12345‘)); // false
RegExp對象的test()
方法用於測試給定的字符串是否符合條件。
切分字符串
用正則表達式切分字符串比用固定的字符更靈活,請看正常的切分代碼:
console.log(‘a b c‘.split(‘ ‘)); // [‘a‘, ‘b‘, ‘‘, ‘‘, ‘c‘]
嗯,無法識別連續的空格,用正則表達式試試:
console.log(‘a b c‘.split(/\s+/)); // [‘a‘, ‘b‘, ‘c‘]
無論多少個空格都可以正常分割。加入,試試:
console.log(‘a,b, c d‘.split(/[\s\,]+/)); // [‘a‘, ‘b‘, ‘c‘, ‘d‘]
再加入;
試試:
console.log(‘a,b;; c d‘.split(/[\s\,\;]+/)); // [‘a‘, ‘b‘, ‘c‘, ‘d‘]
如果用戶輸入了一組標簽,下次記得用正則表達式來把不規範的輸入轉化成正確的數組。
分組
除了簡單地判斷是否匹配之外,正則表達式還有提取子串的強大功能。用()
表示的就是要提取的分組(Group)。比如:
^(\d{3})-(\d{3,8})$
分別定義了兩個組,可以直接從匹配的字符串中提取出區號和本地號碼:
var re = /^(\d{3})-(\d{3,8})$/;
re.exec(‘010-12345‘); // [‘010-12345‘, ‘010‘, ‘12345‘]
re.exec(‘010 12345‘); // null
如果正則表達式中定義了組,就可以在RegExp對象上用exec()
方法提取出子串來。
exec()
方法在匹配成功後,會返回一個Array
,第一個元素是正則表達式匹配到的整個字符串,後面的字符串表示匹配成功的子串。
exec()
方法在匹配失敗時返回null
。
提取子串非常有用。來看一個更兇殘的例子:
var re = /^(0[0-9]|1[0-9]|2[0-3]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])\:(0[0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9]|[0-9])$/;
re.exec(‘19:05:30‘); // [‘19:05:30‘, ‘19‘, ‘05‘, ‘30‘]
這個正則表達式可以直接識別合法的時間。但是有些時候,用正則表達式也無法做到完全驗證,比如識別日期:
var re = /^(0[1-9]|1[0-2]|[0-9])-(0[1-9]|1[0-9]|2[0-9]|3[0-1]|[0-9])$/;
對於‘2-30‘,‘4-31‘這樣的非法日期,用正則還是識別不了,或者說寫出來非常困難,這時就需要程序配合識別了。
貪婪匹配
需要特別指出的是,正則匹配默認是貪婪匹配,也就是匹配盡可能多的字符。舉例如下,匹配出數字後面的0
:
var re = /^(\d+)(0*)$/;
re.exec(‘102300‘); // [‘102300‘, ‘102300‘, ‘‘]
由於\d+
采用貪婪匹配,直接把後面的0
全部匹配了,結果0*
只能匹配空字符串了。
必須讓\d+采用非貪婪匹配(也就是盡可能少匹配),才能把後面的0匹配出來,加個?就可以讓\d+采用非貪婪匹配:
var re = /^(\d+?)(0*)$/;
re.exec(‘102300‘); // [‘102300‘, ‘1023‘, ‘00‘]
全局搜索
JavaScript的正則表達式還有幾個特殊的標誌,最常用的是g
,表示全局匹配:
var r1 = /test/g;
// 等價於:
var r2 = new RegExp(‘test‘, ‘g‘);
全局匹配可以多次執行exec()
方法來搜索一個匹配的字符串。當我們指定g
標誌後,每次運行exec()
,正則表達式本身會更新lastIndex
屬性,表示上次匹配到的最後索引:
var s = ‘JavaScript, VBScript, JScript and ECMAScript‘;
var re=/[a-zA-Z]+Script/g;
// 使用全局匹配:
re.exec(s); // [‘JavaScript‘]
re.lastIndex; // 10
re.exec(s); // [‘VBScript‘]
re.lastIndex; // 20
re.exec(s); // [‘JScript‘]
re.lastIndex; // 29
re.exec(s); // [‘ECMAScript‘]
re.lastIndex; // 44
re.exec(s); // null,直到結束仍沒有匹配到
全局匹配類似搜索,因此不能使用/^...$/
,那樣只會最多匹配一次。
正則表達式還可以指定i
標誌,表示忽略大小寫,m
標誌,表示執行多行匹配。
練習
請嘗試寫一個驗證Email地址的正則表達式。版本一應該可以驗證出類似的Email:
‘use strict‘;
var re = /^[a-z]([a-z0-9]*[-._]?[a-z0-9]+)*@([a-z0-9]*[-_]?[a-z0-9]+)+[\.][a-z]{2,3}([\.][a-z]{2})?$/i;
// 測試:
var
i,
success = true,
should_pass = [‘[email protected]‘, ‘[email protected]‘, ‘[email protected]‘, ‘[email protected]‘],
should_fail = [‘test#gmail.com‘, ‘bill@microsoft‘, ‘bill%[email protected]‘, ‘@voyager.org‘];
for (i = 0; i < should_pass.length; i++) {
if (!re.test(should_pass[i])) {
console.log(‘測試失敗: ‘ + should_pass[i]);
success = false;
break;
}
}
for (i = 0; i < should_fail.length; i++) {
if (re.test(should_fail[i])) {
console.log(‘測試失敗: ‘ + should_fail[i]);
success = false;
break;
}
}
if (success) {
console.log(‘測試通過!‘);
}
版本二可以驗證並提取出帶名字的Email地址:
‘use strict‘;
var re = /^\<([a-zA-Z\_\.\s]+)\>\s+([a-zA-Z0-9\_\.]+\@[a-zA-Z0-9\_]+\.[a-zA-Z]+)$/;
// 測試:
var r = re.exec(‘<Tom Paris> [email protected]‘);
console.log(r);
if (r === null || r.toString() !== [‘<Tom Paris> [email protected]‘, ‘Tom Paris‘, ‘[email protected]‘].toString()) {
console.log(‘測試失敗!‘);
}
else {
console.log(‘測試成功!‘);
}
4. JSON
JSON是JavaScript Object Notation的縮寫,它是一種數據交換格式。
在JSON出現之前,大家一直用XML來傳遞數據。因為XML是一種純文本格式,所以它適合在網絡上交換數據。XML本身不算復雜,但是,加上DTD、XSD、XPath、XSLT等一大堆復雜的規範以後,任何正常的軟件開發人員碰到XML都會感覺頭大了,最後大家發現,即使你努力鉆研幾個月,也未必搞得清楚XML的規範。
終於,在2002年的一天,道格拉斯·克羅克福特(Douglas Crockford)同學為了拯救深陷水深火熱同時又被某幾個巨型軟件企業長期愚弄的軟件工程師,發明了JSON這種超輕量級的數據交換格式。
道格拉斯同學長期擔任雅虎的高級架構師,自然鐘情於JavaScript。他設計的JSON實際上是JavaScript的一個子集。在JSON中,一共就這麽幾種數據類型:
number:和JavaScript的number
完全一致;
boolean:就是JavaScript的true
或false
;
string:就是JavaScript的string
;
null:就是JavaScript的null
;
array:就是JavaScript的Array
表示方式——[]
;
object:就是JavaScript的{ ... }
表示方式。
以及上面的任意組合。
並且,JSON還定死了字符集必須是UTF-8,表示多語言就沒有問題了。為了統一解析,JSON的字符串規定必須用雙引號""
,Object的鍵也必須用雙引號""
。
由於JSON非常簡單,很快就風靡Web世界,並且成為ECMA標準。幾乎所有編程語言都有解析JSON的庫,而在JavaScript中,我們可以直接使用JSON,因為JavaScript內置了JSON的解析。
把任何JavaScript對象變成JSON,就是把這個對象序列化成一個JSON格式的字符串,這樣才能夠通過網絡傳遞給其他計算機。
如果我們收到一個JSON格式的字符串,只需要把它反序列化成一個JavaScript對象,就可以在JavaScript中直接使用這個對象了。
序列化
讓我們先把小明這個對象序列化成JSON格式的字符串:
‘use strict‘;
var xiaoming = {
name: ‘小明‘,
age: 14,
gender: true,
height: 1.65,
grade: null,
‘middle-school‘: ‘\"W3C\" Middle School‘,
skills: [‘JavaScript‘, ‘Java‘, ‘Python‘, ‘Lisp‘]
};
var s = JSON.stringify(xiaoming);
console.log(s);
要輸出得好看一些,可以加上參數,按縮進輸出:
JSON.stringify(xiaoming, null, ‘ ‘);
結果:
{
"name": "小明",
"age": 14,
"gender": true,
"height": 1.65,
"grade": null,
"middle-school": "\"W3C\" Middle School",
"skills": [
"JavaScript",
"Java",
"Python",
"Lisp"
]
}
第二個參數用於控制如何篩選對象的鍵值,如果我們只想輸出指定的屬性,可以傳入Array:
JSON.stringify(xiaoming, [‘name‘, ‘skills‘], ‘ ‘);
結果:
{
"name": "小明",
"skills": [
"JavaScript",
"Java",
"Python",
"Lisp"
]
}
還可以傳入一個函數,這樣對象的每個鍵值對都會被函數先處理:
function convert(key, value) {
if (typeof value === ‘string‘) {
return value.toUpperCase();
}
return value;
}
JSON.stringify(xiaoming, convert, ‘ ‘);
上面的代碼把所有屬性值都變成大寫:
{
"name": "小明",
"age": 14,
"gender": true,
"height": 1.65,
"grade": null,
"middle-school": "\"W3C\" MIDDLE SCHOOL",
"skills": [
"JAVASCRIPT",
"JAVA",
"PYTHON",
"LISP"
]
}
如果我們還想要精確控制如何序列化小明,可以給xiaoming定義一個toJSON()的方法,直接返回JSON應該序列化的數據:
var xiaoming = {
name: ‘小明‘,
age: 14,
gender: true,
height: 1.65,
grade: null,
‘middle-school‘: ‘\"W3C\" Middle School‘,
skills: [‘JavaScript‘, ‘Java‘, ‘Python‘, ‘Lisp‘],
toJSON: function () {
return { // 只輸出name和age,並且改變了key:
‘Name‘: this.name,
‘Age‘: this.age
};
}
};
var r = JSON.stringify(xiaoming); // {"Name":"小明","Age":14}
console.log(r);
拿到一個JSON格式的字符串,我們直接用JSON.parse()把它變成一個JavaScript對象:
JSON.parse(‘[1,2,3,true]‘); // [1, 2, 3, true]
JSON.parse(‘{"name":"小明","age":14}‘); // Object {name: ‘小明‘, age: 14}
JSON.parse(‘true‘); // true
JSON.parse(‘123.45‘); // 123.45
JSON.parse()
還可以接收一個函數,用來轉換解析出的屬性:
‘use strict‘;
var obj = JSON.parse(‘{"name":"小明","age":14}‘, function (key, value) {
if (key === ‘name‘) {
return value + ‘同學‘;
}
return value;
});
console.log(JSON.stringify(obj)); // {name: ‘小明同學‘, age: 14}
練習
用瀏覽器訪問Yahoo的天氣API,查看返回的JSON數據,然後返回城市、氣溫預報等信息:
‘use strict‘
var url = ‘https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20%3D%202151330&format=json‘;
// 從遠程地址獲取JSON:
$.getJSON(url, function (data) {
// 獲取結果:
var city = data.query.results.channel.location.city;
var forecast = data.query.results.channel.item.forecast;
var result = {
city: city,
forecast: forecast
};
alert(JSON.stringify(result, null, ‘ ‘));
});
學習參考教程:http://www.liaoxuefeng.com
JavaScript學習記錄day9-標準對象