本文同步發表於隨性筆記
本系列文章討論JS 物件導向設計相關的特性。 不含CSS,不含HTML!
建議先有些JS基礎再繼續閱讀。
你也可以看看從零開始遲來的Web開發筆記
雖然是「7天寫作松」挑戰,但同樣可以視為系列後續文章
No CSS! No HTML! No Browser!
Just need programming language
這篇是臨時起意補的一篇短篇,用於示例如何模擬私有屬性。儘管這可能不是JS主流設計思想方法,但知道相信對你沒壞事。
在第5天-getter & setter: 屬性描述器,曾經看過這樣的例子:
var 神崎家 = class {
constructor(name){
this.__name = `神崎・${name}`;
}
static born(name){
return new 神崎家(name)
}
set name(new_name){
this.__name = `神崎・${new_name}`;
}
get name(){
let first_name = this.__name.substr(0,2),
last_name = this.__name.substr(3, this.__name.length + 1);
return `${first_name}・H・${last_name}`
}
introduce(){
console.log(`Hi~ My name is ${this.name}`)
}
rename(new_name){
this.name = new_name;
return this.name;
}
}
可能會有人問...為什麼__name
不直接這樣寫就好:
var Person = class{
__name = "Default"
constructor({name, famile_name = "noname"} = {}){
this.__name = name;
this.__famile_name = famile_name;
}
hello(){
console.log(`Hello, ${this.name}`);
}
get name(){
return `${this.__famile_name} の ${this.__name}`;
}
set name(new_name){
this.__name = new_name;
}
}
var boy = new Person({name:"逆迴十六夜"})
boy.hello(); // => Hello, noname の 逆迴十六夜
console.log(boy.name); // => noname の 逆迴十六夜
boy.name = "久遠 飛鳥"
console.log(boy.name); // => noname の 久遠 飛鳥
boy.name = "久遠 飛鳥"
好拉久遠 飛鳥不是
boy
拉
也就是直接把__name
寫在class
裡就好,要寫在constructor
。阿...我只能說沒有理由......只是我一開始寫JS的OOP是用工廠模式。(可以回去看看第二天的內容)
還有一部份,因為我寫習慣Python了XD
上面更好的寫法可能會是:
var Person = class{
__name = "Default"
static family_name = "noname"
constructor(name){
this.__name = name;
}
hello(){
console.log(`Hello, ${this.name}`);
}
get name(){
return `${Person.family_name} の ${this.__name}`;
}
set name(new_name){
this.__name = new_name;
}
get family_name(){
return Person.family_name;
}
}
var boy = new Person("逆迴十六夜")
boy.hello(); // => Hello, noname の 逆迴十六夜
console.log(boy.name); // => noname の 逆迴十六夜
console.log(boy.family_name); // => noname
實際上,boy
並沒有family_name
而是在Person
上,實現了天下一家親。
Object.getOwnPropertyNames(boy) // => [ '__name' ]
Object.getOwnPropertyNames(Person) // => [ 'length', 'prototype', 'name', 'family_name' ]
Object.getOwnPropertyNames(Person.prototype) // => [ 'constructor', 'hello', 'name', 'family_name' ]
好像扯遠了。
用閉包實現私有屬性(closure & private)
用工廠模式有一個好處,可以做很多加工。像是可以透過閉包實現類似私有屬性,讓其他物件無法直接存取資料。
var Noname = class{
static family_name = "noname"
constructor(__name){
var name = __name;
Object.defineProperty(this, "name", {
set(new_name){
name = new_name;
},
get(){
return `${Person.family_name} の ${name}`;
}
});
this.hello = function(){
console.log(`Hello, ${this.name}`);
}
}
get family_name(){
return Person.family_name;
}
}
var boy = new Noname("逆迴十六夜")
boy.hello(); // => Hello, noname の 逆迴十六夜
console.log(boy.name); // => noname の 逆迴十六夜
console.log(boy.family_name); // => noname
好了,這下不但沒法用boy
去修改family_name
,也不能存取__name
了(因為根本沒有)。在constructor
的var name
,可以在constructor
被定義的方法裡共享、保存,這是閉包(closure)。而外部無法存取,進而實現類似private
的概念。如果想要個方法間還不共享變數,大可以再多做加工。不過不稍微共享,我覺得意義不大。
來仔細看看實現結果
儘管上面實現也差不多是本篇最主要說的內容了,但還有些東西是使用這方式你必須知道的:
這樣加工的方法,並不是普通方法
這樣加工的方法,並不是普通方法。與其他方法依附在.prototype
上不同,這樣的方法直接依附在實例上。
Object.getOwnPropertyNames(Person.prototype) // => [ 'constructor', 'hello', 'name', 'family_name' ]
Object.getOwnPropertyNames(Noname.prototype) // => [ 'constructor', 'family_name' ]
Object.getOwnPropertyNames(boy) // => [ 'constructor', 'hello', 'name', 'family_name' ]
Noname.prototype
不再有hello()
的方法,而是出現在boy
下。這意為著你有多少實例,就存在多少方法,這可能提高記憶體的使用量。
當然上例的hello()
我是故意這樣寫的,只是要提醒你,使用工廠模式,或是prototype-based的方式處理,你應該清楚在做什麼,在設計什麼。
※ Note: TypeScripte的private
關鍵字好像是在編譯時檢查,而不做運行時保護。實際上並不同於本篇實現的概念。
本篇完~灑花~~