JavaScript箭头函数
诸葛小愚 人气:0箭头函数作为ES6新增的语法,在使用时不仅能使得代码更加简洁,而且在某些场景避免this指向问题。但是箭头函数不是万能的,也有自己的缺点以及不适用的场景,虽然可以解决this只想问题,但是也可能会带来this指向问题。具体场景具体分析,本文就深入探讨箭头函数。
箭头函数没有自己的this,其this取决于上下文中定义的this。
this指向原理
问题的由来
学懂 JavaScript 语言,一个标志就是理解下面两种写法,可能有不一样的结果。
var obj = { foo: function () {} }; var foo = obj.foo; // 写法一 obj.foo() // 写法二 foo()
上面代码中,虽然obj.foo
和foo
指向同一个函数,但是执行结果可能不一样。请看下面的例子。
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.foo
,foo()
就变成在全局环境执行?
本文就来解释 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指向问题
加载全部内容