一.函數(shù)
抽象
抽象是數(shù)學(xué)中非常常見(jiàn)的概念。舉個(gè)例子:
計(jì)算數(shù)列的和寒波,比如:1 + 2 + 3 + ... + 100,寫起來(lái)十分不方便升熊,于是數(shù)學(xué)家發(fā)明了求和符號(hào)∑俄烁,可以把1 + 2 + 3 + ... + 100記作:
100
∑n
n=1
這種抽象記法非常強(qiáng)大,因?yàn)槲覀兛吹?∑ 就可以理解成求和级野,而不是還原成低級(jí)的加法運(yùn)算页屠。
而且,這種抽象記法是可擴(kuò)展的蓖柔,比如:
100
∑(n2+1)
n=1
還原成加法運(yùn)算就變成了:
(1 x 1 + 1) + (2 x 2 + 1) + (3 x 3 + 1) + ... + (100 x 100 + 1)
可見(jiàn)卷中,借助抽象,我們才能不關(guān)心底層的具體計(jì)算過(guò)程渊抽,而直接在更高的層次上思考問(wèn)題蟆豫。
寫計(jì)算機(jī)程序也是一樣,函數(shù)就是最基本的一種代碼抽象的方式懒闷。
二.函數(shù)的定義和調(diào)用
1.定義函數(shù)
在JavaScript中十减,定義函數(shù)的方式如下:
function abs(x){
if(x>=0){
return x;
}else{
return -x;
}
}
上述abs()函數(shù)的定義如下:
- function 指出這是一個(gè)函數(shù)定義
- abs 是函數(shù)的名稱
- (x)括號(hào)內(nèi)列出函數(shù)的參數(shù),多個(gè)參數(shù)以 , 分隔;
- {....}之間的代碼是函數(shù)體愤估,可以包括若干語(yǔ)句帮辟,甚至可以沒(méi)有任何語(yǔ)句
請(qǐng)注意,函數(shù)體內(nèi)部的語(yǔ)句在執(zhí)行時(shí)玩焰,一旦執(zhí)行到return時(shí)由驹,函數(shù)就執(zhí)行完畢,并將結(jié)果返回昔园。因此蔓榄,函數(shù)內(nèi)部通過(guò)條件判斷和循環(huán)可以實(shí)現(xiàn)非常復(fù)雜的邏輯。
如果沒(méi)有return語(yǔ)句默刚,函數(shù)執(zhí)行完畢后也會(huì)返回結(jié)果甥郑,只是結(jié)果為undefined。
2.調(diào)用函數(shù)
調(diào)用函數(shù)時(shí)荤西,按順序傳入?yún)?shù)即可:
abs(10); // 返回10
abs(-9); // 返回9
由于JavaScript允許傳入任意個(gè)參數(shù)而不影響調(diào)用澜搅,因此傳入的參數(shù)比定義的參數(shù)多也沒(méi)有問(wèn)題伍俘,雖然函數(shù)內(nèi)部并不需要這些參數(shù):
abs(10, 'blablabla'); // 返回10
abs(-9, 'haha', 'hehe', null); // 返回9
傳入的參數(shù)比定義的少也沒(méi)有問(wèn)題:
abs(); // 返回NaN
// 此時(shí)abs(x)函數(shù)的參數(shù)x將收到undefined,計(jì)算結(jié)果為NaN勉躺。
要避免收到undefined癌瘾,可以對(duì)參數(shù)進(jìn)行檢查:
function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
3.arguments
JavaScript還有一個(gè)免費(fèi)贈(zèng)送的關(guān)鍵字arguments,它只在函數(shù)內(nèi)部起作用饵溅,并且永遠(yuǎn)指向當(dāng)前函數(shù)的調(diào)用者傳入的所有參數(shù)妨退。arguments類似Array但它不是一個(gè)Array:
function foo(x) {
alert(x); // 10
for (var i=0; i<arguments.length; i++) {
alert(arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);
利用arguments,你可以獲得調(diào)用者傳入的所有參數(shù)概说。也就是說(shuō)碧注,即使函數(shù)不定義任何參數(shù)嚣伐,還是可以拿到參數(shù)的值:
function abs() {
if (arguments.length === 0) {
return 0;
}
var x = arguments[0];
return x >= 0 ? x : -x;
}
abs(); // 0
abs(10); // 10
abs(-9); // 9
實(shí)際上arguments最常用于判斷傳入?yún)?shù)的個(gè)數(shù)糖赔。你可能會(huì)看到這樣的寫法:
// foo(a[, b], c)
// 接收2~3個(gè)參數(shù),b是可選參數(shù)轩端,如果只傳2個(gè)參數(shù)放典,b默認(rèn)為null:
function foo(a, b, c) {
if (arguments.length === 2) {
// 實(shí)際拿到的參數(shù)是a和b,c為undefined
c = b; // 把b賦給c
b = null; // b變?yōu)槟J(rèn)值
}
// ...
}
要把中間的參數(shù)b變?yōu)椤翱蛇x”參數(shù)基茵,就只能通過(guò)arguments判斷奋构,然后重新調(diào)整參數(shù)并賦值。
三.變量作用域
在JavaScript中拱层,用var申明的變量實(shí)際上是有作用域的弥臼。
如果一個(gè)變量在函數(shù)體內(nèi)部申明,則該變量的作用域?yàn)檎麄€(gè)函數(shù)體根灯,在函數(shù)體外不可引用該變量:
'use strict';
function foo() {
var x = 1;
x = x + 1;
}
x = x + 2; // ReferenceError! 無(wú)法在函數(shù)體外引用變量x
如果兩個(gè)不同的函數(shù)各自申明了同一個(gè)變量径缅,那么該變量只在各自的函數(shù)體內(nèi)起作用。換句話說(shuō)烙肺,不同函數(shù)內(nèi)部的同名變量互相獨(dú)立纳猪,互不影響:
'use strict';
function foo() {
var x = 1;
x = x + 1;
}
function bar() {
var x = 'A';
x = x + 'B';
}
由于JavaScript的函數(shù)可以嵌套,此時(shí)桃笙,內(nèi)部函數(shù)可以訪問(wèn)外部函數(shù)定義的變量氏堤,反過(guò)來(lái)則不行:
'use strict';
function foo() {
var x = 1;
function bar() {
var y = x + 1; // bar可以訪問(wèn)foo的變量x!
}
var z = y + 1; // ReferenceError! foo不可以訪問(wèn)bar的變量y!
}
如果內(nèi)部函數(shù)和外部函數(shù)的變量名重名怎么辦?
'use strict';
function foo() {
var x = 1;
function bar() {
var x = 'A';
alert('x in bar() = ' + x); // 'A'
}
alert('x in foo() = ' + x); // 1
bar();
}
這說(shuō)明JavaScript的函數(shù)在查找變量時(shí)從自身函數(shù)定義開(kāi)始搏明,從“內(nèi)”向“外”查找鼠锈。如果內(nèi)部函數(shù)定義了與外部函數(shù)重名的變量,則內(nèi)部函數(shù)的變量將“屏蔽”外部函數(shù)的變量星著。
四.方法
在一個(gè)對(duì)象中綁定函數(shù)脚祟,稱為這個(gè)對(duì)象的方法。
在JavaScript中强饮,對(duì)象的定義是這樣的:
var xiaoming = {
name: 'xiaoming',
birth: 1990
};
但是由桌,我們給xiaoming綁定一個(gè)函數(shù),就可以做更多事情,例如行您,寫個(gè)age()方法铭乾,返回xiaoming的年齡:
var xiaoming = {
name: 'xiaoming',
birth: 1990,
age: function(){
var y = new Date().getFullYear();
return y - this.birth;
}
} ;
xiaoming.age; //function xiaoming.age()
xiaoming.age(); //今年調(diào)用是27,明年調(diào)用就變成26了
在一個(gè)方法內(nèi)部娃循,this是一個(gè)特殊變量炕檩,它始終指向當(dāng)前對(duì)象,也就是xiaoming這個(gè)變量捌斧。所以笛质,this.birth可以拿到xiaoming的birth屬性。
function getAge(){
var y = new Date().getFullYear();
return y -this.birth;
}
var xiaoming = {
name: 'xiaoming',
birth: 1990,
age: getAge
};
xiaoming.age(); //27,正常結(jié)果
getAge(); //NaN
單獨(dú)調(diào)用函數(shù)getAge()怎么返回了NaN捞蚂?請(qǐng)注意妇押,我們已經(jīng)進(jìn)入到了JavaScript的一個(gè)大坑里。
JavaScript的函數(shù)內(nèi)部如果調(diào)用了this姓迅,那么這個(gè)this到底指向誰(shuí)敲霍?
答案是,視情況而定丁存!
如果以對(duì)象的方法形式調(diào)用肩杈,比如xiaoming.age(),該函數(shù)的this指向被調(diào)用的對(duì)象解寝,也就是xiaoming扩然,這是符合我們預(yù)期的。
如果單獨(dú)調(diào)用函數(shù)聋伦,比如getAge()夫偶,此時(shí),該函數(shù)的this指向全局對(duì)象嘉抓,也就是window索守。
坑爹啊抑片!
更坑爹的是卵佛,如果這么寫:
var fn = xiaoming.age; // 先拿到xiaoming的age函數(shù)
fn(); // NaN
也是不行的!要保證this指向正確敞斋,必須用obj.xxx()的形式調(diào)用截汪!
由于這是一個(gè)巨大的設(shè)計(jì)錯(cuò)誤,要想糾正可沒(méi)那么簡(jiǎn)單植捎。ECMA決定衙解,在strict模式下讓函數(shù)的this指向undefined,因此焰枢,在strict模式下蚓峦,你會(huì)得到一個(gè)錯(cuò)誤:
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
var fn = xiaoming.age;
fn(); // Uncaught TypeError: Cannot read property 'birth' of undefined
這個(gè)決定只是讓錯(cuò)誤及時(shí)暴露出來(lái)舌剂,并沒(méi)有解決this應(yīng)該指向的正確位置。
有些時(shí)候暑椰,喜歡重構(gòu)的你把方法重構(gòu)了一下:
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - this.birth;
}
return getAgeFromBirth();
}
};
xiaoming.age(); // Uncaught TypeError: Cannot read property 'birth' of undefined
結(jié)果又報(bào)錯(cuò)了霍转!原因是this指針只在age方法的函數(shù)內(nèi)指向xiaoming,在函數(shù)內(nèi)部定義的函數(shù)一汽,this又指向undefined了1芟(在非strict模式下,它重新指向全局對(duì)象windowU偌小)
修復(fù)的辦法也不是沒(méi)有岩喷,我們用一個(gè)that變量首先捕獲this:
'use strict';
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var that = this; // 在方法內(nèi)部一開(kāi)始就捕獲this
function getAgeFromBirth() {
var y = new Date().getFullYear();
return y - that.birth; // 用that而不是this
}
return getAgeFromBirth();
}
};
xiaoming.age(); // 25
用var that = this;,你就可以放心地在方法內(nèi)部定義其他函數(shù)监憎,而不是把所有語(yǔ)句都堆到一個(gè)方法中纱意。
五.高階函數(shù)
JavaScript的函數(shù)其實(shí)都指向某個(gè)變量。既然變量可以指向函數(shù)枫虏,函數(shù)的參數(shù)能接收變量妇穴,那么一個(gè)函數(shù)就可以接收另一個(gè)函數(shù)作為參數(shù)爬虱,這種函數(shù)就可以接收另一個(gè)函數(shù)作為參數(shù)隶债,這種函數(shù)稱為高階函數(shù)。
一個(gè)最簡(jiǎn)單的高階函數(shù)
function add(x,y,f){
return f(x) + f(y);
}
當(dāng)我們調(diào)用add(-5,6,Math.ads)時(shí)跑筝,參數(shù)x,y和f分別接收-5死讹,6和函數(shù)Math.abs,根據(jù)函數(shù)定義曲梗,我們可以推導(dǎo)計(jì)算過(guò)程為:
x = -5;
y = 6;
f = Math.abs;
f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11'
編寫高階函數(shù)赞警,就是讓函數(shù)的參數(shù)能夠接收別的函數(shù)
六.閉包(擇日再看)
七.標(biāo)準(zhǔn)對(duì)象
在JavaScript的世界里,一切都是對(duì)象虏两。
但是某些對(duì)象還是和其他對(duì)象不太一樣愧旦。為了區(qū)分對(duì)象的類型,我們用typeof操作符獲取對(duì)象類型定罢,它總是返回一個(gè)字符串:
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'
可見(jiàn)笤虫,number、string祖凫、boolean琼蚯、function和undefined有別于其他類型。特別注意null的類型是object惠况,Array的類型也是object遭庶,如果我們用typeof將無(wú)法區(qū)分出null、Array和通常意義上的object——{}稠屠。
八.Date
在JavaScript中峦睡,Date對(duì)象用來(lái)表示日期和時(shí)間
要獲取系統(tǒng)當(dāng)前時(shí)間翎苫,用:
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號(hào)
now.getDay(); // 3, 表示星期三
now.getHours(); // 19, 24小時(shí)制
now.getMinutes(); // 49, 分鐘
now.getSeconds(); // 22, 秒
now.getMilliseconds(); // 875, 毫秒數(shù)
now.getTime(); // 1435146562875, 以number形式表示的時(shí)間戳
注意拉队,當(dāng)前時(shí)間是瀏覽器從本機(jī)操作系統(tǒng)獲取的時(shí)間,所以不一定準(zhǔn)確阻逮,因?yàn)橛脩艨梢园旬?dāng)前時(shí)間設(shè)定為任何值粱快。
如果要?jiǎng)?chuàng)建一個(gè)指定日期和時(shí)間的Date對(duì)象,可以用:
var d = new Date(2015, 5, 19, 20, 15, 30, 123);
d; // Fri Jun 19 2015 20:15:30 GMT+0800 (CST)
你可能觀察到了一個(gè)非常非呈宥螅坑爹的地方事哭,就是JavaScript的月份范圍用整數(shù)表示是0~11,0表示一月瓜富,1表示二月……鳍咱,所以要表示6月,我們傳入的是5与柑!這絕對(duì)是JavaScript的設(shè)計(jì)者當(dāng)時(shí)腦抽了一下谤辜,但是現(xiàn)在要修復(fù)已經(jīng)不可能了。
第二種創(chuàng)建一個(gè)指定日期和時(shí)間的方法是解析一個(gè)符合ISO 8601格式的字符串:
var d = Date.parse('2015-06-24T19:49:22.875+08:00');
d; // 1435146562875
但它返回的不是Date對(duì)象价捧,而是一個(gè)時(shí)間戳丑念。不過(guò)有時(shí)間戳就可以很容易地把它轉(zhuǎn)換為一個(gè)Date:
var d = new Date(1435146562875);
d; // Wed Jun 24 2015 19:49:22 GMT+0800 (CST)
九.RegExp
所以我們判斷一個(gè)字符串是否是合法的Email的方法是:
創(chuàng)建一個(gè)匹配Email的正則表達(dá)式;
用該正則表達(dá)式去匹配用戶的輸入來(lái)判斷是否合法结蟋。
因?yàn)檎齽t表達(dá)式也是用字符串表示的脯倚,所以,我們要首先了解如何用字符來(lái)描述字符嵌屎。
在正則表達(dá)式中推正,如果直接給出字符,就是精確匹配宝惰。用\d可以匹配一個(gè)數(shù)字植榕,\w可以匹配一個(gè)字母或數(shù)字,所以:
- '00\d' 可以匹配 '007',但無(wú)法匹配'00A';
- '\d\d\d' 可以匹配 '010';
- '\w\w' 可以匹配 'js';
- . 可以匹配任何字符尼夺, 'js.'可以匹配 'jsp' 尊残、'jss' 、 'js!'
要匹配變長(zhǎng)的字符汞斧,在正則表達(dá)式中夜郁,用*表示任意個(gè)字符(包括0個(gè)),用+表示至少一個(gè)字符粘勒,用竞端?表示0個(gè)或1個(gè)字符,用{n}表示n個(gè)字符庙睡,用{n,m}表示n~m個(gè)字符:
來(lái)看個(gè)復(fù)雜的例子: \d{3}\s+\d{3,8} 從左到右解讀一下:
1.\d{3} 表示匹配3個(gè)數(shù)字事富,例如'010';
- \s 可以匹配一個(gè)空格(也包括Tab等空白符)技俐,所以\s+ 表示有至少一個(gè)空格
- \d{3,8} 表示3-8個(gè)數(shù)字,例如 '1234567'
綜合起來(lái)统台,上面的正則表達(dá)式可以匹配以任意個(gè)空格隔開(kāi)的帶區(qū)號(hào)的電話號(hào)碼雕擂。
進(jìn)階
要做到更精確地匹配,可以用[]表示范圍贱勃,比如:
- [0-9a-zA-Z_] 可以匹配一個(gè)數(shù)字井赌、字母或者下劃線;
- [0-9a-ZA-Z-]+可以匹配至少由一個(gè)數(shù)字贵扰、字母或者下劃線組成的字符串仇穗,比如'a100','0_z','js2015'等等;
- [a-zA-Z_$][0-9a-zA-Z_$]*可以匹配由字母或下劃線戚绕、$開(kāi)頭纹坐,后接任意個(gè)由一個(gè)數(shù)字、字母或者下劃線舞丛、$組成的字符串耘子,也就是JavaScript允許的變量名;
- [a-zA-Z_$][0-9a-zA-Z_$]{0, 19}更精確地限制了變量的長(zhǎng)度是1-20個(gè)字符(前面1個(gè)字符+后面最多19個(gè)字符)球切。
十.Json
JSON的字符集必須是UTF-8谷誓,表示多語(yǔ)言就沒(méi)有問(wèn)題了。為了統(tǒng)一解析欧聘,JSON的字符串規(guī)定必須用雙引號(hào)""片林,Object的鍵也必須用雙引號(hào)""端盆。
var xiaoming = {
name: '小明',
age: 14,
gender: true,
heightL 1.65,
grade: null,
'middle-school' : 'one middle school',
skills: ['JavaScript','Java','Python','Liap']
};
JSON.stringify(xiaoming);
// '{"name":"小明","age":14,"gender":true,"height":1.65,"grade":null,"middle-school":"\"W3C\" Middle School","skills":["JavaScript","Java","Python","Lisp"]}'
要輸出得好看一些怀骤,可以加上參數(shù),按縮進(jìn)輸出:
JSON.stringify(xiaoming, null, ' ');
結(jié)果:
{
"name": "小明",
"age": 14,
"gender": true,
"height": 1.65,
"grade": null,
"middle-school": "\"W3C\" Middle School",
"skills": [
"JavaScript",
"Java",
"Python",
"Lisp"
]
}
第二個(gè)參數(shù)用于控制如何篩選對(duì)象的鍵值焕妙,如果我們只想輸出指定的屬性蒋伦,可以傳入Array:
JSON.stringify(xiaoming, ['name', 'skills'], ' ');
結(jié)果
{
"name": "小明",
"skills": [
"JavaScript",
"Java",
"Python",
"Lisp"
]
}
還可以傳入一個(gè)函數(shù),這樣對(duì)象的每個(gè)鍵值對(duì)都會(huì)被函數(shù)先處理:
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定義一個(gè)toJSON()的方法,直接返回JSON應(yīng)該序列化的數(shù)據(jù):
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
};
}
};
JSON.stringify(xiaoming); // '{"Name":"小明","Age":14}'
反序列化
拿到一個(gè)JSON格式的字符串竣贪,我們直接用JSON.parse()把它變成一個(gè)JavaScript對(duì)象:
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()還可以接收一個(gè)函數(shù)哮针,用來(lái)轉(zhuǎn)換解析出的屬性:
JSON.parse('{"name":"小明","age":14}', function (key, value) {
// 把number * 2:
if (key === 'name') {
return value + '同學(xué)';
}
return value;
}); // Object {name: '小明同學(xué)', age: 14}