js中模拟实现私有属性

今天在看《你不知道的js》这本书时,无意看到Object还有个方法叫做 getOwnPropertySymbols() ,用来获取对象的Symbol属性。记得之前看过一些文章说,可以使用Symbol来实现私有属性,如果能直接使用 getOwnPropertySymbols() 方法获取Symbol属性,那还是私有属性么?

今天复习整理一下关于js中创建私有属性的一些问题。

由于js并不是Java那种类式面向对象,因此即使es6添加了class支持,js根上还是基于原型的面向对象,不支持什么私有,公有属性的。

要想实现私有属性,基于现有的js,途径只有一个: 闭包。

至于什么是闭包,网上一大把,有兴趣也可以看看我之前整理的文章

使用原始的闭包

const User = (function () {
    return class User{
        constructor(name) {
            this.getName = function () {
                return name;
            }
            this.setName = function (name) {
                this.getName = function () {
                    return name;
                }
            }
        }
    };
})();

这种方式的确可以实现私有的属性,而且如果有子类继承,也可以如下写法:

const Boy = (function () {
    return class Boy extends User{
        constructor(name){
            super(name);
            this.getGender = function () {
                return 'boy';
            }
        }
    }
})();

但问题也有:

  • 代码组织混乱。由于对构造器函数形成一个闭包,因此所有的setter,getter函数都写在了构造器内。可以从上面的User的写法中看出,setter函数中,还要再定义一遍getter,这中混乱,不是一般能忍受的。如果业务逻辑中还有其他的更多操作,那么混乱程度一下子就上来了。
  • 闭包的内存开销不容小觑。

针对上面的问题,我们可不可以将name单独拿出来放到单独一个地方存储,优化一下代码的组织呢?比如下面这个:

const User = (function () {
    let privateName = null;
    return class User{
        constructor(name) {
            privateName = name;
        }
        getName(){
            return privateName;
        }
        setName(name) {
            privateName = name;
        }
    };
})();

上面这个例子,貌似是可以的,这样代码也清晰了,也能实现私有属性。

但是,仔细分析一下,真的可以么?

如此的话,是不是所有的实例对象都共享一个 privateName 属性?后面的实例会覆盖前面实例的值。看下面:

const u1 = new User('coolcao');
const u2 = new User('lili');
console.log(u2.getName());
console.log(u1.getName());
// lili
// lili

针对上面的问题,我们使用一个 privateName 保存私有属性,会被覆盖,那么我们如果使用一个数组,保存多个,然后针对每个实例,生成一个唯一的存取标识呢?

基于散列实现

const User = (function () {
    const privateData = {};
    let i = 0;
    return class User {
        constructor(name){
            this['_id'] = i ++;
            privateData[this['_id']] = {name: name};
        }
        getName() {
            return privateData[this['_id']].name;
        }
        setName(name) {
            privateData[this['_id']].name = name;
        }
    };
})();

我们使用 privateData 这个对象来保存所有实例的私有属性,针对每个实例,使用一个id进行标识,每实例化一个实例,该id会自动加1。然后将该id作为键值,将私有属性存入 privateData 对象。
嗯,这样看上去比直接在构造器中使用闭包要清晰多了,而且实例与实例之间也不冲突。

在es6之前,这可能是最合适的方案。虽然也会存在问题:

每个实例对象都会引用 privateData ,因此,还是由于闭包的问题,如果实例太多的话,内存是个问题。

WeakMap实现

幸好 es6 来了,带来了一个叫做 WeakMap 的东西,具体这东西是啥呢?可以看看阮老师的教程

简单说来,WeakMap键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。

好了,那么上面的这个强引用关系,我们可以使用 WeakMap 弱引用来代替:

const User = (function () {
    const privateData = new WeakMap();
    class User {
        constructor(name) {
            privateData.set(this,{name:name});
        }
        getName() {
            return privateData.get(this).name;
        }
        setName(name) {
            privateData.get(this).name = name;
        }
    }
})();

如此的代码,干净清爽了许多,而且由于WeakMap是弱引用,如果没有其他引用和该键引用同一个对象,这个对象将会被当作垃圾回收掉。解决了内存泄露的问题。

好了,js模拟闭包,就这几个方式了,从这几个例子来看,都使用了自执行函数(IIFE),因此都会形成闭包。这也是我最开始说的,要想在js实现私有属性,只能使用闭包。

Symbol的问题

话说回来我当初的疑问,ES6的Symbol实现的私有属性有啥问题呢?

看下面例子:

const User = (function () {
    const NAME = Symbol('User#Name');
    return class User{
        constructor(name){
            this[NAME] = name;
        }
        getName(){
            return this[NAME];
        }
        setName(name) {
            this[NAME] = name;
        }
    }
})();

NAME是个Symbol,从外部并不能拿到NAME确切的值,好像是有点私有属性的意思。但是 有一个 Object.getOwnPropertySymbols() 方法可以拿到对象所有的Symbol属性,虽然我不知道具体存了个啥,但是能拿到这个标识,就可以修改属性值了:

const smbs = Object.getOwnPropertySymbols(user);
for(let s of smbs) {
    user[s] = 'good';
}
console.log(user.getName());
// good

因此,严格意义上说,Symbol其实并不能实现私有属性。

不过倒是可以将上面第二种基于散列的方式改为Symbol的方式:

const User = (function () {
    const privateData = {};
    return class User {
        constructor(name){
            this._name = Symbol('name');
            privateData[this._name] = {name: name};
        }
        getName() {
            return privateData[this._name].name;
        }
        setName(name) {
            privateData[this._name].name = name;
        }
    };
})();

这样对外暴露的只是 _name 的这个符号,外部还是无法直接访问每个实例的 privateData 中的值。但这个其实和第二种是一样的,闭包引起的问题还是无法解决。

展望未来

js的私有属性,目前处于 stage2 阶段,目前还未最终确定,不过我们可以先看一下模样:

class Point {
  #x;
  #y;

  constructor(x, y) {
    this.#x = x;
    this.#y = y;
  }

  equals(point) {
    return this.#x === point.#x && this.#y === point.#y;
  }
}

语法如上,使用 # 定义私有属性,在类的内部可以直接 使用 this.#x 的形式引用。
目前使用#进行定义和访问私有属性,未来会不会使用 public,private等关键字不得而知。

目前使用#,可能的原因是,js没有静态类型系统。

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容