標籤:javascript 對象
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; // falsetypeof new Boolean(true); // ‘object‘new Boolean(true) === true; // falsetypeof 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‘); // truetypeof b; // ‘boolean‘var b2 = Boolean(‘false‘); // true! ‘false‘字串轉換結果為true!因為它是非Null 字元串!var b3 = Boolean(‘‘); // falsevar 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. Regex
字串是編程時涉及到的最多的一種資料結構,對字串進行操作的需求幾乎無處不在。比如判斷一個字串是否是合法的Email地址,雖然可以編程提取@前後的子串,再分別判斷是否是單詞和網域名稱,但這樣做不但麻煩,而且代碼難以複用。
Regex是一種用來匹配字串的強有力的武器。它的設計思想是用一種描述性的語言來給字串定義一個規則,凡是符合規則的字串,我們就認為它“匹配”了,否則,該字串就是不合法的。
所以我們判斷一個字串是否是合法的Email的方法是:
建立一個匹配Email的Regex;
用該Regex去匹配使用者的輸入來判斷是否合法。
因為Regex也是用字串表示的,所以,我們要首先瞭解如何用字元來描述字元。
在Regex中,如果直接給出字元,就是精確匹配。用\d
可以匹配一個數字,\w
可以匹配一個字母或數字,所以:
‘00\d‘
可以匹配‘007‘
,但無法匹配‘00A‘
;
‘\d\d\d‘
可以匹配‘010‘
;
‘\w\w‘
可以匹配‘js‘
;
.
可以匹配任一字元,所以:
‘js.‘
可以匹配‘jsp‘
、‘jss‘
、‘js!‘
等等。
要匹配變長的字元,在Regex中,用*
表示任意個字元(包括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‘
。
綜合起來,上面的Regex可以匹配以任意個空格隔開的帶區號的電話號碼。
如果要匹配‘010-12345‘
這樣的號碼呢?由於‘-‘是特殊字元,在Regex中,要用‘\‘
轉義,所以,上面的正則是\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中使用Regex了。
JavaScript有兩種方式建立一個Regex:
第一種方式是直接通過/Regex/
寫出來,第二種方式是通過new RegExp(‘Regex‘)
建立一個RegExp對象。
兩種寫法是一樣的:
var re1 = /ABC\-001/;var re2 = new RegExp(‘ABC\\-001‘);console.log(re1); // /ABC\-001/console.log(re2); // /ABC\-001/
注意,如果使用第二種寫法,因為字串的轉義問題,字串的兩個\\
實際上是一個\
。
先看看如何判斷Regex是否匹配:
var re = /^\d{3}\-\d{3,8}$/;console.log(re.test(‘010-12345‘)); // trueconsole.log(re.test(‘010-1234x‘)); // falseconsole.log(re.test(‘010 12345‘)); // false
RegExp對象的test()
方法用於測試給定的字串是否符合條件。
切分字串
用Regex切分字串比用固定的字元更靈活,請看正常的切分代碼:
console.log(‘a b c‘.split(‘ ‘)); // [‘a‘, ‘b‘, ‘‘, ‘‘, ‘c‘]
嗯,無法識別連續的空格,用Regex試試:
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‘]
如果使用者輸入了一組標籤,下次記得用Regex來把不規範的輸入轉化成正確的數組。
分組
除了簡單地判斷是否匹配之外,Regex還有提取子串的強大功能。用()
表示的就是要提取的分組(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
如果Regex中定義了組,就可以在RegExp對象上用exec()
方法提取出子串來。
exec()
方法在匹配成功後,會返回一個Array
,第一個元素是Regex匹配到的整個字串,後面的字串表示匹配成功的子串。
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‘]
這個Regex可以直接識別合法的時間。但是有些時候,用Regex也無法做到完全驗證,比如識別日期:
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*
只能匹配Null 字元串了。
必須讓\d+採用非貪婪匹配(也就是儘可能少匹配),才能把後面的0匹配出來,加個?就可以讓\d+採用非貪婪匹配:
var re = /^(\d+?)(0*)$/;re.exec(‘102300‘); // [‘102300‘, ‘1023‘, ‘00‘]
全域搜尋
JavaScript的Regex還有幾個特殊的標誌,最常用的是g
,表示全域匹配:
var r1 = /test/g;// 等價於:var r2 = new RegExp(‘test‘, ‘g‘);
全域匹配可以多次執行exec()
方法來搜尋一個匹配的字串。當我們指定g
標誌後,每次運行exec()
,Regex本身會更新lastIndex
屬性,表示上次匹配到的最後索引:
var s = ‘JavaScript, VBScript, JScript and ECMAScript‘;var re=/[a-zA-Z]+Script/g;// 使用全域匹配:re.exec(s); // [‘JavaScript‘]re.lastIndex; // 10re.exec(s); // [‘VBScript‘]re.lastIndex; // 20re.exec(s); // [‘JScript‘]re.lastIndex; // 29re.exec(s); // [‘ECMAScript‘]re.lastIndex; // 44re.exec(s); // null,直到結束仍沒有匹配到
全域匹配類似搜尋,因此不能使用/^...$/
,那樣只會最多匹配一次。
Regex還可以指定i
標誌,表示忽略大小寫,m
標誌,表示執行多行匹配。
練習
請嘗試寫一個驗證Email地址的Regex。版本一應該可以驗證出類似的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‘, ‘[email protected]‘, ‘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‘); // trueJSON.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-標準對象