浅谈class私有变量

class的前世今生

es6 之前,虽然 JSJava 同样都是 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 ?当然是因为 classJS 中的保留关键字,直接用 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的不足

看起来, es6class 的出现拉近了 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

该方法最大的优点就是从本质解决了私有变量的问题。

但是有个很大的问题,在这种情况下,引用私有变量的方法不能定义在原型链上,只能定义在构造函数中,也就是实例上。这导致了两个缺点:

  1. 增加了额外的性能开销
  2. 构造函数包含了方法,较为臃肿,对后续维护造成了一定的麻烦(很多时候,看到代码写成一坨就不想看 -_-)

进阶版闭包

进阶版闭包方式可以基本完美解决上面的那个问题:既然在构造函数内部定义闭包那么麻烦,那我放在 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.show()    //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)

// 无法访问
a._x        // undefined
// 可以访问
a.show()    //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 已经成为前端必备的技能之一,连之前 dissTS 的尤大都已经开始用 TS 重写 Vue 了(尤大:真香)。

最后的最后

又到了日常的求 star 环节,如果大家觉得这篇文章还不错的话,不如给我点个 star 再走呗~

Github

原文地址:https://www.cnblogs.com/karthuslorin/p/10189178.html

时间: 2024-10-12 07:34:02

浅谈class私有变量的相关文章

浅谈C语言变量声明的解析

C语言本身提供了一种不甚明确的变量声明方式--基于使用的声明,如int *a,本质上是声明了*a的类型为int,所以得到了a的类型为指向int的指针.对于简单类型,这样声明并不会对代码产生多大的阅读障碍,而对于复杂的声明,比如标准库的signal函数签名,void (*signal( int sig, void (*handler) (int))) (int),这是什么?一眼看不出来吧,这是一个函数,接受两个参数,一个int,一个函数指针,而这个函数指针指向的函数接受一个int并返回void:返

浅谈javascript的变量作用域

1.变量遵循先声明再使用. console.log(b); b=123; 代码运行结果: Uncaught ReferenceError: b is not defined 2.方法内定义的局部变量外部不能访问. function my(){ var a='hi'; } my(); console.log(a); 代码运行结果: Uncaught ReferenceError: b is not defined 同样是b未定义的错误. 3.一个方法嵌套另一个方法:外面方法可以访问里面声明的变量:

浅谈C# 匿名变量和匿名方法

每次写博客,第一句话都是这样的:程序员很苦逼,除了会写程序,还得会写博客!当然,希望将来的一天,某位老板看到此博客,给你的程序员职工加点薪资吧!因为程序员的世界除了苦逼就是沉默.我眼中的程序员大多都不爱说话,默默承受着编程的巨大压力,除了技术上的交流外,他们不愿意也不擅长和别人交流,更不乐意任何人走进他们的内心,他们常常一个人宅在家中! 闲话说多了,咱进入正题: 首先讲解下匿名变量,在讲解匿名变量之前,我先通过代码展示匿名变量的声明,如下: static void Main() { var A

浅谈对离散型随机变量期望的理解

在看<程序员的数学2--概率统计>关于离散型随机变量的大数定律解释时,有两个概念一定需要弄明白: 随机变量的期望: 随机变量结果的平均值. 在<Probability and Statistics>这本国外的经典教材第四章第一小节中,强调了随机变量的期望只与随机变量的分布有关系: Note: The Expectation of X Depends Only on the Distribution of X. Although E(X) is called the expectat

Android安全开发之启动私有组件漏洞浅谈

0x00 私有组件浅谈 android应用中,如果某个组件对外导出,那么这个组件就是一个攻击面.很有可能就存在很多问题,因为攻击者可以以各种方式对该组件进行测试攻击.但是开发者不一定所有的安全问题都能考虑全面. ? 对于这样的问题,最方便的修复方式就是在确定不影响业务的情况下,将这个存在问题的组件不对外导出变成私有组件.这样做的确很有效,私有组件也很安全.但是,如果存在某个私有组件能被导出组件启动的话,那么这个私有组件其实就不再是私有了.如果攻击者可以通过控制导出的组件对私有组件进行控制,那么攻

浅谈 PHP 变量可用字符

原文:浅谈 PHP 变量可用字符 先来说说php变量的命名规则,百度下一抓一大把:(1) PHP的变量名区分大小写;(2) 变量名必须以美元符号$开始;(3) 变量名开头可以以下划线开始;(4) 变量名不能以数字字符开头. 其实所有编程都类似的命名规范就是:1. 变量第一个字符最好是 字母或_,不能以数字开头2. 第二个字符开始允许 数字,字母,_ 好了,差不多就是这样了,但是这不是我们要说的重点.今天我们说说 PHP 变量的可用字符,不仅仅是 数字,字母,_ 哦. 前几天QQ上一朋友发我一个s

浅谈linux中shell变量$#,[email&#160;protected],$0,$1,$2,$?的含义解释

浅谈linux中shell变量$#,[email protected],$0,$1,$2,$?的含义解释 下面小编就为大家带来一篇浅谈linux中shell变量$#,[email protected],$0,$1,$2的含义解释.小编觉得挺不错的,现在就分享给大家,也给大家做个参考.一起跟随小编过来看看吧 摘抄自:ABS_GUIDE 下载地址:http://www.tldp.org/LDP/abs/abs-guide.pdf linux中shell变量$#,[email protected],$

浅谈C++ IO标准库(1)

IO流:一.C++中标准IO库:1).为面向对象的标准库.2).以继承的形式设计.     A)以iostream为基类,派生出了fstream,strigstream类.注意:fstream.stringstream没有继承关系,open.close为fstream类自有的函数操作,str为stringstream自有的函数操作,故其各函数操作不可混用,而iostream中的函数操作其两子类由于继承关系可以调用.     B) 其禁用了复制和赋值操作,故IO对象不可以复制或赋值.这将导致像ve

C++:浅谈c++资源管理以及对[STL]智能指针auto_ptr源码分析,左值与右值

C++:浅谈c++资源管理以及对[STL]智能指针auto_ptr源码分析 by 小威威 1. 知识引入 在C++编程中,动态分配的内存在使用完毕之后一般都要delete(释放),否则就会造成内存泄漏,导致不必要的后果.虽然大多数初学者都会有这样的意识,但是有些却不以为意.我曾问我的同学关于动态内存的分配与释放,他的回答是:"只要保证new和delete成对出现就行了.如果在构造函数中new(动态分配内存),那么在析构函数中delete(释放)就可以避免内存泄漏了!" 事实果真如此么?