本次浅谈一下大家在开发过程中比较容易忽视的一个问题,类中定义箭头函数方法。
回顾一下如何在JavaScript中定义类方法
class是作为ES6的一部分被引入到JavaScript的中。在此之前,为了创建一个类,我们需要使用function关键字定义一个函数,用this来定义类中含有的属性和函数。
我们用class关键字先创建一个类
class Pet {
name = 'dog';
getName() {
return this.name;
}
}
在这个宠物类中只有一个属性名和一个方法getName。方法定义已经被设置为默认的函数声明。
在ES6版本之后,我们可以这样创建,但以前在ES5中是不可能的。下面看一下ES5版本的代码。
function Pet() {
this.name = 'dog';
}
Pet.prototype.getName = function () {
return this.name;
};
这里是ES5中类的实现--我们创建了一个属性name为dog。用this来指代我们将要创建的对象。如果想要创建函数,我们可以把它作为对象的属性,但最好的方法是添加到我们的函数原型中,就像我们的代码一样。它允许我们在对象中使用getName方法。
这里用到了继承和原型链的知识。不熟悉的小伙伴可以去复习一下。
const pet = new Pet();
const pet2 = new Pet();
console.log(Pet.prototype.getName === pet.getName); // true
console.log(pet.getName === pet2.getName); // true
通过上面的代码可以看到,我们确实在所有的Pet实例中都获取到了getName函数,并且他们指向了同一个function,这意味着如果有1000个实例,我们也只有一个getName函数。我们知道function是引用类型,那么这种操作就可以大大的节省内存空间。
上面class的语法非常简单,并且自动帮我们将函数挂载到了原型链上,我们不需要考虑太多的东西。但是现在ES6中可以使用一种新型的方法来创建函数,让我们来看一下下面的代码。
const getDate = () => new Date()
箭头函数的语法看起来更加简洁。同样可以避免多数因this指向问题而产生的bug。
随着箭头函数的普及,它们开始被到处使用,甚至作为类方法。我们看一下如下代码。
class Pet {
name = 'dog';
getName() {
return this.name;
}
getNameArrow = () => this.name;
}
我们已经定义了getNameArrow箭头函数。它做的是完全一样的事情,而且看起来甚至更好、更干净。但是,这里可能有什么问题呢?
首先,你可能注意到我们使用了=来定义一个方法,这实际上意味着我们定义了一个属性。甚至VScode也为我们强调了这一点。
在ES5与ES6的语法中,所有的属性都会被直接添加到类的实例中,而不是原型。让我们试着证明一下。我们可以通过Babel或TypeScript编译器传递我们的类的代码,甚至是在线版本,然后看看结果。
"use strict";
var Pet = /** @class */ (function () {
function Pet() {
this.name = 'dog';
this.getNameArrow = function () { return this.name; };
}
Pet.prototype.getName = function () {
return this.name;
};
return Pet;
}());
这里我们可以注意到我们的函数以不同的方式定义。getName方法仍然放在原型中,但是getNameArrow被定义为对象中的属性,并且它将为每个实例创建。让我们在实际案例中检查一下。
const pet = new Pet();
const pet2 = new Pet();
console.log(pet.getNameArrow === pet2.getNameArrow); // false
每次当我们创建新的实例时,我们也会创建新的函数getNameArrow。试想一下,当我们需要1000个Pet的实例时,里面有20个方法被创建为箭头函数。这意味着将创建20000个无用的函数。而这仅仅是当我们使用箭头函数的时候。
这就是为什么我们应该避免在类中使用箭头函数的根本原因,或者至少要思考和理解什么时候可以这样做。如果你有不同的看法,欢迎与我探讨哦。