js实现私有属性

私有变量即只能在对象内部被访问 外部不能访问。

这里总结下实现私有变量的六种方式:

Symbol

javascript 复制代码
const _name = Symbol("_name");
export class P {
  constructor() {
    this[_name] = "test";
    this.age = 18;
  }
  say() {
    return `name = ${this[_name]}, age = ${this.age}`;
  }
}

// 利用模块化不导出_name 只导出对象P
const p = new P();
console.log("keys:", Object.keys(p)); // keys: [ 'age' ]
console.log("_name: ", "_name" in p); // _name:  false
console.log("_name: ", p._name, " age: ", p.age); // _name:  undefined  age:  18
console.log("say: ", p.say()); // say: name = test, age = 18

但是Symbol值并不是真正的私有的 是可以通过getOwnPropertySymbols获取到的。

javascript 复制代码
Object.getOwnPropertySymbols(p).forEach((key) => {
  console.log("symbol key: ", p[key]);
});
// symbol key:  test

缺点:并不是真正的属性私有,但是大部分场景是可以满足的 如果没有严格要求是可以使用的。

weakmap

symbol的问题在于我们把属性挂在了对象上然后导出了对象 导致属性被访问到。

javascript 复制代码
let info = new Map();
export class P1 {
  constructor() {
    info.set('_name', 'test')
    this.age = 18;
  }
  say() {
    return `name = ${info.get('_name')}, age = ${this.age}`;
  }
}
const p = new P();
console.log("keys:", Object.keys(p)); // keys: [ 'age' ]
console.log("say: ", p.say()); // say:  name = test, age = 18

这里将属性存储在map里面然后利用模块化 不导出info也不挂在this上 可以实现变量的私有。 然而问题是多个实例会共享配置而且对象被map引用无法被释放。

javascript 复制代码
let info = new Map();

export class P1 {
  constructor() {
    info.set('_name', 'test')
    this.age = 18;
  }
  say() {
    return `name = ${info.get('_name')}, age = ${this.age}`;
  }
}

export class P2 {
    constructor() {
      this.age = 20;
    }
    say() {
      return `name = ${info.get('_name')}, age = ${this.age}`;
    }
}
const p2 = new P2();
console.log("say: ", p2.say()); // say:  name = test, age = 18

所以我们可以改为weakmap实现。

javascript 复制代码
let info = new WeakMap();

export class P1 {
  constructor() {
    info.set(this, 'test')
    this.age = 18;
  }
  say() {
    return `name = ${info.get(this)}, age = ${this.age}`;
  }
}

export class P2 {
    constructor() {
      this.age = 20;
    }
    say() {
      return `name = ${info.get(this)}, age = ${this.age}`;
    }
}
const p1 = new P1();
console.log("say: ", p1.say()); // say:  name = test, age = 18


const p2 = new P2();
console.log("say: ", p2.say()); // say:  name = undefined, age = 18

proxy

通过代理拦截属性的读取,赋值,查询操作 达到属性私有的目的。

javascript 复制代码
class P {
    constructor() {
        this._name = 'test';
        this.age = 18;
    }
    say() {
        return `name = ${this._name}, age = ${this.age}`;
    }
}
const handler = {
    set(target, prop, value, receiver) {
        if (prop.startsWith('_')) {
            return true;
        }
        Reflect.set(target, prop, value, receiver)
    },
    get(target, prop, receiver) {
        if (prop.startsWith('_')) {
            return;
        }
        return Reflect.get(target, prop, receiver);
    },
    ownKeys(target) {
        const keys = Reflect.ownKeys(target);
        return keys.filter((key) => !key.startsWith('_'));
    },
    has(target, prop){
        if (prop.startsWith('_')) {
            return false;
        }
        return Reflect.has(target, prop)
    }
};
const p = new P();
const proxy = new Proxy(p, handler);


console.log('keys:', Object.keys(proxy)) // keys: [ 'age' ]
console.log('_name: ', '_name' in proxy); // _name:  false
console.log('_name: ', proxy._name, ' age: ', proxy.age) // _name:  undefined  age:  18

然后调用

arduino 复制代码
console.log('say:', proxy.say());
// say:name = undefined, age = 18

因为在say方法中this._name中的this指向的是proxy代理对象 然后走到了get被拦截了 所以在这种情况我们需要指定say方法的this指向原始的对象即代理中的target

get钩子修改如下:

kotlin 复制代码
  get(target, prop, receiver) {
    if (prop.startsWith("_")) {
      return;
    }
    // 如果是函数 绑定this执向target原始对象
    if (typeof target[prop] === "function") {
      return target[prop].bind(target); 
    }
    return Reflect.get(target, prop, receiver);
  }

缺点:虽然可以实现属性的私有化但是过于复杂了还需要处理this指向问题,不建议

ES2022

javascript 复制代码
class P {
  #name = "test";
  say() {
    return `name = ${this.#name}`;
  }
}

const p = new P();
console.log(Object.keys(p)); // []
console.log(p.say()); // name = test

'#'标识私有属性,通过babel编译后: babel-plugin-transform-private-property-in-object

javascript 复制代码
function _classPrivateFieldInitSpec(obj, privateMap, value) {
  _checkPrivateRedeclaration(obj, privateMap);
  privateMap.set(obj, value);
}
function _checkPrivateRedeclaration(obj, privateCollection) {
  if (privateCollection.has(obj)) {
    throw new TypeError(
      "Cannot initialize the same private elements twice on an object"
    );
  }
}
function _classPrivateFieldGet(receiver, privateMap) {
  var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get");
  return _classApplyDescriptorGet(receiver, descriptor);
}
function _classExtractFieldDescriptor(receiver, privateMap, action) {
  if (!privateMap.has(receiver)) {
    throw new TypeError(
      "attempted to " + action + " private field on non-instance"
    );
  }
  return privateMap.get(receiver);
}
function _classApplyDescriptorGet(receiver, descriptor) {
  if (descriptor.get) {
    return descriptor.get.call(receiver);
  }
  return descriptor.value;
}
var _name = /*#__PURE__*/ new WeakMap();
var P = /*#__PURE__*/ (function () {
  "use strict";

  function P() {
    _classPrivateFieldInitSpec(this, _name, {
      writable: true,
      value: "test"
    });
  }
  var _proto = P.prototype;
  _proto.say = function say() {
    return "name " + _classPrivateFieldGet(this, _name);
  };
  return P;
})();

可以看到 也是通过WeakMap来实现的。

函数闭包

闭包可以让你在一个函数内部创建一个变量,这个变量对于函数外部是不可见的,但是对于这个函数内部的其他函数来说是可见的

javascript 复制代码
function createObject() {
    var privateProperty = "I am private";

    return {
        getPrivateProperty: function() {
            return privateProperty;
        }
    };
}

var obj = createObject();
console.log(obj.getPrivateProperty()); // "I am private"
console.log(obj.privateProperty);

ts的private

typescript 复制代码
class P {
   private _name='test';
   say() {
      return `name ${this._name}`;
    }
}
const p = new P();
console.log(p._name) // 提示 属性为私有属性,只能在类中访问

但是这个只能作用于编译时 运行时读取还是可以访问到。

typescript 复制代码
class Person {
  private name='test';
  say() {
     return `name ${this.name}`;
   }
}

const p1 = new Person();
console.log(p1['name']) // test
console.log(p1.say()) // name test

因为ts代码经过编译后会被去除 可以看如下编译后的代码

javascript 复制代码
function _defineProperty(obj, key, value) {
  key = _toPropertyKey(key);
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }
  return obj;
}
function _toPropertyKey(t) {
  var i = _toPrimitive(t, "string");
  return "symbol" == typeof i ? i : String(i);
}
function _toPrimitive(t, r) {
  if ("object" != typeof t || !t) return t;
  var e = t[Symbol.toPrimitive];
  if (void 0 !== e) {
    var i = e.call(t, r || "default");
    if ("object" != typeof i) return i;
    throw new TypeError("@@toPrimitive must return a primitive value.");
  }
  return ("string" === r ? String : Number)(t);
}
var P = /*#__PURE__*/ (function () {
  "use strict";

  function P() {
    _defineProperty(this, "_name", "test");
  }
  var _proto = P.prototype;
  _proto.say = function say() {
    return "name " + this._name;
  };
  return P;
})();

_defineProperty(this, "_name", "test"); 依然是将_name属性添加到了this上 所以还是可以访问到。

相关推荐
风清扬_jd几秒前
Chromium 中JavaScript Fetch API接口c++代码实现(二)
javascript·c++·chrome
丁总学Java16 分钟前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
It'sMyGo26 分钟前
Javascript数组研究09_Array.prototype[Symbol.unscopables]
开发语言·javascript·原型模式
懒羊羊大王呀27 分钟前
CSS——属性值计算
前端·css
睡觉然后上课37 分钟前
c基础面试题
c语言·开发语言·c++·面试
李是啥也不会42 分钟前
数组的概念
javascript
无咎.lsy1 小时前
vue之vuex的使用及举例
前端·javascript·vue.js
fishmemory7sec1 小时前
Electron 主进程与渲染进程、预加载preload.js
前端·javascript·electron
fishmemory7sec1 小时前
Electron 使⽤ electron-builder 打包应用
前端·javascript·electron
豆豆2 小时前
为什么用PageAdmin CMS建设网站?
服务器·开发语言·前端·php·软件构建