C++讀書筆記:函式
function abs(x) {
if (x >= 0) {
return x;
} else {
return -x;
}
}
abs()
函式實際上是一個函式物件,而函式名abs
可以視為指向該函式的變數。
var abs = function (x) {
if (x >= 0) {
return x;
} else {
return -x;
}
};
上述兩種定義完全等價,注意第二種方式按照完整語法需要在函式體末尾加一個;
,表示賦值語句結束。
呼叫函式
由於JavaScript允許傳入任意個引數而不影響呼叫,因此傳入的引數比定義的引數多也沒有問題,雖然函式內部並不需要這些引數:
abs(10, 'blablabla'); // 返回10
abs(-9, 'haha', 'hehe', null); // 返回9
傳入的引數比定義的少也沒有問題:
abs(); // 返回NaN
此時abs(x)
函式的引數x
將收到undefined
,計算結果為NaN
。
要避免收到undefined
,可以對引數進行檢查:
function abs(x) {
if (typeof x !== 'number') {
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
arguments
arguments
,它只在函式內部起作用,並且永遠指向當前函式的呼叫者傳入的所有引數。arguments
類似Array
但它不是一個Array
:
function foo(x) {
console.log('x = ' + x); // 10
for (var i=0; i<arguments.length; i++) {
console.log('arg ' + i + ' = ' + arguments[i]); // 10, 20, 30
}
}
foo(10, 20, 30);
arguments
最常用於判斷傳入引數的個數。
rest引數
function foo(a, b,
rest引數只能寫在最後,前面用...
標識,從執行結果可知,傳入的引數先繫結a
、b
,多餘的引數以陣列形式交給變數rest
,所以,不再需要arguments
我們就獲取了全部引數。
小心你的return語句
function foo() {
return
{ name: 'foo' };
}
foo(); // undefined
要小心了,由於JavaScript引擎在行末自動新增分號的機制,上面的程式碼實際上變成了:
function foo() { return; // 自動添加了分號,相當於return undefined; { name: 'foo' }; // 這行語句已經沒法執行到了 }
巢狀函式
function foo() { var x = 1; function bar() { var x = 'A'; console.log('x in bar() = ' + x); // 'A' } console.log('x in foo() = ' + x); // 1 bar(); } foo();
JavaScript的函式在查詢變數時從自身函式定義開始,從“內”向“外”查詢。如果內部函式定義了與外部函式重名的變數,則內部函式的變數將“遮蔽”外部函式的變數。
變數提升
JavaScript的函式定義有個特點,它會先掃描整個函式體的語句,把所有申明的變數“提升”到函式頂部:
語句var x = 'Hello, ' + y;
並不報錯,原因是變數y
在稍後申明瞭。但是console.log
顯示Hello, undefined
,說明變數y
的值為undefined
。這正是因為JavaScript引擎自動提升了變數y
的宣告,但不會提升變數y
的賦值。
對於上述foo()
函式,JavaScript引擎看到的程式碼相當於:
function foo() { var y; // 提升變數y的申明,此時y為undefined var x = 'Hello, ' + y; console.log(x); y = 'Bob'; }
名稱空間
// 唯一的全域性變數MYAPP: var MYAPP = {}; // 其他變數: MYAPP.name = 'myapp'; MYAPP.version = 1.0; // 其他函式: MYAPP.foo = function () { return 'foo'; };
把自己的程式碼全部放入唯一的名字空間MYAPP
中,會大大減少全域性變數衝突的可能。
let
'use strict'; function foo() { for (var i=0; i<100; i++) { // } i += 100; // 仍然可以引用變數i }
為了解決塊級作用域,ES6引入了新的關鍵字let
,用let
替代var
可以申明一個塊級作用域的變數:
'use strict'; function foo() { var sum = 0; for (let i=0; i<100; i++) { sum += i; } // SyntaxError: i += 1; }
常量
ES6標準引入了新的關鍵字const
來定義常量,const
與let
都具有塊級作用域:
'use strict'; const PI = 3.14; PI = 3; // 某些瀏覽器不報錯,但是無效果! PI; // 3.14
解構賦值
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
如果陣列本身還有巢狀,也可以通過下面的形式進行解構賦值,注意巢狀層次和位置要保持一致:
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']]; x; // 'hello' y; // 'JavaScript' z; // 'ES6'
解構賦值還可以忽略某些元素:
let [, , z] = ['hello', 'JavaScript', 'ES6']; // 忽略前兩個元素,只對z賦值第三個元素 z; // 'ES6'
如果需要從一個物件中取出若干屬性,也可以使用解構賦值,便於快速獲取物件的指定屬性:
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678', school: 'No.4 middle school' }; var {name, age, passport} = person;
對一個物件進行解構賦值時,同樣可以直接對巢狀的物件屬性進行賦值,只要保證對應的層次是一致的:
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678', school: 'No.4 middle school', address: { city: 'Beijing', street: 'No.1 Road', zipcode: '100001' } }; var {name, address: {city, zip}} = person; name; // '小明' city; // 'Beijing' zip; // undefined, 因為屬性名是zipcode而不是zip // 注意: address不是變數,而是為了讓city和zip獲得巢狀的address物件的屬性: address; // Uncaught ReferenceError: address is not defined
如果要使用的變數名和屬性名不一致,可以用下面的語法獲取:
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678', school: 'No.4 middle school' }; // 把passport屬性賦值給變數id: let {name, passport:id} = person; name; // '小明' id; // 'G-12345678' // 注意: passport不是變數,而是為了讓變數id獲得passport屬性: passport; // Uncaught ReferenceError: passport is not defined
解構賦值還可以使用預設值,這樣就避免了不存在的屬性返回undefined
的問題:
var person = { name: '小明', age: 20, gender: 'male', passport: 'G-12345678' }; // 如果person物件沒有single屬性,預設賦值為true: var {name, single=true} = person; name; // '小明' single; // true
有些時候,如果變數已經被聲明瞭,再次賦值的時候,正確的寫法也會報語法錯誤:
// 宣告變數: var x, y; // 解構賦值: {x, y} = { name: '小明', x: 100, y: 200}; // 語法錯誤: Uncaught SyntaxError: Unexpected token =
這是因為JavaScript引擎把{
開頭的語句當作了塊處理,於是=
不再合法。解決方法是用小括號括起來:
({x, y} = { name: '小明', x: 100, y: 200});
使用場景
var x=1, y=2; [x, y] = [y, x];//交換變數 var {hostname:domain, pathname:path} = location;//快速獲得域名和路徑 function buildDate({year, month, day, hour=0, minute=0, second=0}) { return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second); } //如果一個函式接收一個物件作為引數,那麼,可以使用解構直接把物件的屬性繫結到變數中。
閉包
函式的返回依然是函式,巢狀函式可以訪問外層函式的區域性變數,從而攜帶了狀態,模擬物件的效果。
function lazy_sum(arr) { var sum = function () { return arr.reduce(function (x, y) { return x + y; }); } return sum; }
坑
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
上面程式碼中的arr
內部的i
會覆蓋之前入隊的值,因為是同一個引用。
返回閉包時牢記的一點就是:返回函式不要引用任何迴圈變數,或者後續會發生變化的變數。
如果一定要引用迴圈變數怎麼辦?方法是再建立一個函式,用該函式的引數繫結迴圈變數當前的值,無論該迴圈變數後續如何更改,已繫結到函式引數的值不變(實質是對迴圈變數進行值複製,讓最內層函式指向不同記憶體):
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錯誤 */
閉包就是攜帶狀態的函式,並且它的狀態可以完全對外隱藏起來。
'use strict'; function create_counter(initial) { var x = initial || 0; return { inc: function () { x += 1; return x; } } }
它用起來像這樣:
var c1 = create_counter(); c1.inc(); // 1 c1.inc(); // 2 c1.inc(); // 3 var c2 = create_counter(10); c2.inc(); // 11 c2.inc(); // 12 c2.inc(); // 13