和不少高级语言不一样,JavaScript 中没有 public
、private
、protected
这些访问修饰符(access modifiers),并且长期以来也没有私有属性这个概念,对象的属性/方法默认都是public的。虽然目前 class 的私有属性特性已经进入了 Stage3 实验阶段(Spec),经过 Babel 已经可使用,而且 Node v12 中也增长了对私有属性的支持,但这并不妨碍咱们用 JS 的现有功能实现一个私有属性特性,以加深对这一律念的理解。git
私有属性(方法)的意义在于将模块的内部实现隐藏起来,而对外接口只经过public成员进行暴露,以减小其余模块对该模块内部实现的依赖或修改,下降模块的维护成本。
IIFE(当即执行函数) 你们应该耳熟能详了,IIFE 常常被用来:github
基于以上特性,用 IIFE 能够给一个对象实现简单的私有属性:babel
let person = (function () { // 私有属性 let _name = "bruce"; return { age: 30, // getter get name() { return _name; }, // setter set name(val) { _name = val; }, greet: function () { console.log(`hi, i'm ${_name} and i'm ${this.age} years old`); } }; })();
测试一下:函数
console.log(person.name); // 'bruce' console.log(person._name); // undefined person.name = "frank"; console.log(person.name); // 'frank' console.log(Object.keys(person)); // ['age', 'name'] person.greet(); // hi, i'm frank and i'm 30 years old
IIFE 的实现简单易懂,可是只能做用于单个对象,而不能给 Class 或者构造函数定义私有属性。测试
利用在构造函数中建立的局部变量能够做为 “私有属性” 使用:this
function Person(name, age) { // 私有属性 let _name = name; this.age = age; this.setName = function (name) { _name = name; }; this.getName = function () { return _name; }; } Person.prototype.greet = function (){ console.log(`hi, i'm ${this.getName()} and i'm ${this.age} years old`); }
测试一下:spa
const person = new Person("bruce", 30); console.log(person.getName()); // bruce person.setName('frank'); console.log(person.getName()); // frank person.greet(); // hi, i'm frank and i'm 30 years old
看起来还行,可是该实现方式须要在构造函数中定义 getter
、setter
方法,这两个方法是绑定在实例上而不是原型上的,若是私有属性增长会致使实例方法暴增,对内存不太友好。prototype
Class中实现和构造函数相似,由于JavaScript中的class本质上是构造函数和原型的语法糖,实现以下:code
class Person { constructor(name, age) { // 私有属性 let _name = name; this.age = age; this.setName = function (name) { _name = name; }; this.getName = function () { return _name; }; } greet() { console.log(`hi, i'm ${this.getName()} and i'm ${this.age} years old`); } }
Class中的实现也会存在和构造函数中同样的问题,并且在 greet()
方法中没法访问 _name
,须要经过调用 getter
方法。这和通常意义上的私有属性仍是有差异的,真正的私有属性在class内部应该是能够正常访问的,而不只仅是在构造函数内部能够访问。对象
以上三种实现或多或少都有一些问题,还好在ES2019中已经增长了对 class 私有属性的原生支持,只须要在属性/方法名前面加上 '#'
就能够将其定义为私有,而且支持定义私有的 static
属性/方法。例如:
class Person { // 私有属性 #name; constructor(name, age) { this.#name = name; this.age = age; } greet() { console.log(`hi, i'm ${this.#name} and i'm ${this.age} years old`); } }
测试一下:
const person = new Person("bruce", 30); console.log(person.name); // undefine person.greet(); // hi, i'm bruce and i'm 30 years old
更多语法能够参考 MDN: Private class field。
咱们能够去babel里面将原生的代码转换一下,看看babel的polyfill是怎么实现的:
发现主要思路竟然使用 WeakMap
。。好吧,仍是太年轻。格式化后的polyfill代码贴在下面,有兴趣的同窗能够研究一下:
"use strict"; function _classPrivateFieldGet(receiver, privateMap) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to get private field on non-instance"); } if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; } function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = privateMap.get(receiver); if (!descriptor) { throw new TypeError("attempted to set private field on non-instance"); } if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } return value; } var _name = new WeakMap(); class Person { constructor(name, age) { _name.set(this, { writable: true, value: void 0, }); _classPrivateFieldSet(this, _name, name); this.age = age; } greet() { console.log( "hi, i'm " .concat(_classPrivateFieldGet(this, _name), " and i'm ") .concat(this.age, " years old") ); } }
天天一个小技巧(Tricks by Day),量变引发质变,但愿你和我一块儿天天多学一点,让技术有趣一点。全部示例将会汇总到个人 tricks-by-day github 项目中,欢迎你们莅临指导 😊