成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

<<編寫可維護(hù)的javascript>> 筆記6(避免使用全局變量)

MoAir / 1380人閱讀

摘要:執(zhí)行環(huán)境在很多方面都有其獨特之處全局變量和函數(shù)便是其中之一事實上的初始執(zhí)行環(huán)境是由多種多樣的全局變量所定義的這寫全局變量在腳本環(huán)境創(chuàng)建之初就已經(jīng)存在了我們說這些都是掛載在全局對象上的全局對象是一個神秘的對象它表示了腳本最外層上下文在瀏覽器中

JavaScript執(zhí)行環(huán)境在很多方面都有其獨特之處. 全局變量和函數(shù)便是其中之一. 事實上, js的初始執(zhí)行環(huán)境是由多種多樣的全局變量所定義的, 這寫全局變量在腳本環(huán)境創(chuàng)建之初就已經(jīng)存在了. 我們說這些都是掛載在"全局對象"(global object) 上的, "全局對象"是一個神秘的對象, 它表示了腳本最外層上下文.

在瀏覽器中, window對象往往重載并等同于全局對象, 因此任何的全局作用域中聲明的變量和函數(shù)都是window對象的屬性:

var color = "red";

function sayColor() {
    alert(color);
}

console.log(window.color); // "red"
console.log(typeof window.sayColor); // "function"

這段代碼中定義了全局變量color和全局函數(shù)sayColor(), 兩者都是window對象的屬性, 盡管我們并沒有顯示的執(zhí)行給window對象掛載屬性的操作.

6.1 全局變量帶來的問題

創(chuàng)建全局變量被認(rèn)為是最糟糕的實踐, 尤其是在團(tuán)隊開發(fā)的大背景下更是問題多多. 隨著代碼量的增長, 全局變量就會導(dǎo)致一些非常重要的可維護(hù)性難題. 全局變量越多, 引入錯誤的概率將會因此變得越來越高.

6.1.1 命名沖突
腳本中的全局變臉和全局函數(shù)越來越多時, 發(fā)生命名沖突的概率也隨之增高, 很可能無意間就使用了一個已經(jīng)聲明了的變量. 所有的變量都被定義為全局變量, 這樣的代碼才是最容易維護(hù)的.

6.1.2 代碼的脆弱性
一個依賴于全局變量的函數(shù)即是深耦合于上下文環(huán)境之中. 如果環(huán)境發(fā)生改變, 函數(shù)很可能就失效了. 在上一個例子中, 如果全局變臉color不再存在, sayColor()的方法將會報錯. 這意味著任何對全局環(huán)境的修改都可能在成某處代碼出錯. 同樣, 任何函數(shù)也會不經(jīng)意間修改全局變量. 導(dǎo)致對全局變量值的依賴變得不穩(wěn)定. 在上個例子中, 如果color被當(dāng)做參數(shù)傳入, 代碼可維護(hù)性會變得更佳.

var color = "red";

function sayColor() {
    alert(color); // 不好的做法用全局變量
}

function sayColor(color) {
    alert(color); // 好的做法
}

修改后這個函數(shù)不再依賴于全局變量, 因此任何對全局環(huán)境的修改都不會影響到它. 由于color是一個參數(shù). 唯一值得注意的是傳入函數(shù)的值得合法性. 其他修改都不會對這個函數(shù)完成它本身的任務(wù)有任何影響.

當(dāng)定義函數(shù)的時候, 最好盡可能多地將數(shù)據(jù)至于局部作用域內(nèi). 在函數(shù)內(nèi)定義的任何都應(yīng)當(dāng)采用這種寫法. 任何來自函數(shù)外部的數(shù)據(jù)都應(yīng)當(dāng)以參數(shù)形式傳進(jìn)來. 這樣做可以將函數(shù)和其外部環(huán)境隔離開來. 并且你的修改不會對程序其他部分造成影響.

6.1.3 難以測試
嘗試著在一個大型web應(yīng)用中實施一些單元測試. 在我即將完成核心框架的搭建時, 我加入了一個團(tuán)隊, 此后我努力讓我的代碼變得易于理解以便后續(xù)為其執(zhí)行測試. 我非常吃驚的發(fā)現(xiàn), 執(zhí)行測試編程一項積極困難的工序, 因為整個框架要依賴于一些全局變量才會正常工作.

任何依賴全局變量才能正常工作的函數(shù), 只有為其重新創(chuàng)建完整的全局環(huán)境才能正確的測試它. 事實上, 這意味著你除了要管理全局環(huán)境的修改, 你還要在兩個全局環(huán)境中管理它們: 生產(chǎn)環(huán)境和測試環(huán)境. 保持兩者的同步是很消耗成本的, 很快你就會發(fā)現(xiàn)(代碼)可維護(hù)性的噩夢才剛剛開始. 越到后來越難于理清頭緒.

確保你的函數(shù)不會對全局變量有依賴, 這將增強你的代碼的可測試性(testability). 當(dāng)然你的函數(shù)可能會依賴原生的js全局對象, 比如Date、Array等. 他們是全局環(huán)境的一部分, 適合js引擎相關(guān)的, 你的函數(shù)總是會用到這些全局對象. 總之, 為了保證你的代碼具有最佳的可測試性, 不要讓函數(shù)對全局變量有依賴.

6.2 意外的全局變量
js中有不少陷阱, 其中有一個就是不小心會創(chuàng)建全局變量. 當(dāng)你給一個未被var的語句證明過的變量賦值時, js就會自動創(chuàng)建愛你一個全局變量, 比如:

function doSomeThing() {
    var count = 10;
        title = "編寫可維護(hù)的js"; // 不好的寫法: 創(chuàng)建了全局變量
}

6.3 單全局變量方式
依賴盡可能少的全局變量, 即只創(chuàng)建一個全局變量.
單全局變量模式已經(jīng)在各種流行的類庫中廣泛使用了.

jQuery定義了兩個全局對象, $和jQuery. 只有在$被其他的類庫使用了的情況下, 為了避免沖突, 應(yīng)當(dāng)使用jQuery.

vue實例化的對象 自己定義的vm等

"單全局變量"的意思是所創(chuàng)建的這個唯一的全局對象名是獨一無二的(不會和內(nèi)置API沖突), 并將你的所有的功能代碼都掛載到這個全局對象上. 因此每個可能的全局變量都成為你唯一的全局對象的屬性, 從而不會創(chuàng)建多個全局變量. 比如, 假設(shè)我想讓一個對象表示本書的一章, 代碼看起來會像下面這樣.

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("第三章");

這段代碼創(chuàng)建了4個全局對象: Book、Chapter1、Chapter2、Chapter3. 單全局變量模式則只會創(chuàng)建一個全局對象并將這些對象都賦值為它的屬性.

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, 其他任何信息都掛載到這個對象上. 因為團(tuán)隊中每個人度知道這個全局對下是哪個, 因此很容易做到繼續(xù)為它添加屬性以避免全局污染.

6.3.1 命名空間
即使你的代碼只有一個全局對象, 也可能污染全局. 大多數(shù)使用單全局變量模式的項目同樣包含"命名空間"的概念. 命名空間是簡單的通過全局對象的單一屬性表示的功能性分組. 比如, YUI就是已超命名空間的思路來管理代碼的. Y.DOM下的所有的方法都是和DOM操作相關(guān)的, Y.Event下所有的方法是和時間相關(guān)的.
將功能按照命名空間進(jìn)行分組, 可以讓你的全局對象變得井然有序, 同事可以讓團(tuán)隊成員能夠知曉性性能應(yīng)該屬于哪個部分, 或者知道去哪里查找已有的功能. 當(dāng)作者在YaHoo!工作的時候, 就有一個不成文的約定, 即每個站點杜江自己的命名空間掛載至Y對象上, 因此"My YaHoo!" 使用Y.My, 郵箱使用Y.Mail, 等等. 這樣團(tuán)隊成員則可以放心大膽的使用其他人的代碼, 而不必?fù)?dān)心沖突.

在js中你可以使用對象來輕而易舉的創(chuàng)建你自己的命名空間, 比如:

var ZakasBooks = {};

// 表示這本書的命名空間
ZakasBooks.MaintainableJs = {};

// 表示另一本書的命名空間
ZakasBooks.HighPerformanceJs = {};

一個常見的約定在每一個文件中都通過創(chuàng)建新的全局對象來聲明自己的命名空間. 在這種情況下, 上面的這個例子給出的方法是夠用的.

同樣有另外一個常見, 每個文件都是需要給一個命名空間掛載東西. 這種情況下, 你需要首先保證這個命名空間是已經(jīng)存在的. 這是全局對象非破壞性的處理命名空間的方式則變得非常有用, 成成這項操作的基本模式是像下面這樣的.

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) 創(chuàng)建這個命名空間, 基本一用法如下.

/*
 * 同時創(chuàng)建YourGlobal.Books和YourGlobal.Books.MaintainableJs
 * 因為之前沒有創(chuàng)建過它們, 因此每個都是全新創(chuàng)建的
 */
YourGlobal.namespace("book.MaintainableJs");

// 現(xiàn)在你可以使用這個命名空間
YourGlobal.Books,MaintainableJs.author = "Nicholas C. Zakas";

/*
 * 不會操作YourGlobal.Books本身, 同時會給它添加HighPerformanceJs
 * 它會保持YourGlobal.Books.MaintainableJs原封不動
 */
YourGlobal.namespace("Books.HighPerformanceJs");

// 仍然是合法的引用
console.log(YourGlobal.Books.MaintainableJs.author);

// 你同樣可以在方法調(diào)用之后立即給它添加新屬性
YourGlobal.namespace("Books").ANewBook = {};

基于你的單全局對象使用namespace()方法可以讓開發(fā)者放心地認(rèn)為命名空間總是存在的. 這樣, 每個文件度可以首先調(diào)用namespace()來聲明開發(fā)者將要使用的命名空間, 這樣做不會對已有的命名空間造成任何破壞. 這個方法可以讓開發(fā)者解放出來, 在使用命名空間之前不必再去判斷它是否存在.

由于你的代碼不是獨立存在的, 因此要圍繞命名空間定義一些約定. 是否應(yīng)該以首字母大寫的形式來定義命名空間, 就像YUI? 還是都用小寫字母形式來定義命名空間, 就像Dojo? 這個是個人喜好問題, 但是首先定義這些約定可以讓后續(xù)團(tuán)隊成員在使用但全局變量時更加高效.

6.3.2 模塊
另一種基于單全局變量的擴(kuò)充方法是使用模塊(modules). 模塊是一種通用的功能片段, 它并沒有創(chuàng)建新的全局變量或者命名空間. 相反, 所有的這些代碼都存放于一個表示執(zhí)行一個任務(wù)或發(fā)布一個接口的但函數(shù)中. 可以用一個名稱來表示這個模塊, 同樣這個模塊可以依賴其它模塊.

js本身不包含模塊概念, 自然也沒有模塊語法(es6支持 import export), 單的確有一些通用的模式來創(chuàng)建模塊. 兩種最流行的類型是"YUI模塊"模式和"異步模塊定義"(Asynchronous Module Definition, 簡稱AMD)模式.

YUI模塊
從字面含義理解, YUI模塊就是使用YUI Js類庫來創(chuàng)建新模塊的一種模式. YUI3中包含了模塊的概念, 寫法如下.

YUI.add("module-name", function(Y) {

    // 模塊正文
    
}, "version", { requires: [ "dependecy1", "dependency2" ] });

我們通過調(diào)用YUI.add()并給它傳入模塊名字、待執(zhí)行的函數(shù)(被稱作工廠方法)和可選的依賴列表來添加YUI模塊. "模塊正文"處則是你寫所有的模塊代碼的地方. 參數(shù)Y是YUI的一個實例, 這個實例包含所有以來的模塊提供的內(nèi)容. YUI中約定在每一個模塊內(nèi)使用命名空間的方式來管理模塊代碼, 比如:

YUI.add("my-books", function(Y) {

    // 添加一個命名空間
    Y.namespace("Books.MaintainableJs");
    
    Y.Books.MaintainableJs.author = "Nicholas C. Zakas";
}, "1.0.0", { requires: [ "dependecy1", "dependency2" ] });

同樣, 依賴也是以Y對象命名空間的形式傳入進(jìn)來. 因此YUI實際上是將命名空間和模塊的概念合并在了一起, 總體上提供一種靈活的解決方案.

通過調(diào)用YUI().use()函數(shù)并傳入想加載的模塊名稱來使用你的模塊.

YUI.use("my-books", "another-modult", function(Y) {
    
    console.log(Y.Books.MaintainableJs.author);
    
})

這段代碼以加載名叫"my-books"和"another-module"的兩個模塊開始, YUI會確保這些模塊的依賴都會完全加載完成, 然后執(zhí)行模塊的正文代碼, 最后才會執(zhí)行行傳入YUI().use()的回調(diào)函數(shù). 回調(diào)函數(shù)會帶回Y對象, Y對象里包含了加載模塊對它做的修改, 這時你的應(yīng)用代碼就可以放心的執(zhí)行了.

"異步模塊定義"(AMD)
AMD模塊和YUI模塊有諸多相似之處. 你指定模塊名稱、依賴和一個工廠方法, 依賴加載完成后執(zhí)行這個工廠方法. 這些內(nèi)容全部作為參數(shù)傳入一個全局函數(shù)define()中, 其中第一個參數(shù)是模塊名稱, 然后是依賴列表, 最后是工廠方法. AMD模塊和YUI模塊最大的不同在于, (AMD中) 每一個依賴都會對應(yīng)到獨立的參數(shù)傳入工廠方法里, 比如:

define("module-name", [ "dependency1", "dependency2" ], function(dependency1, dependency2) {
    
    //模塊正文
})

因此, 每個被命名的依賴最后都會創(chuàng)建一個對象, 這個對象會被帶入工程方法中. AMD以這種方式來嘗試避免命名沖突, 因為直接在模塊中使用命名空間有可能發(fā)生命名沖突. 和YUI模塊中創(chuàng)建新的命名空間的方法不同, AMD模塊則期望從工廠方法中返回它們的公有接口, 比如:

define("my-books", [ "dependency1", "dependency2" ], function(dependency1, dependency2) {
    
    var Books = {};
    Books.MaintainableJs = {
        author: "Nicholas C. Zakas"
    }
    
    return Books;
})

AMD模塊同樣可以是匿名的, 完全省略模塊名稱. 因為模塊加載器可以將js文件名當(dāng)做模塊名稱. 所以如果你有一個名叫my-books.js的文件, 你的模塊可以只通過模塊加載器來加載, 你可以像這樣定義你的模塊.

define([ "dependency1", "dependency2" ], function(dependency1, dependency2) {
    
    var Books = {};
    Books.MaintainableJs = {
        author: "Nicholas C. Zakas"
    }
    
    return Books;
})

想要使用AMD模塊, 你需要使用一個與之兼容的模塊加載器. Dojo的標(biāo)準(zhǔn)模塊加載器支持AMD模塊的加載, 因此你可以向下面這樣來加載"my-books" 模塊.

// 使用Dojo加載AMD模塊
var books = dojo.require("my-books");

console.log(books.MaintainableJs.author);

Dojo同樣將自己的也封裝為AMD模塊, 叫做"dojo", 因此它也可以被其他AMD模塊加載.

另一個模塊加載器是Require.js. RequireJS添加了另一個全局函數(shù)require(), 專門用來加載指定的依賴和執(zhí)行回調(diào)函數(shù), 比如:

// 使用RequireJS加載AMD模塊
require(["my-books"], function(books) {
    
    console.log(books.MaintainableJs.author);
    
})

調(diào)用require()時首先會立即加載依賴, 這些依賴都加載完成后會立即執(zhí)行回調(diào)函數(shù).

RequireJS模塊加載器包含很多內(nèi)置邏輯來讓模塊的加載更加方便, 包括名字到目錄的對應(yīng)表以及多語種選項.

6.4 零全局變量
你的Js代碼注入到頁面時可以做到不用創(chuàng)建全局變量的. 這種方法應(yīng)用場景不多, 因此只有在某些特殊場景下才會有用. 最常見的情形就是一段不會被其他腳本訪問到的完全獨立的腳本. 之所以存在這種情形, 是因為所有所需要的腳本都會合并到一個文件, 或者因為這段非常短小且不提供任何借口的代碼會被插入至一個頁面中. 最常見的用法是創(chuàng)建一個書簽.

書簽是獨立的, 它們并不知曉頁面中包含什么且不需要頁面知道它的存在. 最終我們需要一段"零全局變量"的腳本嵌入到頁面中, 實現(xiàn)方法就是使用一個立即執(zhí)行的函數(shù)調(diào)用并將所有的腳本放置其中, 比如:

(function(win) {

    var doc = win.document;
    
    // 這里定義其他的變量
    
    // 其他相關(guān)代碼
})(window);

這段立即執(zhí)行的代碼傳入了window對象, 因此這段代碼不需要直接引用任何全局變量. 在這函數(shù)內(nèi)部, 變量doc是指向document對象的引用, 只要函數(shù)代碼中沒有直接修改window或doc且所有變量都是用var關(guān)鍵字來定義, 這頓啊腳本則可以注入到頁面中而不會產(chǎn)生任何全局變量. 之后你可以通過將函數(shù)設(shè)置為嚴(yán)格模式(strict mode)來避免創(chuàng)建全局變量.

(function(win) {
    
    "use strict"
    
    var doc = win.document;
    
    // 這里定義其他的變量
    
    // 其他相關(guān)代碼
})(window);

這個函數(shù)包裝器(function wrapper) 可以用于任何不需要創(chuàng)建全局對象的場景. 正如上文提到的, 這種模式的使用場景有限. 只要你的代碼需要被其他的代碼所依賴, 就不能使用這種零全局變量的方式. 如果你的代碼需要在運行時被不斷擴(kuò)展或修改也不能使用零全局變量的方式. 但是, 如果你的腳本非常短, 且不需要和其他代碼產(chǎn)生交互, 可以考慮使用零全局變量的方式來實現(xiàn)代碼.

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/89216.html

相關(guān)文章

  • &lt;&lt;編寫維護(hù)javascript&gt;&gt; 筆記5(UI層松耦合)

    摘要:由于第四章太稀松平常了于是就直接跳到第五章了這里我就草草的說一下第四章的幾個點吧在嚴(yán)格模式的應(yīng)用下不推薦將用在全局作用域中相等推薦盡量使用和守則如果是在沒有別的方法來完成當(dāng)前任務(wù)這時可以使用原始包裝類型不推薦創(chuàng)建類型時用等創(chuàng)建類型從這一章節(jié) 由于第四章太稀松平常了, 于是就直接跳到第五章了.這里我就草草的說一下第四章的幾個點吧 在嚴(yán)格模式的應(yīng)用下 不推薦將use strict;用在全...

    saucxs 評論0 收藏0
  • &lt;&lt;編寫維護(hù)javascript&gt;&gt; 筆記1(基本格式化)

    摘要:程序是寫給人讀的只是偶爾讓計算機執(zhí)行一下當(dāng)你剛剛組建一個團(tuán)隊時團(tuán)隊中的每個人都各自有一套編程習(xí)慣畢竟每個成員都有著不同的背景有些人可能來自某個皮包公司身兼數(shù)職在公司里面什么事都做還有些人會來自不同的團(tuán)隊對某種特定的做事風(fēng)格情有獨鐘或恨之入骨 程序是寫給人讀的,只是偶爾讓計算機執(zhí)行一下. Donald Knuth 當(dāng)你剛剛組建一個團(tuán)隊時,團(tuán)隊中的每個人都各自有一套編程習(xí)慣.畢竟,...

    wfc_666 評論0 收藏0
  • &lt;&lt;編寫維護(hù)javascript&gt;&gt; 筆記8(避免'空比較&#

    摘要:中常常會看到這種代碼變量與的比較這種用法很有問題用來判斷變量是否被賦予了一個合理的值比如不好的寫法執(zhí)行一些邏輯這段代碼中方法顯然是希望是一個數(shù)組因為我們看到的擁有和這段代碼的意圖非常明顯如果參數(shù)不是一個數(shù)組則停止接下來的操作這種寫法的問題在 js中, 常常會看到這種代碼: 變量與null的比較(這種用法很有問題), 用來判斷變量是否被賦予了一個合理的值. 比如: const Contr...

    young.li 評論0 收藏0
  • &lt;&lt;編寫維護(hù)javascript&gt;&gt; 筆記3(語句和表達(dá)式)

    摘要:所有的塊語句都應(yīng)當(dāng)使用花括號包括花括號的對齊方式第一種風(fēng)格第二種風(fēng)格塊語句間隔第一種在語句名圓括號和左花括號之間沒有空格間隔第二種在左圓括號之前和右圓括號之后各添加一個空格第三種在左圓括號后和右圓括號前各添加一個空格我個人喜歡在右括號之后添 所有的塊語句都應(yīng)當(dāng)使用花括號, 包括: if for while do...while... try...catch...finally 3....

    OBKoro1 評論0 收藏0
  • &lt;&lt;編寫維護(hù)javascript&gt;&gt; 筆記7(事件處理)

    摘要:在所有應(yīng)用中事件處理都是非常重要的所有的均通過事件綁定到上所以大多數(shù)前端工程師需要花費很多時間來編寫和修改事件處理程序遺憾的是在誕生之初這部分內(nèi)容并未受太多重視甚至當(dāng)開發(fā)者們開始熱衷于將傳統(tǒng)的軟件架構(gòu)概念融入到里時事件綁定仍然沒有收到多大重 在所有JavaScript應(yīng)用中事件處理都是非常重要的. 所有的JavaScript均通過事件綁定到UI上, 所以大多數(shù)前端工程師需要花費很多時間...

    microelec 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<