JavaScript學習筆記-3(函數)

JavaScript學習筆記-(函數)


函數

1.函數的定義和調用

1. (x)括號內列出函數的參數,多個參數以,分隔

1.
function myAbs(x) {
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
}

2.
var hisFunc = function(x) {
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
}

2. 函數的調用
JavaScript允許傳入任意個參數而不影響調用,因此傳入的參數比定義的參數多也沒有問題,傳入的參數比定義的少也沒有問題,多的話只會按順序使用參數

console.log(hisFunc(1,2,3));//只會調用第一個參數
console.log(hisFunc());

3. arguments
JavaScript還有一個免費贈送的關鍵字arguments,它只在函數內部起作用,并且永遠指向當前函數的調用者傳入的所有參數。arguments類似Array但它不是一個Array,實際上arguments最常用于判斷傳入參數的個數

function funcOne(x) {
    console.log(x);// 10
    for (var i=0; i<arguments.length; i++) {
        console.log(arguments[i]); // a, b, c
    }
}
funcOne('a','b' ,'c');

3. rest參數//貌似沒有支持的版本
由于JavaScript函數允許接收任意個參數,于是我們就不得不用arguments來獲取所有參數

function aFunc(a, b) {
    var i, restArry = [];
    if (arguments.length > 2) {
        for (i = 2; i<arguments.length; i++) {
            restArry.push(arguments[i]);
        }
    }
    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log(restArry);
}
aFunc(1,2,3,4,5,6,7,8);

為了獲取除了已定義參數a、b之外的參數,我們不得不用arguments,并且循環要從索引2開始以便排除前兩個參數ES6引入了rest參數,上面的函數就可以寫成

function aFunc(a, b, ...moreValues) {

    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log(moreValues);
}

aFunc(1,2,3,4,5,6,7,8);

2.變量作用域

1. 如果一個變量在函數體內部申明,則該變量的作用域為整個函數體,在函數體外不可引用該變量
2. 不同函數內部的同名變量互相獨立,互不影響
3. 內部函數可以訪問外部函數定義的變量,反過來則不行
4. JavaScript的函數在查找變量時從自身函數定義開始,從“內”向“外”查找。如果內部函數定義了與外部函數重名的變量,則內部函數的變量將“屏蔽”外部函數的變量

1.變量提升

下面并不報錯 但是x中y值的部分為undefined

'use strict';
function foo() {
    var x = 'Hello, ' + y;
    alert(x);
    var y = 'Bob';
}
foo();
2.全局作用域

不在任何函數內定義的變量就具有全局作用域。實際上,JavaScript默認有一個全局對象window,全局作用域的變量實際上被綁定到window的一個屬性:

var aProperty = "guess";
console.log(aProperty);
console.log(window.aProperty);//被綁定到windowaProperty屬性
以變量命名的函數也是window的一個屬性 這樣我們就可以想到`alert()`也是一個屬性,我們就可以將這個函數實現給替換掉
3.名字空間

全局變量會綁定到window上,不同的JavaScript文件如果使用了相同的全局變量,或者定義了相同名字的頂層函數,都會造成命名沖突,并且很難被發現
減少沖突的一個方法是把自己的所有變量和函數全部綁定到一個全局變量中。例如:

// 唯一的全局變量MYAPP:
var MYAPP = {};

// 其他變量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其他函數:
MYAPP.foo = function () {
    return 'foo';
};

4.局部作用域
function stillCanUse() {
    for (var i=0; i<100; i++) {
        //
    }
    i += 10; // 仍然可以引用變量i
    console.log(i);
}
stillCanUse(1);

為了解決這個問題ES6引入了新的關鍵字let,用let替代var可以申明一個塊級作用域的變量

function foo() {
    var sum = 0;
    for (let i=0; i<100; i++) {
        sum += i;
    }
//    i += 1; // 報錯
}
5.常量

由于var和let申明的是變量,如果要申明一個常量,在ES6之前是不行的,我們通常用全部大寫的變量來表示“這是一個常量,不要修改它的值”:

var PI = 3.141592654;

ES6標準引入了新的關鍵字const來定義常量,const與let都具有塊級作用域:

const PI = 3.14;
PI = 3; // 某些瀏覽器不報錯,但是無效果!
PI; // 3.14

3.方法

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: function () {
        var y = new Date().getFullYear();
        return y - this.birth;
    }
};
xiaoming.age(); // 今年
console.log(xiaoming.age());//26
function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 26, 正常結果
getAge(); // NaN  這是this指向的問題
1.apply

想指定函數的this指向哪個對象,可以用函數本身的apply方法,它接收兩個參數,第一個參數就是需要綁定的this變量,第二個參數是Array,表示函數本身的參數

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

xiaoming.age(); // 26
getAge.apply(xiaoming, []); // 26, this指向xiaoming, 參數為空
另一個與apply()類似的方法是call(),唯一區別是:
apply()把參數打包成Array再傳入
call()把參數按順序傳入
對普通函數調用,我們通常把this綁定為null
2.裝飾器

利用apply(),我們還可以動態改變函數的行為。
JavaScript的所有對象都是動態的,即使內置的函數,我們也可以重新指向新的函數。
現在假定我們想統計一下代碼一共調用了多少次parseInt(),可以把所有的調用都找出來,然后手動加上count += 1,不過這樣做太傻了。最佳方案是用我們自己的函數替換掉默認的parseInt():

var count = 0;
var oldParseInt = parseInt; // 保存原函數
window.parseInt = function () {
    count += 1;
    return oldParseInt.apply(null, arguments); // 調用原函數
};
// 測試:
parseInt('10');
parseInt('20');
parseInt('30');
count; // 3

4.高階函數

JavaScript的函數其實都指向某個變量。既然變量可以指向函數,函數的參數能接收變量,那么一個函數就可以接收另一個函數作為參數,這種函數就稱之為高階函數
一個最簡單的高階函數:

function  aHigh_Order_Function(a,b,f){
    return f(a)+f(b);
}
var aF =function funAdd(x){
    return x*x;
}
console.log(aHigh_Order_Function(1,2,aF));//5
[1]map

map()方法定義在JavaScript的Array中,我們調用Array的map()方法,傳入我們自己的函數,就得到了一個新的Array作為結果
這是個map函數 不是ES6中的Map數據類型

function pow(x) {
    return x * x;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
arr.map(pow); // [1, 4, 9, 16, 25, 36, 49, 64, 81]
console.log(arr);
console.log(arr.map(pow));
[2]reduce

Array的reduce()把一個函數作用在這個Array的[x1, x2, x3...]上,這個函數必須接收兩個參數,reduce()把結果繼續和序列的下一個元素做累積計算,其效果就是

`函數必須接收兩個參數`
1.
function pow(x,y) {
    return x * y;
}
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(arr.reduce(pow));//1*2*3*4*5*6*7*8*9

2.
var aValue =  arr.reduce(function aFun(a,b){
    return a-b;
});
console.log(aValue);//1-2-3-4-5-6-7-8-9
[3]filter

filter也是一個常用的操作,它用于把Array的某些元素過濾掉,然后返回剩下的元素。和map()類似,Array的filter()也接收一個函數。和map()不同的是,filter()把傳入的函數依次作用于每個元素,然后根據返回值是true還是false決定保留還是丟棄該元素。

//保留偶數組成的數組
var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function (x) {
    return x % 2 == 0;
});
console.log(r);
[4]sort

Array的sort()方法默認把所有元素先轉換為String再根據ASCII碼進行排序
sort()方法會直接對Array進行修改
sort()方法也是一個高階函數,它還可以接收一個比較函數來實現自定義的排序。

var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
    if (x < y) {
        return -1;
    }
    if (x > y) {
        return 1;
    }
    return 0;
}); // [1, 2, 10, 20]

5.閉包

高階函數除了可以接受函數作為參數外,還可以把函數作為結果值返回

[1].函數作為返回值
函數作為結果值返回
function lazy_sum(arr) {
    var sum = function () {
        return arr.reduce(function (x, y) {
            return x + y;
        });
    }
    return sum;
}
//當我們調用lazy_sum()時,返回的并不是求和結果,而是求和函數
var afunction =  lazy_sum([1,2,3]);//只是一個函數
console.log(afunction());//這個才是結果6

當我們調用lazy_sum()時,每次調用都會返回一個新的函數,即使傳入相同的參數

var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false
[1].函數作為返回值

注意1.到返回的函數在其定義內部引用了局部變量arr,所以,當一個函數返回了一個函數后,其內部的局部變量還被新函數引用
2.返回的函數并沒有立刻執行,而是直到調用了f()才執行

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push(function () {
            return i * i;
        });
    }
    return arr;
}

var results = count();
var f1 = results[0];//16
var f2 = results[1];//16
var f3 = results[2];//16
全部都是16!原因就在于返回的函數引用了變量i,但它并非立刻執行。等到3個函數都返回時,它們所引用的變量i已經變成了4,因此最終結果為16。   
返回閉包時牢記的一點就是:返回函數不要引用任何循環變量,或者后續會發生變化的變量。
如果一定要引用循環變量怎么辦?方法是再創建一個函數,用該函數的參數綁定循環變量當前的值,無論該循環變量后續如何更改,已綁定到函數參數的值不變:
function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9
注意這里用了一個“創建一個匿名函數并立刻執行”的語法:
(function (x) {
    return x * x;
})(3); // 9
理論上講,創建一個匿名函數并立刻執行可以這么寫:
function (x) { return x * x } (3);
但是由于JavaScript語法解析的問題,會報SyntaxError錯誤,因此需要用括號把整個函數定義括起來:
(function (x) { return x * x }) (3);
通常,一個立即執行的匿名函數可以把函數體拆開,一般這么寫:
(function (x) {
    return x * x;
})(3);
閉包使用實例1.
在沒有class機制,只有函數的語言里,借助閉包,同樣可以封裝一個私有變量
function create_add(initial) {
    var x = initial || 0;
    return {
        inc: function () {
            x += 1;
            return x;
        }
    }
}
var c1 = create_add();
console.log(c1.inc());//1
console.log(c1.inc());//2
console.log(c1.inc());//3

var c2 = create_add(10);
console.log(c2.inc());//11
console.log(c2.inc());//12
console.log(c2.inc());//13
在返回的對象中,實現了一個閉包,該閉包攜帶了局部變量x,并且,從外部代碼根本無法訪問到變量x。換句話說,閉包就是攜帶狀態的函數,并且它的狀態可以完全對外隱藏起來。

閉包還可以把多參數的函數變成單參數的函數
閉包使用實例2.
function make_pow(n) {
    return function (x) {
        return Math.pow(x, n);
    }
}
// 創建兩個新函數:
var pow2 = make_pow(2);
var pow3 = make_pow(3);

pow2(5); // 25
pow3(7); // 343

6.箭頭函數

1.ES6標準新增了一種新的函數:Arrow Function(箭頭函數)。

var fn = x => x*x;
相當于
var fn1 = function(x){
    return x * x;
}

2.箭頭函數相當于匿名函數,并且簡化了函數定義。箭頭函數有兩種格式,一種像上面的,只包含一個表達式,連{ ... }和return都省略掉了。還有一種可以包含多條語句,這時候就不能省略{ ... }和return:

x => {
    if (x > 0) {
        return x * x;
    }
    else {
        return - x * x;
    }
}
如果參數不是一個,就需要用括號()括起來:
//兩個參數
var aFun1 = (a,b)=>a*a+b*b;
console.log(aFun1(1,2));
//沒有參數
var aFun2 = ()=>3.14;
console.log(aFun2());
//多個參數
var aFun3 = (a,b,c,...rest){
    var i, sum = a + b;
    for (i=0; i<rest.length; i++) {
        sum += rest[i];
    }
    return sum;
}

3.如果要返回一個對象,就要注意,如果是單表達式,這么寫的話會報錯:

// SyntaxError:
x => { foo: x }
因為和函數體的{ ... }有語法沖突,所以要改為:
// ok:
x => ({ foo: x })

4.箭頭函數看上去是匿名函數的一種簡寫,但實際上,箭頭函數和匿名函數有個明顯的區別:箭頭函數內部的this是詞法作用域,由上下文確定。

由于JavaScript函數對this綁定的錯誤處理,下面的例子無法得到預期結果:
var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = function () {
            return new Date().getFullYear() - this.birth; // this指向window或undefined
        };
        return fn();
    }
};
現在,箭頭函數完全修復了this的指向,this總是指向詞法作用域,也就是外層調用者ob
var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = () => new Date().getFullYear() - this.birth; // this指向obj對象
        return fn();
    }
};
obj.getAge(); // 25

7.generator

除了return語句,還可以用yield返回多次。

afunc(3)僅僅是創建了一個generator對象,還沒有去執行它
`1.不斷地調用generator對象的next()方法:`
function* afunc(x){
    yield x;
    yield x*x;
    return x*x*x;
}
var a = afunc(3);
console.log(a.next());//{ value: 3, done: false }
console.log(a.next());//{ value: 9, done: false }
console.log(a.next());//{ value: 27, done: true }
console.log(a.next());//{ value: undefined, done: true }
`第二個方法是直接用for ... of循環迭代generator對象`
for (var x of afunc(2)) {
  console.log(x);//2,4 不會打印8  需要把return x*x*x;改成yield x*x*x;
}
function* next_id(x){
    for(;;){
        yield x++;
    }
}
var a = next_id(5);
console.log(a.next());
console.log(a.next());
console.log(a.next());
console.log(a.next());
console.log(a.next());
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容