JavaScript 中实现私有变量

首先,来看一个示例:

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

以上,可以认为我们构建了一个类。

但问题也随着而来,我们可以访问内部的 weightheight 字段。

即:

console.log( r.height)	//3
console.log( r.width)	//2

这样导致最大的问题就是:可以修改内部变量。

r.height = 20;
console.log(  r.getArea())
// 40

本文的目的,就是讨论几种方法,让诸如 weightheight 的变量无法被外部访问。

但众所周知,JS 天生是无法实现私有变量的。。。

然而,在实际工作中,不得不将类内部的变量封装起来,使得外部无法访问这些变量。

来看几种方法。

1. 下划线命名约定

将源代码改为:

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

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

这样,确实实现了私有变量,但问题也随着而来:
JavaScript 中实现私有变量_第1张图片
当进行r.getArea()时候,访问的两个变量会常驻内存。这样可能带来一定的性能问题。

3. Symbol 与 WeakMap

简单看一下 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 的实现与其大同小异,可读性都不太好。。

4. Proxy

这里实现的基本思想是:使用 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; 便会报错,这样就可以防止外部修改函数内部变量。

5. TypeScirpt

在 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 是在编译时进行的检查,进而实现了私有变量。

6.未来

在最新的 ECMA 提案中,将字符 # 作为一种标识符,用来表明变量属于私有变量,例如 #width.

你可能感兴趣的:(JavaScript 中实现私有变量)