技术篇 - 普通函数 和 箭头函数之间的区别

253 阅读7分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

hello, 大家好,我是前端学长Joshua (公众号)
热心于做开源,写文章。目的为帮助在校大学生,刚入职场的小伙伴可以尽快搭建自己的前端学习体系。
如果你有学习上的困惑,欢迎关注我,找我交流,我实时回复大家

初识普通函数 和 箭头函数

通常,我们要定义一个函数,有很多种方式。

目前大体可以分为两大类,
一类是通过function关键字来定义一个函数, 叫做 普通函数
另外一类就是使用ES6的箭头函数的语法,叫做 箭头函数

  1. 普通函数
// 声明式定义一个函数
function read() {
    console.log('前端学长Joshua')
}

// 表达式定义一个函数
const read = function () {
    console.log('前端学长Joshua')
}
  1. 箭头函数
const read = () => {
    console.log('前端学长Joshua')
}

那么,我们就应该会有这样的一个思考,普通函数 和 箭头函数之间有什么区别呢?

普通函数 和 箭头函数之间的区别

this 指向

普通函数

在普通函数里边,this(也称为执行期上下文)的指向是动态的。

动态的执行期上下文,意味this指向取决于普通函数被调用的方式,一共有四种调用普通函数的方式

下面,我们一起来瞧瞧

  1. 直接对普通函数进行简单的调用, this指向全局对象window,在严格模式下是undefined
function read () {
    console.log(this)
}

read() // window
  1. 普通函数作为对象上的方法属性,this指向这个对象
const myObject = {
  method() {
    console.log(this);
  }
};
// 函数指向
myObject.method(); // myObject
  1. 通过 bind / apply 来改变this指向的,this指向call / apply的第一个参数
function myFunction() {
  console.log(this);
}

const myContext = { value: 'A' };

myFunction.call(myContext);  // logs { value: 'A' }
myFunction.apply(myContext); // logs { value: 'A' }
  1. 通过 new 关键字调用普通函数(作为构造函数),this指向构造出来的对象实例
function MyFunction() {
  console.log(this);
}

new MyFunction(); // MyFunction函数构造出来的对象实例

箭头函数

在箭头函数中,是没有自己的执行期上下文的。

箭头函数的this是确定的,在定义函数时就被确定下来的,于外层的函数绑定好的

无论箭头函数在哪里执行,怎么执行,this永远指向外层包裹的函数的this

更加准确的说,箭头函数的this是指向外层包裹,最接近自己的普通函数的this

const myObject = {
  myMethod(items) {
    console.log(this); // logs myObject  

    const callback = () => {
      console.log(this); // logs myObject    
    };

    items.forEach(callback);
  }
};

myObject.myMethod([1, 2, 3])

上边例子: 箭头函数 callback() 中的 this 值等于外部函数 myMethod() 的 this 值。

关于箭头函数this,这个词法解析,是箭头函数的一大特点。

我们在使用箭头函数时,要记住箭头函数是没有自己的this的,它的this就是外边最接近的普通函数的this

所以,我们以后就可以避免写:

const self = this
或
callback.bind(this)

关于call / apply这点
与普通函数相反,使用 myArrowFunc.call(thisVal) 或 myArrowFunc.apply(thisVal) 间接调用箭头函数不会改变 this 的值:上下文值始终按词法解析。

但是,如果是在全局下定义的箭头函数, this指向window

var read = () => {
    console.log(this)
}

var obj = {
    read: read
}

obj.read() // window

构造函数

普通函数

通过普通函数,可以很轻易地构造出一个对象实例出来

function Car(color) {
  this.color = color;
}

const redCar = new Car('red');
redCar instanceof Car; // => true

Car 是一个普通函数,使用new关键字调用这个函数时,就会有一个Car的对象实例被创建出来

箭头函数

由于this的词法解析的结果,箭头函数不能用作构造函数。

如果您尝试调用以 new 关键字为前缀的箭头函数,JavaScript 会抛出错误:

const Car = (color) => {
  this.color = color;
};

const redCar = new Car('red'); // TypeError: Car is not a constructor 

调用 new Car('red'),其中 Car 是一个箭头函数,会抛出 TypeError: Car is not a constructor。

arguments 对象

普通函数

在普通函数中,关键字arguments是类数组对象,它一系列函数执行时传递的参数

function myFunction() {
  console.log(arguments);}

myFunction('a''b'); // logs { 0'a'1'b'length2 }

在 myFunction() 内部,参数是一个类似数组的对象,其中包含调用参数:'a' 和 'b'。

箭头函数

箭头函数, 是没有arguments关键字。

与this相同,arguments 关键字在词法上的解析:箭头函数从外部函数访问arguments。
也就是, this / arguments永远指向外层包裹的函数的 this / arguments

function outer() {
  const inner = () => {    
      console.log(arguments);  
    }
  inner('c''d');
}

outer('a''b'); // logs { 0'a'1'b', length: 2 }

我们可以看见,箭头函数inner, 执行时被传递了参数 - 'c', 'd'。但是箭头函数inner内部打印时,是打印外部的函数outer的arguments

疑问,那么如果箭头函数 希望像 普通函数获取arguemts关键字那样方便的获取参数地话,要怎么办呢?

好想法,我们可以使用...操作符

function myRegularFunction() {
  const myArrowFunction = (...args) => {    
      console.log(args);  
    }
  myArrowFunction('c''d');
}

myRegularFunction('a''b'); // logs ['c', 'd']

执行箭头函数时,...args就是会收集箭头函数的参数 --- ['c', 'd']

隐形的 return

普通函数

在普通函数中,有return XXX 就返回 XXX
如果是 return 或者 是没有 return, 就是返回undefined

function myFunction() {
  return 42;
}

function myEmptyFunction() {
  42;
}

function myEmptyFunction2() {
  42;
  return;
}

myFunction(); // => 42
myEmptyFunction();  // => undefined
myEmptyFunction2(); // => undefined

箭头函数

在箭头函数中,可以和普通函数进行一样的返回操作,但是有一个简洁的使用方式,不需使用return关键字也可以返回我们需要的值

如果箭头函数只是包含一个表达式,并且您省略了该函数的花括号,则该表达式将被隐式返回。 这些是内联箭头函数。

const increment = (num) => num + 1;

increment(41); // => 42

increment() 箭头只包含一个表达式:num + 1。这个表达式由箭头函数隐式返回,没有使用 return 关键字。

在class中的方法属性

在class中this执行问题

  1. 普通函数
    使用普通函数作为方法属性:
class Hero {
  constructor(heroName) {
    this.heroName = heroName;
  }

  logName() {    console.log(this.heroName);  }}

const batman = new Hero('Batman');

有时候呀,我需要使用batman.logName回调函数,比如是 事件的回调 或者是 setTimeout() 的回调,那么这个时候,会有点小问题

setTimeout(batman.logName, 1000);
// after 1 second logs "undefined"

有解决办法嘛?有, 可以通过call / apply来改变this 指向

setTimeout(batman.logName.bind(batman), 1000);
// after 1 second logs "Batman"

但是,如果代码一多,这就会出现很多样板式的代码,阅读性不高

更好的法子是,在class中使用箭头函数

2,箭头函数

class Hero {
  constructor(heroName) {
    this.heroName = heroName;
  }

  logName = () => {    console.log(this.heroName);  }}

const batman = new Hero('Batman');

setTimeout(batman.logName1000);
// after 1 second logs "Batman"

现在,与普通函数相比,使用箭头定义的方法在词法上将 this 指向了类实例。

当然,你可能和我一样会想,箭头函数的this是怎么和类实例绑定到一起的呢?
我们通过babel编译下:

"use strict";

function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerabletrue,
      configurabletrue,
      writabletrue
    });
  } else {
    obj[key] = value;
  }
  return obj;
}

class Hero {
  constructor(heroName) {
    _defineProperty(this"logName"() => {
      console.log(this.heroName);
    });

    this.heroName = heroName;
  }
}

const batman = new Hero("Batman");

本质上,是通过Object.defineProperty将class的this和logName函数进行绑定。而对于普通函数,是不将this 和 普通对象进行绑定的。

总结

  1. this指向:在普通函数中,是动态的,依赖于函数的调用;在箭头函数中,因为语法解析,this指向是确定的,this是与外层函数绑定的,如果是最外层没有函数就是window
  2. arguments: 在普通函数中,可以获取到所有的参数;在箭头函数中,arguments是指向外层的函数的arguments的。如果想要获取到统一获取到箭头函数的参数,可以使用操作符
  3. return: 箭头函数如果只有一个表达式,这个表达式就会被隐式返回,而且不需要使用return关键字
  4. 我们可以在class中使用箭头函数,this会和类实例进行绑定

上述就是普通函数 与 箭头函数之间的区别,希望有用❤❤❤

动下小手

  • 欢迎关注我的GitHub:@huangyangquang ⭐⭐
  • 欢迎关注我的公众号:前端学长Joshua