摘要:執行環境在很多方面都有其獨特之處全局變量和函數便是其中之一事實上的初始執行環境是由多種多樣的全局變量所定義的這寫全局變量在腳本環境創建之初就已經存在了我們說這些都是掛載在全局對象上的全局對象是一個神秘的對象它表示了腳本最外層上下文在瀏覽器中
JavaScript執行環境在很多方面都有其獨特之處. 全局變量和函數便是其中之一. 事實上, js的初始執行環境是由多種多樣的全局變量所定義的, 這寫全局變量在腳本環境創建之初就已經存在了. 我們說這些都是掛載在"全局對象"(global object) 上的, "全局對象"是一個神秘的對象, 它表示了腳本最外層上下文.
在瀏覽器中, window對象往往重載并等同于全局對象, 因此任何的全局作用域中聲明的變量和函數都是window對象的屬性:
var color = "red"; function sayColor() { alert(color); } console.log(window.color); // "red" console.log(typeof window.sayColor); // "function"
這段代碼中定義了全局變量color和全局函數sayColor(), 兩者都是window對象的屬性, 盡管我們并沒有顯示的執行給window對象掛載屬性的操作.
6.1 全局變量帶來的問題
創建全局變量被認為是最糟糕的實踐, 尤其是在團隊開發的大背景下更是問題多多. 隨著代碼量的增長, 全局變量就會導致一些非常重要的可維護性難題. 全局變量越多, 引入錯誤的概率將會因此變得越來越高.
6.1.1 命名沖突
腳本中的全局變臉和全局函數越來越多時, 發生命名沖突的概率也隨之增高, 很可能無意間就使用了一個已經聲明了的變量. 所有的變量都被定義為全局變量, 這樣的代碼才是最容易維護的.
6.1.2 代碼的脆弱性
一個依賴于全局變量的函數即是深耦合于上下文環境之中. 如果環境發生改變, 函數很可能就失效了. 在上一個例子中, 如果全局變臉color不再存在, sayColor()的方法將會報錯. 這意味著任何對全局環境的修改都可能在成某處代碼出錯. 同樣, 任何函數也會不經意間修改全局變量. 導致對全局變量值的依賴變得不穩定. 在上個例子中, 如果color被當做參數傳入, 代碼可維護性會變得更佳.
var color = "red"; function sayColor() { alert(color); // 不好的做法用全局變量 } function sayColor(color) { alert(color); // 好的做法 }
修改后這個函數不再依賴于全局變量, 因此任何對全局環境的修改都不會影響到它. 由于color是一個參數. 唯一值得注意的是傳入函數的值得合法性. 其他修改都不會對這個函數完成它本身的任務有任何影響.
當定義函數的時候, 最好盡可能多地將數據至于局部作用域內. 在函數內定義的任何都應當采用這種寫法. 任何來自函數外部的數據都應當以參數形式傳進來. 這樣做可以將函數和其外部環境隔離開來. 并且你的修改不會對程序其他部分造成影響.
6.1.3 難以測試
嘗試著在一個大型web應用中實施一些單元測試. 在我即將完成核心框架的搭建時, 我加入了一個團隊, 此后我努力讓我的代碼變得易于理解以便后續為其執行測試. 我非常吃驚的發現, 執行測試編程一項積極困難的工序, 因為整個框架要依賴于一些全局變量才會正常工作.
任何依賴全局變量才能正常工作的函數, 只有為其重新創建完整的全局環境才能正確的測試它. 事實上, 這意味著你除了要管理全局環境的修改, 你還要在兩個全局環境中管理它們: 生產環境和測試環境. 保持兩者的同步是很消耗成本的, 很快你就會發現(代碼)可維護性的噩夢才剛剛開始. 越到后來越難于理清頭緒.
確保你的函數不會對全局變量有依賴, 這將增強你的代碼的可測試性(testability). 當然你的函數可能會依賴原生的js全局對象, 比如Date、Array等. 他們是全局環境的一部分, 適合js引擎相關的, 你的函數總是會用到這些全局對象. 總之, 為了保證你的代碼具有最佳的可測試性, 不要讓函數對全局變量有依賴.
6.2 意外的全局變量
js中有不少陷阱, 其中有一個就是不小心會創建全局變量. 當你給一個未被var的語句證明過的變量賦值時, js就會自動創建愛你一個全局變量, 比如:
function doSomeThing() { var count = 10; title = "編寫可維護的js"; // 不好的寫法: 創建了全局變量 }
6.3 單全局變量方式
依賴盡可能少的全局變量, 即只創建一個全局變量.
單全局變量模式已經在各種流行的類庫中廣泛使用了.
jQuery定義了兩個全局對象, $和jQuery. 只有在$被其他的類庫使用了的情況下, 為了避免沖突, 應當使用jQuery.
vue實例化的對象 自己定義的vm等
"單全局變量"的意思是所創建的這個唯一的全局對象名是獨一無二的(不會和內置API沖突), 并將你的所有的功能代碼都掛載到這個全局對象上. 因此每個可能的全局變量都成為你唯一的全局對象的屬性, 從而不會創建多個全局變量. 比如, 假設我想讓一個對象表示本書的一章, 代碼看起來會像下面這樣.
function Book(title) { this.title = title; this.page = 1; } Book.prototype.turnPage = function(direction) { this.page += direction; }; var Chapter1 = new Book("第一章"); var Chapter2 = new Book("第二章"); var Chapter3 = new Book("第三章");
這段代碼創建了4個全局對象: Book、Chapter1、Chapter2、Chapter3. 單全局變量模式則只會創建一個全局對象并將這些對象都賦值為它的屬性.
var MaintainableJS = {}; MaintainableJS.Book = function(title) { this.title = title; this.page = 1; } MaintainableJS.Book.prototype.turnPage = function(direction) { this.page += direction; }; MaintainableJS.Chapter1 = new MaintainableJS.Book("第一章"); MaintainableJS.Chapter2 = new MaintainableJS.Book("第二章"); MaintainableJS.Chapter3 = new MaintainableJS.Book("第三章");
這段代碼只有一個全局對象, 即MaintainableJS, 其他任何信息都掛載到這個對象上. 因為團隊中每個人度知道這個全局對下是哪個, 因此很容易做到繼續為它添加屬性以避免全局污染.
6.3.1 命名空間
即使你的代碼只有一個全局對象, 也可能污染全局. 大多數使用單全局變量模式的項目同樣包含"命名空間"的概念. 命名空間是簡單的通過全局對象的單一屬性表示的功能性分組. 比如, YUI就是已超命名空間的思路來管理代碼的. Y.DOM下的所有的方法都是和DOM操作相關的, Y.Event下所有的方法是和時間相關的.
將功能按照命名空間進行分組, 可以讓你的全局對象變得井然有序, 同事可以讓團隊成員能夠知曉性性能應該屬于哪個部分, 或者知道去哪里查找已有的功能. 當作者在YaHoo!工作的時候, 就有一個不成文的約定, 即每個站點杜江自己的命名空間掛載至Y對象上, 因此"My YaHoo!" 使用Y.My, 郵箱使用Y.Mail, 等等. 這樣團隊成員則可以放心大膽的使用其他人的代碼, 而不必擔心沖突.
在js中你可以使用對象來輕而易舉的創建你自己的命名空間, 比如:
var ZakasBooks = {}; // 表示這本書的命名空間 ZakasBooks.MaintainableJs = {}; // 表示另一本書的命名空間 ZakasBooks.HighPerformanceJs = {};
一個常見的約定在每一個文件中都通過創建新的全局對象來聲明自己的命名空間. 在這種情況下, 上面的這個例子給出的方法是夠用的.
同樣有另外一個常見, 每個文件都是需要給一個命名空間掛載東西. 這種情況下, 你需要首先保證這個命名空間是已經存在的. 這是全局對象非破壞性的處理命名空間的方式則變得非常有用, 成成這項操作的基本模式是像下面這樣的.
var YourGlobal = { namespace: function(ns){ var parts = ns.split("."), object = this, i, len; for(i = 0, len = parts.length; i < len; i++) { if(!object[parts[i]]){ object[parts[i]] = {}; } object = object[parts[i]]; } return object; } }
變量YourGlobal 實際上可以表示任意名字. 最重要的部分在于namespace()方法, 我們給這個方法傳入一個表示命名空間對象的字符串, 它會非破壞性的(nondestructively) 創建這個命名空間, 基本一用法如下.
/* * 同時創建YourGlobal.Books和YourGlobal.Books.MaintainableJs * 因為之前沒有創建過它們, 因此每個都是全新創建的 */ YourGlobal.namespace("book.MaintainableJs"); // 現在你可以使用這個命名空間 YourGlobal.Books,MaintainableJs.author = "Nicholas C. Zakas"; /* * 不會操作YourGlobal.Books本身, 同時會給它添加HighPerformanceJs * 它會保持YourGlobal.Books.MaintainableJs原封不動 */ YourGlobal.namespace("Books.HighPerformanceJs"); // 仍然是合法的引用 console.log(YourGlobal.Books.MaintainableJs.author); // 你同樣可以在方法調用之后立即給它添加新屬性 YourGlobal.namespace("Books").ANewBook = {};
基于你的單全局對象使用namespace()方法可以讓開發者放心地認為命名空間總是存在的. 這樣, 每個文件度可以首先調用namespace()來聲明開發者將要使用的命名空間, 這樣做不會對已有的命名空間造成任何破壞. 這個方法可以讓開發者解放出來, 在使用命名空間之前不必再去判斷它是否存在.
由于你的代碼不是獨立存在的, 因此要圍繞命名空間定義一些約定. 是否應該以首字母大寫的形式來定義命名空間, 就像YUI? 還是都用小寫字母形式來定義命名空間, 就像Dojo? 這個是個人喜好問題, 但是首先定義這些約定可以讓后續團隊成員在使用但全局變量時更加高效.
6.3.2 模塊
另一種基于單全局變量的擴充方法是使用模塊(modules). 模塊是一種通用的功能片段, 它并沒有創建新的全局變量或者命名空間. 相反, 所有的這些代碼都存放于一個表示執行一個任務或發布一個接口的但函數中. 可以用一個名稱來表示這個模塊, 同樣這個模塊可以依賴其它模塊.
js本身不包含模塊概念, 自然也沒有模塊語法(es6支持 import export), 單的確有一些通用的模式來創建模塊. 兩種最流行的類型是"YUI模塊"模式和"異步模塊定義"(Asynchronous Module Definition, 簡稱AMD)模式.
YUI模塊
從字面含義理解, YUI模塊就是使用YUI Js類庫來創建新模塊的一種模式. YUI3中包含了模塊的概念, 寫法如下.
YUI.add("module-name", function(Y) { // 模塊正文 }, "version", { requires: [ "dependecy1", "dependency2" ] });
我們通過調用YUI.add()并給它傳入模塊名字、待執行的函數(被稱作工廠方法)和可選的依賴列表來添加YUI模塊. "模塊正文"處則是你寫所有的模塊代碼的地方. 參數Y是YUI的一個實例, 這個實例包含所有以來的模塊提供的內容. YUI中約定在每一個模塊內使用命名空間的方式來管理模塊代碼, 比如:
YUI.add("my-books", function(Y) { // 添加一個命名空間 Y.namespace("Books.MaintainableJs"); Y.Books.MaintainableJs.author = "Nicholas C. Zakas"; }, "1.0.0", { requires: [ "dependecy1", "dependency2" ] });
同樣, 依賴也是以Y對象命名空間的形式傳入進來. 因此YUI實際上是將命名空間和模塊的概念合并在了一起, 總體上提供一種靈活的解決方案.
通過調用YUI().use()函數并傳入想加載的模塊名稱來使用你的模塊.
YUI.use("my-books", "another-modult", function(Y) { console.log(Y.Books.MaintainableJs.author); })
這段代碼以加載名叫"my-books"和"another-module"的兩個模塊開始, YUI會確保這些模塊的依賴都會完全加載完成, 然后執行模塊的正文代碼, 最后才會執行行傳入YUI().use()的回調函數. 回調函數會帶回Y對象, Y對象里包含了加載模塊對它做的修改, 這時你的應用代碼就可以放心的執行了.
"異步模塊定義"(AMD)
AMD模塊和YUI模塊有諸多相似之處. 你指定模塊名稱、依賴和一個工廠方法, 依賴加載完成后執行這個工廠方法. 這些內容全部作為參數傳入一個全局函數define()中, 其中第一個參數是模塊名稱, 然后是依賴列表, 最后是工廠方法. AMD模塊和YUI模塊最大的不同在于, (AMD中) 每一個依賴都會對應到獨立的參數傳入工廠方法里, 比如:
define("module-name", [ "dependency1", "dependency2" ], function(dependency1, dependency2) { //模塊正文 })
因此, 每個被命名的依賴最后都會創建一個對象, 這個對象會被帶入工程方法中. AMD以這種方式來嘗試避免命名沖突, 因為直接在模塊中使用命名空間有可能發生命名沖突. 和YUI模塊中創建新的命名空間的方法不同, AMD模塊則期望從工廠方法中返回它們的公有接口, 比如:
define("my-books", [ "dependency1", "dependency2" ], function(dependency1, dependency2) { var Books = {}; Books.MaintainableJs = { author: "Nicholas C. Zakas" } return Books; })
AMD模塊同樣可以是匿名的, 完全省略模塊名稱. 因為模塊加載器可以將js文件名當做模塊名稱. 所以如果你有一個名叫my-books.js的文件, 你的模塊可以只通過模塊加載器來加載, 你可以像這樣定義你的模塊.
define([ "dependency1", "dependency2" ], function(dependency1, dependency2) { var Books = {}; Books.MaintainableJs = { author: "Nicholas C. Zakas" } return Books; })
想要使用AMD模塊, 你需要使用一個與之兼容的模塊加載器. Dojo的標準模塊加載器支持AMD模塊的加載, 因此你可以向下面這樣來加載"my-books" 模塊.
// 使用Dojo加載AMD模塊 var books = dojo.require("my-books"); console.log(books.MaintainableJs.author);
Dojo同樣將自己的也封裝為AMD模塊, 叫做"dojo", 因此它也可以被其他AMD模塊加載.
另一個模塊加載器是Require.js. RequireJS添加了另一個全局函數require(), 專門用來加載指定的依賴和執行回調函數, 比如:
// 使用RequireJS加載AMD模塊 require(["my-books"], function(books) { console.log(books.MaintainableJs.author); })
調用require()時首先會立即加載依賴, 這些依賴都加載完成后會立即執行回調函數.
RequireJS模塊加載器包含很多內置邏輯來讓模塊的加載更加方便, 包括名字到目錄的對應表以及多語種選項.
6.4 零全局變量
你的Js代碼注入到頁面時可以做到不用創建全局變量的. 這種方法應用場景不多, 因此只有在某些特殊場景下才會有用. 最常見的情形就是一段不會被其他腳本訪問到的完全獨立的腳本. 之所以存在這種情形, 是因為所有所需要的腳本都會合并到一個文件, 或者因為這段非常短小且不提供任何借口的代碼會被插入至一個頁面中. 最常見的用法是創建一個書簽.
書簽是獨立的, 它們并不知曉頁面中包含什么且不需要頁面知道它的存在. 最終我們需要一段"零全局變量"的腳本嵌入到頁面中, 實現方法就是使用一個立即執行的函數調用并將所有的腳本放置其中, 比如:
(function(win) { var doc = win.document; // 這里定義其他的變量 // 其他相關代碼 })(window);
這段立即執行的代碼傳入了window對象, 因此這段代碼不需要直接引用任何全局變量. 在這函數內部, 變量doc是指向document對象的引用, 只要函數代碼中沒有直接修改window或doc且所有變量都是用var關鍵字來定義, 這頓啊腳本則可以注入到頁面中而不會產生任何全局變量. 之后你可以通過將函數設置為嚴格模式(strict mode)來避免創建全局變量.
(function(win) { "use strict" var doc = win.document; // 這里定義其他的變量 // 其他相關代碼 })(window);
這個函數包裝器(function wrapper) 可以用于任何不需要創建全局對象的場景. 正如上文提到的, 這種模式的使用場景有限. 只要你的代碼需要被其他的代碼所依賴, 就不能使用這種零全局變量的方式. 如果你的代碼需要在運行時被不斷擴展或修改也不能使用零全局變量的方式. 但是, 如果你的腳本非常短, 且不需要和其他代碼產生交互, 可以考慮使用零全局變量的方式來實現代碼.
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/89216.html
摘要:由于第四章太稀松平常了于是就直接跳到第五章了這里我就草草的說一下第四章的幾個點吧在嚴格模式的應用下不推薦將用在全局作用域中相等推薦盡量使用和守則如果是在沒有別的方法來完成當前任務這時可以使用原始包裝類型不推薦創建類型時用等創建類型從這一章節 由于第四章太稀松平常了, 于是就直接跳到第五章了.這里我就草草的說一下第四章的幾個點吧 在嚴格模式的應用下 不推薦將use strict;用在全...
摘要:程序是寫給人讀的只是偶爾讓計算機執行一下當你剛剛組建一個團隊時團隊中的每個人都各自有一套編程習慣畢竟每個成員都有著不同的背景有些人可能來自某個皮包公司身兼數職在公司里面什么事都做還有些人會來自不同的團隊對某種特定的做事風格情有獨鐘或恨之入骨 程序是寫給人讀的,只是偶爾讓計算機執行一下. Donald Knuth 當你剛剛組建一個團隊時,團隊中的每個人都各自有一套編程習慣.畢竟,...
摘要:中常常會看到這種代碼變量與的比較這種用法很有問題用來判斷變量是否被賦予了一個合理的值比如不好的寫法執行一些邏輯這段代碼中方法顯然是希望是一個數組因為我們看到的擁有和這段代碼的意圖非常明顯如果參數不是一個數組則停止接下來的操作這種寫法的問題在 js中, 常常會看到這種代碼: 變量與null的比較(這種用法很有問題), 用來判斷變量是否被賦予了一個合理的值. 比如: const Contr...
摘要:所有的塊語句都應當使用花括號包括花括號的對齊方式第一種風格第二種風格塊語句間隔第一種在語句名圓括號和左花括號之間沒有空格間隔第二種在左圓括號之前和右圓括號之后各添加一個空格第三種在左圓括號后和右圓括號前各添加一個空格我個人喜歡在右括號之后添 所有的塊語句都應當使用花括號, 包括: if for while do...while... try...catch...finally 3....
摘要:在所有應用中事件處理都是非常重要的所有的均通過事件綁定到上所以大多數前端工程師需要花費很多時間來編寫和修改事件處理程序遺憾的是在誕生之初這部分內容并未受太多重視甚至當開發者們開始熱衷于將傳統的軟件架構概念融入到里時事件綁定仍然沒有收到多大重 在所有JavaScript應用中事件處理都是非常重要的. 所有的JavaScript均通過事件綁定到UI上, 所以大多數前端工程師需要花費很多時間...
閱讀 3561·2021-09-22 10:52
閱讀 1588·2021-09-09 09:34
閱讀 1990·2021-09-09 09:33
閱讀 758·2019-08-30 15:54
閱讀 2598·2019-08-29 11:15
閱讀 713·2019-08-26 13:37
閱讀 1667·2019-08-26 12:11
閱讀 2975·2019-08-26 12:00