java中断槽处有圆点和箭头_曾经我以为我很懂箭头函数

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

问题描述

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

class Animal {

sayName = () => {

throw new Error('你应该自己实现这个方法');

}

}

复制代码

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

class Pig extends Animal {

sayName() {

console.log('I am a Pig');

}

}

复制代码

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

1754116eab1f3ea7cd8ee820239ca0a1.png

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

发现问题

经过一顿折腾之后,最后发现是箭头函数的坑。我们只需要把 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 < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

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;

}();

复制代码

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

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 的语法。对于这种写法声明的变量,其实是会直接挂载到实例的属性上面,而不是挂载到原型链上。

本文地址在->本人博客地址, 欢迎给个 start 或 follow

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[曾经我以为我很懂箭头函数]http://www.zyiz.net/tech/detail-134448.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一个简单的轮播图实现的代码示例。以下是基于 JavaScript 和 HTML/CSS 的轮播图示例代码: HTML: ```html <div class="slider"> <div class="slides"> <img src="image1.jpg"> <img src="image2.jpg"> <img src="image3.jpg"> </div> <div class="arrow prev"></div> <div class="arrow next"></div> <div class="dots"> <span class="dot active"></span> <span class="dot"></span> <span class="dot"></span> </div> </div> ``` CSS: ```css .slider { position: relative; overflow: hidden; } .slides { display: flex; width: 100%; } .slides img { width: 100%; height: auto; } .arrow { position: absolute; top: 50%; transform: translateY(-50%); width: 50px; height: 50px; background: #fff; cursor: pointer; z-index: 2; } .prev { left: 20px; } .next { right: 20px; } .dots { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); display: flex; justify-content: center; align-items: center; } .dot { width: 10px; height: 10px; border-radius: 50%; background: #fff; margin: 0 10px; cursor: pointer; transition: all 0.3s ease; } .dot.active { transform: scale(1.5); } ``` JavaScript: ```js const slides = document.querySelector(".slides"); const images = document.querySelectorAll(".slides img"); const prev = document.querySelector(".prev"); const next = document.querySelector(".next"); const dots = document.querySelectorAll(".dot"); let index = 0; function updateSlide() { slides.style.transform = `translateX(-${index * 100}%)`; } function setActiveDot() { dots.forEach((dot, i) => { dot.classList.remove("active"); if (i === index) { dot.classList.add("active"); } }); } function prevSlide() { index--; if (index < 0) { index = images.length - 1; } updateSlide(); setActiveDot(); } function nextSlide() { index++; if (index >= images.length) { index = 0; } updateSlide(); setActiveDot(); } function autoPlay() { setInterval(() => { nextSlide(); }, 3000); } prev.addEventListener("click", prevSlide); next.addEventListener("click", nextSlide); dots.forEach((dot, i) => { dot.addEventListener("click", () => { index = i; updateSlide(); setActiveDot(); }); }); autoPlay(); ``` 这段代码实现了一个基本的轮播图,包括箭头圆点和自动播放效果。其中 `slides` 是轮播图容器,`images` 是轮播图中的图片,`prev` 和 `next` 是箭头元素,`dots` 是圆点容器,`index` 是当前轮播图的索引。 `updateSlide()` 函数用于更新轮播图的位置,`setActiveDot()` 函数用于设置当前圆点的样式。`prevSlide()` 和 `nextSlide()` 函数分别用于向前和向后切换轮播图。`autoPlay()` 函数用于自动播放轮播图,每隔 3 秒钟自动切换到下一张轮播图。 最后,通过事件监听器将箭头圆点与对应的函数绑定,启动自动播放效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值