JavaScript实现私有属性
JavaScript被很多人认为并不是一种面向对象语言,原因有很多种,比如JavaScript没有类,不能提供传统的类式继承;再比如JavaScript不能实现信息的隐藏,不能实现私有成员。本文并不是为了打破以上误解(实际上笔者自己也有困惑),只是简单介绍几种JavaScript实现私有属性的方式,以及各自的优劣。
1. 基于编码规范约定实现方式
很多编码规范把以下划线_
开头的变量约定为私有成员,便于同团队开发人员的协同工作。实现方式如下:
function Person(name){
this._name = name;
}
var person = new Person('Joe');
这种方式只是一种规范约定,很容易被打破。而且也并没有实现私有属性,上述代码中的实例person可以直接访问到_name
属性:
alert(person._name); //'Joe'
2. 基于闭包的实现方式
另外一种比较普遍的方式是利用JavaScript的闭包特性。构造函数内定义局部变量和特权函数,其实例只能通过特权函数访问此变量,如下:
function Person(name){
var _name = name;
this.getName = function(){
return _name;
}
}
var person = new Person('Joe');
这种方式的优点是实现了私有属性的隐藏,Person 的实例并不能直接访问_name
属性,只能通过特权函数getName获取:
alert(person._name); // undefined
alert(person.getName()); //'Joe'
使用闭包和特权函数实现私有属性的定义和访问是很多开发者采用的方式,Douglas Crockford也曾在博客中提到过这种方式。但是这种方式存在一些缺陷:
- 私有变量和特权函数只能在构造函数中创建。通常来讲,构造函数的功能只负责创建新对象,方法应该共享于prototype上。特权函数本质上是存在于每个实例中的,而不是prototype上,增加了资源占用。
3. 基于强引用散列表的实现方式
JavaScript不支持Map数据结构,所谓强引用散列表方式其实是Map模式的一种变体。简单来讲,就是给每个实例新增一个唯一的标识符,以此标识符为key,对应的value便是这个实例的私有属性,这对key-value保存在一个Object内。实现方式如下:
var Person = (function() {
var privateData = {},
privateId = 0;
function Person(name) {
Object.defineProperty(this, "_id", { value: privateId++ });
privateData[this._id] = {
name: name
};
}
Person.prototype.getName = function() {
return privateData[this._id].name;
};
return Person;
}());
上述代码的有以下几个特征:
- 使用自执行函数创建Person类,变量privateData和privateId被所有实例共享;
- privateData用来储存每个实例的私有属性name的key-value,privateId用来分配每个实例的唯一标识符
_id
; - 方法getName存在于prototype上,被所有实例共享。
这种方式在目前ES5环境下,基本是最佳方案了。但是仍然有一个致命的缺陷:散列表privateData对每个实例都是强引用,导致实例不能被垃圾回收处理。如果存在大量实例必然会导致memory leak。
造成以上问题的本质是JavaScript的闭包引用,以及只能使用字符串类型最为散列表的key值。针对这两个问题,ES6新增的WeakMap可以良好的解决。
4. 基于WeakMap的实现方式
WeakMap有以下特点:
- 支持使用对象类型作为key值;
- 弱引用。
根据WeakMap的特点,便不必为每个实例都创建一个唯一标识符,因为实例本身便可以作为WeakMap的key。改进后的代码如下:
var Person = (function() {
var privateData = new WeakMap();
function Person(name) {
privateData.set(this, { name: name });
}
Person.prototype.getName = function() {
return privateData.get(this).name;
};
return Person;
}());
改进的代码不仅仅干净了很多,而且WeakMap是一种弱引用散列表, 这意味着,如果没有其他引用和该键引用同一个对象,这个对象将会被当作垃圾回收掉。解决了内存泄露的问题。
不幸的是,目前浏览器对WeakMap的支持率并不理想,投入生产环境仍然需要等待。
参考文献:
- WeakMap - MDN;
- WeakMap Objects - ES6;
- Private_Properties - MDN;
- Private instance members with weakmaps in JavaScript by Nicholas C. Zakas
- SpringMVC + Mybatis bug调试 SQL正确,查数据库却返回NULL
- 原生javascript实现图片轮播效果代码
- Spring AOP中 args和arg-names的区别
- Golong 语言开发 go-websocket-sample 测试值得拥有
- Java面试系列23-spring(2)-配置数据库驱动、依赖、Mapping等
- 【Golang语言社区】 Go语言中使用 Protobuf
- Java面试系列21-xml
- tensorflow载入数据的三种方式 之 TF生成数据的方法
- JS游戏开发 可移动地图的实现
- Java面试系列-多线程
- pymongo.errors:Sort operation used more than the maximum 33554432 bytes of RAM. Add an index,
- PG学习初体验--源码安装和简单命令(r8笔记第97天)
- Pymongo: TypeError: if no direction is specified, key_or_list must be an instance of list
- Java面试系列19-Struts2
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 用C++跟你聊聊“观察者模型”
- 【tensorflow2.0】构建模型的三种方法
- django实战(一)--dango自带的分页(极简)
- 线程池 -- 动态链接库
- 【tensorflow2.0】训练模型的三种方法
- 用C++跟你聊聊“建造者模式”
- 【tensorflow2.0】使用TPU训练模型
- 用C++跟你聊聊“外观模式”
- 【tensorflow2.0】使用tensorflow-serving部署模型
- 用C++跟你聊聊“模板方法模式”
- 用C++跟你聊聊“原型模式” (复制/拷贝构造函数)
- 【numpy】生成图片格式的数据
- 【numpy】新版本中numpy(numpy>1.17.0)中的random模块
- 用C++跟你聊聊“代理模式”
- mybatis与spring整合步骤以及自己遇到的错误