博客原文地址:Claiyre的個人博客
如需轉載,請在文章開頭注明原文地址
在許多傳統的OO語言中,對象可以包含數據,還可擁有方法,也就是屬於該對象的函數。但在JavaScript中,函數也被認為是一個對象,一個Function對象,因此函數被稱為JavaScript的一級公民
( first-class objects)!
普通函數定義與初始化
1.通過構造器Function
我們知道,JavaScript中Array,Date等基本類型的聲明是通過其對應的構造器(關鍵字)來聲明的。如
var arr1 = new Array(); #聲明一個空數組
var date1 = new Date(); #聲明一個時間對象,獲取當前時間
與定義其他基本對象類型如Array,Date一樣,JavaScript的函數是通過關鍵字Function來定義並初始化的
new Function([arg1],[arg2](,[arg3,...]),functionBody);
舉個栗子
var f1 = new Function('name','age','console.log("hello," + name);console.log("You are " + age)');
f1("Claiyre",20);
當然,除了這種‘吃力不討好’的定義方法外,你還有其他更簡潔的方法來定義一個函數!
2.通過函數聲明(function statement)
語法:
function name([arg1,[,arg2,[...argn]]]) {
functionBody;
}
name是指定的函數名,arg是函數的參數,可以為任意多個(當然,不能超過可以表示的范圍),functionBody為函數體。
栗子如下
function sayHi (name,age){
console.log("hello," + name);
console.log("you are " + age);
}
3.通過函數表達式(function expression)
語法
function [name]([arg1,[,arg2,[...argn]]]) {
console.log("hello," + name);
console.log("you are " + age);
}
4.總結
- 方式2和方式3擁有幾乎相同的語法,極其相似。但聰明的你一定發現了它們的不同之處了對不對O(∩_∩)O——函數表達式定義一個函數時可以省略函數名,從而創建一個匿名函數或一個立即執行函數(IIFE)。
- 相比於方式2和方式3,方式1用Function構造器聲明函數的方式更為低效,因為后者生成的對象是在函數被創建時解析的,前者是和其他代碼一起解析的。
- 通過函數調用的方式調用Function 的構造函數(不用new關鍵字)跟方式1是一樣的
- 使用Function構造器,並不會在創建它的上下文(Context)中創建閉包,所以它們只能訪問自身局部變量和全局變量,相當於它們總是在全局作用域中被創建的,即便實際並不是!
myName = 'outer';
var person = {
myName: 'inner',
sayHi: new Function('like','console.log("I am "+ myName + "!I like " + like)')
};
person.sayHi("banana");
結果如下:
generator(生成器)函數的定義
generator是ES6標准引入的新的數據類型,旨在利用生成器函數返回一個對象,該對象遵守迭代器協議,而generator的環境(context)在每次執行后都會被保存,以便迭代器對象再次使用,每次迭代就相當於生成一個新的函數,這也是命名為generator(函數生成器)的緣故吧。迭代器協議是ES6標准引入的新協議之一,具體參見 the iterable protocol
與普通函數類似,generator函數也有三種定義方式,但與普通函數稍有不同,具體如下
- 通過GeneratorFunction構造器
new GeneratorFunction ([arg1[, arg2[, ...argN]],] functionBody)
- 通過function* 聲明(function* statement)
function* name([param[, param[, ... param]]]) {
statements
}
- 通過function表達式(function expression)
function* [name]([param1[, param2[, ..., paramN]]]) {
statements
}
調用一個生成器函數並不會立即執行它的主體,而是返回一個與該函數對應的迭代器對象,當調用這個迭代器對象的next()方法時,生成器函數的主體才會被執行,直到遇見第一個 yield表達式
紙上談兵終覺淺,我們來看一個例子
function* idMaker(){
var index = 0;
while(index < 3)
yield index++;
}
var gen = idMaker(); // 此時並沒有執行idmaker的主體
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // undefined
即可用yield指定要返回的值,也可用 yield* 委派另一個generator函數對象
如下
function* generatorOne(index){
yield index;
yield* generatorTwo(index);
yield index+10;
}
function* generatorTwo(index){
yield index+1;
yield index+2;
yield index+3;
}
var ge = generatorOne(10);
console.log(ge.next());
console.log(ge.next());
console.log(ge.next());
console.log(ge.next());
console.log(ge.next());
console.log(ge.next());
運行結果如下:
我們可以看到ge.next()返回了一個對象,該對象有兩個屬性,value是其返回的值,done代表迭代器是否迭代到了盡頭
generator就像是一個不用占用任何全局變量就可以記住執行狀態的函數,它的好處當然不只是上面例子那樣簡單,關於優點和用處,個人認為廖雪峰老師寫的已相當不錯,參見generator
箭頭函數的定義
ES6標准有一個深受廣大程序猿喜愛的新屬性,那就是箭頭函數。它的定義就是用一個箭頭。其具體語法如下
let add = x => x+1 ;
add(10); //11
是不是很簡便呢?
當然如果有多個參數的話,需要給所有參數加上括號
let add = (x,y) => x+y ;
add(5,6);
以此類推,如果函數體有多條語句的話,也應該給函數體加上{}
此外,箭頭函數的另一個特殊之處是它的this總是指向詞法作用域,不能被改變,比如:
var myName = "outer";
var person = {
myName:"inner",
age:20
};
var sayHi= ()=>console.log(this.myName);
sayHi(); //outer
sayHi.call(person); //outer
參考資料:
MDN JavaScript