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());