热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

箭头函数的理解

箭头函数的理解
曾经我觉得我自己已经很了解箭头函数了,不可能再被坑了。可是前几天我遇到了一个很奇怪的问题,在苦恼了很久后,发现就是箭头函数带来的坑。因此,就有了这一篇文章~

问题描述

比如我有一个基类 Animal,它有一个基础方法 sayName。之后每一个继承于它的子类,都需要自己实现这个 sayName 方法来证明自己的身份。基类代码实现很简单:

class Animal {
	sayName = () => {
		throw new Error('你应该自己实现这个方法');
  }
}

那么我现在要继承于 Animal 基类来实现一个 Pig 子类,实现也很简单:

class Pig extends Animal {
	sayName() {
		console.log('I am a Pig');
	}
}

诶,这么简单就搞定了吗?哪里有坑啊?然而,实际上跑起来你就会发现,结果不如预期:

诶,为什么会这样呢。到底哪里出现了问题呢?这短短的几行代码,为啥就能报错呢。

发现问题

经过一顿折腾之后,最后发现是箭头函数的坑。我们只需要把 Animal 基类的 sayName 改为 普通函数,或者把 Pig 子类的 sayName 改为箭头函数,就可以解决这个问题。那么,箭头函数到底搞了什么鬼呢?

写到这里,我忽然想起来,这个问题我曾经被一个面试官面试过!当时面试官问的是对于类而言,箭头函数和类普通函数、constructor 里 bind 的函数有什么区别。当时回答的头头是道,结果遇到继承的情况下,就翻水水了。那么要解答上面这个问题,那就先来解答面试的这个问题吧。

箭头函数和类普通函数、constructor 里 bind 的函数有什么区别

为了比较直观的看这个问题,我们可以借助 babel的代码编译结果来更好的看出区别。

首先我们先输入一段简单的代码

class A {
  	constructor() {
		this.b = this.b.bind(this);    	
    }
  
    a() {
    	console.log('a');
    }
	  b() {
    	console.log('b')
    }
    c = () => {
    	console.log('c')
    }
}

我们来看看会babel编译成什么样子:

"use strict";

function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } }

function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i 

编译后的代码有一大半都是辅助的函数,我们可以只看重点的一部分:

var A = /*#__PURE__*/function () {
  function A() {
    _classCallCheck(this, A);

    _defineProperty(this, "c", function () {
      console.log('c');
    });

    this.b = this.b.bind(this);
  }

  _createClass(A, [{
    key: "a",
    value: function a() {
      console.log('a');
    }
  }, {
    key: "b",
    value: function b() {
      console.log('b');
    }
  }]);

  return A;
}();

从编译后的结果,我们可以看出彼此的区别:

  • 普通函数: 在 babel 编译后,会被放在函数的 prototype 上

  • constructor 里 bind 的函数: 在编译后,它不仅会被放在函数的 prototype 里,而且每一次实例化,都会产生一个绑定当前实例上下文的变量(this.b = this.b.bind(this))。

  • 箭头函数:在 babel 编译后,每一次实例化的时候,都会调用 defineProperty 将箭头函数内容绑定在当前实例上下文上。

从编译后的结果来看的话,对于实际开发的时候,如果需要绑定上下文的话,最好还是用箭头函数。因为使用 bind 方式的话,不仅会产生一个 prototype 的函数,每一次实例化都会额外产生多一个函数。

更新

看了下余腾靖的评论,了解到了更本质的东西。

class 对于 = 号声明的方法、变量,都会将其作为实例的属性,而对于非 = 号声明的属性,则是放在原型链上。比如

class A {
    a() {
        
    }
    b = 2;
    c = () => {
    }
}

对于这个类, 在实例化的时候,b, c 会作为实例的属性,而 a 则是放在原型链上。

那么为什么会这样实现呢?其实我们可以看 tc39 的规范里就说到了这一点: Field declarations

对于直接写等号声明的实例,其实就是 Field declarations 的语法,等于直接声明了这样一个实例属性。

回到主题

在我们解决了上一个问题之后,让我们回到主题。了解了类的箭头函数在实际编译情况下的编译结果后,其实对于我们那个问题就比较好理解了。

Q: 为什么子类使用普通函数的方式声明 sayName 的时候,执行就会出问题呢。

A: 子类使用普通函数的方式声明 sayName 的话,子类声明的 sayName 会被放在构造函数的 prototype 上。可是由于基类的 sayName 是使用箭头函数的方式,因此每一个实例都会直接有一个 sayName 变量。根据 Javascript 变量的访问规则,首先会在变量本身上找,找不到后才会在原型链上找。因此,在查找 sayName 的时候,就直接找到基类声明的 sayName 函数了,就不会再在原型链上找,因此就出现了问题。

Q: 为什么子类使用箭头函数的方式声明 sayName,执行就没有问题。

A: es6 的类在初始化的时候,会先执行基类的构造函数,之后再执行本身的构造函数。因此,在基类初始化之后,子类声明的箭头函数 sayName 覆盖了基类的,所以执行就没有问题。

总结

曾经我以为我自己很了解箭头函数了,没想到还是被坑了,果然还是学无止境啊!不过也对类内箭头函数有了更深刻的认识。

但是经过评论区各位大佬的提醒,发现其实并不是箭头函数引起的问题,在 class 里用 = 号声明的变量属于 Field declarations 的语法。对于这种写法声明的变量,其实是会直接挂载到实例的属性上面,而不是挂载到原型链上。

推荐教程:《JS教程》

以上就是箭头函数的理解的详细内容,更多请关注 第一PHP社区 其它相关文章!


推荐阅读
  • 本文介绍了如何使用PHP向系统日历中添加事件的方法,通过使用PHP技术可以实现自动添加事件的功能,从而实现全局通知系统和迅速记录工具的自动化。同时还提到了系统exchange自带的日历具有同步感的特点,以及使用web技术实现自动添加事件的优势。 ... [详细]
  • 2018年人工智能大数据的爆发,学Java还是Python?
    本文介绍了2018年人工智能大数据的爆发以及学习Java和Python的相关知识。在人工智能和大数据时代,Java和Python这两门编程语言都很优秀且火爆。选择学习哪门语言要根据个人兴趣爱好来决定。Python是一门拥有简洁语法的高级编程语言,容易上手。其特色之一是强制使用空白符作为语句缩进,使得新手可以快速上手。目前,Python在人工智能领域有着广泛的应用。如果对Java、Python或大数据感兴趣,欢迎加入qq群458345782。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • PHP玩家基地系统毕业设计(附源码、运行环境)的用户登录界面、游戏管理和玩家作品管理
    本文介绍了一个PHP玩家基地系统的毕业设计,包括用户登录界面、游戏管理和玩家作品管理等功能。附带源码和运行环境,并提供免费赠送本源代码和数据库的方式,请私信获取详细信息。摘要共计约XXX字。 ... [详细]
  • Monkey《大话移动——Android与iOS应用测试指南》的预购信息发布啦!
    Monkey《大话移动——Android与iOS应用测试指南》的预购信息已经发布,可以在京东和当当网进行预购。感谢几位大牛给出的书评,并呼吁大家的支持。明天京东的链接也将发布。 ... [详细]
  • 本文介绍了《中秋夜作》的翻译及原文赏析,以及诗人当代钱钟书的背景和特点。通过对诗歌的解读,揭示了其中蕴含的情感和意境。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • 本文描述了作者第一次参加比赛的经历和感受。作者是小学六年级时参加比赛的唯一选手,感到有些紧张。在比赛期间,作者与学长学姐一起用餐,在比赛题目中遇到了一些困难,但最终成功解决。作者还尝试了一款游戏,在回程的路上感到晕车。最终,作者以110分的成绩取得了省一会的资格,并坚定了继续学习的决心。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的详细步骤
    本文详细介绍了搭建Windows Server 2012 R2 IIS8.5+PHP(FastCGI)+MySQL环境的步骤,包括环境说明、相关软件下载的地址以及所需的插件下载地址。 ... [详细]
author-avatar
houjiajun
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有