1. 程式人生 > 其它 >普通函式與箭頭函式的區別是什麼?

普通函式與箭頭函式的區別是什麼?

前言

在JavaScript中,我們可以有多種方式定義函式,如:函式宣告、函式表示式和箭頭函式

// 函式宣告
function normalFn() {
    return 'normalFn';
}
// 函式表示式
const normalFn = function() {
    return 'normalFn';
}
// 箭頭函式
const arrowFn = () => {
    return 'arrowFn';
}

其中,箭頭函式是在ES2015(ES6)標準中新增的,其語法與 ES6 之前的函式宣告及函式表示式兩種定義方式不同。本文中,將函式宣告和函式表示式兩種定義方式歸為普通函式。

那麼,普通函式和箭頭函式有什麼區別呢?

1. this 指向

在JavaScript中,this的指向是個基礎且重要的知識點。

1.1 普通函式

在普通函式中,this的指向(執行上下文)是動態的,其值取決於函式是如何被呼叫的,通常有以下 4 種呼叫方式:

1)直接呼叫時,其指向為全域性物件(嚴格模式下為 undefined)

function fnc() {
    console.log(this);
}

fnc(); // 全域性物件(global 或 window)

2)方法呼叫時,其指向為呼叫該方法的物件

var obj = {
    fnc1: function(){
        console.log(this === obj);
    }
}
obj.fnc2 = function() {
    console.log(this === obj);
}
function fnc3() {
    console.log(this === obj);
}
obj.fnc3 = fnc3;
obj.fnc1(); // true
obj.fnc2(); // true
obj.fnc3(); // true

3)new 呼叫時,其指向為新建立的例項物件

function fnc() {
  console.log(this);
}

new fnc(); // fnc 的例項 fnc {}

4)call、apply、bind 呼叫時,其指向為三種方法的第一個引數

function fnc() {
  console.log(this);
}

const ctx = { value: 'a' };

fnc.call(ctx);      // { value: 'a' }
fnc.apply(ctx);     // { value: 'a' }
fnc.bind(ctx)();    // { value: 'a' }

在舊版的 JavaScript 中,經常使用 bind 顯式的設定 this 的指向,這種模式通常可以在 ES6 出現之前的某些早期版本的框架(如react)中找到。而箭頭函式的出現則提供了一種更便捷的方式解決此問題。

1.2 箭頭函式

無論如何執行或在何處執行,箭頭函式內部的 this 值始終等於外部函式的值,即箭頭函式不會改變 this 的指向,

const obj = {
  fnc(arr) {
    console.log(this); // obj
    const cb = () => {
      console.log(this); // obj
    };
    arr.forEach(cb);
  }
};

obj.fnc([1, 2, 3]); 

注意:由於箭頭函式沒有自己的 this 指標,通過 call() 、 apply() 和 bind() 方法呼叫時,只能傳遞引數,而不能繫結 this,他們的第一個引數會被忽略。如下:(例子來源於MDN)

var adder = {
  base : 1,

  add : function(a) {
    var f = v => v + this.base;
    return f(a);
  },

  addThruCall: function(a) {
    var f = v => v + this.base;
    var b = {
      base : 2
    };

    return f.call(b, a);
  }
};

console.log(adder.add(1));         // 輸出 2
console.log(adder.addThruCall(1)); // 仍然輸出 2

2. 建構函式

在 JavaScript 中,函式和類的繼承是通過prototype屬性實現的,且prototype擁有屬性constructor指向建構函式,如下:

function fnc() {}
console.lof(fnc.prototype) // {constructor: ƒ}

而採用箭頭函式定義函式時,其是沒有prototype屬性的,也就無法指向建構函式。

const arrowFnc = () => {}

console.log(arrowFnc.prototype) // undefined

針對普通函式與箭頭函式在建構函式上的區別,可以引出一個問題 --箭頭函式可以通過 new 進行例項化嗎?

const arrowFnc = () => {}
const arrowIns = new arrowFnc() // Uncaught TypeError: arrowFnc is not a constructor

答案是【不可以】,那麼為什麼呢?

沒有自己的 this,也就意味著無法呼叫 apply、call 等

沒有 prototype 屬性,而 new 命令在執行時需要將建構函式的 prototype 賦值給新的物件的 _proto_

function newOperator(Con, ...args) {
  let obj = {};
  Object.setPrototypeOf(obj, Con.prototype); // 相當於 obj.__proto__ = Con.prototype
  let result = Con.apply(obj, args);
  return result instanceof Object ? result : obj;
}

更具體的原因是,JavaScript函式兩個內部方法: [[Call]] 和 [[Construct]],當函式被直接呼叫時執行的是 [[Call]] 方法,即直接執行函式體,而 new 呼叫時是執行的 [[Construct]]方法。箭頭函式沒有 [[Construct]]方法,因此不能被用作建構函式進行例項化。

3. 作為方法屬性

'use strict';
var obj = {
  i: 10,
  b: () => console.log(this.i, this),
  c: function() {
    console.log(this.i, this)
  }
}
obj.b(); // undefined, Window{...}
obj.c(); // 10, Object {...}

可以看到,箭頭函式是沒有 this 繫結的,其指向始終與上一級保持一致。

上文中提到,當建構函式或類被 new 呼叫時,其 this 指向為新建立的例項物件,需要注意的是,這裡的 this 是 constructor 中的 this,而不是該函式或類的任意地方。如下所示:

class Person {
  constructor(name) {
    this.name = name;
  }

  getName() {
    console.log(this.name, this);
  }
}

const p = new Person('Tom');

p.getName();                // Tom
setTimeout(p.getName, 100); // undefined, Window{...}

為了避免這種錯誤,我們通常需要在 constructor 中繫結 this,如下所示:

class Person {
  constructor(name) {
    this.name = name;
    this.getName = this.getName.bind(this);
  }

  getName() {
    console.log(this.name);
  }
}
const p = new Person('Tom');
setTimeout(p.getName, 100); // Tom

這種寫法,很容易在 React 中發現,其實也是為了填補 JavaScript 的坑。當然,也可以使用箭頭函式來避免這種錯誤,並簡化寫法,如下:

class Person {
  constructor(name) {
    this.name = name;
  }

  getName = () => {
    console.log(this.name);
  }
}
const p = new Person('Tom');
setTimeout(p.getName, 100); // Tom

在使用箭頭函式時,this 是具有詞法約束的,也就是說箭頭函式會自動將 this 繫結到定義它的上下文。

4. 引數

普通函式與箭頭函式在引數上區別主要在於,箭頭函式不繫結arguments物件。

const fn = () => arguments[0];
fn(1); // Uncaught ReferenceError: arguments is not defined

當我們需要使用引數時,可以考慮使用剩餘引數,如下:

const fn = (...args) => args[0];
fn(1, 2); // 1

另外,當函式引數個數為 1 時,箭頭函式可以省略括號,進行縮寫,如下所示:

const fn = x => x * x;

https://www.98891.com/article-87-1.html

5. 返回值

在處理函式的返回值時,相比於普通函式,箭頭函式可以隱式返回。

const sum = (a, b) => {
  return a + b
}
const sum = (a, b) => (a + b);

隱式返回通常會建立一個單行操作用於 map、filter 等操作,注意:如果不能將函式主題編寫為單行程式碼的話,則必須使用普通的函式體語法,即不能省略花括號和 return。

[1,2,3].map(i => i * 2); // [2,4,6]
[1,2,3].filter(i => i != 2); // [1,3]

總結

本文主要介紹了普通函式與箭頭函式的區別,相對於普通函式來說,ES6 箭頭函式的主要區別如下:

箭頭函式不繫結arguments,可以使用...args代替;

箭頭函式可以進行隱式返回;

箭頭函式內的this是詞法繫結的,與外層函式保持一致;

箭頭函式沒有prototype屬性,不能進行 new 例項化,亦不能通過 call、apply 等繫結 this;

在定義類的方法時,箭頭函式不需要在constructor中繫結this。