2018年2月22日 星期四

[ JavaScript Gossip ] JavaScript 核心 : 定義物件 (建構式 Constructor)

Source From Here 
前言 : 
如果你有以下建立物件的需求 : 
  1. var p1 = { name : 'Justin', age : 35};  
  2. var p2 = { name : 'Momor', age : 32};  
  3. var p3 = { name : 'Hamimi', age : 2};  
這些物件在建立時,具有相同的特性名稱,只不過特性值不同,其實你可以定義一個函式 : 
  1. function Person(name, age) {  
  2.     this.name = name;  
  3.     this.age = age;  
  4. }  
接著如下呼叫,就可以有相同的效果 : 
  1. var p1 = new Person('Justin', 35);  
  2. var p2 = new Person('Momor', 32);  
  3. var p3 = new Person('Hamimi', 2);  
 Person 這樣的函式,接在 new 之後使用時,俗稱為 建構式(Constructor,通常對從類別為基礎的語言過來的人,也會說這就像是一個類別(Class),不過這只是比擬,實際上當然與類別有所差別. 

建構式 : 
實際上你使用 new 運算子後接上一個函式時,相當於作這樣的動作(一部份是這樣,不過還有別的細節,之後再談): 
> function Person(name, age) { 
> this.name = name; 
> this.age = age; 
> this.toString = function() { 
> return '[' + this.name + ', ' + this.age + ']'; 
> }; 
> }
 
> var p = {}; 
> Person.call(p, 'Justin', 35); 
> p 
[Justin, 35]

這也說明了,為什麼使用 new 接上函式,傳回的物件會有 name  age,因為 Person 中,this 所參考的就是 p 所參考的物件,所以在 this 上新增特性,就相當於在 p 所參考物件上新增特性. 一個函式作為建構式使用時,基本上無需撰寫 return,如果建構式有傳回值,那傳回值就會被當作最後名稱所參考的值. 例如 : 
> function Person(name, age){ return[]; } 
> var o = new Person(); 
> o instanceof Person; 
false 
> o instanceof Array; 
true

所以要以比擬的方式來說,new 之後接上建構式,預設相當於這樣 : 
> function Person(name, age){ this.name = name; this.age = age; this.toString=function(){ return this.name + ', ' + this.age; }; return this; } 
undefined 
> var p = new Person() 
undefined 
> var p = {} 
undefined 
> p = Person.call(p, 'John', 38) 
{ name: 'John', age: 38, toString: [Function] } 
> p.toString() 
'John, 38'

每個透過 new 建構的物件,都會有個 constructor 特性,參考至當初建構它的函式. 例如 : 
> function Person(){} 
undefined 
> var p = new Person(); 
undefined 
> p.constructor == Person 
true

這可以作為物件類型的參考依據,不過要注意的是,constructor 是可以修改的! 由於透過建構式所建立的物件,所有的特性都是直接新增在物件上,也因此可以直接透過 . 運算子加以存取. 例如 : 
> function Person(name, age){ this.name = name; this.age = age; } 
undefined 
> var p = new Person('John', 38) 
undefined 
> p.name 
'John' 
> p.age 
38

對熟悉物件導向私有性基本觀念的人來說,可能覺得這不安全,這相當於在物件導向觀念中,每個類別成員都是公開成員的意味. JavaScript 本身並不支援物件導向公開、私用性等觀念,如果你想模擬,則可以如下 : 
> function Person(name, age){ this.getName = function(){ return name; }; this.age = age; } 
undefined 
> var p = new Person('John', 38) 
undefined 
> console.log(p.name) 
undefined 
undefined
 
> console.log(p.getName()) 
John 
undefined
 
> console.log(p.age) 
38 
undefined

以上假設的是,name 不可以被設定,但可以透過 getName() 來取得,之所以會有這樣的效果,其實就是 閉包 的作用. 上例中,在物件上新增了 getName 特性,參考至一個函式,該函式形成閉包綁定了參數 name,參數也就是區域變數,並非物件上的特性,所以無法透過 . 運算子取得,因此模擬了私用性! 由於閉包綁定的是自由變數本身,所以也可以如下,在設定值(或取得值)時予以保護 : 
> function Account(){ 
... var balance = 0; 
... this.getBalance = function(){ return balance; }; 
... this.setBalance = function(money){ 
..... if(money < 0){ throw new Error('Can\'t set negative balance!'); } 
..... balance = money; 
..... }; 
... } 
undefined 
> var acct = new Account(); 
undefined 
> acct.getBalance() 
0 
> acct.setBalance(1000) 
undefined 
> acct.getBalance() 
1000 
> acct.setBalance(-1000) 
Error: Can't set negative balance! 
at Account.setBalance (repl:6:22) 
at repl:1:6 
at sigintHandlersWrap (vm.js:22:35) 
at sigintHandlersWrap (vm.js:73:12) 
at ContextifyScript.Script.runInThisContext (vm.js:21:12) 
at REPLServer.defaultEval (repl.js:340:29) 
at bound (domain.js:280:14) 
at REPLServer.runBound [as eval] (domain.js:293:12) 
at REPLServer. (repl.js:539:10) 
at emitOne (events.js:101:20)


沒有留言:

張貼留言

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...