探索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 相关文章推荐
javascript 文章截取部分无损html显示实现代码
May 04 Javascript
js前台分页显示后端JAVA数据响应
Mar 18 Javascript
js中生成map对象的方法
Jan 09 Javascript
基于jquery实现人物头像跟随鼠标转动
Aug 23 Javascript
JavaScript的Backbone.js框架入门学习指引
May 07 Javascript
jQuery.Uploadify插件实现带进度条的批量上传功能
Jun 08 Javascript
基于jQuery实现咖啡订单管理简单应用
Feb 10 Javascript
AngularJs用户输入动态模板XSS攻击示例详解
Apr 21 Javascript
详解基于Vue-cli搭建的项目如何和后台交互
Jun 29 Javascript
浅谈webpack+react多页面开发终极架构
Nov 11 Javascript
vue递归组件实战之简单树形控件实例代码
Aug 27 Javascript
使用layer弹窗提交表单时判断表单是否输入为空的例子
Sep 26 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 常用的header头部定义汇总
2015/06/19 PHP
magento后台无法登录解决办法的两种方法
2016/12/09 PHP
Javascript 表单之间的数据传递代码
2008/12/04 Javascript
javascript学习笔记(一) 在html中使用javascript
2012/06/18 Javascript
纯Javascript实现Windows 8 Metro风格实现
2013/10/15 Javascript
jQuery中[attribute=value]选择器用法实例
2014/12/31 Javascript
Bootstrap模仿起筷首页效果
2016/05/09 Javascript
Vue.js 父子组件通讯开发实例
2016/09/06 Javascript
JS实现选定指定HTML元素对象中指定文本内容功能示例
2017/02/13 Javascript
vue.js数据绑定的方法(单向、双向和一次性绑定)
2017/07/13 Javascript
vue-cli下的vuex的简单Demo图解(实现加1减1操作)
2018/02/26 Javascript
Bootstrap Table 双击、单击行获取该行及全表内容
2018/08/31 Javascript
微信小程序调用微信支付接口的实现方法
2019/04/29 Javascript
简单了解vue中父子组件如何相互传递值(基础向)
2019/07/12 Javascript
微信小程序用户授权、位置授权及获取微信绑定手机号
2019/07/18 Javascript
Python 字典dict使用介绍
2014/11/30 Python
使用Nginx+uWsgi实现Python的Django框架站点动静分离
2016/03/21 Python
详解Python中 __get__和__getattr__和__getattribute__的区别
2016/06/16 Python
Python GUI Tkinter简单实现个性签名设计
2018/06/19 Python
详解Python中pandas的安装操作说明(傻瓜版)
2019/04/08 Python
使用GitHub和Python实现持续部署的方法
2019/05/09 Python
Flask框架实现的前端RSA加密与后端Python解密功能详解
2019/08/13 Python
详解一种用django_cache实现分布式锁的方式
2019/09/01 Python
基于python调用psutil模块过程解析
2019/12/20 Python
python实现人机猜拳小游戏
2020/02/03 Python
Django之choices选项和富文本编辑器的使用详解
2020/04/01 Python
python3 循环读取excel文件并写入json操作
2020/07/14 Python
详解scrapy内置中间件的顺序
2020/09/28 Python
使用Python+Appuim 清理微信的方法
2021/01/26 Python
Eastbay官网:美国最大的运动鞋网络零售商
2016/07/27 全球购物
法国娇韵诗官方旗舰店:Clarins是来自法国的天然护肤品牌
2018/06/30 全球购物
万宝龙英国官网:Montblanc手表、书写工具、皮革和珠宝
2018/10/16 全球购物
Currentbody澳大利亚:美容仪专家
2019/11/11 全球购物
实习销售业务员自我鉴定
2013/09/21 职场文书
《春天来了》教学反思
2014/04/07 职场文书
工伤劳动仲裁代理词
2015/05/25 职场文书