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

資訊專欄INFORMATION COLUMN

前端基本功-常見概念(三)

happen / 1073人閱讀

摘要:前端基本功常見概念一點這里前端基本功常見概念二點這里前端基本功常見概念三點這里超文本標(biāo)記語言,顯示信息,不區(qū)分大小寫升級版的,區(qū)分大小寫可擴展標(biāo)記語言被用來傳輸和存儲數(shù)據(jù)規(guī)范采用異步方式加載模塊,模塊的加載不影響它后面語句的運行。

前端基本功-常見概念(一) 點這里
前端基本功-常見概念(二) 點這里
前端基本功-常見概念(三) 點這里

1.HTML / XML / XHTML

html:超文本標(biāo)記語言,顯示信息,不區(qū)分大小寫

xhtml:升級版的html,區(qū)分大小寫

xml:可擴展標(biāo)記語言被用來傳輸和存儲數(shù)據(jù)

2.AMD/CMD/CommonJs/ES6 Module

AMD:AMD規(guī)范采用異步方式加載模塊,模塊的加載不影響它后面語句的運行。所有依賴這個模塊的語句,都定義在一個回調(diào)函數(shù)中,等到加載完成之后,這個回調(diào)函數(shù)才會運行。

AMD是requirejs 在推廣過程中對模塊定義的規(guī)范化產(chǎn)出,提前執(zhí)行,推崇依賴前置。用define()定義模塊,用require()加載模塊,require.config()指定引用路徑等

首先我們需要引入require.js文件和一個入口文件main.js。main.js中配置require.config()并規(guī)定項目中用到的基礎(chǔ)模塊。

    /** 網(wǎng)頁中引入require.js及main.js **/
    
    
    
    /** main.js 入口文件/主模塊 **/
    // 首先用config()指定各模塊路徑和引用名
    require.config({
      baseUrl: "js/lib",
      paths: {
        "jquery": "jquery.min",  //實際路徑為js/lib/jquery.min.js
        "underscore": "underscore.min",
      }
    });
    // 執(zhí)行基本操作
    require(["jquery","underscore"],function($,_){
      // some code here
    });

引用模塊的時候,我們將模塊名放在[]中作為reqiure()的第一參數(shù);如果我們定義的模塊本身也依賴其他模塊,那就需要將它們放在[]中作為define()的第一參數(shù)。

    // 定義math.js模塊
    define(function () {
        var basicNum = 0;
        var add = function (x, y) {
            return x + y;
        };
        return {
            add: add,
            basicNum :basicNum
        };
    });
    // 定義一個依賴underscore.js的模塊
    define(["underscore"],function(_){
      var classify = function(list){
        _.countBy(list,function(num){
          return num > 30 ? "old" : "young";
        })
      };
      return {
        classify :classify
      };
    })
        
    // 引用模塊,將模塊放在[]內(nèi)
    require(["jquery", "math"],function($, math){
      var sum = math.add(10,20);
      $("#sum").html(sum);
    });

CMD:seajs 在推廣過程中對模塊定義的規(guī)范化產(chǎn)出,延遲執(zhí)行,推崇依賴就近

require.js在申明依賴的模塊時會在第一之間加載并執(zhí)行模塊內(nèi)的代碼:

    define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) { 
        // 等于在最前面聲明并初始化了要用到的所有模塊
        if (false) {
          // 即便沒用到某個模塊 b,但 b 還是提前執(zhí)行了
          b.foo()
        } 
    });

CMD是另一種js模塊化方案,它與AMD很類似,不同點在于:AMD 推崇依賴前置、提前執(zhí)行,CMD推崇依賴就近、延遲執(zhí)行。此規(guī)范其實是在sea.js推廣過程中產(chǎn)生的。

    /** CMD寫法 **/
    define(function(require, exports, module) {
        var a = require("./a"); //在需要時申明
        a.doSomething();
        if (false) {
            var b = require("./b");
            b.doSomething();
        }
    });
    

    /** sea.js **/
    // 定義模塊 math.js
    define(function(require, exports, module) {
        var $ = require("jquery.js");
        var add = function(a,b){
            return a+b;
        }
        exports.add = add;
    });
    // 加載模塊
    seajs.use(["math.js"], function(math){
        var sum = math.add(1+2);
    });

CommonJs:Node.js是commonJS規(guī)范的主要實踐者,它有四個重要的環(huán)境變量為模塊化的實現(xiàn)提供支持:module、exports、require、global。實際使用時,用module.exports定義當(dāng)前模塊對外輸出的接口(不推薦直接用exports),用require加載模塊。

    // 定義模塊math.js
    var basicNum = 0;
    function add(a, b) {
      return a + b;
    }
    module.exports = { //在這里寫上需要向外暴露的函數(shù)、變量
      add: add,
      basicNum: basicNum
    }
    
    // 引用自定義的模塊時,參數(shù)包含路徑,可省略.js
    var math = require("./math");
    math.add(2, 5);
    
    // 引用核心模塊時,不需要帶路徑
    var http = require("http");
    http.createService(...).listen(3000);

commonJS用同步的方式加載模塊。在服務(wù)端,模塊文件都存在本地磁盤,讀取非??欤赃@樣做不會有問題。但是在瀏覽器端,限于網(wǎng)絡(luò)原因,更合理的方案是使用異步加載。

ES6 Module:ES6 在語言標(biāo)準(zhǔn)的層面上,實現(xiàn)了模塊功能,而且實現(xiàn)得相當(dāng)簡單,旨在成為瀏覽器和服務(wù)器通用的模塊解決方案。其模塊功能主要由兩個命令構(gòu)成:export和import。export命令用于規(guī)定模塊的對外接口,import命令用于輸入其他模塊提供的功能。

/** 定義模塊 math.js **/
var basicNum = 0;
var add = function (a, b) {
    return a + b;
};
export { basicNum, add };

/** 引用模塊 **/
import { basicNum, add } from "./math";
function test(ele) {
    ele.textContent = add(99 + basicNum);
}

如上例所示,使用import命令的時候,用戶需要知道所要加載的變量名或函數(shù)名。其實ES6還提供了export default命令,為模塊指定默認輸出,對應(yīng)的import語句不需要使用大括號。這也更趨近于ADM的引用寫法。

/** export default **/
//定義輸出
export default { basicNum, add };
//引入
import math from "./math";
function test(ele) {
    ele.textContent = math.add(99 + math.basicNum);
}

ES6的模塊不是對象,import命令會被 JavaScript 引擎靜態(tài)分析,在編譯時就引入模塊代碼,而不是在代碼運行時加載,所以無法實現(xiàn)條件加載。也正因為這個,使得靜態(tài)分析成為可能。

ES6 模塊與 CommonJS 模塊的差異

CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。

CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內(nèi)部的變化就影響不到這個值。

ES6 模塊的運行機制與 CommonJS 不一樣。JS 引擎對腳本靜態(tài)分析的時候,遇到模塊加載命令import,就會生成一個只讀引用。等到腳本真正執(zhí)行時,再根據(jù)這個只讀引用,到被加載的那個模塊里面去取值。換句話說,ES6 的import有點像 Unix 系統(tǒng)的“符號連接”,原始值變了,import加載的值也會跟著變。因此,ES6 模塊是動態(tài)引用,并且不會緩存值,模塊里面的變量綁定其所在的模塊。

CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。

運行時加載: CommonJS 模塊就是對象;即在輸入時是先加載整個模塊,生成一個對象,然后再從這個對象上面讀取方法,這種加載稱為“運行時加載”。

- 編譯時加載: ES6 模塊不是對象,而是通過 export 命令顯式指定輸出的代碼,import時采用靜態(tài)命令的形式。即在import時可以指定加載某個輸出值,而不是加載整個模塊,這種加載稱為“編譯時加載”。


CommonJS 加載的是一個對象(即module.exports屬性),該對象只有在腳本運行完才會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態(tài)定義,在代碼靜態(tài)解析階段就會生成。


本節(jié)參考文章:前端模塊化:CommonJS,AMD,CMD,ES6

3.ES5的繼承/ES6的繼承

ES5的繼承時通過prototype或構(gòu)造函數(shù)機制來實現(xiàn)。ES5的繼承實質(zhì)上是先創(chuàng)建子類的實例對象,然后再將父類的方法添加到this上(Parent.apply(this))。

ES6的繼承機制完全不同,實質(zhì)上是先創(chuàng)建父類的實例對象this(所以必須先調(diào)用父類的super()方法),然后再用子類的構(gòu)造函數(shù)修改this。

具體的:ES6通過class關(guān)鍵字定義類,里面有構(gòu)造方法,類之間通過extends關(guān)鍵字實現(xiàn)繼承。子類必須在constructor方法中調(diào)用super方法,否則新建實例報錯。因為子類沒有自己的this對象,而是繼承了父類的this對象,然后對其進行加工。如果不調(diào)用super方法,子類得不到this對象。

ps:super關(guān)鍵字指代父類的實例,即父類的this對象。在子類構(gòu)造函數(shù)中,調(diào)用super后,才可使用this關(guān)鍵字,否則報錯。


區(qū)別:(以SubClass,SuperClass,instance為例)

ES5中繼承的實質(zhì)是:(那種經(jīng)典寄生組合式繼承法)通過prototype或構(gòu)造函數(shù)機制來實現(xiàn),先創(chuàng)建子類的實例對象,然后再將父類的方法添加到this上(Parent.apply(this))。

先由子類(SubClass)構(gòu)造出實例對象this

然后在子類的構(gòu)造函數(shù)中,將父類(SuperClass)的屬性添加到this上,SuperClass.apply(this, arguments)

子類原型(SubClass.prototype)指向父類原型(SuperClass.prototype)

所以instance是子類(SubClass)構(gòu)造出的(所以沒有父類的[[Class]]關(guān)鍵標(biāo)志)

所以,instance有SubClass和SuperClass的所有實例屬性,以及可以通過原型鏈回溯,獲取SubClass和SuperClass原型上的方法

ES6中繼承的實質(zhì)是:先創(chuàng)建父類的實例對象this(所以必須先調(diào)用父類的super()方法),然后再用子類的構(gòu)造函數(shù)修改this

先由父類(SuperClass)構(gòu)造出實例對象this,這也是為什么必須先調(diào)用父類的super()方法(子類沒有自己的this對象,需先由父類構(gòu)造)

然后在子類的構(gòu)造函數(shù)中,修改this(進行加工),譬如讓它指向子類原型(SubClass.prototype),這一步很關(guān)鍵,否則無法找到子類原型(注,子類構(gòu)造中加工這一步的實際做法是推測出的,從最終效果來推測)

然后同樣,子類原型(SubClass.prototype)指向父類原型(SuperClass.prototype)

所以instance是父類(SuperClass)構(gòu)造出的(所以有著父類的[[Class]]關(guān)鍵標(biāo)志)

所以,instance有SubClass和SuperClass的所有實例屬性,以及可以通過原型鏈回溯,獲取SubClass和SuperClass原型上的方法

靜態(tài)方法繼承實質(zhì)上只需要更改下SubClass.__proto__到SuperClass即可

本節(jié)參考文章:鏈接

4.HTTP request報文/HTTP response報文
請求報文 響應(yīng)報文
請求行 請求頭 空行 請求體 狀態(tài)行 響應(yīng)頭 空行 響應(yīng)體

HTTP request報文結(jié)構(gòu)是怎樣的

首行是Request-Line包括:請求方法,請求URI,協(xié)議版本,CRLF
首行之后是若干行請求頭,包括general-header,request-header或者entity-header,每個一行以CRLF結(jié)束
請求頭和消息實體之間有一個CRLF分隔
根據(jù)實際請求需要可能包含一個消息實體 一個請求報文例子如下:

GET /Protocols/rfc2616/rfc2616-sec5.html HTTP/1.1
Host: www.w3.org
Connection: keep-alive
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Referer: https://www.google.com.hk/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: authorstyle=yes
If-None-Match: "2cc8-3e3073913b100"
If-Modified-Since: Wed, 01 Sep 2004 13:24:52 GMT

name=qiu&age=25

請求報文

HTTP response報文結(jié)構(gòu)是怎樣的

首行是狀態(tài)行包括:HTTP版本,狀態(tài)碼,狀態(tài)描述,后面跟一個CRLF
首行之后是若干行響應(yīng)頭,包括:通用頭部,響應(yīng)頭部,實體頭部
響應(yīng)頭部和響應(yīng)實體之間用一個CRLF空行分隔
最后是一個可能的消息實體 響應(yīng)報文例子如下:

HTTP/1.1 200 OK
Date: Tue, 08 Jul 2014 05:28:43 GMT
Server: Apache/2
Last-Modified: Wed, 01 Sep 2004 13:24:52 GMT
ETag: "40d7-3e3073913b100"
Accept-Ranges: bytes
Content-Length: 16599
Cache-Control: max-age=21600
Expires: Tue, 08 Jul 2014 11:28:43 GMT
P3P: policyref="http://www.w3.org/2001/05/P3P/p3p.xml"
Content-Type: text/html; charset=iso-8859-1

{"name": "qiu", "age": 25}

響應(yīng)報文

5.面向?qū)ο蟮墓S模式/構(gòu)造函數(shù)

工廠模式集中實例化了對象,避免實例化對象大量重復(fù)問題

//工廠模式
function createObject(a,b){
    var obj = new Object();    //集中實例化
    obj.a = a;
    obj.b = b;
    obj.c = function () {
        return this.a + this.b;
    };
    return obj;        //返回實例化對象
}
var box = createObject("abc",10);
var box1 = createObject("abcdef",20);
alert(box.c());        //返回abc10
alert(box1.c());       //返回abcdef20
//構(gòu)造函數(shù)
function Create(a,b) {
    this.a =a;
    this.b =b;
    this.c = function () {
        return this.a + this.b;
    };
}
var box = new Create("abc",10);
alert(box.run());    //返回abc10

構(gòu)造函數(shù)相比工廠模式:

沒有集中實例化

沒有返回對象實例

直接將屬性和方法賦值給this

解決了對象實例歸屬問題

構(gòu)造函數(shù)編寫規(guī)范:

構(gòu)造函數(shù)也是函數(shù),但是函數(shù)名的第一個字母大寫

必須使用new運算符 + 函數(shù)名(首字母大寫)例如:var box = new Create();

構(gòu)造函數(shù)和普通函數(shù)的區(qū)別:

普通函數(shù),首字母無需大寫

構(gòu)造函數(shù),用普通函數(shù)調(diào)用方式無效

查看歸屬問題,要創(chuàng)建兩個構(gòu)造函數(shù):

function Create(a,b) {
    this.a =a;
    this.b =b;
    this.c = function () {
        return this.a + this.b;
    };
}

function DeskTop(a,b) {
    this.a =a;
    this.b =b;
    this.c = function () {
        return this.a + this.b;
    };
}

var box = new Create("abc",10);
var box1 = new DeskTop("def",20);
alert(box instanceof Object);
//這里要注意:所有的構(gòu)造函數(shù)的對象都是Object.
alert(box instanceof Create);    //true
alert(box1 instanceof Create);   //false
alert(box1 instanceof DeskTop);    //true
6. new Promise / Promise.resolve()

Promise.resolve()可以生成一個成功的Promise

Promise.resolve()語法糖

例1:
Promise.resolve("成功")等同于new Promise(function(resolve){resolve("成功")})

例2:

var resolved = Promise.resolve("foo");

resolved.then((str) => 
    console.log(str);//foo
)

相當(dāng)于

var resolved = new Promise((resolve, reject) => {
   resolve("foo")
});

resolved.then((str) => 
    console.log(str);//foo
)

Promise.resolve方法有下面三種形式:

Promise.resolve(value);

Promise.resolve(promise);

Promise.resolve(theanable);

這三種形式都會產(chǎn)生一個新的Promise。其中:

第一種形式提供了自定義Promise的值的能力,它與Promise.reject(reason)對應(yīng)。兩者的不同,在于得到的Promise的狀態(tài)不同。

第二種形式,提供了創(chuàng)建一個Promise的副本的能力。

第三種形式,是將一個類似Promise的對象轉(zhuǎn)換成一個真正的Promise對象。它的一個重要作用是將一個其他實現(xiàn)的Promise對象封裝成一個當(dāng)前實現(xiàn)的Promise對象。例如你正在用bluebird,但是現(xiàn)在有一個Q的Promise,那么你可以通過此方法把Q的Promise變成一個bluebird的Promise。

實際上第二種形式可以歸在第三種形式中。

本節(jié)參考文章:ES6中的Promise.resolve()

推薦閱讀:性感的Promise...

7.偽類 / 偽元素 偽類
偽類 用于當(dāng)已有元素處于的某個狀態(tài)時,為其添加對應(yīng)的樣式,這個狀態(tài)是根據(jù)用戶行為而動態(tài)變化的。

當(dāng)用戶懸停在指定的元素時,我們可以通過 :hover 來描述這個元素的狀態(tài)。雖然它和普通的 CSS 類相似,可以為已有的元素添加樣式,但是它只有處于 DOM 樹無法描述的狀態(tài)下才能為元素添加樣式,所以將其稱為偽類。

偽元素
偽元素 用于創(chuàng)建一些不在文檔樹中的元素,并為其添加樣式。

我們可以通過 :before 來在一個元素前增加一些文本,并為這些文本添加樣式。雖然用戶可以看到這些文本,但是這些文本實際上不在文檔樹中。

本節(jié)參考文章:前端面試題-偽類和偽元素、總結(jié)偽類與偽元素

8.DOMContentLoaded / load

DOM文檔加載的步驟為:

解析HTML結(jié)構(gòu)。

DOM樹構(gòu)建完成。//DOMContentLoaded

加載外部腳本和樣式表文件。

解析并執(zhí)行腳本代碼。

加載圖片等外部文件。

頁面加載完畢。//load

觸發(fā)的時機不一樣,先觸發(fā)DOMContentLoaded事件,后觸發(fā)load事件。

原生js

// 不兼容老的瀏覽器,兼容寫法見[jQuery中ready與load事件](http://www.imooc.com/code/3253),或用jQuery
document.addEventListener("DOMContentLoaded", function() {
   // ...代碼...
}, false);

window.addEventListener("load", function() {
    // ...代碼...
}, false);

jQuery

// DOMContentLoaded
$(document).ready(function() {
    // ...代碼...
});

//load
$(document).load(function() {
    // ...代碼...
});

head 中資源的加載

head 中 js 資源加載都會停止后面 DOM 的構(gòu)建,但是不影響后面資源的下載。

css資源不會阻礙后面 DOM 的構(gòu)建,但是會阻礙頁面的首次渲染。

body 中資源的加載

body 中 js 資源加載都會停止后面 DOM 的構(gòu)建,但是不影響后面資源的下載。

css 資源不會阻礙后面 DOM 的構(gòu)建,但是會阻礙頁面的首次渲染。

DomContentLoaded 事件的觸發(fā)
上面只是講了 html 文檔的加載與渲染,并沒有講 DOMContentLoaded 事件的觸發(fā)時機。直截了當(dāng)?shù)亟Y(jié)論是,DOMContentLoaded 事件在 html文檔加載完畢,并且 html 所引用的內(nèi)聯(lián) js、以及外鏈 js 的同步代碼都執(zhí)行完畢后觸發(fā)。
大家可以自己寫一下測試代碼,分別引用內(nèi)聯(lián) js 和外鏈 js 進行測試。

load 事件的觸發(fā)
當(dāng)頁面 DOM 結(jié)構(gòu)中的 js、css、圖片,以及 js 異步加載的 js、css 、圖片都加載完成之后,才會觸發(fā) load 事件。

注意:
頁面中引用的js 代碼如果有異步加載的 js、css、圖片,是會影響 load 事件觸發(fā)的。video、audio、flash 不會影響 load 事件觸發(fā)。

推薦閱讀:再談 load 與 DOMContentLoaded
本節(jié)參考文章:DOMContentLoaded與load的區(qū)別、事件DOMContentLoaded和load的區(qū)別

9. 為什么將css放在頭部,將js文件放在尾部

因為瀏覽器生成Dom樹的時候是一行一行讀HTML代碼的,script標(biāo)簽放在最后面就不會影響前面的頁面的渲染。那么問題來了,既然Dom樹完全生成好后頁面才能渲染出來,瀏覽器又必須讀完全部HTML才能生成完整的Dom樹,script標(biāo)簽不放在body底部是不是也一樣,因為dom樹的生成需要整個文檔解析完畢。

我們再來看一下chrome在頁面渲染過程中的,綠色標(biāo)志線是First Paint的時間。納尼,為什么會出現(xiàn)firstpaint,頁面的paint不是在渲染樹生成之后嗎?其實現(xiàn)代瀏覽器為了更好的用戶體驗,渲染引擎將嘗試盡快在屏幕上顯示的內(nèi)容。它不會等到所有HTML解析之前開始構(gòu)建和布局渲染樹。部分的內(nèi)容將被解析并顯示。也就是說瀏覽器能夠渲染不完整的dom樹和cssom,盡快的減少白屏的時間。假如我們將js放在header,js將阻塞解析dom,dom的內(nèi)容會影響到First Paint,導(dǎo)致First Paint延后。所以說我們會 將js放在后面,以減少First Paint的時間,但是不會減少DOMContentLoaded被觸發(fā)的時間

本節(jié)參考文章:DOMContentLoaded與load的區(qū)別

10.clientheight / offsetheight
clientheight:內(nèi)容的可視區(qū)域,不包含border。clientheight=padding+height-橫向滾動軸高度。

這里寫圖片描述

offsetheight,它包含padding、border、橫向滾動軸高度。 
offsetheight=padding+height+border+橫向滾動軸高度

scrollheight,可滾動高度,就是將滾動框拉直,不再滾動的高度,這個很好理解。 It includes the element’s padding, but not its border or margin.

本節(jié)參考文章:css clientheight、offsetheight、scrollheight詳解

11.use strict 有什么意義和好處

使調(diào)試更加容易。那些被忽略或默默失敗了的代碼錯誤,會產(chǎn)生錯誤或拋出異常,因此盡早提醒你代碼中的問題,你才能更快地指引到它們的源代碼。

防止意外的全局變量。如果沒有嚴(yán)格模式,將值分配給一個未聲明的變量會自動創(chuàng)建該名稱的全局變量。這是JavaScript中最常見的錯誤之一。在嚴(yán)格模式下,這樣做的話會拋出錯誤。

消除 this 強制。如果沒有嚴(yán)格模式,引用null或未定義的值到 this 值會自動強制到全局變量。這可能會導(dǎo)致許多令人頭痛的問題和讓人恨不得拔自己頭發(fā)的bug。在嚴(yán)格模式下,引用 null或未定義的 this 值會拋出錯誤。

不允許重復(fù)的屬性名稱或參數(shù)值。當(dāng)檢測到對象中重復(fù)命名的屬性,例如:

var object = {foo: "bar", foo: "baz"};)

或檢測到函數(shù)中重復(fù)命名的參數(shù)時,例如:

function foo(val1, val2, val1){})

嚴(yán)格模式會拋出錯誤,因此捕捉幾乎可以肯定是代碼中的bug可以避免浪費大量的跟蹤時間。

使 eval() 更安全。在嚴(yán)格模式和非嚴(yán)格模式下, eval() 的行為方式有所不同。最顯而易見的是,在嚴(yán)格模式下,變量和聲明在 eval() 語句內(nèi)部的函數(shù)不會在包含范圍內(nèi)創(chuàng)建(它們會在非嚴(yán)格模式下的包含范圍中被創(chuàng)建,這也是一個常見的問題源)。

在 delete 使用無效時拋出錯誤。 delete 操作符(用于從對象中刪除屬性)不能用在對象不可配置的屬性上。當(dāng)試圖刪除一個不可配置的屬性時,非嚴(yán)格代碼將默默地失敗,而嚴(yán)格模式將在這樣的情況下拋出異常。

本節(jié)參考文章:經(jīng)典面試題(4)

12.常見 JavaScript 內(nèi)存泄漏

意外的全局變量

JavaScript 處理未定義變量的方式比較寬松:未定義的變量會在全局對象創(chuàng)建一個新變量。在瀏覽器中,全局對象是 window 。

function foo(arg) {
    bar = "this is a hidden global variable";
}
真相是:

```
function foo(arg) {
    window.bar = "this is an explicit global variable";
}
```
函數(shù) foo 內(nèi)部忘記使用 var ,意外創(chuàng)建了一個全局變量。此例泄漏了一個簡單的字符串,無傷大雅,但是有更糟的情況。

另一種意外的全局變量可能由 this 創(chuàng)建:

```
function foo() {
    this.variable = "potential accidental global";
}
// Foo 調(diào)用自己,this 指向了全局對象(window)
// 而不是 undefined
foo();
```
在 JavaScript 文件頭部加上 "use strict",可以避免此類錯誤發(fā)生。啟用嚴(yán)格模式解析 JavaScript ,避免意外的全局變量。

被遺忘的計時器或回調(diào)函數(shù)
在 JavaScript 中使用 setInterval 非常平常。一段常見的代碼:

var someResource = getData();
setInterval(function() {
    var node = document.getElementById("Node");
    if(node) {
        // 處理 node 和 someResource
        node.innerHTML = JSON.stringify(someResource));
    }
}, 1000);

此例說明了什么:與節(jié)點或數(shù)據(jù)關(guān)聯(lián)的計時器不再需要,node 對象可以刪除,整個回調(diào)函數(shù)也不需要了。可是,計時器回調(diào)函數(shù)仍然沒被回收(計時器停止才會被回收)。同時,someResource 如果存儲了大量的數(shù)據(jù),也是無法被回收的。

對于觀察者的例子,一旦它們不再需要(或者關(guān)聯(lián)的對象變成不可達),明確地移除它們非常重要。老的 IE 6 是無法處理循環(huán)引用的。如今,即使沒有明確移除它們,一旦觀察者對象變成不可達,大部分瀏覽器是可以回收觀察者處理函數(shù)的。

觀察者代碼示例:

var element = document.getElementById("button");
function onClick(event) {
    element.innerHTML = "text";
}
element.addEventListener("click", onClick);

對象觀察者和循環(huán)引用注意事項

老版本的 IE 是無法檢測 DOM 節(jié)點與 JavaScript 代碼之間的循環(huán)引用,會導(dǎo)致內(nèi)存泄漏。如今,現(xiàn)代的瀏覽器(包括 IE 和 Microsoft Edge)使用了更先進的垃圾回收算法,已經(jīng)可以正確檢測和處理循環(huán)引用了。換言之,回收節(jié)點內(nèi)存時,不必非要調(diào)用 removeEventListener 了。

脫離 DOM 的引用
有時,保存 DOM 節(jié)點內(nèi)部數(shù)據(jù)結(jié)構(gòu)很有用。假如你想快速更新表格的幾行內(nèi)容,把每一行 DOM 存成字典(JSON 鍵值對)或者數(shù)組很有意義。此時,同樣的 DOM 元素存在兩個引用:一個在 DOM 樹中,另一個在字典中。將來你決定刪除這些行時,需要把兩個引用都清除。

var elements = {
    button: document.getElementById("button"),
    image: document.getElementById("image"),
    text: document.getElementById("text")
};
function doStuff() {
    image.src = "http://some.url/image";
    button.click();
    console.log(text.innerHTML);
    // 更多邏輯
}
function removeButton() {
    // 按鈕是 body 的后代元素
    document.body.removeChild(document.getElementById("button"));
    // 此時,仍舊存在一個全局的 #button 的引用
    // elements 字典。button 元素仍舊在內(nèi)存中,不能被 GC 回收。
}

此外還要考慮 DOM 樹內(nèi)部或子節(jié)點的引用問題。假如你的 JavaScript 代碼中保存了表格某一個 的引用。將來決定刪除整個表格的時候,直覺認為 GC 會回收除了已保存的 以外的其它節(jié)點。實際情況并非如此:此 是表格的子節(jié)點,子元素與父元素是引用關(guān)系。由于代碼保留了 的引用,導(dǎo)致整個表格仍待在內(nèi)存中。保存 DOM 元素引用的時候,要小心謹慎。

閉包

閉包是 JavaScript 開發(fā)的一個關(guān)鍵方面:匿名函數(shù)可以訪問父級作用域的變量。

避免濫用

本節(jié)參考文章:4類 JavaScript 內(nèi)存泄漏及如何避免

13.引用計數(shù) / 標(biāo)記清除

js垃圾回收有兩種常見的算法:引用計數(shù)和標(biāo)記清除。

引用計數(shù)就是跟蹤對象被引用的次數(shù),當(dāng)一個對象的引用計數(shù)為0即沒有其他對象引用它時,說明該對象已經(jīng)無需訪問了,因此就會回收其所占的內(nèi)存,這樣,當(dāng)垃圾回收器下次運行就會釋放引用數(shù)為0的對象所占用的內(nèi)存。

標(biāo)記清除法是現(xiàn)代瀏覽器常用的一種垃圾收集方式,當(dāng)變量進入環(huán)境(即在一個函數(shù)中聲明一個變量)時,就將此變量標(biāo)記為“進入環(huán)境”,進入環(huán)境的變量是不能被釋放,因為只有執(zhí)行流進入相應(yīng)的環(huán)境,就可能會引用它們。而當(dāng)變量離開環(huán)境時,就標(biāo)記為“離開環(huán)境”。

垃圾收集器在運行時會給儲存在內(nèi)存中的所有變量加上標(biāo)記,然后會去掉環(huán)境中的變量以及被環(huán)境中的變量引用的變量的標(biāo)記,當(dāng)執(zhí)行完畢那些沒有存在引用 無法訪問的變量就被加上標(biāo)記,最后垃圾收集器完成清除工作,釋放掉那些打上標(biāo)記的變量所占的內(nèi)存。

 function problem() {
    var A = {};
    var B = {};
    A.a = B;
    B.a = A;
}
引用計數(shù)存在一個弊端就是循環(huán)引用問題(上邊)
標(biāo)記清除不存在循環(huán)引用的問題,是因為當(dāng)函數(shù)執(zhí)行完畢之后,對象A和B就已經(jīng)離開了所在的作用域,此時兩個變量被標(biāo)記為“離開環(huán)境”,等待被垃圾收集器回收,最后釋放其內(nèi)存。

分析以下代碼:

    function createPerson(name){
        var localPerson = new Object();
        localPerson.name = name;
        return localPerson;
    }
    var globalPerson = createPerson("Junga");
    globalPerson = null;//手動解除全局變量的引用

在這個

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

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

相關(guān)文章

  • 前端本功-常見概念(一)

    摘要:前端基本功常見概念一點這里前端基本功常見概念二點這里前端基本功常見概念三點這里什么是原型鏈當(dāng)一個引用類型繼承另一個引用類型的屬性和方法時候就會產(chǎn)生一個原型鏈。函數(shù)式編程是聲明式而不是命令式,并且應(yīng)用程序狀態(tài)通過純函數(shù)流轉(zhuǎn)。 前端基本功-常見概念(一) 點這里前端基本功-常見概念(二) 點這里前端基本功-常見概念(三) 點這里 1.什么是原型鏈 當(dāng)一個引用類型繼承另一個引用類型的屬性和方...

    bladefury 評論0 收藏0
  • 前端本功-常見概念()

    摘要:前端基本功常見概念一點這里前端基本功常見概念二點這里前端基本功常見概念三點這里超文本標(biāo)記語言,顯示信息,不區(qū)分大小寫升級版的,區(qū)分大小寫可擴展標(biāo)記語言被用來傳輸和存儲數(shù)據(jù)規(guī)范采用異步方式加載模塊,模塊的加載不影響它后面語句的運行。 前端基本功-常見概念(一) 點這里前端基本功-常見概念(二) 點這里前端基本功-常見概念(三) 點這里 1.HTML / XML / XHTML html...

    Steven 評論0 收藏0
  • 前端2018現(xiàn)在上車還還得及么

    摘要:面向?qū)ο笕筇卣骼^承性多態(tài)性封裝性接口。第五階段封裝一個屬于自己的框架框架封裝基礎(chǔ)事件流冒泡捕獲事件對象事件框架選擇框架。核心模塊和對象全局對象,,,事件驅(qū)動,事件發(fā)射器加密解密,路徑操作,序列化和反序列化文件流操作服務(wù)端與客戶端。 第一階段: HTML+CSS:HTML進階、CSS進階、div+css布局、HTML+css整站開發(fā)、 JavaScript基礎(chǔ):Js基礎(chǔ)教程、js內(nèi)置對...

    stormgens 評論0 收藏0
  • 前端2018現(xiàn)在上車還還得及么

    摘要:面向?qū)ο笕筇卣骼^承性多態(tài)性封裝性接口。第五階段封裝一個屬于自己的框架框架封裝基礎(chǔ)事件流冒泡捕獲事件對象事件框架選擇框架。核心模塊和對象全局對象,,,事件驅(qū)動,事件發(fā)射器加密解密,路徑操作,序列化和反序列化文件流操作服務(wù)端與客戶端。 第一階段: HTML+CSS:HTML進階、CSS進階、div+css布局、HTML+css整站開發(fā)、 JavaScript基礎(chǔ):Js基礎(chǔ)教程、js內(nèi)置對...

    mylxsw 評論0 收藏0

發(fā)表評論

0條評論

閱讀需要支付1元查看
<