前端 学习 JS JS中的箭头函数 Juns 2024-02-28 2024-02-28 JS 中的箭头函数 众所周知,箭头函数是 ES6 引入的新特性之一,我们也知道他相当于是简写的函数,还知道没有自己的 this,以及不能作为构造函数,但是具体深究一下看看,还有哪些是我们不知道的吧 🤷♂️
基本用法 函数声明:
let foo = name => { console .log (name) }
他也就等同于
function foo (name ) { console .log (name) }
没啥区别,其中(name)
外面的()
是可以省略的。
然后呢他还有一些简写,比如{ }
中直接就返回值了,可以这么简写:
let foo = name => `name: ${name} ` let foo2 = name => { return `name: ${name} ` }
他们俩是一样的,不过需要注意,返回对象也是{ }
😫,这个时候需要包一层( )
let foo = name => ({ name })let foo2 = name => { return { name, } }
所以说箭头函数看上去更加简洁好用,行数也短一些,看着就舒服 😆
和普通函数的区别 直接 log 看看先 👀
let fn = name => { console .log (name) } let fn2 = function (name ) { console .log (name) } console .dir (fn)console .dir (fn2)
可以看出,箭头函数缺少了arguments
、caller
、prototype
arguments 箭头函数没有自己的 arguments,但是他可以继承父级的 arguments
let fn = name => { console .log (arguments ) } let fn2 = function (name ) { console .log (arguments ) } fn2 () fn ()
可以看出,在全局作用域下,箭头函数是没有 arguments 的,但是在函数内部,他是可以继承父级的 arguments。
当他处于普通函数的作用域中的时候,arguments 就是上层普通函数的 arguments
let fn2 = function (name ) { console .log ('fn2:' , arguments ) let fn = name => { console .log ('fn:' , arguments ) } fn () } fn2 ('111' )
但是我们也知道,ES6 还有一个 rest 参数,可以用它来起到 arguments 的作用:
let fn = (a, ...rest ) => { console .log (a, rest) } fn (1 , 2 , 3 , 4 , 5 , 6 )
this 指向 和 arguments 一样,箭头函数没有自己的 this
var testName = 'windowName' const fn = ( ) => { console .log (this .testName ) } const fn2 = function ( ) { console .log (this .testName ) } fn () fn2 ()
这里都是指向了 window。箭头函数是没有自己的 this 的,所以他的 this 是继承父级的 this,也就是 window
var testName = 'windowName' let obj = { testName : 'objName' , fn : () => { console .log (this .testName ) }, fn2 : function ( ) { console .log (this .testName ) }, } obj.fn () obj.fn2 ()
这里就不一样了,fn2 的 this 就是指向当前的这个 obj,而 fn 的 this 是继承父级的 this,也就是 window。这个函数转换成 ES5 的语法就是这样:
var _this = this var testName = 'windowName' var obj = { testName : 'objName' , fn : function fn ( ) { console .log (_this.testName ) }, fn2 : function fn2 ( ) { console .log (this .testName ) }, } obj.fn () obj.fn2 ()
正是因为他没有自己的 this,所以 call、apply、bind 无法改变他的 this,他的 this 在声明的时候就已经确定了。
var testName = 'windowName' let obj = { testName : 'objName' , fn : () => { console .log (this .testName ) }, fn2 : function ( ) { console .log (this .testName ) }, } obj.fn .call ({ testName : '222' }) obj.fn2 .call ({ testName : '222' })
prototype 我们在之前学习了原型链相关的知识,也就知道,声明一个函数的时候,会自动创建他的原型对象 prototype,但是箭头函数可不会。
let fn = name => { console .log (name) } let fn2 = function (name ) { console .log (name) } console .log (fn.prototype ) console .log (fn2.prototype )
不能作为构造函数
99.9%的人都不知道的箭头函数不能当做构造函数的秘密
在前面我们也学习了 new 的原理:
function myNew (ctor, ...args ) { if (typeof ctor !== 'function' ) { throw new Error ('构造函数必须是function' ) } const obj = {} obj.__proto__ = ctor.prototype const res = ctor.apply (obj, args) return res instanceof Object ? res : obj }
可以看到,使用 new 创建一个实例的时候,需要用到构造函数的 prototype,还需要改变构造函数的 this 指向,但是箭头函数没有自己的 prototype,也没有自己的 this,所以他不能作为构造函数。
yield 箭头函数不可以使用 yield 命令,所以不能用作 Generator 函数,这方面我还不太了解,仅仅是知道。
使用场景 回调函数 主要还是由于普通函数的 this 指向问题,导致作为回调函数时,需要使用 bind、apply、call 来改变 this 指向,而箭头函数没有自己的 this,所以就不会有这个问题。
比如 forEach、map,就经常使用箭头函数作为回调函数,主要是看着简洁,没有 this 问题。
const words = ['hello' , 'WORLD' , 'Whatever' ]const downcasedWords = words.map (word => word.toLowerCase ())
不建议使用的情况 对象上的方法 我们知道,箭头函数没有自己的 this,但是对象方法我们可能需要使用 this,所以说最好不用箭头函数就行,可以直接用方法的简写
let obj = { name : '111' , fn : () => { console .log (this .name ) }, fn2 ( ) { console .log (this .name ) }, fn3 : function ( ) { console .log (this .name ) }, }
定义原型方法 我们也知道,在原型上定义方法,一般需要访问 this,箭头函数没有自己的 this 又会导致问题咯
function Cat (name ) { this .name = name } Cat .prototype .sayCatName = () => { console .log (this === window ) return this .name } const cat = new Cat ('Mew' )cat.sayCatName ()
定义事件回调函数 这个是需要分情况的,有的时候回调函数的 this 指代当前发生事件的 DOM 节点,这个时候用 this 就很舒服,比如:
const button = document .getElementById ('myButton' )button.addEventListener ('click' , function ( ) { console .log (this === button) this .innerHTML = 'Clicked button' })
这里使用箭头函数就会造成 this 指代 window
作为类中的方法 这里推荐看这个文章:曾经我以为我很懂箭头函数
题目 window .name = 'window_name' let obj1 = { name : 'obj1_name' , print : () => console .log (this .name ), } let obj2 = { name : 'obj2_name' }obj1.print () obj1.print .call (obj2)
答案👈🏻
都是'window_name'
function A ( ) { this .foo = 1 } A.prototype .bar = () => console .log (this .foo ) let a = new A ()a.bar ()
答案👈🏻
'undefined',因为window没有foo属性
let obj1 = { name : '111' , print : function ( ) { return () => console .log (this .name ) }, } let obj2 = { name : '222' }obj1.print ()() obj1.print ().call (obj2) obj1.print .call (obj2)()
答案👈🏻
'111', '111', '222',自己分析吧🧐
var flag = 'window flag' function Person (fg ) { let o = { flag : fg, fn : () => { console .log (this ) }, fn2 ( ) { console .log (this ) }, } return o } let p = new Person ('111' )p.fn () p.fn2 ()
答案👈🏻
Person {}
{ flag: 'window flag', fn: [Function: fn], fn2: [Function: fn2] }
箭头函数里面的this,就是Person函数的this
改编一下上一题:
var flag = 'window flag' function Person (fg ) { let flag = 'Person flag' this .flag = 'Person this flag' let _this = this let o = { flag : fg, fn : () => { console .log (this .flag ) console .log (this === _this) }, fn2 ( ) { console .log (this .flag ) }, } return o } let p = new Person ('111' )p.fn () p.fn2 ()
答案👈🏻
Person this flag
true
111
上面两题推荐看这个文章:对阮一峰《ES6 入门》中箭头函数 this 描述的探究 ,这个还是比较综合的。
下面这一题慢慢分析吧,原文分析在这里:2022 年了你还不了解箭头函数与普通函数的区别吗?
var name = '南玖' function Person (name ) { this .name = name ;(this .foo1 = function ( ) { console .log (this .name ) }), (this .foo2 = () => console .log (this .name )), (this .foo3 = function ( ) { return function ( ) { console .log (this .name ) } }), (this .foo4 = function ( ) { return () => { console .log (this .name ) } }) } var person1 = new Person ('nan' )var person2 = new Person ('jiu' )person1.foo1 () person1.foo1 .call (person2) person1.foo2 () person1.foo2 .call (person2) person1.foo3 ()() person1.foo3 .call (person2)() person1.foo3 ().call (person2) person1.foo4 ()() person1.foo4 .call (person2)() person1.foo4 ().call (person2)
参考文章