浅谈class私有变量
class 的前世今生
在 es6 之前,虽然 JS 和 Java 同样都是 OOP (面向对象)语言,但是在 JS 中,只有对象而没有类的概念。
在 JS 中,生成实例对象的传统方法是通过构造函数,如下所示:
function A (x) {
this.x = x
}
// 在原型链上挂载原型方法
A.prototype.showX = function () {
return this.x
}
// 生成对象实例
let a = new A(1)
// 调用原型方法
a.showX() // 1
对比传统 OOP 语言中的类写法,这种写法让许多学过其他 OOP 语言的 JS 初学者感到困惑。
为了实现在 JS 中写 Java 的心愿,当时有人将构造函数写法封装成了类似于 Java 中类的写法的 klass 语法糖 。
有人会问,为什么是 klass 而不是 class ?当然是因为 class 是 JS 中的保留关键字,直接用 class 会报错。
就这样凑合着过了好多年,直到 es6 发布,在 es6 中, klass 终于备胎转正,摇身一变变成了 class ,终于从官方角度实现了梦想。
之前的代码转换成 class 是这样的:
class A {
// 构造函数,相当于之前的函数A
constructor(x) {
this.x = x
}
// 相当于挂载在原型链上的原型方法
showX () {
return this.x
}
}
// 生成对象实例
let a = new A(1)
// 调用原型方法
a.showX() // 1
可以发现, class 的写法更接近传统 OOP 语言。
class 的不足
看起来, es6 中 class 的出现拉近了 JS 和传统 OOP 语言的距离。但是,就如之前所说的 klass 一样,它仅仅是一个语法糖罢了,不能实现传统 OOP 语言一样的功能。在其中,比较大的一个痛点就是私有变量问题。
何为私有变量?私有变量就是只能在类内部访问的变量,外部无法访问的变量。在开发中,很多变量或方法你不想其他人访问,可以定义为私有变量,防止被其他人使用。在 Java 中,可以使用 private 实现私有变量,但是可惜的是, JS 中并没有该功能。
来看下下面这个代码:
class A {
constructor(x) {
this.x = x
}
// 想要通过该方法来暴露x
showX () {
return this.x
}
}
let a = new A(1)
// 直接访问x成功
a.x // 1
可以看到,虽然本意是通过方法 showX 来暴露 x 的值,但是可以直接通过 a.x
来直接访问 x 的值。
很明显,这影响了代码的封装性。要知道,这些属性都是可以使用 for...in
来遍历出来的。
所以,实现 class 的私有变量功能是很有必要的。
实现 class 私有变量
虽然, class 本身没有提供私有变量的功能,但是,我们可以通过通过一些方式来实现类似于私有变量的功能。
约定命名
首先,是目前使用最广的方式:约定命名,又称为:自己骗自己或者潜规则。
该方式很简单,就是团队自行约定一种代表着私有变量的命名方式,一般是在私有变量的名称前加上一个下划线。代码如下:
class A {
constructor(x) {
// _x 是一个私有变量
this._x = x
}
showX () {
return this._x
}
}
let a = new A(1)
// _x 依然可以被使用
a._x // 1
a.showX() //1
可以发现,该方法最大的优点是简单、方便,所以很多团队都采用了这种方式。
但是,该方式并没有从本质上解决问题,如果使用 for...in
依然可以遍历出所谓的私有变量,可以说是治标不治本。
不过,该方式有一点值得肯定,那就是通过约定规范来方便他人阅读代码。
闭包
闭包在很多时候被拿来解决模块化问题,显而易见,私有变量本质上也是一种模块化问题,所以,我们也可以使用闭包来解决私有变量的问题。
我们在构造函数中定义一个局部变量,然后通过方法引用,该变量就成为了真正的私有变量。
class A {
constructor (x) {
let _x = x
this.showX = function () {
return _x
}
}
}
let a = new A(1)
// 无法访问
a._x // undefined
// 可以访问
a.showX() // 1
该方法最大的优点就是从本质解决了私有变量的问题。
但是有个很大的问题,在这种情况下,引用私有变量的方法不能定义在原型链上,只能定义在构造函数中,也就是实例上。这导致了两个缺点:
- 增加了额外的性能开销
- 构造函数包含了方法,较为臃肿,对后续维护造成了一定的麻烦(很多时候,看到代码写成一坨就不想看 -_-)
进阶版闭包
进阶版闭包方式可以基本完美解决上面的那个问题:既然在构造函数内部定义闭包那么麻烦,那我放在 class 外面不就可以了吗?
我们可以通过 IIFE
(立即执行函数表达式) 建立一个闭包,在其中建立一个变量以及 class ,通过 class 引用变量实现私有变量。
代码如下:
// 利用闭包生成IIFE,返回类A
const A = (function() {
// 定义私有变量_x
let _x
class A {
constructor (x) {
// 初始化私有变量_x
_x = x
}
showX () {
return _x
}
}
return A
})()
let a = new A(1)
// 无法访问
a._x // undefined
// 可以访问
a.showX() //1
可以发现,该方法完美解决了之前闭包的问题,只不过写法相对复杂一些,另外,还需要额外创建 IIFE
,有一点额外的性能开销。
注1:该方式也可以不使用 IIFE
,可以直接将私有变量置于全局,但是这不利于封装性,所以,我在这里采用了 IIFE
的方式。
注2:对于 IIFE
是否是个闭包,在 You-Dont-Know-JS 这本书中有过争议,有兴趣的同学可以前去了解一下,在此不再赘述。
Symbol
这种方式利用的是 Symbol
的唯一性—— 敌人最大的优势是知道我方key值,我把key值弄成唯一的,敌人不就无法访问了吗? (人质是这次任务的关键,当敌人不再拥有人质时,任务也就完成了)
代码如下:
// 定义symbol
const _x = Symbol('x')
class A {
constructor (x) {
// 利用symbol声明私有变量
this[_x] = x
}
showX () {
return this[_x]
}
}
let a = new A(1)
// 自行定义一个相同的Symbol
const x = Symbol('x')
// 无法访问
a[x] // undefined
// 可以访问
a.showX() //1
从结果来看,完美地实现了 class 私有变量。
个人认为,这是目前最完美的实现私有变量的方式,唯一的缺点就是 Symbol
不太常用,很多同学不熟悉。
私有属性提案
针对 es6 中的 class 没有私有属性的问题,产生了一个提案——在属性名之前加上 #
,用于表示私有属性。
class A {
#x = 0
constructor (x) {
#x = x
}
showX () {
return this.#x
}
}
很多同学会有一个问题,私有属性提案为什么不使用 private 而使用 #
?是人性的扭曲还是道德的沦丧? 这一点和编译器性能有关(其实我个人认为还有一大原因是向 Python 靠拢,毕竟从 es6 以来, JS 一直向着 Python 发展),有兴趣的同学可以去了解了解。
不过该提案仅仅还是提案罢了,并没有进入标准,所以依然无法使用。
最后
如果上述所有方法全都满足不了你,还有一个终极方法—— TypeScript 。使用 TS ,让你享受在 JS 中写 Java 的快感!区区私有变量,不在话下。
就今年的发展趋势来看, TS 已经成为前端必备的技能之一,连之前 diss 过 TS 的尤大都已经开始用 TS 重写 Vue 了(尤大:真香)。
最后的最后
又到了日常的求 star 环节,如果大家觉得这篇文章还不错的话,不如给我点个 star 再走呗~
浅谈class私有变量的更多相关文章
- 浅谈C# 匿名变量
每次写博客,第一句话都是这样的:程序员很苦逼,除了会写程序,还得会写博客!当然,希望将来的一天,某位老板看到此博客,给你的程序员职工加点薪资吧!因为程序员的世界除了苦逼就是沉默.我眼中的程序员大多都不 ...
- 浅谈C语言变量声明的解析
C语言本身提供了一种不甚明确的变量声明方式——基于使用的声明,如int *a,本质上是声明了*a的类型为int,所以得到了a的类型为指向int的指针.对于简单类型,这样声明并不会对代码产生多大的阅读障 ...
- 浅谈javascript的变量作用域
1.变量遵循先声明再使用. console.log(b); b=123; 代码运行结果: Uncaught ReferenceError: b is not defined 2.方法内定义的局部变量外 ...
- 浅谈JS的变量提升
JS的解析机制,是JS的又一大重点知识点,在面试题中更经常出现,今天就来唠唠他们的原理.首先呢,我们在我们伟大的浏览器中,有个叫做JS解析器的东西,它专门用来读取JS,执行JS.一般情况是存在作用域就 ...
- 浅谈javascript中变量作用域和内存(1)
先理解两个概念:基本类型和引用类型的值 1.基本类型和引用类型的值 (1)定义: 基本类型:指简单的数据段,比如按值访问的js五种基本数据类型undefined.null.boolean.number ...
- 浅谈javascript中变量作用域和内存(2)
1.无块级作用域 javascript没有块级作用域,这会让其他程序员在理解js代码上很痛苦.在其他很多语言,比如C,大括号括起来的代码块都有自己的作用域 举个例子 if(true) { var na ...
- 浅谈 PHP 变量可用字符
原文:浅谈 PHP 变量可用字符 先来说说php变量的命名规则,百度下一抓一大把:(1) PHP的变量名区分大小写;(2) 变量名必须以美元符号$开始;(3) 变量名开头可以以下划线开始;(4) 变量 ...
- Android安全开发之启动私有组件漏洞浅谈
0x00 私有组件浅谈 android应用中,如果某个组件对外导出,那么这个组件就是一个攻击面.很有可能就存在很多问题,因为攻击者可以以各种方式对该组件进行测试攻击.但是开发者不一定所有的安全问题都能 ...
- c#Winform程序调用app.config文件配置数据库连接字符串 SQL Server文章目录 浅谈SQL Server中统计对于查询的影响 有关索引的DMV SQL Server中的执行引擎入门 【译】表变量和临时表的比较 对于表列数据类型选择的一点思考 SQL Server复制入门(一)----复制简介 操作系统中的进程与线程
c#Winform程序调用app.config文件配置数据库连接字符串 你新建winform项目的时候,会有一个app.config的配置文件,写在里面的<connectionStrings n ...
随机推荐
- 9.21 form 和Ajax详解
form 表单 参考连接 : http://www.cnblogs.com/liwenzhou/p/8747872.html
- CentOS7.5安装Tomcat8
一.tomcat的简介 这是Apache Tomcat Servlet / JSP容器的文档包的顶级入口点 .的Apache Tomcat 8.0版实现了Servlet 3.1和JavaServer ...
- 使用SQL手动创建数据库并创建一个具有该数据库所有权限的用户
$ mysql -u adminusername -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. ...
- HOJ 2091 Chess(三维简单DP)
Chess My Tags (Edit) Source : Univ. of Alberta Local Contest 1999.10.16 Time limit : 1 sec Memory li ...
- PyCharm+Eclipse共用Anaconda的数据科学环境
1.安装anaconda2 安装好之后,本地python环境就采用anaconda自带的python2.7的环境. 2.安装py4j 在本地ctrl+r打开控制台后,直接使用pip安装py4j,因为a ...
- 选择排序之python
选择排序( Selection sort) 1.算法描述: 通过n-i次关键字之间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<=i<=n)个记录进行交换. 对尚未完成排 ...
- centos7设置iptables
https://www.linuxidc.com/Linux/2017-10/147238.htm
- virtualenvwrapper 的安装和使用(转)
原文:http://www.cnblogs.com/asmer-stone/p/5470144.html virtualenvwrapper是用来管理virtualenv的扩展包,用着很方便. 1. ...
- TortoiseGit的使用
TortoiseGit只是一个外壳而已,它调用的是msysgit,相当于msysgit的windows gui而已,如果喜欢用git命令行,那就不需要安装它. 所以要先安装msysgit(window ...
- linux桌面与命令模式切换
在图形下面按Ctrl+alt+F1(F2\F3\F4)进入命令模式 在命令模式下,按Ctrl+alt+F7回到图形,或登录用户输入startx进入图形!