「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
欢迎一起代码交流。
前言: JavaScript编程语言并不是像Java等面向对象式语言,其中原因有很多种,比如JavaScript没有直接的类啊,现在的class都是语法糖,不能提供传统的类式继承啊;还有比如JavaScript不能实现信息的隐藏,即不能实现私有属性的功能。
所以呢,在此只是简单介绍几种JavaScript实现私有属性的方式,以及各自的优劣,请各位看官且,请我听细细道来~
1. 基于代码规范实现
很多编码规范把以下划线_
开头的变量约定为私有成员,便于同团队开发人员的协同工作,代码如下:
function Test(word) {
this._word = word;
}
const t = new Test("nice")
但是呢,这样的约束方式比较容易被打破,其实并没有实际实现私有属性的效果,并且可以直接获取私有属性,可靠性并不强:
console.log(t._word) // "nice"
2. 基于闭包原理实现
为了达到私有效果,大家平时使用比较多的方式就是通过Javascript闭包特性。通过高阶函数或者闭包达到私有属性效果。代码如下:
function Test (name) {
var _name = name;
this.getName = () => {
return _name;
}
}
const t = new Test("tiger");
这种方式隐藏了私有属性,不能直接访问私有属性,只能通过getName
方法获取:
console.log(t._name); // undefined
console.log(t.getName()); // "tiger"
闭包方式有很多开发者采用,但是这个方式实现有一个缺陷:
- 其实,用闭包创建私有变量是不行的,因为实例之间会共享变量,虽然几个变量都实例化了,但是操作的还是同一个属性,这显然是不可取的,这会导致属性的泄露。
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
};
}
Person.prototype.getName = function () {
return privateData[this._id].name;
};
return Person;
}());
当然,你也可以通过Symbol
数据类型进行键的取值,利用 Symbol
变量可以作为对象 key
的特点,我们可以模拟实现更真实的私有属性。
具体代码如下:
var Person = (function () {
const d = Symbol('d'); // 私有属性symbol
const m = Symbol('m'); // 私有方法symbol
class Person {
constructor(first_name, last_name) {
this[d] = {
// 所有的私有属性
// 通过`this[d].xxxx`来访问私有属性
first_name: first_name,
last_name: last_name
}
}
get full_name() {
return this[m].get_full_name.call(this)
}
}
Person.prototype[m] = {
// 私有方法
// 通过`this[m].get_full_name.call(this)`可以访问私有方法
get_full_name() {
return `${this[d].first_name} ${this[d].last_name}`;
},
};
return Person;
})();
虽然Symbol
唯一,不会暴露,外界拿不到,但是这个也不是毫无破绽,ES6 的 Object.getOwnPropertySymbols
可以获取symbol
属性的,而且散列表d
对每个实例都是强引用,导致实例不能被垃圾回收处理。如果存在大量实例必然会导致内存溢出(memory leak)。所以为了解决上述问题,我们可以使用接下来的方法:WeakMap
4. 基于WeakMap的实现方式
WeakMap
有以下特点:
- 支持使用对象类型作为
key
值; - 弱引用。
如果对
WeakMap
不了解的话,请点这里哦
根据WeakMap
的特点,便不必为每个实例都创建一个唯一标识符,因为实例本身便可以作为WeakMap
的key
:
var Person = (function () {
var privateData = new WeakMap();
function Person(name, age) {
privateData.set(this, {
name,
age
});
}
Person.prototype.getName = function () {
return privateData.get(this).name;
};
Person.prototype.getAge = function () {
return privateData.get(this).age;
};
return Person;
}());
改进后的代码不仅仅干净整洁了不少,而且WeakMap是一种弱引用散列表, 这意味着,如果没有其他引用和该键引用同一个对象,这个对象将会被当作垃圾回收掉,解决了内存泄露的问题。
但是呢,目前部分浏览器不支持WeakMap的一些方法。
5. 新式做法 - TypeScript 中的处理方式
TypeScript 是 JavaScript 的一个超集,它会编译为原生 JavaScript 用在生产环境。允许指定私有的、公共的或受保护的属性是 TypeScript 的特性之一。
class Person {
private name;
private age;
constructor(name, age) {
this.name = name;
this.age = age;
}
get userInfo() {
return '姓名:' + this.name + ',年龄:' + this.age;
}
}
const person = new Person('JeSenFu', 200);
console.log(person.userInfo); // 姓名:JeSenFu,年龄:200
使用 TypeScript 需要注意的重要一点是,它只有在 编译 时才获知这些类型,而私有、公共修饰符在编译时才有效果。如果你尝试访问 person.name
,你会发现,居然是可以的。只不过 TypeScript 会在编译时给你报出一个错误,但不会停止它的编译。
TypeScript 是不会自作聪明的,并不会做任何的事情来尝试阻止代码在运行时访问私有属性,它只是对代码进行一个约束。大家要知道它并不能直接解决问题。
其实啊,TypeScript 的 class 私有变量最终编译也是通过 WeakMap
来实现的~。
好啦,本文的内容就结束啦~,欢迎大家的点赞加收藏~,如果有什么疑问可以一起讨论哦~❤