读书笔记:编写高质量代码--web前端开发修炼之道(二:5章)
讀書筆記:編寫高質量代碼--web前端開發修煉之道
這本書看得斷斷續續,不連貫,筆記也是有些馬虎了,想了解這本書內容的童鞋可以借鑒我的這篇筆記,希望對大家有幫助。
筆記有點長,所以分為一,二兩個部分:
第一部分:1-4章的筆記
第二部分:5章以及一些總結性的建議筆記等
轉載本文,請帶上本文地址(http://www.cnblogs.com/xmmcn/archive/2012/12/04/2800979.html),謝謝:)
?
第五章:高質量的JavaScript
1、團隊合作--如何避免js沖突
?? a 使用匿名函數控制變量作用域
???? "(function(){})()":這種形式很巧妙,先定義一個匿名函數,然后立即執行它。
?? b 利用全局作用域的變量在各匿名函數間搭起橋梁
???? 需要嚴格控制全局變量的數量,大量濫用就違背了使用匿名函數的初衷。
?? c 命名空間
script(function(){var a = 123, b='hello world';GLOBAL.A = {};GLOBAL.A.CAT = {};GLOBAL.A.CAT.name = a;GLOBAL.A.CAT.sayName = function(){alert(GLOBAL.A.CAT.name);};}); ?? d 代碼注釋
???? 添加必要的注釋,可以大大提高代碼的可維護性,對于團隊合作來說,更是十分有必要的。
2、給程序一個統一的入口--window.onload 和 DOMReady
3、extend
?? javascript 是支持面向對象的語言,但是它并不提供 extend 方法用于繼承。我們自己定義:
script:function extend(subClass, superClass){var F = function(){};F.prototype = superClass.prototyte;subClass.prototyte = new F();subClass.prototyte.constructor = subClass;subClass.superClass = superClass.prototyte;if(superClass.prototyte.constructor == Object.prototype.constructor){superClass.prototype.constructor = superClass;}function Animal(name){this.name = name;this.type = 'animal';}Animal.prototype = {say: function(){alert('I\'m a(an) '+ this.type +', my name is '+ this.name);}};function Bird(name){this.constructor.superClass.constructor.apply(this, arguments);this.type = 'brid';}extend(Bird, Animal);Bird.prototype.fly = function{alert('I\'m flying');}var canary = new Brid('xiaocui');canary.say();// I'm a(an) bird, my name is xiaocuicanary.fly();// i'm flying 4、保持代碼彈性
5、可復用性
?? 組件需要一個根節點,以保持每個組件之間的獨立性
6、避免產生副作用
7、通過傳參實現定制
?? 如果一個函數內某個因素很不穩定,我們可以將它從函數內部分離出來,以參數的形式傳入,從而將不穩定因素和函數解耦。
8、控制 this 關鍵字的指向
?? 在javascript里,this指針的確是讓人捉摸不透的東西。例如javascript偽協議和內聯時間對于this的指向不同:
html:// 彈出 “A”<a href="#" onclick="alert(this.tagName)">click me</a>// 彈出“underfined”<a href="javascript:alert(this.tagName)">click me</a>// 彈出“true”<a href="javascript:alert(this==window)">click me</a>setTimeout 和 setInterval 也會改變 this 的指向,如下:javascript:var name = "somebody";var adang = {name: 'adang',say: function(){alert('I\'m '+ this.name);}};adang.say();// I'm adangsetTimeout(adang.say, 1000);// I'm somebodysetInterval(adang.say, 100);// I'm somebody另外,"DomNode.onXXX" 也會改變 this 的指向,如:javascript:var name = "somebody";var btn = document.getElementById('btn');var adang = {name: 'adang',say: function(){alert('I\'m '+ this.name);}};btn.onclick = adang.say;// I'm BUTTON
?? 使用匿名函數可以解決這個問題,如下:
View Code javascript:var name = "somebody";var btn = document.getElementById('btn');var adang = {name: 'adang',say: function(){alert('I\'m '+ this.name);}};adang.say();// I'm adangsetTimeout(function(){adang.say();}, 1000);// I'm adangsetInterval(function(){adang.say();}, 1000);// I'm adangbtn.onclick = function(){adang.say();};// I'm adangsetTimeout(function(){alert(this == window);}, 1000);// truebtn.onclick = function(){alert(this == btn);};// true ?? setTimeout, setInterval 和 DomNode.onXXX 改變的都是直接調用函數里的 this 的指向,其中 setTimeout 和 setInterval 將直接調用的函數里的 this 指向 window。DomNode.onXXX 將直接調用的函數里的 this 指向 DomNode。使用匿名函數將我們的處理函數封裝起來,可以將我們的處理函數由直接調用變成通過匿名函數間接調用。
?? 另外,還可以通過 call 和 apply 函數來改變處理函數的 this 指向,如:
javascript:var name = "somebody";var btn = document.getElementById('btn');var adang = {name: 'adang',say: function(){alert('I\'m '+ this.name);}};adang.say.call(btn);// I'm BUTTONsetTimeout(function(){adang.say.call(btn);}, 1000);// I'm BUTTONsetInterval(function(){adang.say.call(btn);}, 1000);// I'm BUTTONbtn.onclick = function(){adang.say.apply(btn);};// I'm BUTTON ?? 在 javascript 里使用繼承就需要用到 call 或 apply 函數。
?? 在 this 改變指向之前,將它指向的對象保存到一個變量中也是常用的方法,如:
html:<input type="button" value="click me" id="btn" name="BUTTON" />javascript:var name = "somebody";var adang = {name: 'adang',say: function(){alert('I\'m '+ this.name);},init: function(){// this 指向 adang 對象var This = this;document.getElementById('btn').onclick = function(){// this 指向 btn 的 DOM 節點,This 指向 ading 對象This.say();// I'm adangthis.say();// 報錯,this.say is not a function};}};adang.init();
?? this 關鍵字會改變指向,只要避開這個關鍵字就可以得到一個穩定的引用。
9、預留回調接口
10、變成中的DRY規則
?? DRY:don't repeat yourself,強調在程序中不要將相同的代碼重復編寫多次,更好的做法是只寫一次,然后多處引用。(減少代碼量,方便修改維護)
11、用 hash 對象傳參
?? 使用 {key: value, xxx,} 對象傳遞參數
12、面向過程編程和面向對象編程
?? 面向過程:
???? 將程序分成“數據”和“處理函數”兩部分,程序以“處理函數”為核心,如果要執行什么操作,就將“數據”傳給響應的“處理函數”,返回我們需要的結果。
?? 面向過程有三個方面的問題:
???? a 數據和處理函數沒有直接的關聯,在執行操作的時候,我們不但要選擇相應的處理函數,還要自己準備處理函數需要的數據,也就是說,在執行才做時,我們需要同時關注處理函數和數據。
???? b 數據和處理函數都暴露在同一作用域內,沒有私有和公有的概念,整個程序中所有的數據和處理函數都可以互相訪問,在開發階段初期也許開發速度會很快,但到了開發后期和維護階段,由于整個程序耦合得非常緊密,任何一個處理函數和數據都有可能關聯到其他地方,容易牽一發而動全身,從而加大了修改難度。
???? c 面向過程的思維方式是典型的計算機思維方式--輸入數據給處理器,處理器內部執行運算,處理器返回結果。而實際生活中,我們的思路卻不是這樣.
?? 面向對象(Object Oriented):
???? 拋開計算機思維,使用生活中的思維進行編程。
???? 面向過程的思維是描述一個個“動作”,而面向對象的思維就是描述一個個“物件”,客觀生活中的物件,都可以通過面向對象思維映射到程序中--“物件”對應“對象”,“狀態”對應“屬性”,“行為”對應“動作”。
???? 面向過程編程:
javascript:var name = 'adang', state = 'awake';var say = function(oName){alert('I\'m '+ oName);};var sleep = function(oState){oSate = 'asleep';};say(name);sleep(state);面向對像編程:javascript:var adang = {name: 'adang',state: 'awake',say: function(){alert('I\'m '+ this.name);},sleep: function(){this.state = 'asleep';}};adng.say();adang.sleep(); ?? 面向對象(Object Oriented),簡稱OO。OO其實包括OOA(Object Oriented Analysis,面向對象分析)、OOD(Object Oriented Design,面向對象設計)和OOP(Object Oriented Programming,面向對象的程序設計)。面向對象的語法只對應OOP,只是OO的一部分。
???? 一個典型的OO編程過程應該是先整理需求,根據需求進行OOA,將真實世界的客觀物件抽象成為程序中的類或對象,這個過程經常會用到的是UML語言,也稱UML建模,OOA的輸出結果是一個個類或對象的模型圖。接下來要進行OOD,這一步的目的是處理類之間的耦合關系,設計類或對象的接口,此時會用到各種設計模式,例如觀察者模式、責任鏈模式等。OOA和OOD是個反復迭代的過程,他們本身沒有清晰的邊界,是互相影響、制約的。等OOA和OOD結束之后,才到OOP,進行實際的編碼工作。
???? OOA和OOD是面向對象編程的思想和具體的語言無關,而OOP是面向兌現編程的工具,和選用的語言相關。OOP是使用面向對象技術的基礎,面向對象的思維最后是要通過OOP來實施的。
?? javascript的面向對象編程
???? javascript是基于原型的語言,通過 new 實例化出來的對象,其屬性和行為來自于兩部分,一部分來自于構造函數,另一部分來自于原型。
?? 共有和私有
javascript:// 定義 Animal 類function Animal(name){// 共有屬性this.name = name || 'xxx';this.type = 'animal';// 私有屬性var age = 20;// 私有方法var more = function(){alert('I\'m moving now');}}Animal.prototype = {// 公有方法say: function(){alert('I\'m a(an) '+ this.type +', my name is '+ this.name +',I\'m '+ age);},act: function(){move();}};// 實例化Animal類varmyDog = new Animal('wangcai');myDog.say();// 報錯,age未定義myDog.act();// 報錯,move is not defined ???? 從上我們可以知道,公有方法不能訪問私有屬性和私有行為,那么如何解決呢?最好的方法就是把所有的公有方法都私有化(都寫在類的構造函數里面),這樣屬性和行為都共同作用在構造函數的作用域里,如下:
View Code javascript:function Animal(name){this.name = name || 'xxx';this.style = 'animal';var age = 20;var move = function(){alert('xxxx');};this.say = function(){alert('xxxx');};this.act = function(){move();};}Animal.prototype = {};var myDog = new Animal('waicai');myDog.say();myDog.act(); ???? 將所有屬性和行為全部寫在構造函數里,的確方便,但并不推薦這么做。因為一個類的原型在內存中只有一個,寫在原型中的行為,可以被所有實例所共享,實例化的時候,并不會在實例的內存中復制一份,而寫在類里的行為,實例化的時候會在每個實例里復制一份,占用更多的內存空間。
???? 寫在原型中的行為一定是公有的,而且無法訪問私有屬性,所以如何處理私有行為和私有屬性是個難題。一般來說,如果對屬性和行為的私有性有非常高的強制性,比如說多人合作,為了確保維護不會出現問題,在開發之初明確各個類的接口,除了必要的接口設為共有,其他所有接口一律設為私有,以此來降低類之間的耦合程度,確保可維護性,這時我們不得不犧牲內存,將私有行為放在構造函數里,實現真正的私有;
???? 命名來確定私有,比如 this._age = 20;
???? 監聽屬性的 valueChange:
javascript:function Animal(name){var name = name, type = 'animal';var _age = 20;var master = 'adang';// 添加 master 屬性,默認為adangthis.getName = function(){return name;};this.setName = function(o){if(o != 'waicai' && o != 'xiaoqiang'){alert('您設置的那么值不合要求');return;}name = o;this._valueChangeHandler('name');// 觸發 name 屬性的 valueChange 事件};this.getMaster = function(){// master 屬性的獲取方法return master;};this.setMaster = function(o){// master 屬性的設置方法master = o;thi._valueChangeHandler('master');// 觸發 master 屬性的 valueChange 事件};this.getType = function(){};this.setType = function(o){alert('賦值失敗,Animal類的 type 屬性是只讀的 ');};this._getAge = function(){return _age;};this._setAge = function(o){_age = o;};}Animal.prototype = {_move: function(){alert('I\'m moving now');},say: function(){alert('I\'m a(an) '+ this.getType() +', my name is '+ this.getName() +', I\'m '+ this._getAge());},act: function(){this._move();},onChange: function(valueName, fun){// 公有行為,用于注冊屬性的 valueChange 事件this['_'+ valueName +'ChangeHandlers'] = this['_'+ valueName +'ChangeHandlers'] || [];this['_'+ valueName +'ChangeHandlers'].push(fun);},_valueChangeHandler: function(valueName){var o = this['_'+ valueName +'ChangeHandlers'];if(o){for(var i=0,n=o.length; i<n; i++){var methodName = 'get'+ valueName.charAt(0).toUpperCase() + valueName.slice(1);o[i](this[methodName]);// 把 this.getType() 作為屬性來調用,返回值作為參數傳遞}}}};var myDog = new Animal('wangcai');// 給 myDog 注冊 name 屬性的 valueChange 事件myDog.onChange('name', function(o){if(o == 'xiaoqiang'){alert('1');}else{alert('2');}});// 給 myDog 換個新名字 xiaoqiangmyDog.setName('xiaoqiang');// 1//給 myDog 再注冊一個 name 屬性的 valueChange 事件myDog.onChange('name', function(o){alert('my new name is '+ o);});..........
???? 在真實世界中,我們很多的思維習慣都是狀態驅動的,編程時監聽屬性的 valueChage 事件可以幫助我們更接近真實世界的思維習慣。
13、繼承
?? 正統的面向對象的語言都會提供 extend 之類的方法用于處理類的繼承,但javascript并不提供 extend 方法。
?? 在javascript中實例的屬性和行為是由構造函數和原型兩部分共同組成的,我們定義2個類:Animal 和 Bird ,他們在內存中的表現如下:
?? 圖例:
???? Animal類??????? Animal的構造函數??????? Animal的原型
???? Bird類????????? Bird的構造函數????????? Bird的原型
?? 二者互不聯系。
?? 如果想讓 Bird 繼承自 Animal,那么我們需要把 Animal 構造函數和原型中的屬性和行為全部傳給Bird的構造函數和原型。參考以下代碼:
javascript:// 先定義 Animal 類function Animal(name){this.name = name, this.type = 'animal';}Animal.prototype = {say: function(){alert('I\'m a(an) '+ this.type +', my name is '+ this.name);}};// 我們再定義一個類 Bird// example 1function Bird(name){Animal(name);}Bird.prototype = {};// 實例化Bird對象var myBird = new Bird('xiaocui');alert(myBird.type);// undefined ?? 為什么這個會錯呢?
?? 在javascript中,function 有 2 種不同的用法:
?? a 作為函數存在,直接使用“()”進行調用,例如:function test(){}; test(); test 被用作函數,直接被 “()”符號調用
?? b 作為類的構造函數存在,使用 new 調用,例如“function test(){}; new test();” test 作為類的構造函數,通過 new 進行 test 類的實例化。
?? 這 2 種方法的調用,function 內部的 this 指向會有所不同---作為函數的 function,其 this 指向的是 window 對象,而作為類構造函數的 function,其 this 指向的是實例對象。
?? 讓 Animal 內部的 this 指向 Bird 類的實例,可以通過 call 或 apply 方法實現,如下:
javascript:// example 2function Bird(name){Animal.call(this, name);}Bird.prototype = {};// 實例化Bird對象var myBird = new Bird('xiaocui');alert(myBird.type);// animal ?? 構造函數的屬性和行為已經實現了繼承,接下來我們要實現原型中屬性和行為的繼承。
View Code javascript:// example 3function Bird(name){Animal.call(this, name);}Bird.prototype = Animal.prototype;Bird.prototype.fly = function(){alert('I\'m flying');};// 實例化Bird對象var myBird = new Bird('xiaocui');myBird.say();// xxxxxmyBird.fly();// I'm flyingvar myDog = new Animal('wangcai');myDog.fly();// I'm flying ?? 我們只想給 Bird 類添加 fly 行為,為什么 Animal 類也獲得了 fly 行為呢?這涉及 傳值 和 傳址 兩個問題---在javascript中,賦值語句會用 傳值 和傳址 兩種不同的方式進行賦值,如果是數值型、布爾型、字符型等基本數據類型,在進行賦值時會將數據復制一份,將復制的數據進行賦值,也就是通常所說的傳值,如果是數組、hash對象等復雜數據類型(數組、hash對象可包括簡單類型數據),在進行賦值時會直接用內存地址賦值,而不是將數據復制一份,這就是通常所說的 傳址。eg:
View Code javascript:var a = 10, b = a;// 基本數據類型b++;var c = [1, 2, 3], d = c;d.push(4);alert(a);// 10alert(b);// 11alert(c.join(','));// 1,2,3,4alert(d.join(','));// 1,2,3,4 ?? c, d 指向同一份數據地址,所以改變其中一個,另外一個也會改變,而基本數據類型不會。
?? 那么如何復制對象呢?最簡單的做法是遍歷數組或者hash對象,將數組或hash對象這種復雜的數據拆分成一個個簡單數據,然后分別賦值,eg:
javascript:var a = [1, 2, 3], b = {name: 'adang', sex: 'male', tel: '123456'};var c = [], d = {};for(var p in a){c[p] = a[p];}for(var p in b){d[p] = b[p];}c.push(4);d.email = 'xxx@gmail.com';alert(a);// 1,2,3alert(c);// 1,2,3,4alert(b.email);// undefinedalert(d.email);// xxx@gmail.com ?? prototype 本質上也是個 hash 對象,所以直接使用它賦值時會進行傳址,這也是為什么子類擴張prototype,而父類也會帶上子類的prototype。我們可以用 for in 來遍歷prototype,從而實現prototype的傳值。但因為 prototype 和 function(用作類的function)的關系,我們還有另一種方法實現prototype的傳值---new SomeFunction(),eg:
View Code javascript:// example 4function Bird(name){Animal.call(this, name);}Bird.prototype = new Animal();// 這里不是很明白,new 一個父類實例賦值給 Bird.prototype 就能實現 原型 的繼承了嗎?如果是,是為什么呢?Bird.prototype.constructor = Bird;Bird.prototype.fly = function(){alert('I\'m flying');};// 實例化Bird對象var myBird = new Bird('xiaocui');myBird.say();// xxxxxmyBird.fly();// I'm flyingvar myDog = new Animal('wangcai');myDog.fly();// 報錯,myDog.fly is not a function ?? 我們發現這里有這么一句:Bird.prototype.constructor = Bird; 這是因為 Bird.prototype = new Animal(); 時,Bird.prototype.constructor 指向了 Animal,我們需要將它糾正,重新指回 Bird。
?? 這樣的方式可以順利的實現javascript的繼承,但是我們還可以進一步將它們進行封裝,定義個 extend 函數:
javascript:function extend(subClass, superClass){var F = function(){};// 作為class的函數F.prototype = superClass.prototype;subClass.prototype = new F();subClass.prototype.constructor = subClass;// 指回來subClass.superclass = superClass.prototype;if(superClass.prototype.constructor == Object.prototype.constructor){superClass.prototype.constructor = superClass;}}function Animal(name){this.name = name;this.type = 'animal';}Animal.prototype = {say: function(){alert('I\'m a(an) '+ this.type +' , my name is '+ this.name);}};function Bird(name){this.constructor.superclass.constructor.apply(this, arguments);this.type = 'Bird';}extend(Bird, Animal);Bird.prototype.fly = function(){alert('I\'m flying');}var canary = new Bird('xiaocui');canary.say();// .........canary.fly();// I'm flying 14、用面向對象方式重寫代碼
?? 下面以 電話本程序 為例.
?? 首先,我們要進行 OOA,從現實邏輯中抽象出類。使用面向過程的編程方式時,處理函數是非常核心的部分,在命名的時候它很可能是個動詞,例如 getTel、addItem,而面向對象的編程方式,最核心的部分是類,類的命名往往是個名詞,例如 Animal、Bird。我們說明面向對象編程的時候,往往用一些客觀世界真實存在的東西來舉例,例如人、貓、狗,但類不一定是客觀存在的某個物件,例如這次我們要寫的電話本程序,就很難對應到客觀設計界的某個真實存在的物件,它更像是一個邏輯上的物件,管理者關于電話記錄的所有邏輯。它保存著許多電話記錄,它可以用來添加、刪除和查詢電話記錄。我們給它取個什么名字好呢?PhonebookManager,電話本管理者。
?? 現在我們首先要明確類名以及類的接口,至于接口里的具體邏輯如何實現,我們暫時先不管,在 OOA 這一步,我們只需要用 UML 語言將類描述出來即可,如下圖:
?? UML圖:
?? |--------------------|
?? |? PhonebookManager? |
?? |--------------------|
?? |? -phonebook:hash?? |
?? |--------------------|
?? |? +getTel():string? |
?? |? +addItem():void?? |
?? |? +removeItem():void|
?? |--------------------|
?? UML 描述類的方式很簡單,一個方框代表一個類,將方框劃成上中下三欄,第一欄填入類名,第二欄填入類的屬性,第三欄填入類的行為,其中公有屬性和公有行為需要在屬性和行為名前加上“+”號,私有屬性和行為需要加上“-”號。
?? 在本例中,PhonebookManager 類有一個電話本屬性 phonebook,記錄著保存在 PhonebookManager 中的電話記錄,有 getTel、addItem、removeItem行為,分別用于查詢電話、添加記錄和刪除記錄。因為我們需要讓類的對外提供的接口盡可能的少,除了必要的接口應該設為公有,其他的都應該設為私有。
?? 一般來說,OOA結束之后,我們需要進行OOD,但本例實在太簡單了,用不上OOD。只有一些復雜的邏輯處理才可能用得上OOD。
?? 接下來,我們要將OOA和OOD的成果用程序編寫出來,也就是OOP的環節。
javascript:// 定義電話本管理類function PhonebookManager(o){this._phonebook = o;}PhonebookManager.pototype = {// 查詢電話getTel: function(oName){var tel = '';for(var i=0; i<this._phonebook.length; i++){if(this._phonebook[i].name == oName){tel = this._phonebook[i].tel;break;}}return tel;},// 添加記錄addItem: function(oName, oTel){this._phonebook.push({name: oName, tel: oTel});},// 刪除記錄removeItem: function(oName){var n;for(var i=0; i<this._phonebook.length; i++){if(this._phonebook[i].name == oName){n = i;break;}}if(n != undefined){this._phonebook.splice(n, 1);}}};使用:javascript:var myPhonebookManager = new PhonebookManager({{name: 'zhang', tel: '111'},{name: 'wang', tel: '222'}});var zhangTel = myPhonebookManager.getTel('zhang');myPhonebookManager.addItem('huang', '333');myPhonebookManager.removeItem('wang');
?? 電話本的重構已經完成了,進行tab 的重構。
?? (略)
15、prototype 和內置類
?? javascript語言中提供了一些內置類,包括 Array、String、Function等,它們剔紅了javascript的大部分基本數據類型。這些內置類通常會提供一些方法和屬性,例如 Array 類提供的 length 屬性,push、pop方法,String提供length屬性,replace、split方法,Function 提供 call、apply方法等。
?? 需要說明的是,這些內置類不一定需要通過 new 的方式進行實例化,我們平時習慣更簡短的方式調用它們,但其本質上是一樣的。
javascript:var a = bew String('hello world');// 通過new String()實例化 string 類型對象var b = 'hello world';// 直接通過 '' 實例化 string 類型對象alert(a.length);alert(b.length);var c = new Array(1, 2, 3);var d = [1, 2, 3];c.push(4);d.pop();alert(c);alert(d); ?? 只要是類就會有原型,不管它是自定義類還是javascript的內置類,我們可以通過修改內置類的原型,讓javascript基本類型的對象獲得一些有趣的功能。例如,在很多語言中,Array具有each、map等方法,但javascript沒有。沒關系,既然原生的javascript并不提供這些方法,那么我們自己擴展它好了。
View Codejavascript:Array.prototype.each = function(fun){for(var i=0,n=this.length; i<n; i++){fun(this[i], i);}};Array.prototype.clone = function(){var o = [];this.each(function(v, k){o[k] = v;});return o;};Array.prototype.map = function(fun){var o = [];this.each(function(v, k){o[k] = function(v, k);});return o;};// 因為在IE中 delete 是保留字,所以方法名改用DeleteArray.prototype.Delete = function(a){var o = this.clone();for(var i=o.length, n=0; i>n; i--){if(o[i] == a){o.splice(i, 1);}}return o;};var a = [1, 2, 3, 4, 5];var str = '';a.each(function(v, k){str += k+':'+ v +', ';});alert(str);// 0:1, 1:2, 2:3, 3:4, 4:5,var b = a.map(function(v, k){return v*10;});alert(a);// 1,2,3,4,5alert(b);// 10,20,30,40,50var c = b.Delete(20);alert(c);// 10,30,40,50
?? 這段代碼中最難理解的地方在于 擴展中this代表什么?以前我們說過,無論在類的構造函數中還是在原型中,this都指向實例化的對象。明白了這一點,以上代碼就不難理解了。
?? 除了可以擴展內置類的方法,我們還可以重寫內置類的方法。
javascript:var a = [1, 2, 3];alert(a);// 1,2,3Array.prototype.toString = function(){return 'I\'m an array';}alert(a);// I'm an array值得一提的是,alert(a) 時,自動調用了 a 的toString 方法。在需要字符串時,對象會隱式地自動調用 toString 方法,包括我們自定義的對象。javascript:function Dog(o){this.name = o;}var myDog = new Dog('wang cai');alert(myDog);// [object object]Dog.prototype.toString = function(){return 'my name is '+ this.name;}alert(myDog);// my name is wang caivar me = {name: 'adang',email: 'xxx@163.com',toString: function(){return 'I\'m adang,my email is xxx@163.com';}};alert(me);// I'm adang,my email is xxx@163.com ?? 給自定義類定義 toString 方法,可以為我們在調試時提供更多有用的信息。
?? 內置類的方法可以重寫,但屬性卻不能重寫,比如。
javascript:Array.prototype.length = 1;String.prototype.length = 1;alert([1, 2, 3].length);// 3alert('abc'.length);// 3 ?? 在javascript中,包括內置類和自定義類,所有類的祖先都是Object,所以如果想對所有對象都擴展方法,可以通過修改Object類的原型實現,如:
View Code javascript:Object.prototype.test = function(){alert('hello world');}var a = [1, 2, 3], b = 'abc', c = {}, d = true, e = function(){};a.test();b.test();c.test();d.test();e.test();function Dog(o){this.name = o;}Dog.prototype.toString = function(){return 'my name is '+ this.name;}var f = new Dog(wang cai);f.test(); ?? 修改內置類的原型可以再編程時給我們帶來很大方便,但也有些人非常排斥這種做法,認為它對內置類的原型造成了“污染”,因為內置類的原型也可以理解為是全局作用域的,如果對它進行修改,在多人合作時有可能對別人的代碼造成影響。修改內置類的原型等于修改了統一的游戲規則,雖然可以帶來很大的方便,但同時也會給多人合作帶來沖突隱患,它是有副作用的。
?? 所以有些人更愿意使用這樣的方式來擴張內置類的方法,如下:
javascript:function myArray(o){this.getArray = function(){return o;}}myArray.prototype = {each: function(fun){var o = this.getArray();for(var i=0, n=o.length; i<n; i++){fun(o[i], i);}}};var a = new myArray([1, 2, 3]), str = '';a.each(function(v, k){str += k +':'+ 'v'+ ', ';});alert(str);// 0:1, 1:2, 2:3,
16、標簽的自定義屬性
View Codehtml:<a id="a" class="b" title="百度" href="http://www.baidu.com" onclick="alert(this.href);return false;" data-type="鏈接">baidu</a>javascript:var node = document.getElementById('a');alert(typeof node);// object// 使用 getAttribute('xxx') 獲取節點對象屬性alert(node.getAttribute('id'));// IE 和 Firefox : aalert(node.getAttribute('class'));// IE : null// Firefox : balert(node.getAttribute('className'));// IE : b// Firefox : nullalert(node.getAttribute('title'));// IE 和 Firefox : 百度alert(node.getAttribute('href'));// IE 和 Firefox : http://www.baidu.comalert(node.getAttribute('onclick'));// IE : function onclick(){alert(this.href);return false;}// Firefox : alert(this.href);return false;alert(node.getAttribute('innerHTML'));// IE : baidu// Firefox : null// 使用 node.xxx 獲取節點對象屬性alert(node.id);// IE 和 Firefox : aalert(node.className);// IE 和 Firefox : balert(node.title);// IE 和 Firefox : 百度alert(node.href);// IE 和 Firefox : http://www.baidu.comalert(node.onclick);// IE : function onclick(){alert(this.href);return false;}// Firefox : function onclick(event){alert(this.href);return false;}alert(node.innerHTML);// IE 和 Firefox : baidu
?? 除了常規屬性,我們還可以給 html 標簽定義一些自定義屬性,這些自定義屬性同樣在javascript中獲取。但和常規屬性不同,Firefox 下無法通過 node.xxx 獲取到自定義屬性值,只能使用 node.getAttribute('xxx') 獲取。
?? 所以,從兼容性考慮,筆者建議對于常規屬性,統一使用 node.xxx 的方式讀取,對于自定義屬性,統一使用 node.getAttribute('xxx') 讀取。
?? 將復類型的數據轉化成字符串,稱為數據的序列化,其逆操作叫做數據的反序列化。
?? 借助html自定義屬性,我們可以儲存各種各樣的數據,因為屬性只能是字符串類型的,所以我們要先把復雜數據序列化,做成 長得像hash或數組的字符串 才行。
?? 數據的反序列化一般使用 eval 函數實現。
javascript:var strObject = '{name: "huang", tel: "123"}';var obj = eval('('+ strObject +')');alert(obj.name);// huangalert(obj.tel);// 123 17、標簽的內聯事件和event對象
?? event 對象在IE和Firefox下的表現是不同的。在IE下,event是window對象的一個屬性,是在全局作用域下的,而在Firefox里,event對象作為事件的參數存在,如下:
html:<input type="button" id="btn" value="click me" />javascript:document.getElementById('btn').onclick = function(){alert(arguments.length);// IE下彈出0;Firefox下彈出1};
?? 如果在標簽的內聯事件中觸發事件又如何呢?代碼:
View Codehtml:<input type="button" id="btn" value="click me" onclick="handler();" />javascript:function handler(){alert(arguments.length);}
?? 在IE肯Firefox下,這段代碼彈出的都是 0。
?? 在標簽內聯事件中,我們使用 arguments[0] 可以再Firefox 下訪問到 event 對象。
?? 不使用標簽內聯事件時,我們可以給處理函數傳參,從而指定 arguments[0] 的變量名。
html:<input type="button" id="btn" value="click me" />javascript:document.getElementById('btn').onclick = function(e){e = window.event || e;// 兼容 ie 和 firefox ,指向 event 對象};
?? 在標簽內聯事件中,我們沒辦法指定參數名,是不是就沒辦法直接寫個變量在 ie 和 firefox 下兼容的指向 event 對象呢?不是的,可以用 event 這個變量名兼容的指向 event 對象,注意,只能是 event,諸如 a,b,Event 之類都是不行的。
View Codehtml:<input type="button" id="btn" value="click me" onclick="alert(event.type);" />
?? 這段代碼在ie 和 firefox下都正確的彈出 "click"。
?? 有趣的是,標簽內聯事件中我們甚至可以寫注釋,可以使用字符串:
html:<input type="button" id="btn" value="click me" onclick="alert(1);//alert(2);alert(3);" /><input type="button" id="btn" value="click me" onclick="alert(1);/*alert(2);*/alert(3);" /><input type="button" id="btn" value="click me" onclick="var a='abc';alert(typeof a);" />
?? 如果我們既用標簽內聯事件綁定了事件,又用 DomNode.onXXX 綁定了事件,那么 DomNode 綁定的事件函數會替代標簽內聯事件:
View Codehtml:<input type="button" id="btn" value="click me" onclick="alert(123);" />javascript:doccument.getElementById('btn').onclick = function(){alert(456);};
?? 其結果就是,彈出 456 ,不彈出 123。
?? 再如果重復綁定呢:
javascript:doccument.getElementById('btn').onclick = function(){alert(123);};doccument.getElementById('btn').onclick = function(){alert(456);}; ?? 結果會彈出 456 ,不彈出 123 。后面的會覆蓋前面的。
?? 如果通過 attachEvent 和 addEventListener 來綁定事件:
html:<input type="button" id="btn" value="click me" onclick="alert(123);" />javascript:function handler(){alert(456);}var btn = document.getElementById('btn');if(document.all){btn.attachEvent("onclick", handler);}else{btn.addEventListener("click", handler, false);}
?? 先彈出 123,然后彈出 456.
18、利用事件冒泡機制
?? 冒泡的思路是在祖先節點上監聽事件,結合 event.target/event.srcElement 來實現最終效果。利用冒泡可以讓事件掛鉤更干凈,有效減少內存開銷。
19、改變DOM樣式的三種方式
?? javascript 編程很重要的一個功能就是用于改變DOM節點的樣式。
?? a 最簡單最直接的方式就是設置 DomNode 的 style 屬性。
html:<span id="test">hello world</span>javascript:var node = document.getElementById('test');node.style.color = 'red';node.style.backgroundColor = 'black';node.style.fontSize = '40px';node.style.fontWeight = 'bold';
?? 這種寫法拖沓又冗長,而且過多的承擔起了變現層的職責,而變現層應該是由css控制的,所以又有了下面的方式,控制 DomNode 的class屬性
View Codestyle:.testStyle {color: red;background-color: black;font-size: 40px;font-weight: bold;}html:<span id="test">hellow world</span>javascript:var node = document.getElementById('test');node.className = 'testStyle';
?? 第三種方式:
?
html:<span id="test">hellow world</span>javascript:function addStyleNode(str){var styleNode = document.createElement('style');styleNode.type = 'text/css';if(styleNode.styleSheet){styleNode.styleSheet.cssText = str;}else{styleNode.innerHTML = str;}document.getElementsByTagName('head')[0].appendChild(styleNode);}addStyleNode('span {font-size: 40px;background: #000;color: #fff;}#test {color: red;}');
?? 需要注意的是,style的DOM節點在Firefox下可以直接對innerHTML屬性進行讀寫操作,但在IE下,它的innerHTML屬性是只讀的。IE下要通過styleSheet.cssText進行寫操作。
20、寫在規則前面的話
?? 項目的可維護性第一。好的可維護性可以從四個方面獲得:
?? a 代碼的耦合,高度模塊化,將頁面內的元素視為一個個模塊,互相獨立,盡量避免耦合過高的代碼,從html、css、javascript三個層面考慮模塊化。
?? b 良好的注釋。
?? c 注意代碼的彈性,在性能和彈性的選擇上,一般情況下以彈性為優先考慮條件,在保證彈性的基礎上,適當優化性能。
?? d 嚴格按照規范編寫代碼。
21、一些總結性的結論(參考):
?? 為避免命名沖突,命名規則如下:
?? a 公共組件因為高度重用,命名從簡,不要加前綴。
?? b 各欄目的響應代碼,需要前綴,前綴為工程師姓名拼音的首字母。例如:"ad_"。(我覺得該條可酌情考慮)
?? c 模塊組件化,組件中的class或id名采用駱駝命名法和下劃線相結合的方式,單詞之間的分隔靠大寫字母分開,從屬關系靠下劃線分隔。例如:.textList_first {}
?? d 命名清晰,不怕命名長,確保css優先級權重足夠低,方便擴展時的覆蓋操作。
?? e 命名要有意義,盡量使用英語命名,不要用拼音。
?? 分工安排:
?? a 公共組件(包括common.css和common.js)一人維護,各子頻道專人負責,每個頻道正常情況下一人負責,要詳細寫明注釋,如果多人合作,維護的人員要添加注釋信息。
?? b 視覺設計師設計完設計圖后,先和交互設計師溝通,確定設計可行,然后先將設計圖給公共組件維護者,看設計圖是否需要提取公共組件,然后再提交給相應頻道的前端工程師。如果有公共組件要提取,公共組件維護者需要對頻道前端工程師說明。
?? c 如果確定沒有公共組件需提取,交互設計師直接和各欄目的前端工程師交流,對照著視覺設計師的設計圖進行需求說明,前端工程師完成需求。
?? d 前端工程師在制作頁面時,需先去common文件中查詢是否已經存在設計圖中的組件,如果有,直接調用;如果沒有,則在app.css和app.js中添加相應的代碼(app指各頻道自己的文件)。
?? e 前端工程師在制作過程中,發現高度重用的模塊,卻未被加入到公共組件中,需向公共組件維護人員進行說明,然后公共組件維護人決定是否添加該組件。如果確定添加,則向前端工程師們說明添加了新組件,讓前端工程師們檢查之前是否添加了類似組件,統一更新成新組件的用法,刪除之前自定義的css和javascript。雖然麻煩,但始終把可維護性放在首位。
?? f 公共組件維護者的公共組件說明文檔,需提供配套的圖片和說明文字,方便閱讀。
?? 注釋規則:
?? a 公共組件和各欄目的維護者都需要在文件頭部加上注釋說明:
?? /**
?? * 文件用途說明
?? * 作者姓名
?? * 聯系方式
?? * 制作日期
?? **/
?? /**
?? * 公共組件
?? * author: adang
?? * email: xxx@gmail.com
?? * date: 2012-10-11
?? **/
?? b 大的模塊注釋
?? //=============
?? // 代碼用途
?? //=============
?? c 小的注釋
?? // 代碼說明
?? 注意:注釋單獨占一行
?? html規范:
?? a 統一文檔類型說明。
?? b 統一文件編碼。
?? c 統一TAB縮進長度(四個空格)。
?? d 標簽名,屬性名全部小寫,屬性加引號,單標簽需閉合。
?? e html應在保證彈性的基礎上盡量減少嵌套層數。
???? 嚴格區分作為內容的圖片和作為背景的圖片。作為背景的圖片采用css sprite整合。大圖的安排也遵從 common+app 的方式。css sprite 雖然減少了http請求,但需background-position定位增加了可維護成本。如果圖片有修改,建議不要刪除已添加的圖片,而是在空白處新增修改后的圖片,減少修改的風險。
?? f 標簽語義化,webdevelper去樣式可讀性良好。
?? g 方便服務端嵌套模板,html需為模塊添加注釋。格式為:
?? <!--頭部開始{-->
?? .....
?? <!--}頭部結束-->
?? css 規范:
?? a css reset 用YUI的css reset。
?? b css 采用 css reset+common.css+app.css的形式。
?? c app.css采用分工制,一個前端工程師負責一個欄目,如果多人維護,需要添加注釋。
?? d 為方便組件模塊化和提高彈性,正常情況下,為避免外邊界沖突,組件不設置外邊界,外邊界用組合css的方式實現,如:
?? html:
?? <p>12345</p>
?? <ul class="textList">
???? <li>1)xxxx</li>
???? <li>2)xxxx</li>
?? </ul>
?? <p>abcde</p>
?? <ul class="textList2">
???? <li>1)xxxx</li>
???? <li>2)xxxx</li>
?? </ul>
?? css:
?? .textList, .textList2 {margin-top: 10px;xxx}
?? .textList2 {margin-top: 20px;}
?? 上面的方式,不靈活,可參考下面的代碼組織方式:
?? html:
?? <p>12345</p>
?? <ul class="textList marginTop10">
???? <li>1)xxxx</li>
???? <li>2)xxxx</li>
?? </ul>
?? <p>abcde</p>
?? <ul class="textList marginTop20">
???? <li>1)xxxx</li>
???? <li>2)xxxx</li>
?? </ul>
?? css:
?? .textList {xxx}
?? e 為便面組件的上下外邊距重合問題和IE的haslayout引發的bug,各模塊除特殊需求,一律采用 marginTop 設置上下外邊距。
?? f 優先對已存在的common.css中類進行組合,較少自定義類的數量。
?? g css用一行的寫法,避免行數太長,不利查找。
?? h 正是發布前應進行壓縮,壓縮后文件的命名應添加“_min”后綴。
?? javascript 規范:
?? a 底層javascript庫采用YUI。
?? b 統一頭部中只載入YUI load組件,其他組件通過loader對象加載。
?? c javascript盡量便面使用全局變量,通過命名空間或匿名函數將變量封裝到閉包中。
?? d 正是發布前應進行壓縮,壓縮后文件的命名應添加“_min”后綴。
?
筆記第一部分:1-4章的筆記
轉載請帶上原文地址(http://www.cnblogs.com/xmmcn/archive/2012/12/04/2800979.html),謝謝:)
?
轉載于:https://www.cnblogs.com/xmmcn/archive/2012/12/04/2800979.html
總結
以上是生活随笔為你收集整理的读书笔记:编写高质量代码--web前端开发修炼之道(二:5章)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 现在一条180混凝土搅拌站需要多少钱?(
- 下一篇: Eclipse 调试器(引用IT168)