基本封裝方法
請看下面的例子:
var Person = function(name,age){ this.name = name; this.age = age || "未填写"; this.hobbys = []; } Person.prototype = { sayName:function(){ console.log(this.name); }, sayAge:function(){ console.log(this.age); }, addHobby:function(hobbys){ this.hobbys = this.hobbys.concat(hobbys); } } var person1 = new Person("Jane","20"); var person2 = new Person("TabWeng","21"); person1.addHobby(['sing','drawing']); person2.addHobby(['football','study','running']); person1.sayName(); console.log(person1.hobbys.toString()); person2.sayName(); console.log(person2.hobbys.toString());
運行結果:
Jane
sing,drawing
TabWeng
football,study,running
TabWeng
和方法寫在原型上,需要每個實例各自都有的副本的屬性和方法放在建構函式中。
現在有個問題,名稱的輸入不能有數字,要怎麼解決呢?解決的方法可以寫一個檢查名稱的函數,這個函數寫在原型上。
var Person = function(name,age){ //校验名称 if(this.checkName(name)){ throw new Error("名字 "+name+" 不能存在数字"); } this.name = name; this.age = age || "未填写"; this.hobbys = []; } Person.prototype = { //校验函数 checkName:function(name){ re = /\d/; return re.test(name); }, sayName:function(){ console.log(this.name); }, sayAge:function(){ console.log(this.age); }, addHobby:function(hobbys){ this.hobbys = this.hobbys.concat(hobbys); } } var person1 = new Person("Helen666","20"); var person2 = new Person("TabWeng","21"); person1.addHobby(['sing','drawing']); person2.addHobby(['football','study','running']); person1.sayName(); console.log(person1.hobbys.toString()); person2.sayName(); console.log(person2.hobbys.toString());
這段程式碼中,我們寫了一個checkName()函數,來校驗名稱,暫且只是校驗不能有數字吧,然後再建構函數裡的第一行程式碼中進行校驗,若校驗不通過,則拋出異常。
這裡我傳入一個名稱Helen666,結果拋出如下異常:
Error: 名字 Helen666 不能存在數字
這樣就做到了一個基本的封裝,實現內部校驗。
但是又有個問題,我們還可以這樣來定義名稱:
var person1 = new Person("Helen","20"); person1.name = "Helen666"; person1.sayName(); //Helen666
這樣名稱還是可以修改為不合法的名稱,於是我們想到用get方法 和set方法來做控制,只能透過set方法來賦值,同時透過set方法進行校驗,而透過get方法來獲得值。現在的程式碼修改如下:
// Interfacevar People = new Interface("People",["setName","getName","setAge","getAge","addHobby","getHobby","sayName","sayAge"]);var Person = function(name,age){ //implement People this.setName(name); this.setAge(age); this._hobbys = [];}Person.prototype = { //校验函数 checkName:function(name){ re = /\d/; return re.test(name); }, sayName:function(){ console.log(this._name); }, sayAge:function(){ console.log(this._age); }, addHobby:function(hobbys){ this._hobbys = this._hobbys.concat(hobbys); }, getHobby:function(){ return this._hobbys; }, setName:function(name){ if(this.checkName(name)){ throw new Error("名字 "+name+" 不能含有数字"); } this._name = name; }, getName:function(){ return this._name; }, setAge:function(age){ this._age = age || "未设置"; }, getAge:function(){ return this._age; }}var person1 = new Person("Helen","20");person1.addHobby(['sing','drawing']);function record(person){ Interface.ensureImplements(person,People); person.sayName(); console.log(person.getHobby().toString());}record(person1);
運行結果:
Helen
sing,drawing
首先,這段程式碼我們使用了接口,定義了People接口,而person來實現這個接口,注意註釋的內容。 (關於介面,請看這篇 JavaScript使用介面)
其次,我們使用了get方法 和 set方法來取值和賦值,我們可以約定程式設計師只能透過set來賦值,而在set方法裡面我們對所賦予的值進行了校驗,以確保準確。但這只是一種約定,程式設計師依然可以透過 person1.name = "123" 來賦值,修改內部屬性。
為了規範和起到提醒作用,我們把內部屬性的命名進行規範,在這些屬性前面加上“_”,比如**_name** 、**_age** ,這樣如果程式設計師要直接修改屬性,那麼他就必須這樣寫person1._name = "123",這明顯是一種故意的做法,一般程式設計師不會這麼做,起到規範和提醒的作用。
儘管如此,這種僅僅是用規定進行約束,還是無法阻止透過person1._name進行修改,下面的方法可以做到把內部屬性真正做到私有化。
透過閉包進行封裝
// Interface var People = new Interface("People",["setName","getName","setAge","getAge","addHobby","getHobby","sayName","sayAge"]); var Person = function(name,age){ //implement People // 私有变量 var _name,_age,_hobbys = []; this.addHobby = function(hobbys){ _hobbys = _hobbys.concat(hobbys); }, this.getHobby = function(){ return _hobbys; }, this.setName = function(name){ if(this.checkName(name)){ throw new Error("名字 "+name+" 不能含有数字"); } _name = name; }, this.getName = function(){ return _name; }, this.setAge = function(age){ _age = age || "未设置"; }, this.getAge = function(){ return _age; } this.setName(name); this.setAge(age); } Person.prototype = { checkName:function(name){ re = /\d/; return re.test(name); }, sayName:function(){ console.log(this.getName()); }, sayAge:function(){ console.log(this.getAge()); } } var person1 = new Person("Helen","20"); person1.addHobby(['sing','drawing']); function record(person){ Interface.ensureImplements(person,People); person.sayName(); console.log(person.getHobby().toString()); } record(person1);
在建構函式中,屬性不使用this,外部也就無法存取到這個屬性,而閉包透過作用域鏈可以存取到這個屬性,那麼我們就透過閉包設定了為屬性賦值的唯一入口,從而起到了嚴格校驗這些屬性的作用。
儘管如此,在構造函數中定義方法很多時候是沒必要的,因為這樣每創建一個實例,就會產生一個方法的副本,這是需要內存支持的,所以在使用的過程中,如果能用上面的基本封裝方法,盡量用,除非對於私有屬性有非常嚴格的校驗要求才用閉包這種方法。