详解JavaScript什么情况下不建议使用箭头函数

目录
  • this指向原理
    • 问题的由来
    • 内存的数据结构
    • 函数
    • 环境变量
  • 箭头函数的缺点
  • 不适用的场景
  • 总结

箭头函数作为ES6新增的语法,在使用时不仅能使得代码更加简洁,而且在某些场景避免this指向问题。但是箭头函数不是万能的,也有自己的缺点以及不适用的场景,虽然可以解决this只想问题,但是也可能会带来this指向问题。具体场景具体分析,本文就深入探讨箭头函数。

箭头函数没有自己的this,其this取决于上下文中定义的this。

this指向原理

问题的由来

学懂 JavaScript 语言,一个标志就是理解下面两种写法,可能有不一样的结果。

var obj = {
  foo: function () {}
};

var foo = obj.foo;

// 写法一
obj.foo()

// 写法二
foo()

上面代码中,虽然obj.foofoo指向同一个函数,但是执行结果可能不一样。请看下面的例子。

var obj = {
  foo: function () { console.log(this.bar) },
  bar: 1
};

var foo = obj.foo;
var bar = 2;

obj.foo() // 1
foo() // 2

这种差异的原因,就在于函数体内部使用了this关键字。很多教科书会告诉你,this指的是函数运行时所在的环境。对于obj.foo()来说,foo运行在obj环境,所以this指向obj;对于foo()来说,foo运行在全局环境,所以this指向全局环境。所以,两者的运行结果不一样。

这种解释没错,但是教科书往往不告诉你,为什么会这样?也就是说,函数的运行环境到底是怎么决定的?举例来说,为什么obj.foo()就是在obj环境执行,而一旦var foo = obj.foofoo()就变成在全局环境执行?

本文就来解释 JavaScript 这样处理的原理。理解了这一点,你就会彻底理解this的作用。

内存的数据结构

JavaScript 语言之所以有this的设计,跟内存里面的数据结构有关系。

var obj = { foo:  5 };

上面的代码将一个对象赋值给变量obj。JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 },然后把这个对象的内存地址赋值给变量obj

也就是说,变量obj是一个地址(reference)。后面如果要读取obj.foo,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的foo属性。

原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo属性,实际上是以下面的形式保存的。

{
  foo: {
    [[value]]: 5
    [[writable]]: true
    [[enumerable]]: true
    [[configurable]]: true
  }
}

注意,foo属性的值保存在属性描述对象的value属性里面。

函数

这样的结构是很清晰的,问题在于属性的值可能是一个函数。

var obj = { foo: function () {} };

这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo属性的value属性。

{
  foo: {
    [[value]]: 函数的地址
    ...
  }
}

由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。

var f = function () {};
var obj = { f: f };

// 单独执行
f()

// obj 环境执行
obj.f()

环境变量

JavaScript 允许在函数体内部,引用当前环境的其他变量。

var f = function () {
  console.log(x);
};

上面代码中,函数体里面使用了变量x。该变量由运行环境提供。

现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。

var f = function () {
  console.log(this.x);
}

上面代码中,函数体里面的this.x就是指当前运行环境的x

var f = function () {
  console.log(this.x);
}

var x = 1;
var obj = {
  f: f,
  x: 2,
};

// 单独执行
f() // 1

// obj 环境执行
obj.f() // 2

上面代码中,函数f在全局环境执行,this.x指向全局环境的x

obj环境执行,this.x指向obj.x

回到本文开头提出的问题,obj.foo()是通过obj找到foo,所以就是在obj环境执行。一旦var foo = obj.foo,变量foo就直接指向函数本身,所以foo()就变成在全局环境执行。

箭头函数的缺点

1.箭头函数没有arguments参数列表,普通函数可以直接获取到

arguments是调用函数时,传递给函数的一个类似数组的对象,几乎所有的函数都有此局部变量,可直接访问并使用传递给函数的参数列表,箭头函数除外。该变量不是数组对象,只是类似于数组,没有数组的常用方法。

let fn1 = () => {
    console.log('arguments', arguments);
}
fn1(1, 2); // arguments is not defined
let fn2 = function() {
    console.log('arguments', arguments);
}
fn2(1, 2); // Arguments对象,可查看具体的参数

2.无法通过apply、call、bind改变this的指向。箭头函数的this默认指向父作用域或者当前调用对象,无法通过call等修改,但是function申明的函数可以修改this

this指向是js中经常容易出错的地方。箭头函数的this指向是固定的,一般都是指向父作用域,默认指向window,不能在apply、call、bind中改变this的指向。普通函数的this指向不是固定的,有可能根据传入的对象改变。

console.log('this1', this); // 指向window
let fn3 = () => {
    console.log('this2', this); // 指向window
}
fn3.call({x: 'y'}); // 传入新的对象
// fn3.apply({x: 'y'});
let fn4 = function() {
    console.log('this3', this); // 指向{x: 'y'}
}
fn4.call({x: 'y'});

不适用的场景

1.对象的方法,不建议使用箭头函数

let obj = {
    key: 'key',
    getKey: () => {
        return this.key;
    },
    getKey2() {
        return this.key;
    }
};
obj.getKey(); // this指向window,返回值取决于window中是否有对应的属性
obj.getKey2(); // this指向obj,返回 'key'

2.对象的原型的方法,不建议使用箭头函数

每个对象都有原型,原型也是一个对象,因此也不能添加箭头函数的方法

let obj = {
    key: 'key'
};
obj.__proto__.getKey = () => {
    console.log('this', this); // this指向window
    return this.key;
}
obj.getKey();

3.箭头函数不能用作构造函数

定义一个构造函数可通过函数定义或者使用class定义一个类。箭头函数不能用作构造函数,可使用普通函数

let fn5 = (userName, passwd) => {
    this.userName = userName;
    this.passwd = passwd;
}
let f1 = new fn5('张三', '123'); // fn5 is not a constructor
console.log(f1.userName);
let fn6 = function (userName, passwd) {
    this.userName = userName;
    this.passwd = passwd;
}
let f2 = new fn6('张三', '123');
console.log(f2.userName); // 张三

4.监听事件中需要使用this时不建议使用箭头函数

比如在addEventListener中,如果要在回调函数中使用this,那么就不建议使用箭头函数,而是应该普通函数,更好的是使用已定义的函数名,便于回收事件监听,避免可能的内存泄漏。

dom.addEventListener('click', () => {
    console.log('this', this); // this指向window
})

5.Vue的生命周期以及methods中的方法不建议使用箭头函数

页面中创建的Vue实例,本质上来说也就是一个对象,其生命周期就是对应的属性,methods也是一个对象。在Vue的生命周期或者methods中使用箭头函数,则this的指向将不是当前Vue实例,而是window对象,如果在方法中使用了this,则可能会抛出错误。

export default {
    mounted() {},
    // mounted: () => {}
    methods: {
        getKey() {},
        // getKey: () => {}
    }
}

总结

  • 箭头函数有优点,也有缺点,不可盲目使用,一定要清楚的知道为什么要使用箭头函数,为什么不能使用箭头函数
  • 箭头函数可解决this指向,也可能带来this指向问题

到此这篇关于详解JavaScript什么情况下不建议使用箭头函数的文章就介绍到这了,更多相关JavaScript箭头函数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • JavaScript哪些场景不能使用箭头函数

    目录 1. 定义对象方法 2.定义原型方法 3. 定义事件回调函数 4. 定义构造函数 1. 定义对象方法  JS 中对象方法的定义方式是在对象上定义一个指向函数的属性,当方法被调用的时候,方法内的 this 就会指向方法所属的对象. let obj = { array: [1, 2, 3], sum: () => { console.log(this === window); // true return this.array.reduce((result, item) => result

  • js中什么时候不能使用箭头函数

    目录 箭头函数 箭头函数有什么缺点? 什么时候不能使用箭头函数? 1. 对象方法中,不适用箭头函数 为什么对象方法中,箭头函数的this指向不是这个对象? 2. 原型方法中,不适用箭头函数 3. 构造函数也不行! 4. 动态上下文中的回调函数 5. Vue 生命周期和 method 中也不能使用箭头函数 划重点 箭头函数 箭头函数是和我们工作密切相关的东西:可以说箭头函数的诞生,给我们的工作带来了极大的便利.但是箭头函数有什么缺点?什么时候不能使用箭头函数? 这你了解吗?我们觉得箭头函数很高级,

  • 详解JavaScript什么情况下不建议使用箭头函数

    目录 this指向原理 问题的由来 内存的数据结构 函数 环境变量 箭头函数的缺点 不适用的场景 总结 箭头函数作为ES6新增的语法,在使用时不仅能使得代码更加简洁,而且在某些场景避免this指向问题.但是箭头函数不是万能的,也有自己的缺点以及不适用的场景,虽然可以解决this只想问题,但是也可能会带来this指向问题.具体场景具体分析,本文就深入探讨箭头函数. 箭头函数没有自己的this,其this取决于上下文中定义的this. this指向原理 问题的由来 学懂 JavaScript 语言,

  • 详解Javascript 中的 class、构造函数、工厂函数

    到了ES6时代,我们创建对象的手段又增加了,在不同的场景下我们可以选择不同的方法来建立.现在就主要有三种方法来构建对象,class关键字,构造函数,工厂函数.他们都是创建对象的手段,但是却又有不同的地方,平时开发时,也需要针对这不同来选择. 首先我们来看一下,这三种方法是怎样的 // class 关键字,ES6新特性 class ClassCar { drive () { console.log('Vroom!'); } } const car1 = new ClassCar(); consol

  • 详解JavaScript的闭包、IIFE、apply、函数与对象

    目录 一.闭包(Closure) 1.1.闭包相关的问题 1.2.理解闭包 二.对象 2.1.对象常量(字面量) 2.2.取值 2.3.枚举(遍历) 2.4.更新与添加 2.5.对象的原型 2.6.删除 2.7.封装 三.函数 3.1.参数对象 (arguments) 3.2.构造函数 3.3.函数调用 3.3.1.call 3.3.2.apply 3.3.3.caller 3.3.4.Callee 3.5.立即执行函数表达式 (IIFE) 3.5.1.匿名函数与匿名对象 3.5.2.函数与函数

  • 详解JavaScript Promise和Async/Await

    概述 一般在开发中,查询网络API操作时往往是比较耗时的,这意味着可能需要一段时间的等待才能获得响应.因此,为了避免程序在请求时无响应的情况,异步编程就成为了开发人员的一项基本技能. 在JavaScript中处理异步操作时,通常我们经常会听到 "Promise "这个概念.但要理解它的工作原理及使用方法可能会比较抽象和难以理解. 四个示例 那么,在本文中我们将会通过实践的方式让你能更快速的理解它们的概念和用法,所以与许多传统干巴巴的教程都不同,我们将通过以下四个示例开始: 示例1:用生

  • 详解JavaScript面向对象实战之封装拖拽对象

    目录 概述 1.如何让一个DOM元素动起来 2.如何获取当前浏览器支持的transform兼容写法 3.如何获取元素的初始位置 5.我们需要用到哪些事件? 6.拖拽的原理 7. 我又来推荐思维导图辅助写代码了 8.代码实现 part1.准备工作 part2.功能函数 part3.声明三个事件的回调函数 9.封装拖拽对象 概述 为了能够帮助大家了解更多的方式与进行对比,我会使用三种不同的方式来实现拖拽. 不封装对象直接实现: 利用原生JavaScript封装拖拽对象: 通过扩展jQuery来实现拖

  • 详解JavaScript实现监听路由变化

    目录 history pushState()方法 pushState()使用场景 replaceState() 方法 popstate事件 pushState和replaceState如何监听呢? 获取当前状态 对比 总结 前端实现路由变化主要有两种方式,这两种方式最大特点就是实现URL切换无刷新功能 通过hash改变,利用window.onhashchange 监听. 通过history的改变,进行js操作加载页面,然而history并不像hash那样简单,因为history的改变,除了浏览器

  • 详解JavaScript中的变量命名规范

    目录 驼峰命名 根据变量类型来命名 普通变量/属性 布尔变量/属性 普通函数/方法 回调.钩子函数 类 注意一致性 介词一致性 顺序一致性 表里一致性 时间一致性 其他注意事项 避免使用不常用的缩写 避免使用容易混淆的字母和数字 避免变量命名过于抽象 驼峰命名 首先,和其他语言一样,大部分变量建议采用驼峰命名法. var articleTitle = 'javascript变量命名规范' 而对于常量,使用大写字母和下划线来组合命名. const COUNTRY_NAME = 'China' 根据

  • 详解javascript实现瀑布流绝对式布局

    瀑布流也应该算是流行几年了吧.首先是由Pinterest掀起的浪潮,然后国内设计如雨后春笋般,冒出很多瀑布流的例子,比如,蘑菇街,Mark之(不过最近涉黄,好像被喝茶了),还有淘宝的 "哇哦". 这些都是很棒的例子, 今天我们就聊一聊瀑布流. 一.绝对式布局: JS实现原理 其实瀑布式主要的难点就在于,如果将图片整齐的排列在对应的列下,以及什么时候开始刷新加载图片. 而图片整齐的排列的主要逻辑和算法即,先获取容器内可以放多少列,然后,通过计算,存放第一列的高度,再遍历剩下(除第一列的元

  • 详解JavaScript基本类型和引用类型

    一.值的类型        早在介绍JS的数据类型的时候就提到过基本类型和引用类型,不过在说两种类型之前,我们先来了解一下变量的值的类型.在ECMAScript中,变量可以存在两种类型的值,即原始值和引用值. (1)原始值        存储在栈中的简单数据段,也就是说,它们的值直接存储在变量访问的位置. (2)引用值        存储在堆中的对象,也就是说,存储在变量处的值是一个指针,指向存储对象的内存处.        为变量赋值时,ECMAScript的解释程序必须判断该值是原始类型,还

  • js对象实例详解(JavaScript对象深度剖析,深度理解js对象)

    这算是酝酿很久的一篇文章了. JavaScript作为一个基于对象(没有类的概念)的语言,从入门到精通到放弃一直会被对象这个问题围绕. 平时发的文章基本都是开发中遇到的问题和对最佳解决方案的探讨,终于忍不住要写一篇基础概念类的文章了. 本文探讨以下问题,在座的朋友各取所需,欢迎批评指正: 1.创建对象 2.__proto__与prototype 3.继承与原型链 4.对象的深度克隆 5.一些Object的方法与需要注意的点 6.ES6新增特性 下面反复提到实例对象和原型对象,通过构造函数 new

随机推荐