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


    前言

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

  • 相关阅读:
    基于maven的profile实现动态选择配置文件
    [公告]博客迁移通知
    itellij idea导入web项目并部署到tomcat
    DWR实现后台推送消息到web页面
    DWR实现扫一扫登录功能
    微信企业号开发[目录]
    微信企业号开发[一]——创建应用
    微信企业号开发[三]——调用微信接口
    微信企业号开发[二]——获取用户信息
    JavaScript编码规范指南
  • 原文地址:https://www.cnblogs.com/qianxiaox/p/14933986.html
Copyright © 2020-2023  润新知