关于ES5中构造函数的问题
我们用组合构造函数和原型模式来创建构造器,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。那么。如何让构造函数模式中的方法不被实例所创建呢?比如下面这个例子:
function Person(name, family) {
this.name = name;
this.family = family;
var records = [{type: "in", amount: 0}];
this.addTransaction = function(trans) {
if(trans.hasOwnProperty("type") && trans.hasOwnProperty("amount")) {
records.push(trans);
}
}
this.balance = function() {
var total = 0;
records.forEach(function(record) {
if(record.type === "in") {
total += record.amount;
}
else {
total -= record.amount;
}
});
return total;
};
};
Person.prototype.getFull = function() {
return this.name + " " + this.family;
};
Person.prototype.getProfile = function() {
return this.getFull() + ", total balance: " + this.balance();
};
var me = new Person('sun','yang');
me.balance//0
me.getProfile()//"sun yang, total balance: 0"
在这个Person构造函数内部有一个records 的私有变量,这个变量我们是不希望通过函数内部以外的方法去操作这个变量, 但是又想通过构造函数new出来的对象能获取构造函数内部的私有变量,所以在构造函数内定义了两个方法addTransaction和balance(有点像闭包的概念),这两个方法是处理变量的,并把处理的结果赋给Person.prototype上共享给实例。那么问题来了
在构造函数内部的addTransaction和balance两个方法只是为了处理变量,并不想被实例创建一个副本,这只是一个简单的例子,如果在一个复杂的构造器,里面有很多这样的方法,那岂不是会造成巨大的内存浪费吗?
另外我在ES6中看到有Class的静态方法,似乎可以解决这样的问题,将上面的例子改写成es6的形式:
class Person{
constructor(name, family) {
this.name = name;
this.family = family;
}
static addTransaction = function(trans) {
if(trans.hasOwnProperty("type") && trans.hasOwnProperty("amount")) {
records.push(trans);
}
}
static balance = function() {
let records = [{type: "in", amount: 0}];
let total = 0;
records.forEach(function(record) {
if(record.type === "in") {
total += record.amount;
}
else {
total -= record.amount;
}
});
return total;
}
getFull = function() {
return this.name + " " + this.family;
}
getProfile = function() {
return this.getFull() + ", total balance: " + Person.balance();
}
}
let me = new Person('sun','yang');
console.log(me)
如上,我这样写没有报错,另外这两个只是用作处理构造函数内部的方balanceaddTransaction也没被实例所创建。,那么我想知道,虽然我这样写是没有报错,但是自己能力水平很差,可能是自己理解错了,也可能这就不是个问题。如果我没理解错的话,ES6可以这么写,那ES5这个问题是怎么解决的呢?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
须知ES6的class只是语法糖,他的底层还是原型链继承这套东西。可以使用babel编译一下有助于理解。
通过构造函数this定义的属性,都在prototype里面,如果子类发现自身没有该属性都会顺着原型链去寻找,所以不希望子类继承的属性不要用this定义在构造函数里面,子类独有的,让子类自己拥有。构造函数就拿来构造对象你还期待她做什么?
那么又有些方法想用构造函数来处理又不希望被继承,怎么办?
那就不用要this,举个例子:
在new Person()的时候会打印出这句话,但是不会被继承
为什么要在构造函数中添加
局部变量
呢?感觉这样做没有意义啊。把
records
变成这个对象的属性, 就像这样this.records
,然后把方法放到原型
中去,这样所有实例
共享这两个方法。题主为了创建一个私有变量,在构造函数
Person
里用了一个闭包。为了访问这个闭包中的records
变量,题主不得不在构造函数里用 this 给对象添加两个方法,并且不能把这两个方法放到原型中。因为如果放到原型中就无法访问闭包中的records
了。所以我总结一下题主的需求是想要保持
records
私有的同时,又不想让实例化出来的对象具有addTransaction
和balance
的属性,而是想把这两个属性放在原型里。那么答案其实很简单。在 JavaScript 中,要创造私有变量,就必须使用闭包,问题就是闭包用在哪里。既然题主已经使用了闭包,那为什么不把整个构造函数和
records
变量放在一个闭包里呢?不过按照这个方法,Person
本身就不能用new
调用了,而是要像普通函数一样调用了。而且实例化出来的各个对象的原型也都不是同一个原型了,题主能接受吗。。。另外,我怀疑要在对象拥有私有属性的前提下,让原型中的方法能够访问私有属性,而且还要保证实例化出来的对象拥有的是同一个原型,这件事是不可能做到的。因为所有实例化出来的对象的原型是同一个对象。JavaScript 中的作用域不会随着调用的上下文而动态改变,原型对象的作用域始终是定义它的时候的作用域。而一个作用域中只能有一个
records
,所以所有实例化的对象所访问的都是这同一个records
,不可能有自己的records
。除非你把records
变成一个对象,然后把各个实例化对象的私有属性用键值对保存在records
里。如果题主找到了完美的办法,请告诉我。。。
代码如下:
你可以把处理函数放在构造函数外面,比如function handle(){},然后在构造函数里面去调用这个handle()函数就好了,
静态方法与实例方法
首先,如 @maser_yoda 所说 es6 的
class
就是一个语法糖。不过用 Babel 翻译出来,附加代码有点多,所以我用 TypeScript 来翻译下面这段代码(没用到特殊语法,所以翻译结果不会有问题)这个代码就很容易搞明白,由于 JavaScript 的 function 也是对象,所以
static setName()
其实是定义在构造函数这个对象上的,这种方法在其它静态语言中也称为类方法。而非静态的setName()
则是定义在原型中,即就是实例方法(或对象方法)。由于类(静态)方法不能访问实例属性(因为类永远不会明白你想把方法作用于哪个实例),所以静态方法里你是不可能去访问实例
records
属性的。你的代码里没加this
限定,所以访问的是一个全局的records
。成员私有化
JavaScript 并没有从语言的层面提供私有化解决方案,所以要在 JavaScript 里实现严格意义上的私有化是不能实现的。
在很早以前就存在一种解决方法——不使用原型方法,而是使用闭包+实例方法,也就是你第一段代码里用的方法,
records
那个闭包变量。缺点你也发现了,就是每个实例都会有相同的方法存在,浪费内存资源。我在 ES5 中模拟 ES6 的 Symbol 实现私有成员 中讨论了一种使用原型方法的解决方案。这种方案不能从语法上阻止别人去找到真实的成员变量,只是加大了找到它们的难度而已。
对于模拟 Symbol 的问题,现在有 Babel,所以一般不需要模拟 Symbol,Babel 会做替代处理(不过替换的情况下名称非常有规律,很容易查出来)。