二.函數(shù)

一.函數(shù)

抽象

抽象是數(shù)學(xué)中非常常見(jiàn)的概念。舉個(gè)例子:

計(jì)算數(shù)列的和,比如:1 + 2 + 3 + ... + 100,寫(xiě)起來(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)題。

寫(xiě)計(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ì)看到這樣的寫(xiě)法:

// 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ù),就可以做更多事情,例如,寫(xiě)個(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。

坑爹??!

更坑爹的是,如果這么寫(xiě):

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了!(在非strict模式下,它重新指向全局對(duì)象window?。?/p>

修復(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'

編寫(xiě)高階函數(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';

  1. \s 可以匹配一個(gè)空格(也包括Tab等空白符),所以\s+ 表示有至少一個(gè)空格
  2. \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, '  ');

上面的代碼把所有屬性值都變成大寫(xiě):

{
  "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}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 函數(shù)不能在條件語(yǔ)句中聲明函數(shù)name屬性返回緊跟在function關(guān)鍵字之后的那個(gè)函數(shù)名。length屬性返回函數(shù)...
    恰皮閱讀 368評(píng)論 0 0
  • 第5章 引用類型(返回首頁(yè)) 本章內(nèi)容 使用對(duì)象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,684評(píng)論 0 4
  • 我們走累了,蹲在路邊啃西瓜。賣(mài)西瓜的大嫂非常健談,跟兒子玩得很開(kāi)心。 迎面走來(lái)一隊(duì)人馬,看我們西瓜吃得香甜,聽(tīng)兒子...
    王小花和mo閱讀 248評(píng)論 0 1
  • 文/段子手小阿鑫 做人不能忘記四條,話不要說(shuō)錯(cuò),床不要睡錯(cuò),門(mén)檻不要踏錯(cuò),口袋不要摸錯(cuò)。” 每個(gè)人心里,都有一個(gè)向...
    段子手小阿鑫閱讀 224評(píng)論 0 0
  • 2016年年末的時(shí)候偶然間下載了一個(gè)神奇的APP,沒(méi)想到從此開(kāi)啟了我全新的人生。這個(gè)APP就是喜馬拉雅,從聽(tīng)葉...
    happ_iness閱讀 1,066評(píng)論 1 47

友情鏈接更多精彩內(nèi)容