前言
在 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。