1. 程式人生 > 其它 >C++讀書筆記:函式

C++讀書筆記:函式

JS函式

函式定義

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) {
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引數只能寫在最後,前面用...標識,從執行結果可知,傳入的引數先繫結ab,多餘的引數以陣列形式交給變數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來定義常量,constlet都具有塊級作用域:

'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