箭头函数转化为普通函数_理解 JavaScript 箭头函数
引言
ES6 出现了箭头函数,我们在现代 JavaScript 的开发中经常使用箭头函数,它令我们的代码看上去更加简洁。
思考
在开始正题前,我们先思考一段代码,然后在文章的最后,我们还会回到这里看看我们的代码:
let team = { work: 'my-team', langs: ['JS','PHP','JAVA'], show() { this.langs.forEach(function(lang) { console.log(this.work + ' ' + lang); }) }}team.show();
先看一下结果吧,这段代码在严格模式下报错:TypeError: Cannot read property "work" of undefined ,非严格模式会输出:
undefined JS
undefined PHP
undefined JAVA
因为在严格模式下,这段代码的 this 不指向 window 对象,而是 undefined ,调用 undefined.work 显然是错误的,在非严格模式下可以正常执行,因为 this 指向了 window 对象,访问一个对象不存在的属性,值就是 undefined 。
箭头函数
有一种非常简单和简洁的语法创建函数,它通常比函数表达式更好,我们来看一看它的真面目:
let arrow = (arg1, arg2, ...argN) => expression;
这儿创建了一个箭头函数,它接收参数 arg1 ... argN ,然后使用这些参数对右侧的表达式求值,并返回结果。
再来看一下函数表达式是如何定义的:
let exp = function(arg1, arg2, ...argN) { return expression;}
我们来看一个具体的箭头函数的例子:
let arrow = (str1, str2) => str1 + ' ' + str2;console.log(arrow('hello','world')); // hello world
正如您所见,(str1, str2) => str1 + ' ' + str2 表示一个接收 str1 ,str2 两个参数的函数。在执行时,它计算表达式 str1 + ' ' + str2 的值,然后将结果直接返回 (没有写 return 关键字)。
与函数表达式相比,箭头函数没有 function 和 return 关键字。
如果只有一个参数,可以省略参数的括号,使箭头函数表现得更 “精巧” ,就像这样:
let arrow = str1 => str1 + ' world';console.log(arrow('hello')); // hello world
如果没有参数,括号就必须得存在了,例如:
let arrow = () => 'hello world';console.log(arrow());
箭头函数可以像表达式一样使用。
我们来看一个例子,这是一个动态创建的函数:
let lan = 'js';let work = (lan === 'js') ? () => console.log('code in js') : () => console.log('code in HTML and CSS');work(); // code in js
到目前为止,我们的箭头函数都只有一行代码,并且不用写 return 关键字就可以直接返回 '=>' 右侧的值,这看起来非常简洁。
多行代码的箭头函数
有时候,我们的函数中可能会需要一些更复杂的功能,有可能需要多行语句,这时候,我们应该在 '=>' 右侧用 '{}' 将我们的函数体扩起来,并且在需要返回值时,显式的在函数体内写上 return 关键字,如果没有 return 关键字,函数将默认返回 undefined 。
就像下面这样:
let arrow = (str1, str2) => { let result = str1 + ' ' + str2; return result;}console.log(arrow('hello', 'world'));
箭头函数中的 this
在函数内部有个 ThisBindingStatus 属性,它标识了函数内部 this 绑定情况的,即是否绑定好了本地的 this 值,如果这个属性值是 lexical ,代表这是个箭头函数,没有 this 值。
简单定义一下箭头函数中的 this :箭头函数是和父级上下文绑定在一起的。简单来讲,如果箭头函数内出现了 this ,则它指向包含箭头函数的 “那个容器” 所在范围的 this 值。以下几点可能需要特别注意:
如果箭头函数的容器还是箭头函数:
由于箭头函数没有 this 的概念,所以对于箭头函数的嵌套情形,内部箭头函数的 this 指向,直接看最外部箭头函数的 this 指向。即不管嵌套多少层,都共享最外层的 this 指向。外部箭头函数的指向,参考上面的定义,如下面的代码:
// 共享 say 的 this 指向// 包含 say 的 “那个容器”是 obj// “那个容器”所在的范围是全局var name = 'ooo';let o = { name: 'hello', say: () => { let f = () => { console.log(this.name); } f(); }}o.say(); // ooo// 共享 f 的 this 指向// 包含 f 的 “那个容器”是 say// “那个容器”所在的范围 objvar name = 'ooo';let o = { name: 'hello', say: function() { let f = () => { let innerF = () => { console.log(this.name); } innerF(); } f(); }}o.say(); // hello
如果箭头函数在一个构造函数中:
- 箭头函数直接作为构造函数的属性或箭头函数包含在作为构造函数属性的普通函数内时,箭头函数中的 this 都指向构造函数的 this 值。
function Per(){ this.name = 'kylin'; this.nor = function () { console.log(this.name); } this.say = () => { console.log(this.name) } this.arrowSay = () => { let s = () => { console.log(this.name) } s(); } this.funSay = function() { let s = () => { console.log(this.name) } s(); }}let p = new Per();p.say(); // kylinp.arrowSay(); // kylinp.funSay(); // kylinvar p2 = p.say;p2(); // kylin 箭头函数本来就没 this ,this 一直就是 Per 的var p3 = p.nor;p3(); // global this 在引用时丢失了
如果箭头函数出现在回调、setTimeout 、forEach 等功能性函数调用中:
- 箭头函数的 this 直接指向包含这些功能性函数的 “那个容器” 的 this 值。注意箭头函数嵌套情形,如果使用它们的 “那个容器” 是箭头函数,还是没有 this 值,还要根据上面的定义去找。
function 声明的普通函数:
- 如果是一个普通的函数声明,那么函数的 this 在非严格模式下指向 window 对象,严格模式下 this 值为 undefined 。箭头函数中的 this 则不受严格模式影响,因为它本身就没 this 。
我们再看一些示例,熟悉一下箭头函数:
// 用 var 可以让 str 绑定到 window 上// 这样看结果清晰些var str = 'world';let arrow = () => { let str = 'hello'; console.log(this.str);}arrow();// 输出 world // 包含箭头函数的 “那个容器” 是全局// “那个容器”所在范围的 this 是 window
别急,还有一段代码:
// 这个例子箭头函数在一个普通函数中var str = 'world'; function say() { let str = 'hello'; let f = () => { // 输出 world // 包含箭头函数的 “那个容器” 是 say 函数 // “那个容器” 是一个普通函数 // 普通函数 this 是 window console.log(this.str); } f();}say();
或者下面这个例子:
// 这个例子箭头函数在对象的函数属性中var str = 'world';let obj = { str: 'hello', say() { let hi = () => { console.log(this.str) } hi(); }}// hello 包含箭头函数的 “那个容器” 是 say 函数// “那个容器” 所属范围是 obj ,obj 对象的 this 是// 它自身,所以箭头函数的 this 指向 objobj.say();
还有这个例子:
// 箭头函数不在对象的函数属性中// 直接就是对象的属性var str = 'world';let obj = { str: 'hello', say: () => { console.log(this.str); }}// 输出 world // 包含箭头函数的 “那个容器” 是 obj 对象// “那个容器”所在的范围是全局,全局的 this 是 window ,// 所以箭头函数的 this 指向 windowobj.say();
再看一个例子了:
// 箭头函数不在对象的函数属性中var name = 'world';function outer() { let name = 'hello'; function inner() { let say = () => { console.log(this.name) } say(); } inner();}// 输出 world // 包含箭头函数的 “那个容器” 是 inner 函数// “那个容器” 所在的范围是 outer 函数 ,// 由于 outer 是普通函数,所以 this 指向 window// 所以箭头函数的 this 指向 windowouter();
结合上面的规则,最后思考一个例子:
var name = 'world'function Person() { this.name = 'hello' this.say = () => { console.log('p.say: ' + this.name); } this.sayFun = function () { let f = () => { console.log('constructor: ' + this.name); } return f; } setTimeout(() => { this.name += ' person' }, 1000) this.o = { name: 'obj', fun: () => { console.log('o.fun:' + this.name) }, funArrowIn: () => { let f = () => console.log('o.funArrowIn:' + this.name); f(); }, funTest: function() { let print = () => { console.log('o.funTest: ' + this.name) } print(); }, funcSetTimeoutArrow: () => { setTimeout(() => { console.log('o.funcSetTimeoutArrow: ' + this.name); }, 3000) }, funcSetTimeout: function() { setTimeout(() => { console.log('o.funcSetTimeout: ' + this.name) }, 1000) } }}let p = new Person();p.say(); // p.say: hellop.sayFun()(); // constructor: hellop.o.fun(); // o.fun:hellop.o.funTest(); // o.funTest: objp.o.funArrowIn(); // o.funArrowIn:hellop.o.funcSetTimeoutArrow(); // o.funcSetTimeoutArrow: hello personp.o.funcSetTimeout(); // o.funcSetTimeout: objsetTimeout(()=>{ console.log(p.name)}, 3000) // hello person
现在,你应该了解箭头函数的 this 了,我们回到上面正文部分的例子,今天不打算讲解普通函数中的 this ,我们直接将代码中的普通函数改成箭头函数看看结果:
let team = { work: 'my-team', langs: ['JS','PHP','JAVA'], show () { this.langs.forEach((lang) => console.log(this.work + ' ' + lang)); }}team.show();// my-team JS// my-team PHP// my-team JAVA
可以正常输出了,依据前面的定义,箭头函数定义在了一个名叫 show 的函数定义中,这个函数是 team 对象的属性,所以 this 就指向这个 “show 函数的容器” team 。或者可以说,show 函数的 this 是 team ,包含 forEach 的 “容器” 是 show 函数,所以 this 就是 show 函数的 this 。
总结
箭头函数对于只有一行代码的程序来说非常方便。它们有两种形态:
- 右侧是表达式:'=>' 右边是表达式,计算表达式的值,不用 return 直接自动返回。
- 右侧使用大括号:使用 (…args) => { 函数体 } 的形式,括号允许我们在函数中编写多个语句,但是我们需要一个显式的 return 来返回一些东西,如果没有显式的 return ,函数默认返回 undefined 。
箭头函数还有几方面需要注意:
- 没有 this 和 super 关键字。
- 没有 arguments 对象。
- 不能使用 new 实例化,因为它没 this 。
- 尽量在 setTimeout(func) 、arr.forEach(func) 以及你自定义的回调函数中使用箭头函数,因为箭头函数可以避免烦人的 this 指向问题。
文章中的图片来源于网络,若有侵权行为,请在后台与我联系。