普通函数与箭头函数的区别是什么?

更新日期: 2021-05-20 阅读: 2k 标签: 函数

前言

在 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;


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。

来自:https://github.com/iChengbo/comments/issues/11


本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://fly63.com/article/detial/10302

相关推荐

JavaScript push() 方法详解

push() 方法主要用于向数组的末尾添加一个或多个元素,其返回值为添加后新的长度,即push后的数组长度,该值为number类型。介绍:一个数组中添加新元素、把一个数组的值赋值到另一个数组上、在对象使用push

什么是纯函数_以及为什么要用纯函数?

当我第一次听到 “纯函数 (Pure Function)” 这个术语的时候我很疑惑。常规的函数做错了什么?为什么要变纯? 为什么我需要纯的函数?除非你已经知道什么是纯函数,否则你可能会问同样的疑惑

让我们来创建一个JavaScript Wait函数

Async/await以及它底层promises的应用正在猛烈地冲击着JS的世界。在大多数客户端和JS服务端平台的支持下,回调编程已经成为过去的事情。当然,基于回调的编程很丑陋的。

什么是函数的副作用——理解js编程中函数的副作用

函数副作用是指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。副作用的函数不仅仅只是返回了一个值,而且还做了其他的事情

js中sort函数用法总结_sort排序算法原理

js中sort方法用于对数组的元素进行排序,并返回数组。默认排序顺序是根据字符串Unicode码点。如果要得到自己想要的结果,不管是升序还是降序,就需要提供比较函数了。该函数比较两个值的大小,然后返回一个用于说明这两个值的相对顺序的数字

javascript封装函数

使用函数有两步:1、定义函数,又叫声明函数, 封装函数。2、调用函数var 变量 = 函数名(实参);对函数的参数和返回值的理解

js中reduce()方法

reduce() 方法接收一个函数作为累加器,reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(上一次回调的返回值),当前元素值,当前索引,原数组。

javascript回调函数的理解和使用方法(callback)

在js开发中,程序代码是从上而下一条线执行的,但有时候我们需要等待一个操作结束后,再进行下一步操作,这个时候就需要用到回调函数。 在js中,函数也是对象,确切地说:函数是用Function()构造函数创建的Function对象。

js调用函数的几种方法_ES5/ES6的函数调用方式

这篇文章主要介绍ES5中函数的4种调用,在ES5中函数内容的this指向和调用方法有关。以及ES6中函数的调用,使用箭头函数,其中箭头函数的this是和定义时有关和调用无关。

js构造函数

JS中的函数即可以是构造函数又可以当作普通函数来调用,当使用new来创建对象时,对应的函数就是构造函数,通过对象来调用时就是普通函数。在我们平时工作中,经常会需要我们创建一个对象,而我们更多的是使用对像直接量,直接创建

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!