首先,来看一个示例:
function Rectangular(width, height) {
this.width = width;
this.height = height;
}
Rectangular.prototype.getArea = function() {
return this.width * this.height;
}
const r = new Rectangular(2,3);
console.log( r.getArea())
// 6
以上,可以认为我们构建了一个类。
但问题也随着而来,我们可以访问内部的 weight
和 height
字段。
即:
console.log( r.height) //3
console.log( r.width) //2
这样导致最大的问题就是:可以修改内部变量。
r.height = 20;
console.log( r.getArea())
// 40
本文的目的,就是讨论几种方法,让诸如 weight
和 height
的变量无法被外部访问。
但众所周知,JS 天生是无法实现私有变量的。。。
然而,在实际工作中,不得不将类内部的变量封装起来,使得外部无法访问这些变量。
来看几种方法。
将源代码改为:
function Rectangular(width, height) {
this._width = width;
this._height = height;
}
Rectangular.prototype.getArea = function() {
return this._width * this._height;
}
在变量名字前添加下划线 _, 可以告知他人,这个变量属于私有变量。
但可惜,这只是一种约定,实际上并无太大变化。
console.log( r._height) //3
console.log( r._width) //2
闭包是指能够访问其他函数内部变量的函数。(在下例中,函数 getArea
就是一个闭包)
function Rectangular(width, height) {
let _width = width;
let _height = height;
this.getArea = function() {
return _width * _height;
}
}
const r = new Rectangular(2,3);
console.log( r.getArea())
console.log( r._height) //undefined
console.log( r._width) //undefined
这样,确实实现了私有变量,但问题也随着而来:
当进行r.getArea()
时候,访问的两个变量会常驻内存。这样可能带来一定的性能问题。
简单看一下 Symbol 的实现。
function Rectangular(width, height) {
let _width = Symbol('width');
let _height = Symbol('height');
this[_width] = width;
this[_height] = height;
this.getArea = function() {
return this[_width] * this[_height];
}
}
const r = new Rectangular(2,3);
console.log( r.getArea())
console.log( r[_height])
WeakMap 的实现与其大同小异,可读性都不太好。。
这里实现的基本思想是:使用 Proxy 重新定义一些操作,例如,拦截访问 _
开头变量名称。
const handler = {
get: function(target, key) {
if (key[0] === '_') {
throw new Error('Attempt to access private property');
}
return target[key];
},
set: function(target, key, value) {
if (key[0] === '_') {
throw new Error('Attempt to access private property');
}
target[key] = value;
}
}
class Rectangular {
constructor(width, height) {
this._width = width;
this._height = height;
}
get getArea() {
return this._width * this._height;
}
}
const r = new Proxy(new Rectangular(2,3), handler);
console.log( r.getArea) //6
// console.log( r._height) //3
当执行 console.log( r._height) //3;
便会报错,这样就可以防止外部修改函数内部变量。
在 TypeScirpt 中,类内部提供了一个 private
字段,
class Rectangular {
private width;
private height;
constructor( width, height){
this.width = width;
this.height = height;
}
getArea = function() {
return this.width * this.height;
}
}
const r = new Rectangular(2,3);
console.log( r.getArea()) // 6
此时,再访问 r.width
或者 r.height
会直接报错。
这样看起来很简洁很完美对不对?(当然了,ts 的性能问题那就是另一回事了)
可惜,将该 ts 代码编译为 js 代码后,可以看到:
var Rectangular = /** @class */ (function () {
function Rectangular(width, height) {
this.getArea = function () {
return this.width * this.height;
};
this.width = width;
this.height = height;
}
return Rectangular;
}());
var r = new Rectangular(2, 3);
console.log(r.getArea());
其实是使用了 立即执行函数。
然而,这段 js 代码执行依旧可以访问 r.width
这类属性。。
据我推断,typescript 是在编译时进行的检查,进而实现了私有变量。
在最新的 ECMA 提案中,将字符 #
作为一种标识符,用来表明变量属于私有变量,例如 #width
.