目錄
函數的聲明
函數的屬性與特性
函數作用域
函數參數的傳遞方式
arguments 對象
閉包
函數是一段可以反複調用的代碼塊。函數還能接受輸入的參數,不同的參數會傳回不同的值。
函數的聲明
- function指令
function print(s) {
console.log(s);
}
- 函數表達式
var print = function(s) {
console.log(s);
};
- Function 構造函數
var add = new Function(
'x',
'y',
'return x + y'
);
// 等同于
function add(x, y) {
return x + y;
}
你可以傳遞任意數量的參數給
Function
構造函數,隻有最後一個參數會被當做函數體,如果隻有一個參數,該參數就是函數體。
函數的屬性與特性
- name屬性
函數的
name
屬性傳回函數的名字。
function f1() {}
f1.name // "f1"
// 如果是通過變量指派定義的函數,那麼name屬性傳回變量名。
var f2 = function () {};
f2.name // "f2"
- leng屬性
函數的
length
屬性傳回函數預期傳入的參數個數,即函數定義之中的參數個數。
function f(a, b) {}
f.length // 2
不管調用時傳入多少個參數,leng屬性值始終等于2,
length
屬性就是定義時的參數個數,與調用無關。
- 函數名提升
JavaScript 引擎将函數名視同變量名,是以采用
function
指令聲明函數時,整個函數會像變量聲明一樣,被提升到代碼頭部。是以,下面的代碼不會報錯。
f();
function f() {}
函數作用域
作用域分全局作用域 和 函數作用域,全局作用域,變量在整個程式中一直存在,所有地方都可以讀取;函數作用域,變量隻在函數内部存在。
- 在函數内部定義的變量,外部無法讀取,稱為“局部變量”。
function f(){
var v = 1;
}
v // ReferenceError: v is not defined
- 函數内部定義的變量,會在該作用域内覆寫同名全局變量。
var v = 1;
function f(){
var v = 2;
console.log(v);
}
f() // 2
v // 1
- 函數作用域内部也會産生“變量提升”現象。
指令聲明的變量,不管在什麼位置,變量聲明都會被提升到函數體的頭部var
- 函數本身也是一個值,也有自己的作用域。就是其聲明時所在的作用域,與其運作時所在的作用域無關(與調用無關)。
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
// 函數執行時所在的作用域,是定義時的作用域,而不是調用時所在的作用域。
var x = function () {
console.log(a);
};
function y(f) {
var a = 2;
f();
}
y(x)
// ReferenceError: a is not defined
// 同樣的,函數體内部聲明的函數,作用域綁定函數體内部。
function foo() {
var x = 1;
function bar() {
console.log(x);
}
return bar;
}
var x = 2;
var f = foo();
f() // 1
函數參數的傳遞方式
- 函數參數如果是原始類型的值(數值、字元串、布爾值),傳遞方式是傳值傳遞
這意味着,在函數體内修改參數值,不會影響到函數外部。
var p = 2;
function f(p) {
p = 3;
}
f(p);
p // 2
- 函數參數是複合類型的值(數組、對象、其他函數),傳遞方式是傳址傳遞。
也就是說,傳入函數的原始值的位址,是以在函數内部修改參數,将會影響到原始值。
var obj = { p: 1 };
function f(o) {
o.p = 2;
}
f(obj);
obj.p // 2
如果函數内部修改的,不是參數對象的某個屬性,而是替換掉整個參數,這時不會影響到原始值。
var obj = [1, 2, 3];
function f(o) {
o = [2, 3, 4];
}
f(obj);
obj // [1, 2, 3]
這是因為,形式參數(
o
)的值實際是參數
obj
的位址,重新對
o
指派導緻
o
指向另一個位址,儲存在原位址上的值當然不受影響。
就是說 o 其實作為參數代表數組 [1,2,3] 的位址,又被賦予了 [2,3,4] 的位址,是以不會影響到 obj。
arguments 對象
- 由于 JavaScript 允許函數有不定數目的參數,是以需要一種機制,可以在函數體内部讀取所有參數。這就是
對象的由來。arguments
arguments
對象包含了函數運作時的所有參數,
arguments[0]
就是第一個參數,
arguments[1]
就是第二個參數,以此類推。這個對象隻有在函數體内部,才可以使用。
- 雖然
很像數組,但它是一個對象。數組專有的方法(比如arguments
和slice
),不能在forEach
對象上直接使用。arguments
-
對象帶有一個arguments
屬性,傳回它所對應的原函數。callee
var f = function () {
console.log(arguments.callee === f);
}
f() // true
閉包
了解閉包,首先必須了解變量作用域。前面提到,JavaScript 有兩種作用域:全局作用域和函數作用域。函數内部可以直接讀取全局變量。但是函數外部無法讀取函數内部聲明的變量。出于種種原因,需要得到函數内的局部變量。正常情況下,這是辦不到的,隻有通過變通方法才能實作。那就是在函數的内部,再定義一個函數。
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
上面代碼中,函數
f1
的傳回值就是函數
f2
,由于
f2
可以讀取
f1
的内部變量,是以就可以在外部獲得
f1
的内部變量了。
閉包就是函數
f2,
由于在 JavaScript 語言中,隻有函數内部的子函數才能讀取内部變量,是以可以把閉包簡單了解成“定義在一個函數内部的函數”。
閉包的最大用處用兩個:
- 可以讀取外層函數内部的變量
- 讓外層函數的變量始終保持在記憶體中
為什麼閉包能夠傳回外層函數的内部變量:
閉包用到了外層變量,導緻外層函數不能從記憶體釋放。隻要閉包沒有被垃圾回收機制清除,外層函數提供的運作環境也不會被清除,它的内部變量就始終儲存着目前值,供閉包讀取。