1. 程式人生 > >【javascript】私有化變數

【javascript】私有化變數

之前的文章 瓜皮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函式每一次呼叫返回的結果都是不同的,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)
  }
}

參考這段程式碼 然後稍微改下就行了! ,這種方式,既沒有告訴你門牌號,也沒有找到門牌號的路徑,除非自己暴露出來。。有沒有覺得很贊呢。。