(JS基礎)Function

定義函數

函數本質是對象。每一個函數都是 Function 類型的實例。
javascript

定義函數有兩種方式:函數聲明、函數表達式。
java

函數聲明

使用 "function" 關鍵字 (會變量提高,但不建議這樣作),代碼以下:
編程

// 變量提高
console.log(fn);  // [Function: fn]
// "function
function fn() {
  // 函數內容...
}複製代碼

函數表達式

使用 "var" 或 "let" 關鍵字 ("var"會致使聲明提高,一樣不建議這樣作),代碼以下:數組

// 聲明提高
console.log(fn1);   // undefined
var fn1 = function () {
  // 處理代碼...
}
// "let" 聲明不會被提高
console.log(fn1);   // 報錯!!!
let fn2 = function () {
  // 處理代碼...
}複製代碼

其實函數表達式是先定義了匿名函數(也叫拉姆達函數),再將匿名函數賦值給變量。
promise

其餘

  • 函數沒有重載
JavaScript 的函數 沒有重載,但能夠經過判斷參數個數或參數是否爲 undefined 來區別處理。看以下例子:
function plus(num1, num2) {
  if (!num1) {
    return 0
  }
  if (num2) {
    return num1 + num2
  }
  return num1
}
console.log(plus());      // 0
console.log(plus(11));    // 11
console.log(plus(2, 1));  // 3複製代碼
  • 儘可能不要使用Funtion()new Funtion()建立函數
let fn1 = new Function('return 1');
let fn2 = Function('return 2');
console.log(fn1(), fn2());    // 1 2複製代碼

雖然以上代碼無語法錯誤,但和eval()函數同樣,可能會被瀏覽器阻止
瀏覽器


ES6

函數參數默認值及解構賦值

參數定義時,能夠爲其提供默認值。這部份內容和解構賦值有關,點擊連接看另外一篇文章。下面是簡單的例子:閉包

// 參數的默認值
function fn1(x, y = 2) {
  console.log(x, y)
}
// 參數的解構賦值
function fn2({ x, y }) {
  console.log(x, y)
}
// 參數聲明時使用解構賦值
function fn3({x, y = 5} = {}) {
  console.log(x, y);
}
// 使用展開運算符和解構賦值
function fn4(...value) {
  console.log(value)
}
fn1(10);                // 10 2
fn2({ y: 22, x: 11 });  // 11 22
fn3();                  // undefined 5
fn4(1, 2, 3);           // [1,2,3]
複製代碼

函數對象的 length 屬性

length 屬性能夠獲取沒有指定默認值的參數個數。看下面例子:app

function fn1(x, y) { }
function fn2(x, y = 1) { }
console.log(fn1.length);    // 2
console.log(fn2.length);    // 1複製代碼

箭頭函數 =>

箭頭函數的最大特色就是"綁定" this 的指向,使其指向定義時所在的對象。基本語法以下:異步

// 箭頭後的圓括號表示直接返回的值
let addOne = (x, y) => (value + 1)
// 能夠像普通匿名函數同樣,處理和返回值 (單個參數能夠省略箭頭左邊的括號)
let addTen = value => {
  value += 10;
  return value
}
console.log(addOne(1, 2));   // 3
console.log(addTen(1));      // 11複製代碼

使用注意點async

  • 箭頭函數內部不存在"this"對象,因此"this"指向定義時所在的對象,而不是使用時所在的對象。
  • 不能做爲構造函數,即不能使用new運算符。
  • 箭頭函數內部不存在"arguments"對象,但可使用擴展運算符(...)。
  • 不能用做 Generator 函數,即不能使用 yield 運算符。

函數對象的 name 屬性

函數的 name 屬性,返回該函數的函數名。

function foo() {}
foo.name              // "foo"
// 函數賦值,但name仍返回原來函數的名字
baz = foo;
baz.name              // "foo"
// 綁定的函數會被加上"bound "
foo.bind({}).name     // "bound foo"
// 匿名函數,返回空字符串
(function(){}).name   // ""
複製代碼

Promise 對象

關於Promise的介紹在另外一篇文章

異步函數 (async function)

"async function" 的本質是Promise對象,Promise的語法糖。因此,一樣可使用then()catch()。其內部能使用(也只能在其內部使用)的一個關鍵字await表示等待該行代碼執行結束,而後再繼續後面的代碼。與Promise不一樣,"async function" 就是函數,只有被調用時纔會執行,而非定義後立刻執行。下面給出簡單例子:

// 2s 以後返回雙倍的值
function doubleAfter2seconds(num) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2 * num)
        }, 2000);
    } )
}
// 定義async函數
async function testResult() {
    console.log('---2s後打印resolve---');
    let result = await doubleAfter2seconds(30);
    setTimeout(() => {
        console.log('再過1.5s後打印"await"後語句')
    }, 1500)
    console.log(`result:${result}`);
}
testResult(30);
/* 打印結果: ---2s後打印resolve--- result:60 再過1.5s後打印"await"後語句 */複製代碼

爲了加深理解,能夠試試下面的一道題:

async function async1() {
  console.log( 'async1 start' );
  await async2();
  console.log( 'async1 end' );
}
async function async2() {
  console.log( 'async2' );
}
 
console.log( 'script start' );
setTimeout( function () {
  console.log( 'setTimeout' );
}, 0);
async1();
new Promise(function (resolve) {
  console.log( 'promise1' );
  resolve();
}).then(function () {
  console.log( 'promise2' );
});
console.log( 'script end' );複製代碼

結果以下:

script start
async1 start
async2
promise1
script end
promise2
async1 end
setTimeout複製代碼

迭代器 (Iterator)

迭代器是一種特殊對象,它具備一些專門爲迭代過程設計的專有接口,全部的迭代器對象都有一個next()方法,每次調用都返回一個結果對象。結果對象有兩個屬性:一個是value,表示下一個將要返回的值;另外一個是done,它是一個布爾類型的值,當沒有更多可返回數據時返回true。迭代器還會保存一個內部指針,用來指向當前集合中值的位置,每調用一次next()方法,都會返回下一個可用的值。若是在最後一個值返回後再調用next()方法,那麼返回的對象中屬性done的值爲true,屬性value若未定義,則爲undefined

以爲複雜的話,想一想數組對象,它就是內置迭代器,循環遍歷時(如for),就是調用迭代器,每一回的循環都.next().value獲取值,直到結束。

下面以Array對象的values()方法獲取一個迭代器:

let arr = ['a', 4, { a: 1 }];
let myIterator = arr.values();
console.log(myIterator.next().value);   // 'a'
console.log(myIterator.next().value);   // 4
console.log(myIterator.next().value);   // { a: 1 }
console.log(myIterator.next().done);    // true複製代碼


生成器 (Generator)

生成器是生成迭代器的函數,經過function *function*定義,函數中的特有關鍵字yield,表示當前迭代器返回的值,直到迭代器使用next(),則跳到下一個yield停頓,有點相似於return。下面給出簡單例子:

const arr = ['blue', 'green', 'red'];
function *creatIterator(arr) {
  for (let i = 0; i < arr.length; i++) {
    // 表示每次調用都返回arr[i]的值,與return相似
    yield arr[i];
  }
}
const item = creatIterator(arr);
console.log('第一次調用:' + item.next().value);
console.log('第二次調用:' + item.next().value);
console.log('第三次調用:' + item.next().value);
console.log('第四次調用:' + item.next().done);
/* 打印結果: 第一次調用:blue 第二次調用:green 第三次調用:red 第四次調用:true */複製代碼


函數體內部的屬性

函數被定義時,則自動建立兩個屬性:argumentsthis,且只能在函數內部使用。函數執行時會建立caller屬性,指向調用本函數的外部函數。注意,這三個屬性都在嚴格模式下('use strict')沒法使用

arguments 對象

arguments 是一個對應於傳遞給函數的參數的類數組對象。不是數組,但能夠轉化成數組。函數內部一定存在的一個對象。看下面一個例子:

function fn () {
  console.log(arguments);
}
fn('a', '', 'c');    // { '0': 'a', '1': '', '2': 'c' }複製代碼

能夠看出 arguments 依次包含 3 個參數。

arguments 對象內部還有 3 個不可遍歷的屬性

  • argument.length參數的總個數
  • argument.callee:指向函數自己,可用於遞歸函數,但嚴格模式下調用會報錯
  • "迭代器":表示可使用 "for-of" 遍歷 arguments 對象。

this 對象

通常來講,this 指向執行本函數的上下文,但能夠經過 "apply" 、 "call" 和 "bind" 方法改變指向。更多詳細描述請查看另外一篇博文

函數執行時的屬性(caller)

當本函數執行時,caller屬性指向調用本函數的外部函數;當本函數執行結束後,caller會被賦值爲null。看以下例子:

function foo() {
  function fn() {
    console.log('fn內部:',fn.caller);
  }
  function baz() {
    let obj = {}
    obj.myFn = fn;
    let globalFn = fn;
    obj.myFn();
    globalFn();
  }
  baz();
  console.dir('fn外部:',fn.caller);
}
foo();
// fn內部: function baz()
// fn內部: function baz()
// fn外部: null複製代碼


遞歸函數

遞歸的定義是函數內部調用函數自身。上面提到能夠用 "arguments.callee" 獲取函數自己,但嚴格模式下是不容許的,看如下例子:

"use strict";
function fn(num) {
  console.log(num--)
  if (num > 0) {
    // 嚴格模式下會報錯
    // arguments.callee(num);
    // 建議使用
    fn(num);
  }
}複製代碼


幾個關於函數的概念

頭等函數 (first-class functions)

當一門編程語言的函數能夠被看成變量同樣用時,則稱這門語言擁有頭等函數。例如,在這門語言中,函數能夠被看成參數傳遞給其餘函數,能夠做爲另外一個函數的返回值,還能夠被賦值給一個變量。如下示例都證實 JavaScript 是擁有頭等函數。

// 函數複製給變量
let foo = function () { }
// 函數返回函數
function baz() {
  return function () { }
}
// 函數做爲參數
function foz(foo) { }複製代碼

高階函數 (higher-order functions)

高階函數是一個接受其餘函數做爲參數將函數做爲返回值返回的函數。如下給出兩種高階函數的例子。

function fn() { }
// 接受函數做爲參數的高階函數
function hof1(fn) { }
// 以函數作爲返回值的高階函數
function hof2() {
  return function () { }
}複製代碼

一元函數 (unary functions)

一元函數是一個只接受一個參數的函數。與之對應的是多元函數 (multivariate functions)。

function uf(value) { }複製代碼

柯里化 (currying)

柯里化是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數且返回結果的新函數的技術。

// 未柯里化的函數
function sum(a, b, c, d) {
  return a + b * c - d
}
// 柯里化後的函數
function curryingSum1(a) {
  return function (b) {
    return function (c) {
      return function (d) {
        return a + b * c - d
      }
    }
  }
}
// 箭頭函數版的柯里化函數
let curryingSum2 = a => b => c => d => a + b * c - d
// 使用柯里化函數
curryingSum2(1)(2)(3)(4); // 3複製代碼

上面例子能夠看得出,箭頭函數比較適合寫柯里化的函數。

函數反作用

函數反作用是指函數在正常工做任務以外對外部環境所施加的影響,函數不只僅只是返回了一個值,並且還作了其餘的事情。以下例子:

let myObject = { a: 1 }
function fn() {
  myObject.a = 11
  return true
}複製代碼

函數反作用的函數也稱爲非純函數 (Impure Function)。

純函數 (pure functions)

輸入輸出數據流全是顯式(Explicit)的,意思是,函數與外界交換數據只有一個惟一渠道 —— 參數和返回值。函數從函數外部接受的全部輸入信息都經過參數傳遞到該函數內部。函數輸出到函數外部的全部信息都經過返回值傳遞到該函數外部。簡單說就是沒有反作用的函數

function add1(a) {
  return a + 1
}複製代碼


閉包函數

閉包函數指的是有權訪問另外一個函數做用域中的變量的函數。這部分是比較大的內容,能夠點擊連接查看詳細。


函數高級用法

本篇不做詳述,另開篇幅整理此類容,請點擊連接

相關文章
相關標籤/搜索