JavaScript函數

一、函數描述

1.1 函數是頭等(first-class)對象

  • 由於它們能夠像任何其餘對象同樣具備屬性和方法。
  • 它們與其餘對象的區別在於函數能夠被調用。
  • 每一個函數其實都是一個Function對象

1.2 返回值

  • 默認返回undefined
  • 使用 return 語句來指定一個要返回的值(使用new關鍵字調用一個構造函數除外)

1.3 函數參數

  • 調用函數時,傳遞給函數的值被稱爲函數的實參(值傳遞),
  • 對應位置的函數參數名叫做形參。
  • 若是實參是一個包含原始值(數字,字符串,布爾值)的變量,則就算函數在內部改變了對應形參的值,返回後,該實參變量的值也不會改變。
  • 若是實參是一個對象引用,則對應形參會和該實參指向同一個對象。假如函數在內部改變了對應形參的值,返回後,實參指向的對象的值也會改變。

1.4 this 指向

  • 在函數執行時,this 關鍵字並不會指向正在運行的函數自己,而是指向調用該函數的對象
  • 若是你想在函數內部獲取函數自身的引用,只能使用函數名或者使用arguments.callee屬性(嚴格模式下不可用),若是該函數是一個匿名函數,則你只能使用後者。

二、函數定義

2.1 函數聲明

2.1.1 用法

function name([參數, 參數, ... 參數) { statements }複製代碼
function myFunc(theObject) {
  theObject.make = "Toyota";
}var mycar = {make: "Honda", model: "Accord", year: 1998};var x, y;

x = mycar.make;     // x獲取的值爲 "Honda"myFunc(mycar);
y = mycar.make;     // y獲取的值爲 "Toyota"// (make屬性被函數改變了)複製代碼

2.1.2 函數聲明提高

  • JavaScript 中的函數聲明被提高到了函數定義,能夠在函數聲明以前使用該函數。
  • 函數聲明同時也建立了一個和函數名相同的變量。所以,以函數聲明定義的函數可以在它們被定義的做用域內經過函數名而被訪問到
hoisted(); // "foo"function hoisted() {     console.log("foo");
}/* equal to*/var hoisted; 

hoisted = function() {  console.log("foo");
}
hoisted();// "foo" 複製代碼

2.2 函數表達式

2.2.1 用法

let 變量名稱 = function 函數名稱(參數, 參數, ...參數) {
   statements
};複製代碼
  • 函數名稱可被省略,此種狀況下的函數是匿名函數(anonymous)
  • 函數名稱只是函數體中的一個本地變量。
const square = function(number) { return number * number; };var x = square(4); // x gets the value 16const factorial = function fac(n) {return n<2 ? 1 : n*fac(n-1)};console.log(factorial(3));複製代碼

2.2.2 沒有提高

 notHoisted(); // TypeError: notHoisted is not a functionvar notHoisted = function() {   console.log('bar');
};複製代碼

2.3 函數生成器聲明 (function* 聲明)

定義一個生成器函數 (generator function),它返回一個  Generator  對象。數組

function* 函數名(參數,參數, ... 參數) { statements }複製代碼
function* generator(i) {  yield i;  yield i + 10;
}const gen = generator(10);console.log(gen.next().value);// expected output: 10console.log(gen.next().value);// expected output: 20複製代碼
  • 生成器函數在執行時能暫停,後面又能從暫停處繼續執行。
  • 調用一個生成器函數並不會立刻執行它裏面的語句,而是返回一個這個生成器的 迭代器 ( iterator )對象。
  • 當這個迭代器的 next() 方法被首次(後續)調用時,其內的語句會執行到第一個(後續)出現yield的位置爲止,yield 後緊跟迭代器要返回的值。
  • 或者若是用的是 yield*(多了個星號),則表示將執行權移交給另外一個生成器函數(當前生成器暫停執行)。
  • next()方法返回一個對象,這個對象包含兩個屬性:value 和 done,
  • value 屬性表示本次 yield 表達式的返回值,
  • done 屬性爲布爾類型,表示生成器後續是否還有 yield 語句,即生成器函數是否已經執行完畢並返回。

2.4 函數生成器表達式 (function*表達式)

  • function* 表達式和 function* 聲明比較類似,並具備幾乎相同的語法。
  • function* 表達式和function* 聲明之間主要區別就是函數名,
  • 即在建立匿名函數時,function*表達式能夠省略函數名
var x = function*(y) {   yield y * y;
};複製代碼

2.5 箭頭函數表達式 (=>)

(param1, param2, …, paramN) => { statements }複製代碼
  • 更簡短的函數
  • 不綁定this

2.5.1 更短的函數

var elements = [  'Hydrogen',  'Helium',  'Lithium',  'Beryllium'];

elements.map(function(element) {  return element.length;
}); // 返回數組:[8, 6, 7, 9]// 上面的普通函數能夠改寫成以下的箭頭函數elements.map((element) => {  return element.length;
}); // [8, 6, 7, 9]// 當箭頭函數只有一個參數時,能夠省略參數的圓括號elements.map(element => { return element.length;
}); // [8, 6, 7, 9]// 當箭頭函數的函數體只有一個 `return` 語句時,能夠省略 `return` 關鍵字和方法體的花括號elements.map(element => element.length); // [8, 6, 7, 9]// 在這個例子中,由於咱們只須要 `length` 屬性,因此可使用參數解構// 須要注意的是字符串 `"length"` 是咱們想要得到的屬性的名稱,而 `lengthFooBArX` 則只是個變量名,// 能夠替換成任意合法的變量名elements.map(({ "length": lengthFooBArX }) => lengthFooBArX); // [8, 6, 7, 9]複製代碼

2.5.2 不綁定this

function Person() {  // Person() 構造函數定義 `this`做爲它本身的實例.
  this.age = 0;  setInterval(function growUp() {// 在非嚴格模式, growUp()函數定義 `this`做爲全局對象,// 與在 Person()構造函數中定義的 `this`並不相同.this.age++;
  }, 1000);
}var p = new Person();複製代碼
<!--經過將this值分配給封閉的變量,能夠解決this問題-->function Person() {  var that = this;
  that.age = 0;  setInterval(function growUp() {// 回調引用的是`that`變量, 其值是預期的對象.that.age++;
  }, 1000);
}複製代碼
<!--箭頭函數不會建立本身的this,它只會從本身的做用域鏈的上一層繼承this-->function Person(){  this.age = 0;  setInterval(() => {this.age++; // |this| 正確地指向 p 實例
  }, 1000);
}var p = new Person();複製代碼

2.6 Function構造函數表達式(不推薦)

//結果爲true證實沒函數都是一個Function對象(function(){}).constructor === Function //true複製代碼

2.6.1 描述

  • 使用 Function 構造函數生成的 Function 對象是在函數建立時解析的。
  • 這比你使用函數聲明或者函數表達式並在你的代碼中調用更爲低效,
  • 由於使用後者建立的函數是跟其餘代碼一塊兒解析的。
  • 以調用函數的方式調用 Function 的構造函數(而不是使用 new 關鍵字) 跟以構造函數來調用是同樣的。
<!--能夠正常執行-->const sum = new Function("a", "b", "return a + b");console.log(sum(2, 6));

<!--能夠正常執行-->const sum2 = Function.prototype.constructor( "a", "b", "return a + b" );console.log(sum2(2, 6));複製代碼
<!--用法-->
new Function (參數, 參數, ...參數, functionBody字符串)

//例如
const sum = new Function('a', 'b', 'return a + b');
console.log(sum(2, 6));
// expected output: 8複製代碼

2.6.2 實例屬性

  • Function.caller 獲取調用函數的具體對象。(存在兼容性問題)
  • Function.length 獲取函數的接收參數個數。
  • Function.name 獲取函數的名稱。

2.6.3 實例方法

  • Function.prototype.apply() 在一個對象的上下文中應用另外一個對象的方法;參數可以以數組形式傳入。
  • Function.prototype.bind()
    • bind()方法會建立一個新函數,稱爲綁定函數.
    • 當調用這個綁定函數時,綁定函數會以建立它時傳入 bind()方法的第一個參數做爲 this,
    • 傳入 bind()方法的第二個以及之後的參數加上綁定函數運行時自己的參數按照順序做爲原函數的參數來調用原函數.
  • Function.prototype.call() 在一個對象的上下文中應用另外一個對象的方法;參數可以以列表形式傳入。
  • Function.prototype.toString() 獲取函數的實現源碼的字符串。覆蓋了 Object.prototype.toString 方法。

2.6.4 與函數聲明、函數表達式區別

  • 和函數表達式同樣沒有函數聲明提高。不能在聲明前調用
  • 不能建立當前環境閉包,只能被建立於全局環境
  • 只能訪問全局變量和本身的局部變量
    const x = 10;function createFunction1() {const x = 20;return new Function("return x;"); // 這裏的 x 指向最上面全局做用域內的 x}function createFunction2() {const x = 20;function f() {return x; // 這裏的 x 指向上方本地做用域內的 x}return f;
    }const f1 = createFunction1();console.log(f1()); // 10const f2 = createFunction2();console.log(f2()); // 20複製代碼

2.6.5 儘可能避免使用的緣由

  • 每次構造函數被調用,傳遞給Function構造函數的函數體字符串都要被解析一次 。安全

  • 雖然函數表達式每次都建立了一個閉包,但函數體不會被重複解析,所以函數表達式仍然要快於"new Function(...)"閉包

  • 在經過解析Function構造函數字符串產生的函數裏,內嵌的函數表達式和函數聲明不會被重複解析。app

2.7 生成器函數的構造函數(不推薦)

  • GeneratorFunction構造器生成新的生成器函數 對象。
  • 在JavaScript中,生成器函數實際上都是GeneratorFunction的實例對象。
  • GeneratorFunction並非一個全局對象。它能夠經過下面的代碼獲取。
Object.getPrototypeOf(function*(){}).constructor複製代碼

2.7.1 語法

new GeneratorFunction ([arg1[, arg2[, ...argN]],] functionBody字符串)複製代碼
var GeneratorFunction = Object.getPrototypeOf(function*(){}).constructorvar g = new GeneratorFunction("a", "yield a * 2");var iterator = g(10);console.log(iterator.next().value); // 20複製代碼

2.7.2 儘可能避免使用的緣由

  • 當建立函數時,將使用GeneratorFunction構造函數建立的生成器函數對象進行解析。
  • 這比使用function* 表達式 聲明生成器函數效率更低,
  • 使用GeneratorFunction構造函數建立的生成器函數不會爲其建立上下文建立閉包;它們始終在全局範圍內建立。
  • 當運行它們時,它們只能訪問本身的本地變量和全局變量,而不是從GeneratorFunction構造函數調用的範圍的變量。

三、函數做用域

  • 在函數內定義的變量不能在函數以外的任何地方訪問,由於變量僅僅在該函數的域的內部有定義。
  • 一個函數能夠訪問其定義所在做用域內的任何變量和函數。例如,定義在全局域中的函數能夠訪問全部定義在全局域中的變量。
  • 在另外一個函數中定義的函數也能夠訪問在其父函數中定義的全部變量和父函數有權訪問的任何其餘變量。
// 下面的變量定義在全局做用域(global scope)中var num1 = 20,
    num2 = 3,
    name = "Chamahk";// 本函數定義在全局做用域function multiply() {  return num1 * num2;
}

multiply(); // 返回 60// 嵌套函數的例子function getScore() {  var num1 = 2,
      num2 = 3;  function add() {return name + " scored " + (num1 + num2);
  }  return add();
}

getScore(); // 返回 "Chamahk scored 5"複製代碼

四、閉包

  • JavaScript 容許函數嵌套,
  • 內部函數能夠訪問定義在外部函數中的全部變量和函數,以及外部函數能訪問的全部變量和函數。
  • 外部函數卻不可以訪問定義在內部函數中的變量和函數。這給內部函數的變量提供了必定的安全性。
  • 因爲內部函數能夠訪問外部函數的做用域,所以當內部函數生存週期大於外部函數時,外部函數中定義的變量和函數的生存週期將比內部函數執行時間長。
  • 當內部函數以某一種方式被任何一個外部函數做用域訪問時,一個閉包就產生了。
var pet = function(name) {          //外部函數定義了一個變量"name"
  var getName = function() {//內部函數能夠訪問 外部函數定義的"name"return name;
  }  //返回這個內部函數,從而將其暴露在外部函數做用域
  return getName;
};
myPet = pet("Vivie");

myPet();    
複製代碼

4.1 命名衝突

  • 當同一個閉包做用域下兩個參數或者變量同名時,就會產生命名衝突。
  • 更近的做用域有更高的優先權,因此最近的優先級最高,最遠的優先級最低。這就是做用域鏈。
  • 鏈的第一個元素就是最裏面的做用域,最後一個元素即是最外層的做用域。
function outside() {  var x = 5;  function inside(x) {return x * 2;
  }  return inside;
}

outside()(10); // returns 20 instead of 10複製代碼

5.arguments對象

  • arguments 是一個對應於傳遞給函數的參數的類數組對象
  • arguments變量只是 」類數組對象「,並非一個數組。
  • 稱其爲類數組對象是說它有一個索引編號和length屬性。
  • 儘管如此,它並不擁有所有的Array對象的操做方法。
  • arguments對象是全部(非箭頭)函數中均可用的局部變量。
function func1(a, b, c) {  console.log(arguments[0]);  // expected output: 1

  console.log(arguments[1]);  // expected output: 2

  console.log(arguments[2]);  // expected output: 3}

func1(1, 2, 3);複製代碼

5.1 屬性

  • arguments.callee  : 當前正在執行的函數。
  • arguments.caller  : 調用當前執行函數的函數。
  • arguments.length  : 傳給函數的參數的數目。

6. 函數參數

從ECMAScript 6開始,有兩個新的類型的參數:默認參數,剩餘參數ide

6.1 默認參數

在JavaScript中,函數參數的默認值是 undefined。在過去,用於設定默認參數的通常策略是在函數的主體中測試參數值是否爲undefined,若是是則賦予這個參數一個默認值。函數

function multiply(a, b) {
  b = (typeof b !== 'undefined') ?  b : 1;  return a*b;
}

multiply(5); // 5複製代碼
<!--使用默認參數,在函數體的檢查就再也不須要-->function multiply(a, b = 1) {  return a*b;
}

multiply(5); // 5複製代碼

6.2 剩餘參數

剩餘參數語法容許將不肯定數量的參數表示爲數組。測試

function multiply(multiplier, ...theArgs) {  return theArgs.map(x => multiplier * x);
}var arr = multiply(2, 1, 2, 3);console.log(arr); // [2, 4, 6]複製代碼
相關文章
相關標籤/搜索