之前的文章 瓜皮csdn把之前正确的排版,给全部打乱了。。。
----
js中如何像java一样,将实例变量设置为私有呢? 因为没有类似的关键字private
方法一:
在ES6之前,我们是通过来完成封装的,看例子:
// 结构赋值和函数默认参数的使用function initStudent({name = '', id = null, address =''} = {}) { let _name = name, _id = id, _address =address return { setName(name) { _name = name }, getName() { return _name }, setId(id) { _id = id }, getId() { return _id }, setAddress(address) { _address = addredss }, getAddress(){ return _address }, toString() { return `student' name is ${_name}, address is ${_address} ,id is ${_id}` } }}
这样的话,我们这样`var student = initStudent({id: '001', name: 'Kevin', address: 'somewhere'})`构造一个对象,由于闭包的关系,返回的对象关联了一个函数,这个函数引用了当前函数词法作用域外层也就是initStudent函数作用域的一个局部变量,尽管initStudent执行完毕但是由于存在这样一层引用,外层的作用域在内存中并没有释放掉,数据仍然存在内存中(如果不明白,简易去看下《js高程》的作用域链和闭包章节)。
这个对象 student 不能直接访问name属性,console.log(student.name) 的结果是 undefined
但是我们直接这样操作student.name = 'Kevin2', 成功了。。。然后 你比较student.name 和student.getName()是不同的。 这里我们优化一下代码:
// 结构赋值和函数默认参数的使用function initStudent({name = '', id = null, address =''} = {}) { let _name = name, _id = id, _address =address let obj = { setName(name) { _name = name }, getName() { return _name }, setId(id) { _id = id }, getId() { return _id }, setAddress(address) { _address = addredss }, getAddress(){ return _address }, toString() { return `student' name is ${_name}, address is ${_address} ,id is ${_id}` } } const getErrorFun =function(prop) { return function () { throw new Error(`you cannot set ${prop} value directly, use setMethod`) } } Object.defineProperties(obj , { 'name': { set: getErrorFun('name') }, 'id': { set: getErrorFun('id') }, 'address': { set: getErrorFun('address') }, }) return obj}
这样就不能直接设定name等属性了,而且外部也不能直接访问和设定,必须通过get set方法去操作。如果像遍历对象的属性的话,可以自己在返回的对象里面增加一个*[Symbol.iterator]的方法,这个方法是一个生成器。
方法二
es6的
因为Symbol函数每一次调用返回的结果都是不同的,Symbol('x') === Symbol('x') 的值false, 可以理解为每次都生产了一个uuid,我们利用这个特性。我们修改上面的代码 如下:
// 用Symbol来封装const _name = Symbol('name'), _id = Symbol('id'), _address = Symbol('address')class Student { constructor({name = '', id = null, address =''} = {}) { this[_name] = name this[_id] = id this[_address] = address } get name(){ return this[_name] } set name(name) { this[_name] = name } get address(){ return this[_address] } set address(address) { this[_address] = address } get id(){ return this[_id] } set id(id) { this[_id] = id } }
这里用了getter setter设置属性的存储函数和获取函数,拦截该属性的默认set和get行为【我犯了一个错误,上面闭包的方式实现也可以用这个方法,但是我当时想的是模拟java里面私有属性的公共方法】,如果非要使用getName setName这种形式,其实也行,我们改一下代码:
const _name = Symbol('name'), _id = Symbol('id'), _address = Symbol('address')class Student { constructor({name = '', id = null, address =''} = {}) { this[_name] = name this[_id] = id this[_address] = address } get name(){ return this[_name] } set name(name) { throw new Error('cannot set value directly, use setMethod') } getName(){ return this[_name] } setName(name){ this[_name] = name } }
我们通过创建一个实例,var s = new Student({name: 'kevin', id: 'iook', address: 'somewhere'}) , s.name报错, 其实我们也可以将默认的getter函数给屏蔽掉,只能通过s.getName去获取。这样也完成了我们想要的。
其实这种方法和第一种类似,第一种通过闭包隐藏访问途径, 第二种直接隐藏key的名字,有途径也没有用【但是实际上是还是可以通过Object.getOwnPropertySymbols(obj)来访问】。
方法三!!
用map来实现,wow,这段代码来自
const privateMap = new WeakMap()const Point = class{ constructor(x, y) { privateMap.set(this, {x, y}) } get length(){ let { x, y } = privateMap.get(this) return Math.sqrt(x ** 2 + y ** 2) }}
参考这段代码 然后稍微改下就行了! ,这种方式,既没有告诉你门牌号,也没有找到门牌号的路径,除非自己暴露出来。。有没有觉得很赞呢。。