探索JavaScript中私有成员的相关知识


Posted in Javascript onJune 13, 2019


首先挖个坑 —— 这是一段 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 相关文章推荐
PNG背景在不同浏览器下的应用
Jun 22 Javascript
jquery实现checkbox 全选/全不选的通用写法
Feb 22 Javascript
利用js实现禁止复制文本信息
Jun 03 Javascript
JQuery的常用选择器、过滤器、方法全面介绍
May 25 Javascript
Bootstrap Table使用方法解析
Oct 19 Javascript
vue项目中v-model父子组件通信的实现详解
Dec 10 Javascript
vue2.0实现前端星星评分功能组件实例代码
Feb 12 Javascript
快速了解vue-cli 3.0 新特性
Feb 28 Javascript
vue监听键盘事件的快捷方法【推荐】
Jul 11 Javascript
vue element upload实现图片本地预览
Aug 20 Javascript
使用 JavaScript 创建并下载文件(模拟点击)
Oct 25 Javascript
微信小程序实现轨迹回放的示例代码
Dec 13 Javascript
详解vue中的父子传值双向绑定及数据更新问题
Jun 13 #Javascript
基于Vue实现平滑过渡的拖拽排序功能
Jun 12 #Javascript
Vue + Elementui实现多标签页共存的方法
Jun 12 #Javascript
JavaScript使用面向对象实现的拖拽功能详解
Jun 12 #Javascript
JS实现点击生成UUID的方法完整实例【基于jQuery】
Jun 12 #jQuery
小程序组件之自定义顶部导航实例
Jun 12 #Javascript
vue项目中将element-ui table表格写成组件的实现代码
Jun 12 #Javascript
You might like
php 无限极分类
2008/03/27 PHP
PHP 开发环境配置(测试开发环境)
2010/04/28 PHP
PHP Zip解压 文件在线解压缩的函数代码
2010/05/26 PHP
php+ajax实现无刷新数据分页的办法
2015/11/02 PHP
复制Input内容的js代码_支持所有浏览器,修正了Firefox3.5以上的问题
2010/06/21 Javascript
javascript中style.left和offsetLeft的用法说明
2014/03/07 Javascript
JS实现带有3D立体感的银灰色竖排折叠菜单代码
2015/10/20 Javascript
JavaScript原生对象常用方法总结(推荐)
2016/05/13 Javascript
js实现添加可信站点、修改activex安全设置,禁用弹出窗口阻止程序
2016/08/17 Javascript
select隐藏选中值对应的id,显示其它id的简单实现方法
2016/08/25 Javascript
JQuery获取鼠标进入和离开容器的方向
2016/12/29 Javascript
vue2组件之select2调用的示例代码
2017/10/12 Javascript
基于node下的http小爬虫的示例代码
2018/01/11 Javascript
微信小程序日历效果
2018/12/29 Javascript
微信小程序学习笔记之登录API与获取用户信息操作图文详解
2019/03/29 Javascript
vue 实现cli3.0中使用proxy进行代理转发
2019/10/30 Javascript
React 实现车牌键盘的示例代码
2019/12/20 Javascript
全面了解Python环境配置及项目建立
2016/06/30 Python
利用python程序生成word和PDF文档的方法
2017/02/14 Python
详解Python最长公共子串和最长公共子序列的实现
2018/07/07 Python
对python xlrd读取datetime类型数据的方法详解
2018/12/26 Python
Python中的heapq模块源码详析
2019/01/08 Python
python for 循环获取index索引的方法
2019/02/01 Python
深入了解Django中间件及其方法
2019/07/26 Python
python异常触发及自定义异常类解析
2019/08/06 Python
Carter’s OshKosh加拿大:购买婴幼儿服装和童装
2018/11/27 全球购物
请说出你所知道的线程同步的方法
2013/04/19 面试题
南京软件公司的.net程序员笔试题
2014/08/31 面试题
大课间活动制度
2014/01/18 职场文书
项目建议书格式
2014/03/12 职场文书
团日活动总结报告
2014/06/25 职场文书
董事长助理岗位职责
2015/02/11 职场文书
2019通用版劳动合同范本!
2019/07/11 职场文书
css实现两栏布局,左侧固定宽,右侧自适应的多种方法
2021/08/07 HTML / CSS
mysql5.7的安装及Navicate长久免费使用的实现过程
2021/11/17 MySQL
apache ftpserver搭建ftp服务器
2022/05/20 Servers