一 函數
JavaScript的函數不但是“頭等公民”,而且可以像變量一樣使用,具有非常強大的抽象能力。
什麼是“抽象”?
抽象是數學中非常常見的概念。比如:連加、連乘符號等可擴展的抽象記法。
藉助抽象,我們才能不關心底層的具體計算過程,而直接在更高的層次上思考問題。寫計算機程序也是一樣,函數就是最基本的一種代碼抽象的方式。
1、函數的定義和調用
和c語言幾乎一樣,略有一丟丟不同。
function abs(x) { //function指出這是一個函數定義(記)
if (x >= 0) {
return x;
} else {
return -x;
}
}
函數體內部的語句在執行時,一旦執行到return時,函數就執行完畢,並將結果返回。因此,函數內部通過條件判斷和循環可以實現非常複雜的邏輯。
如果沒有return語句,函數執行完畢後也會返回結果,只是結果爲undefined。
調用函數時,按順序傳入參數即可:
abs(10); // 返回10
abs(-9); // 返回9
abs(); // 返回NaN
abs(-9, 'haha', 'hehe', null); // 返回9(傳入的參數比定義的參數多不會報錯)
亂入——不等於號用【!== 】
2、arguments & rest
JavaScript還有一個免費贈送的關鍵字arguments,它只在函數內部起作用,並且永遠指向當前函數的調用者傳入的所有參數。arguments類似Array但它不是一個Array:
function foo(x) {
console.log('x = ' + x); // 10
for (var i=0; i<arguments.length; i++) { //arguments最常用於判斷傳入參數的個數
console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);
利用arguments,你可以獲得調用者傳入的所有參數。
也就是說,即使函數不定義任何參數,還是可以拿到參數的值——
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
rest與arguments的作用差不了太多。rest參數只能寫在最後,前面用…標識,從下面的運行結果可知,傳入的參數先綁定a、b,多餘的參數以數組形式交給變量rest。
function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
foo(1, 2, 3, 4, 5);
// 結果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]
foo(1);
// 結果:
// a = 1
// b = undefined
// Array []
例題:用rest參數編寫一個sum()函數——
function sum(...rest) {
var x = 0;
for(var k=0;k<rest.length;k++){
x=x+rest[k];
}
return x;
}
注意:JavaScript的函數可以嵌套,此時,內部函數可以訪問外部函數定義的變量,反過來則不行(跟c一樣)。但如果內部函數定義了與外部函數重名的變量,則內部函數的變量在內部將“屏蔽”外部函數的變量。
3、名字空間
JavaScript實際上只有一個全局作用域。參看這個鏈接。
全局變量會綁定到window上,不同的JavaScript文件如果使用了相同的全局變量,或者定義了相同名字的頂層函數,都會造成命名衝突,並且很難被發現。減少衝突的一個方法是把自己的所有變量和函數全部綁定到一個全局變量中。例如:
// 唯一的全局變量MYAPP:
var MYAPP = {};
// 其他變量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函數:
MYAPP.foo = function () {
return 'foo';
};
把自己的代碼全部放入唯一的名字空間MYAPP中,會大大減少全局變量衝突的可能。許多著名的JavaScript庫都是這麼幹的。
4、局部作用域
由於JavaScript的變量作用域實際上是函數內部,我們在for循環等語句塊中是無法定義具有局部作用域的變量的。爲了解決塊級作用域,ES6引入了新的關鍵字let,
用let替代var可以申明一個塊級作用域的變量:
'use strict';
function foo() {
var sum = 0;
for (let i=0; i<100; i++) { //let替代var,申明局部變量i
sum += i;
}
// SyntaxError:
i += 1;
}
5、常量
ES6標準引入了新的關鍵字const來定義常量,const與let都具有塊級作用域:
'use strict';
const PI = 3.14;
PI = 3; // 某些瀏覽器不報錯,但是無效果!
PI; // 3.14
6、解構賦值
可以同時對一組變量進行賦值。
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
// x = hello, y = JavaScript, z = ES6
解構賦值在很多時候可以大大簡化代碼。例如,交換兩個變量x和y的值,可以這麼寫,不再需要臨時變量:
var x=1, y=2;
[x, y] = [y, x] //交換x、y的值
7、對象的方法
“方法”功能是爲了解決js語言本身的一些缺陷,所以難以用邏輯理解。下面僅記錄一種用法,更深的理解參見這個鏈接。
在一個對象中綁定函數,稱爲這個對象的方法。
例題——
我們給一個對象(xiaoming)綁定一個函數,就可以做更多的事情。比如,寫個age()方法,返回xiaoming的年齡:
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth; //注意:this關鍵字
}
};
xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年調用是25,明年調用就變成26了
this關鍵字——是一個特殊變量,它始終指向當前對象,也就是xiaoming這個變量。所以,this.birth可以拿到xiaoming的birth屬性。
二 高階函數
JavaScript的函數其實都指向某個變量。既然變量可以指向函數,函數的參數能接收變量,那麼一個函數就可以接收另一個函數作爲參數,這種函數就稱之爲高階函數。
一個最簡單的高階函數:
function add(x, y, f) {
return f(x) + f(y);
}
當我們調用add(-5, 6, Math.abs)時,參數x,y和f分別接收-5,6和函數Math.abs,根據函數定義,我們可以推導計算過程爲:
x = -5;
y = 6;
f = Math.abs;
f(x) + f(y) —> Math.abs(-5) + Math.abs(6) —> 11;
return 11;
廖雪峯教程裏有常見的高階函數的用法(如下),暫時跳過~