Class field declarations for JavaScript(JavaScript 类的字段声明)现在已进入了 stage-3,个中包括一项 OOP 开辟者都很关注的内容:Private fields。JavaScript 一向没有私有成员并不是没有缘由,所以这一发起给 JavaScript 带来了新的应战。但同时,JavaScript 在 ES2015 宣布的时刻已在斟酌私有化的题目了,所以要完成私有成员也并不是毫无基本。
笔者在专栏《JavaScript 全栈工程师养成记》的第四章讲到了原型 OOP 关联和继承 OOP 关联的症结区分。本日这里就研究一下 JavaScript 私有成员的题目。
坑
起首挖个坑 —— 这是一段 JS 代码,BusinessView
中要干两件事变,即对表单和舆图举行规划。
代表将
_
前缀商定为私有
class BaseView {
layout() {
console.log("BaseView Layout");
}
}
class BusinessView extends BaseView {
layout() {
super.layout();
this._layoutForm();
this._layoutMap();
}
_layoutForm() {
// ....
}
_layoutMap() {
// ....
}
}
然后,由于营业的生长,发明有许多视图都存在舆图规划。这里选用继承的体式格局来完成,所以从 BusinessView
中把舆图相干的内容笼统成一个基类叫 MapView
:
class MapView extends BaseView {
layout() {
super.layout();
this._layoutMap();
}
_layoutMap() {
console.log("MapView layout map");
}
}
class BusinessView extends MapView {
layout() {
super.layout();
this._layoutForm();
this._layoutMap();
}
_layoutForm() {
// ....
}
_layoutMap() {
console.log("BusinessView layout map");
}
}
上面这两段代码是很典范的基于继承的 OOP 头脑,本意是希冀各个条理的类都能够经由历程 layout()
来举行各条理应当担任的规划使命。但抱负和实际总是有差异的,在 JavaScript 中运转就会发明 BusinessView._layoutMap()
被实行了两次,而 MapView._layoutMap()
未实行。为何?
虚函数
JavaScript 中假如在先人和子孙类中定义了雷同的称号的要领,默许会挪用子孙类中的这个要领。假如想挪用先人类中的同名要领,须要在子孙类中经由历程 super.
来挪用。
这里能够剖析一下这个历程:
在子类建立对象的时刻,其类和一切先人类的定义都已加载了。这个时刻
- 挪用
BusinessView.layout()
- 找到
super.layout()
,最先挪用MapView.layout()
MapView.layout()
中挪用this._layoutMap()
- 因而从当前对象(
BusinessView
对象)寻觅_layoutMap()
- 找到,挪用它
- 因而从当前对象(
你看,由于 BusinessView
定义了 _layoutMap
,所以压根都没去搜刮原型链。对的,这是基于原型关联的 OOP 的范围。假如我们看看 C# 的处置惩罚历程,就会发明有所差别
- 挪用
BusinessView.layout()
- 找到
base.layout()
,最先挪用MapView.layout()
MapView.layout()
中挪用this._layoutMap()
- 在
MapView
中找到_layoutMap()
搜检是不是虚函数
- 假如是,往子类找到末了一个重载(override)函数,挪用
- 假如不是,直接挪用
- 在
发明区分了吗?症结是在于推断“虚函数”。
然则,这跟私有成员又有什么关联呢?由于私有函数肯定不是虚函数,所以在 C# 中,假如将 _layoutMap
定义为私有,那 MapView.layout()
挪用的就肯定是 MapView._layoutMap()
。
虚函数的观点有点小庞杂。不过能够简朴理解为,假如一个成员要领被声明为虚函数,在挪用的时刻就会延着其虚函数链找到末了的重载来举行挪用。
JavaScript 中虽然商定 _
前缀的是私有,那也只是正人之约,它本质上依然不是私有。正人之约对人有用,计算机又不晓得你有这个商定……。然则,假如 JavaScript 真的完成了私有成员,那末计算机就晓得了,_layoutMap()
是个私有要领,应当挪用本类中的定义,而不是去寻觅子类中的定义。
处理当下的私有化题目
JavaScript 当下没有私有成员,然则我们又须要切时有用地处理私有成员题目,怎样办?当然有方法,用 Symbol
和闭包来处理。
注重,这里的闭包不是指点在函数函数中天生闭包,请继承往下看
起首搞清楚,我们变通的对待这个私有化题目 —— 就是让先人类挪用者在挪用某个要领的时刻,它不会先去子类中寻觅。这个题目从语法上处理不了,JavaScript 就是要从详细的实例从后往前往寻觅指定称号的要领。然则,假如找不到这个要领名呢?
之所以能找到,由于要领名是字符串。一个字符串在全局作用域内都示意着一样的意义。然则 ES2015 带来了 Symbol
,它必需实例化,而且每次实例化出来肯定代表着差别的标识 —— 假如我们将类定义在一个闭包中,在这个闭包中声明一个 Symbol
,用它来作为私有成员的称号,题目就处理了,比方
const MapView = (() => {
const _layoutMap = Symbol();
return class MapView extends BaseView {
layout() {
super.layout();
this[_layoutMap]();
}
[_layoutMap]() {
console.log("MapView layout map");
}
}
})();
const BusinessView = (() => {
const _layoutForm = Symbol();
const _layoutMap = Symbol();
return class BusinessView extends MapView {
layout() {
super.layout();
this[_layoutForm]();
this[_layoutMap]();
}
[_layoutForm]() {
// ....
}
[_layoutMap]() {
console.log("BusinessView layout map");
}
}
})();
而当代基于模块的定义,甚至连闭包都能够省了(模块体系会自动关闭作用域)
const _layoutMap = Symbol();
export class MapView extends BaseView {
layout() {
super.layout();
this[_layoutMap]();
}
[_layoutMap]() {
console.log("MapView layout map");
}
}
const _layoutForm = Symbol();
const _layoutMap = Symbol();
export class BusinessView extends MapView {
layout() {
super.layout();
this[_layoutForm]();
this[_layoutMap]();
}
[_layoutForm]() {
// ....
}
[_layoutMap]() {
console.log("BusinessView layout map");
}
}
革新事后的代码就能够按预期输出了:
BaseView Layout
MapView layout map
BusinessView layout map
跋文
笔者在多年开辟历程当中养成了剖析和处理题目的一系列思维习惯,所以经常能够敏捷的透过征象看到须要处理的本质性题目,并基于现有前提来处理它。确切,Symbol
涌现的来由之一就是处理私有化题目,然则为何要用以及怎样用就须要去剖析和思索了。
进修能够让人处理雷同的题目,但思索能够让人处理类似的题目。迎接读者们来进修笔者的专栏《JavaScript 全栈工程师养成记》,并随着笔者一同思索、剖析和处理软件开辟历程当中的若干题目。