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 全栈工程师养成记》,并跟着笔者一起思考、分析和解决软件开发过程中的若干问题。

JavaScript 私有成员相关推荐

  1. JavaScript中的私有成员

    JavaScript中的私有成员 Douglas Crockford www.crockford.com 翻译:ShiningRay @ Nirvana Studio JavaScript 是世界上最 ...

  2. Javascript中公有成员,私有成员,静态成员

    一.实现类的公有成员: 1) 定义的成员属性和方法能够被任何实例访问,对任何的实例都是公开的,成为公有成员.在javascript中,一般的属性和方法的定义都是公有的,请看下面的例子: functio ...

  3. 深入理解JavaScript模拟私有成员

    一般的面向对象语言C++或JAVA,对象都是有私有成员的.js中没有类的改变,同样也没有对象的私有成员这个概念.但是可以通过某些特殊写法,模拟出私有成员. 1.特权模式: (1)在构造函数内部声明的变 ...

  4. java受保护的数据与_Javascript类定义语法,私有成员、受保护成员、静态成员等介绍...

    摘要:这篇JavaScript栏目下的"Javascript类定义语法,私有成员.受保护成员.静态成员等介绍",介绍的技术点是"javascript类.JavaScrip ...

  5. 4)公有成员\私有成员和静态成员

    1)实现类的公有成员.     前面定义的任何类成员都属于公有成员的范畴,该类的任何实例都对外公开这些属性和方法.         2)实现类的私有成员.     私有成员,即只在类的内部实现中可以共 ...

  6. ​JavaScript私有属性的多种实现方式总汇

    来源 | http://www.fly63.com JavaScript被很多人认为并不是一种面向对象语言,原因有很多种,比如JavaScript没有类,不能提供传统的类式继承:再比如JavaScri ...

  7. python 私有和保护成员变量如何实现?—— 单下划线 开始的成员变量叫做保护变量,意思是只有类实例和子类实例能访问到这些变量; 双下划线 开始的是私有成员,意思是只有类对象自己能访问...

    默认情况下,Python中的成员函数和成员变量都是公开的(public),在python中没有类似public,private等关键词来修饰成员函数和成员变量. 在python中定义私有变量只需要在变 ...

  8. C++友元函数访问私有成员

    #include<iostream> using namespace std; class CTimeInfo { public: void setTime();//默认函数构造 void ...

  9. java 反射私有变量赋值_通过反射,操作私有成员变量(取/赋值),调用私有方法...

    Java的反射工具很强大,有句著名的话:No reflection ,no frameworks. 工作中直到涉及到UT,才体会到它的重要性,现归纳整理一个小例子: 反射工具类:import java ...

最新文章

  1. [ CodeVS冲杯之路 ] P1116
  2. duilib 预开篇
  3. 记asp.net VB与C# 页面参数传值
  4. python消费kafka逻辑处理导致cpu升高_请教:Python模块KafkaConsumer会被Kerberos的状态影响嘛?...
  5. Qt Quick中的The Visual Canvas
  6. neo4j安装和启动
  7. 模拟网页行为之工具篇二
  8. 编写代码注释的最佳实践
  9. 解决cacti创建ping主机时不出图的问题
  10. php网页怎么设置背景音乐,怎么给网页添加背景音乐
  11. QT编译程序出现[ui_Widget.h] Error 1
  12. 深度学习端到端的优缺点和网络泛化性
  13. 电脑遇到黑屏问题第一时间怎么解决
  14. 从经济学角度解释:为什么画家总是死后成名?
  15. HR最讨厌的几种求职者·
  16. webpack 降级
  17. X5跨端移动开发框架开源项目简介
  18. Markdown输入数学公式
  19. Redis进阶-事件机制
  20. 快速学习-Saturn Executor部署

热门文章

  1. Spring的工具类,方便在非spring管理环境中获取bean
  2. Redis中使用Java代码的方式实现发布订阅流程
  3. Springboot文件上传提示:failed to convert java.lang.String to org.springframework.util.unit.DataSize
  4. Chrome 浏览器调试移动端
  5. 熟读《阿里巴巴java开发手册》(五、 MySQL 数据库)
  6. 7、mybatis主配置文件之mappers
  7. php认识正则吗,php正则表达式有什么用
  8. 企业联合体的形式_母公司是否可以用子公司资质进行投标,且不以联合体的形式?...
  9. 极致的线上产品设计与终极数据分析工具,两者缺一不可(下)
  10. 【python】 读取Excel文件并绘制图表