您現在的位置是:首頁 > 棋牌

前端技術:JavaScript進階篇

  • 由 全棧技術部落格週刊 發表于 棋牌
  • 2022-03-17
簡介而不是在函式呼叫時全域性執行上下文環境是在全域性作用域確定之後, js程式碼馬上執行之前建立函式執行上下文是在呼叫函式時, 函式體程式碼執行之前建立區別2:作用域是靜態的, 只要函式定義好了就一直存在, 且不會再變化執行上下文是動態的, 呼

前端js是什麼意思

JavaScript高階

1。`this`關鍵字

this

關鍵字是一個非常重要的語法點。毫不誇張地說,不理解它的含義,大部分開發任務都無法完成。

JavaScript 語言之所以有 this 的設計,跟記憶體裡面的資料結構有關係。

var obj = { foo: 5 };

上面的程式碼將一個物件賦值給變數obj。JavaScript 引擎會先在記憶體裡面,生成一個物件{ foo: 5 },然後把這個物件的記憶體地址賦值給變數obj。也就是說,變數obj是一個地址(reference)。後面如果要讀取obj。foo,引擎先從obj拿到記憶體地址,然後再從該地址讀出原始的物件,返回它的foo屬性。

原始的物件以字典結構儲存,每一個屬性名都對應一個屬性描述物件。JavaScript 允許在函式體內部,引用當前環境的其他變數。

var f = function () {

console。log(x);

};

上面程式碼中,函式體裡面使用了變數x。該變數由執行環境提供。

現在問題就來了,由於函式可以在不同的執行環境執行,所以需要有一種機制,能夠在函式體內部獲得當前的執行環境(

context

)。所以,

this

就出現了,它的設計目的就是在函式體內部,指代函式當前的執行環境。

var f = function () {

console。log(this。x);

}

上面程式碼中,函式體裡面的this。x就是指當前執行環境的x。

this

使用的場合

1。

this

全域性環境使用:它指的就是頂層物件

window

2。

this

可以用在建構函式之中,表示例項物件。

var person = {

name: ‘張三’,

describe: function () {

return‘姓名:’+ this。name; // this 就是person物件

}

};

person。describe()

// “姓名:張三”

3。

this

在物件的方法:指向就是方法執行時所在的物件

2。call、apply、bind的區別

this

的動態切換,固然為

JavaScript

創造了巨大的靈活性,但也使得程式設計變得困難和模糊。有時,需要把

this

固定下來,避免出現意想不到的情況。

JavaScript

提供了

call

apply

bind

這三個方法,來切換/固定

this

的指向。

1。`Function。prototype。call()`函式例項的`call`方法

var obj = {};

var f = function () {

returnthis;

};

f() === window// true

f。call(obj) === obj // true

call

方法的引數,應該是一個物件。如果引數為空、

null

undefined

,則預設傳入全域性物件。

var n = 123;

var obj = { n: 456 };

functiona() {

console。log(this。n);

}

a。call() // 123

a。call(null) // 123

a。call(undefined) // 123

a。call(window) // 123

a。call(obj) // 456

call方法還可以接受多個引數。

func。call(thisValue, arg1, arg2, 。。。)

call的第一個引數就是this所要指向的那個物件,後面的引數則是函式呼叫時所需的引數。

functionadd(a, b) {

return a + b;

}

add。call(this, 1, 2) // 3

上面程式碼中,call方法指定函式add內部的this綁定當前環境(物件),並且引數為1和2,因此函式add執行後得到3。

2。Function。prototype。apply()

apply

方法的作用與

call

方法類似,也是改變

this

指向,然後再呼叫該函式。唯一的區別就是,它接收一個數組作為函式執行時的引數,使用格式如下。

func。apply(thisValue, [arg1, arg2, 。。。])

functionf(x, y){

console。log(x + y);

}

f。call(null, 1, 1) // 2

f。apply(null, [1, 1]) // 2

call()和apply()這兩個方法都是函式物件的方法,需要透過函式物件來呼叫。

當函式呼叫call()和apply()時,函式都會立即

執行

都可以用來改變函式的this物件的指向。

第一個引數都是this要指向的物件(函式執行時,this將指向這個物件),後續引數用來傳實參。

JS提供的絕大多數函式以及我們自己建立的所有函式,都可以使用call 和apply方法。它們的第一個引數是一個物件。因為你可以直接指定 this 繫結的物件,因此我們稱之為顯式繫結。

例1:

functionfoo() {

console。log(this。a);

}

var obj = {

a: 2

};

// 將 this 指向 obj

foo。apply(obj); //列印結果:2

第一個引數的傳遞

1、thisObj不傳或者為null、undefined時,函式中的this會指向window物件(非嚴格模式)。

2、傳遞一個別的函式名時,函式中的this將指向這個

函式的引用

3、傳遞的值為數字、布林值、字串時,this會指向這些基本型別的包裝物件Number、Boolean、String。

4、傳遞一個物件時,函式中的this則指向傳遞的這個物件。

call()和apply()的區別

call()和apply()方法都可以將實參在物件之後依次傳遞,但是apply()方法需要將實參封裝到一個

陣列

中統一傳遞(即使只有實參只有一個,也要放到陣列中)。

var persion1 = {

name: “小王”,

gender: “男”,

age: 24,

say: function (school, grade) {

alert(this。name + “ , ” + this。gender + “ ,今年” + this。age + “ ,在” + school + “上” + grade);

}

}

var person2 = {

name: “小紅”,

gender: “女”,

age: 18

}

call呼叫

persion1。say。call(persion2, “實驗小學”, “六年級”);

apply呼叫

persion1。say。apply(persion2, [“實驗小學”, “六年級”]);

call()和apply()的作用

改變this的指向

實現繼承。Father。call(this)

3。Function。prototype。bind()

bind

方法用於將函式體內的

this

繫結到某個物件,然後返回一個新函式。

都能改變this的指向

call()/apply()是

立即呼叫函式

bind()是將函式返回,因此後面還需要加

()

才能呼叫。

bind()傳參的方式與call()相同。

2。嚴格模式

嚴格模式是從 ES5 進入標準的,主要目的有以下幾個。

明確禁止一些不合理、不嚴謹的語法,減少 JavaScript 語言的一些怪異行為。

增加更多報錯的場合,消除程式碼執行的一些不安全之處,保證程式碼執行的安全。

提高編譯器效率,增加執行速度。

為未來新版本的 JavaScript 語法做好鋪墊。

總之,嚴格模式體現了 JavaScript 更合理、更安全、更嚴謹的發展方向。

進入嚴格模式的標誌,是一行字串

use strict

,嚴格模式可以用於整個指令碼,也可以只用於單個函式。

嚴格模式必須從程式碼一開始就生效

// 函式嚴格模式

functionstrict() {

‘use strict’;

return‘這是嚴格模式’;

}

functionstrict2() {

‘use strict’;

functionf() {

return‘這也是嚴格模式’;

}

return f();

}

functionnotStrict() {

return‘這是正常模式’;

}

3。非同步操作概述

單執行緒模型指的是,

JavaScript

只在一個執行緒上執行。也就是說,JavaScript 同時只能執行一個任務,其他任務都必須在後面排隊等待。

注意,

JavaScript

只在一個執行緒上執行,不代表 JavaScript 引擎只有一個執行緒。事實上,

JavaScript

引擎有多個執行緒,單個指令碼只能在一個執行緒上執行(稱為主執行緒),其他執行緒都是在後臺配合。

這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等著,會拖延整個程式的執行。常見的瀏覽器無響應(假死),往往就是因為某一段 JavaScript 程式碼長時間執行(比如死迴圈),導致整個頁面卡在這個地方,其他任務無法執行。JavaScript 語言本身並不慢,慢的是讀寫外部資料,比如等待 Ajax 請求返回結果。這個時候,如果對方伺服器遲遲沒有響應,或者網路不通暢,就會導致指令碼的長時間停滯。

如果排隊是因為計算量大,CPU 忙不過來,倒也算了,但是很多時候 CPU 是閒著的,因為 IO 操作(輸入輸出)很慢(比如 Ajax 操作從網路讀取資料),不得不等著結果出來,再往下執行。JavaScript 語言的設計者意識到,這時 CPU 完全可以不管 IO 操作,掛起處於等待中的任務,先執行排在後面的任務。等到 IO 操作返回了結果,再回過頭,把掛起的任務繼續執行下去。這種機制就是 JavaScript 內部採用的“事件迴圈”機制(Event Loop)。

單執行緒模型雖然對 JavaScript 構成了很大的限制,但也因此使它具備了其他語言不具備的優勢。如果用得好,JavaScript 程式是不會出現堵塞的,這就是為什麼 Node 可以用很少的資源,應付大流量訪問的原因。

為了利用多核 CPU 的計算能力,HTML5 提出 Web Worker 標準,允許 JavaScript 指令碼建立多個執行緒,但是子執行緒完全受主執行緒控制,且不得操作 DOM。所以,這個新標準並沒有改變 JavaScript 單執行緒的本質。

同步任務(synchronous)和非同步任務(asynchronous)

同步任務是那些沒有被引擎掛起、在主執行緒上排隊執行的任務。只有前一個任務執行完畢,才能執行後一個任務。

非同步任務是那些被引擎放在一邊,不進入主執行緒、而進入任務佇列的任務。只有引擎認為某個非同步任務可以執行了(比如 Ajax 操作從伺服器得到了結果),該任務(採用回撥函式的形式)才會進入主執行緒執行。排在非同步任務後面的程式碼,不用等待非同步任務結束會馬上執行,也就是說,非同步任務不具有“堵塞”效應。

任務佇列和事件迴圈【很像Android的訊息傳遞機制】

JavaScript 執行時,除了一個正在執行的主執行緒,引擎還提供一個任務佇列(task queue),裡面是各種需要當前程式處理的非同步任務。(實際上,根據非同步任務的型別,存在多個任務佇列。為了方便理解,這裡假設只存在一個佇列。)

首先,主執行緒會去執行所有的同步任務。等到同步任務全部執行完,就會去看任務佇列裡面的非同步任務。如果滿足條件,那麼非同步任務就重新進入主執行緒開始執行,這時它就變成同步任務了。等到執行完,下一個非同步任務再進入主執行緒開始執行。一旦任務佇列清空,程式就結束執行。

非同步任務的寫法通常是回撥函式。一旦非同步任務重新進入主執行緒,就會執行對應的回撥函式。如果一個非同步任務沒有回撥函式,就不會進入任務佇列,也就是說,不會重新進入主執行緒,因為沒有用回撥函式指定下一步的操作。

JavaScript 引擎怎麼知道非同步任務有沒有結果,能不能進入主執行緒呢?答案就是引擎在不停地檢查,一遍又一遍,只要同步任務執行完了,引擎就會去檢查那些掛起來的非同步任務,是不是可以進入主執行緒了。這種迴圈檢查的機制,就叫做事件迴圈(Event Loop)。

非同步操作的模式:

採用回撥函式;

採用事件監聽;

釋出/訂閱;

JavaScript定時器

JavaScript

提供定時執行程式碼的功能,叫做定時器

(timer)

,主要由

setTimeout()

setInterval()

這兩個函式來完成。它們向任務佇列新增定時任務。

setTimeout

函式用來指定某個函式或某段程式碼,在多少毫秒之後執行。它返回一個整數,表示定時器的編號,以後可以用來取消這個定時器。

var timerId = setTimeout(func|code, delay);

setInterval函式的用法與setTimeout完全一致,區別僅僅在於setInterval指定某個任務每隔一段時間就執行一次,也就是無限次的定時執行。

// setTimeout

console。log(1);

setTimeout(timeOutPut, 100);

console。log(3);

// 1

//3

//定時器執行100毫秒輸出:2

functiontimeOutPut() {

console。log(“定時器執行100毫秒輸出:”+2);

}

var count = 0;

var consoleInterval = setInterval(function () {

count++;

console。log(“迴圈輸出%d,次數%d”, 2, count);

if (count === 10) {

clearInterval(consoleInterval);

}

}, 1000);

迴圈輸出2,次數%count 1

迴圈輸出2,次數%count 2

迴圈輸出2,次數%count 3

迴圈輸出2,次數%count 4

迴圈輸出2,次數%count 5

迴圈輸出2,次數%count 6

迴圈輸出2,次數%count 7

迴圈輸出2,次數%count 8

迴圈輸出2,次數%count 9

迴圈輸出2,次數%count 10

var id1 = setTimeout(f, 1000);

var id2 = setInterval(f, 1000);

clearTimeout(id1);

clearInterval(id2);

setTimeout

setInterval

函式,都返回一個整數值,表示計數器編號。將該整數傳入

clearTimeout

clearInterval

函式,就可以取消對應的定時器。

Promise 物件

Promise

物件是

JavaScript

的非同步操作解決方案,為非同步操作提供統一介面。它起到代理作用

(proxy)

,充當非同步操作與回撥函式之間的中介,使得非同步操作具備同步操作的介面。

Promise

可以讓非同步操作寫起來,就像在寫同步操作的流程,而不必一層層地巢狀回撥函式。

1。

Promise

是一個物件,也是一個建構函式。

Promise

設計思想:所有非同步任務都返回一個

Promise

例項。

Promise

例項有一個

then

方法,用來指定下一步的回撥函式。

functionf1(resolve, reject) {

// 非同步程式碼。。。

}

var p1 = newPromise(f1);

// f1的非同步操作執行完成,就會執行f2

p1。then(f2);

Promise

物件透過自身的狀態,來控制非同步操作。

Promise

例項具有三種狀態。

非同步操作未完成(pending)

非同步操作成功(fulfilled)

非同步操作失敗(rejected)

上面三種狀態裡面,

fulfilled

rejected

合在一起稱為

resolved

(已定型)。

var promise = newPromise(function (resolve, reject) {

// 。。。

if (/* 非同步操作成功 */){

resolve(value);

} else { /* 非同步操作失敗 */

reject(newError());

}

});

Promise

建構函式接受一個函式作為引數,該函式的兩個引數分別是

resolve

reject

。它們是兩個函式,由

JavaScript

引擎提供,不用自己實現。

resolve

函式的作用是,將

Promise

例項的狀態從“未完成”變為“成功”(即從

pending

變為

fulfilled

),在非同步操作成功時呼叫,並將非同步操作的結果,作為引數傳遞出去。

reject

函式的作用是,將

Promise

例項的狀態從“未完成”變為“失敗”(即從

pending

變為

rejected

),在非同步操作失敗時呼叫,並將非同步操作報出的錯誤,作為引數傳遞出去。

Promise 的優點

1。讓回撥函式變成了規範的鏈式寫法,程式流程可以看得很清楚。它有一整套介面,可以實現許多強大的功能,比如同時執行多個非同步操作,等到它們的狀態都改變以後,再執行一個回撥函式;再比如,為多個回撥函式中丟擲的錯誤,統一指定處理方法等等。

2。Promise 還有一個傳統寫法沒有的好處:它的狀態一旦改變,無論何時查詢,都能得到這個狀態。這意味著,無論何時為 Promise 例項添加回調函式,該函式都能正確執行。所以,你不用擔心是否錯過了某個事件或訊號。如果是傳統寫法,透過監聽事件來執行回撥函式,一旦錯過了事件,再添加回調函式是不會執行的。

4。作用域和閉包

1.執行上下文

執行上下文主要有兩種情況:

全域性程式碼:一段

script

標籤裡,有一個全域性的執行上下文。所做的事情是:變數定義、函式宣告【

在執行全域性程式碼前將window確定為全域性執行上下文。

函式程式碼:每個函數里有一個上下文。所做的事情是:變數定義、函式宣告、this、arguments【

在呼叫函式, 準備執行函式體之前, 建立對應的函式執行上下文物件(虛擬的, 存在於棧中)。

2.this

this

指的是,呼叫函式的那個物件。

this

永遠指向函式執行時所在的物件。

解析器在呼叫函式每次都會向函式內部傳遞進一個隱含的引數,這個隱含的引數就是this。

根據函式的呼叫方式的不同,this會指向不同的物件:【重要】

1。以函式的形式呼叫時,this永遠都是window。比如fun();相當於window。fun();

2。以方法的形式呼叫時,this是呼叫方法的那個物件

3。以建構函式的形式呼叫時,this是新建立的那個物件

4。使用call和apply呼叫時,this是指定的那個物件

需要特別提醒的是:this的指向在函式定義時無法確認,只有函式執行時才能確定。

前端技術:JavaScript進階篇

3.作用域

作用域指一個變數的作用範圍。它是靜態的(相對於上下文物件), 在編寫程式碼時就確定了。 作用:隔離變數,不同作用域下同名變數不會有衝突。

作用域的分類:

全域性作用域

函式作用域

沒有塊作用域(ES6有了)

if (true) {

var name = ‘smyhvae’;

}

console。log(name);

上方程式碼中,並不會報錯,因為:雖然 name 是在塊裡面定義的,但是 name 是全域性變數。

全域性作用域:直接編寫在script標籤中的JS程式碼,都在全域性作用域。

在全域性作用域中:

在全域性作用域中有一個全域性物件window,它代表的是一個瀏覽器的視窗,它由瀏覽器建立我們可以直接使用。

建立的變數都會作為window物件的屬性儲存。

建立的函式都會作為window物件的方法儲存。

全域性作用域中的變數都是全域性變數,在頁面的任意的部分都可以訪問到。

變數的宣告提前:(變數提升)

使用

var

關鍵字宣告的變數( 比如

var a = 1

),會在所有的程式碼執行之前被宣告(但是不會賦值),但是如果宣告變數時不是用

var

關鍵字(比如直接寫a = 1),則變數不會被宣告提前。

舉例1:

console。log(a);

var a = 123;

列印結果:undefined

舉例2:

console。log(a);

a = 123; //此時a相當於window。a

程式會報錯:

前端技術:JavaScript進階篇

函式的宣告提前:

使用

函式宣告

的形式建立的函式

function foo(){}

會被宣告提前

也就是說,它會在所有的程式碼執行之前就被建立,所以我們可以在函式宣告之前,呼叫函式。

使用

函式表示式

建立的函式

var foo = function(){}

不會被宣告提前

,所以不能在宣告前呼叫。

很好理解,因為此時foo被聲明瞭,且為undefined,並沒有給其賦值

function(){}

所以說,下面的例子,會報錯:

前端技術:JavaScript進階篇

函式作用域

呼叫函式時建立函式作用域,函式執行完畢以後,函式作用域銷燬。

每呼叫一次函式就會建立一個新的函式作用域,他們之間是互相獨立的。

在函式作用域中可以訪問到全域性作用域的變數,在全域性作用域中無法訪問到函式作用域的變數。

在函式中要訪問全域性變數可以使用window物件。(比如說,全域性作用域和函式作用域都定義了變數a,如果想訪問全域性變數,可以使用

window。a

提醒1:

在函式作用域也有宣告提前的特性:

使用var關鍵字宣告的變數,是在函式作用域內有效,而且會在函式中所有的程式碼執行之前被宣告

函式宣告也會在函式中所有的程式碼執行之前執行

因此,在函式中,沒有var宣告的變數都會成為

全域性變數

,而且並不會提前宣告。

舉例1:

var a = 1;

functionfoo() {

console。log(a);

a = 2; // 此處的a相當於window。a

}

foo();

console。log(a); //列印結果是2

上方程式碼中,foo()的列印結果是

1

。如果去掉第一行程式碼,列印結果是

Uncaught ReferenceError: a is not defined

提醒2:

定義形參就相當於在函式作用域中聲明瞭變數。

functionfun6(e) {

console。log(e);

}

fun6(); //列印結果為 undefined

fun6(123);//列印結果為123

作用域與執行上下文的區別

區別1:

全域性作用域之外,每個函式都會建立自己的作用域,作用域在函式定義時就已經確定了。而不是在函式呼叫時

全域性執行上下文環境是在全域性作用域確定之後, js程式碼馬上執行之前建立

函式執行上下文是在呼叫函式時, 函式體程式碼執行之前建立

區別2:

作用域是靜態的, 只要函式定義好了就一直存在, 且不會再變化

執行上下文是動態的, 呼叫函式時建立, 函式呼叫結束時就會自動釋放

執行上下文(物件)是從屬於所在的作用域

全域性上下文環境==>全域性作用域

函式上下文環境==>對應的函式使用域

作用域鏈

當在函式作用域操作一個變數時,它會先在自身作用域中尋找,如果有就直接使用(

就近原則

)。如果沒有則向上一級作用域中尋找,直到找到全域性作用域;如果全域性作用域中依然沒有找到,則會報錯

ReferenceError

外部函式定義的變數可以被內部函式所使用,反之則不行。

<!DOCTYPE html>

Title

理解:

多個上下級關係的作用域形成的鏈, 它的方向是從下向上的(從內到外)

查詢變數時就是沿著作用域鏈來查詢的

查詢一個變數的查詢規則:

var a = 1

functionfn1() {

var b = 2

functionfn2() {

var c = 3

console。log(c)

console。log(b)

console。log(a)

console。log(d)

}

fn2()

}

fn1()

在當前作用域下的執行上下文中查詢對應的屬性, 如果有直接返回, 否則進入2

在上一級作用域的執行上下文中查詢對應的屬性, 如果有直接返回, 否則進入3

再次執行2的相同操作, 直到全域性作用域, 如果還找不到就丟擲找不到的異常

閉包

閉包就是能夠讀取其他函式內部資料(變數/函式)的函式。只有函式內部的子函式才能讀取區域性變數,因此可以把閉包簡單理解成“定義在一個函式內部的函式”。

上面這兩句話,是阮一峰的文章裡的,你不一定能理解,來看下面的講解和舉例。

1。如何產生閉包?

當一個巢狀的內部(子)函式引用了巢狀的外部(父)函式的變數或函式時, 就產生了閉包。

2。閉包到底是什麼?

使用chrome除錯檢視

理解一: 閉包是巢狀的內部函式(絕大部分人)

理解二: 包含被引用變數 or 函式的物件(極少數人)

注意: 閉包存在於巢狀的內部函式中。

3。產生閉包的條件

1。函式巢狀

2。內部函式引用了外部函式的資料(變數/函式)。

來看看條件2:

functionfn1() {

functionfn2() {

}

return fn2;

}

fn1();

上面的程式碼不會產生閉包,因為內部函式fn2並沒有引用外部函式fn1的變數。

PS:還有一個條件是

外部函式被呼叫,內部函式被宣告

。比如:

functionfn1() {

var a = 2

var b = ‘abc’

functionfn2() { //fn2內部函式被提前宣告,就會產生閉包(不用呼叫內部函式)

console。log(a)

}

}

fn1();

functionfn3() {

var a = 3

var fun4 = function () { //fun4採用的是“函式表示式”建立的函式,此時內部函式的宣告並沒有提前

console。log(a)

}

}

fn3();

常見的閉包

1。 將一個函式作為另一個函式的返回值

2。 將函式作為實參傳遞給另一個函式呼叫。

閉包1:將一個函式作為另一個函式的返回值

functionfn1() {

var a = 2

functionfn2() {

a++

console。log(a)

}

return fn2

}

var f = fn1(); //執行外部函式fn1,返回的是內部函式fn2

f() // 3 //執行fn2

f() // 4 //再次執行fn2

當f()第二次執行的時候,a加1了,也就說明了:閉包裡的資料沒有消失,而是儲存在了記憶體中。如果沒有閉包,程式碼執行完倒數第三行後,變數a就消失了。

上面的程式碼中,雖然呼叫了內部函式兩次,但是,閉包物件只建立了一個。

也就是說,要看閉包物件建立了一個,就看:

外部函式執行了幾次

(與內部函式執行幾次無關)。

閉包2。 將函式作為實參傳遞給另一個函式呼叫

functionshowDelay(msg, time) {

setTimeout(function() { //這個function是閉包,因為是巢狀的子函式,而且引用了外部函式的變數msg

alert(msg)

}, time)

}

showDelay(‘atguigu’, 2000)

上面的程式碼中,閉包是裡面的funciton,因為它是巢狀的子函式,而且引用了外部函式的變數msg。

閉包的作用

作用1。 使用函式內部的變數在函式執行完後, 仍然存活在記憶體中(延長了區域性變數的生命週期)

作用2。 讓函式外部可以操作(讀寫)到函式內部的資料(變數/函式)

我們讓然拿這段程式碼來分析:

functionfn1() {

var a = 2

functionfn2() {

a++

console。log(a)

}

return fn2;

}

var f = fn1(); //執行外部函式fn1,返回的是內部函式fn2

f() // 3 //執行fn2

f() // 4 //再次執行fn2

作用1分析

上方程式碼中,外部函式fn1執行完畢後,變數a並沒有立即消失,而是儲存在記憶體當中。

作用2分析:

函式fn1中的變數a,是在fn1這個函式作用域內,因此外部無法訪問。但是透過閉包,外部就可以操作到變數a。

達到的效果是:

外界看不到變數a,但可以操作a

比如上面達到的效果是:我看不到變數a,但是每次執行函式後,讓a加1。當然,如果我真想看到a,我可以在fn2中將a返回即可。

回答幾個問題:

問題1。 函式執行完後, 函式內部宣告的區域性變數是否還存在?

答案:一般是不存在, 存在於閉包中的變數才可能存在。

閉包能夠一直存在的根本原因是

f

,因為

f

接收了

fn1()

,這個是閉包,閉包裡有a。注意,此時,fn2並不存在了,但是裡面的物件(即閉包)依然存在,因為用

f

接收了。

問題2。 在函式外部能直接訪問函式內部的區域性變數嗎?

不能,但我們可以透過閉包讓外部操作它。

閉包的生命週期

產生: 巢狀內部函式fn2被宣告時就產生了(不是在呼叫)

死亡: 巢狀的內部函式成為垃圾物件時。(比如f = null,就可以讓f成為垃圾物件。意思是,此時f不再引用閉包這個物件了)

閉包的應用:定義具有特定功能的js模組

將所有的資料和功能都封裝在一個函式內部(私有的),只向外暴露一個包含n個方法的物件或函式。

模組的使用者, 只需要透過模組暴露的物件呼叫方法來實現對應的功能。

方式一:

(1)myModule。js:(定義一個模組,向外暴露多個函式,供外界呼叫)

functionmyModule() {

//私有資料

var msg = ‘Smyhvae Haha’

//操作私有資料的函式

functiondoSomething() {

console。log(‘doSomething() ’ + msg。toUpperCase()); //字串大寫

}

functiondoOtherthing() {

console。log(‘doOtherthing() ’ + msg。toLowerCase()) //字串小寫

}

//透過【物件字面量】的形式進行包裹,向外暴露多個函式

return {

doSomething1: doSomething,

doOtherthing2: doOtherthing

}

}

上方程式碼中,外界可以透過doSomething1和doOtherthing2來操作裡面的資料,但不讓外界看到。

(2)index。html:

<!DOCTYPE html>

05_閉包的應用_自定義JS模組

<!——

閉包的應用 : 定義JS模組

* 具有特定功能的js檔案

* 將所有的資料和功能都封裝在一個函式內部(私有的)

* 【重要】只向外暴露一個包含n個方法的物件或函式

* 模組的使用者, 只需要透過模組暴露的物件呼叫方法來實現對應的功能

——>

varmodule = myModule();

module。doSomething1();

module。doOtherthing2();

方式二:同樣是實現方式一種的功能,這裡我們採取另外一種方式。

(1)myModule2。js:(是一個立即執行的匿名函式)

(function () {

//私有資料

var msg = ‘Smyhvae Haha’

//操作私有資料的函式

function doSomething() {

console。log(‘doSomething() ’ + msg。toUpperCase())

}

function doOtherthing() {

console。log(‘doOtherthing() ’ + msg。toLowerCase())

}

//外部函式是即使執行的匿名函式,我們可以把兩個方法直接傳給window物件

window。myModule = {

doSomething1: doSomething,

doOtherthing2: doOtherthing

}

})()

(2)index。html:

<!DOCTYPE html>

05_閉包的應用_自定義JS模組2

<!——

閉包的應用2 : 定義JS模組

* 具有特定功能的js檔案

* 將所有的資料和功能都封裝在一個函式內部(私有的)

* 只向外暴露一個包信n個方法的物件或函式

* 模組的使用者, 只需要透過模組暴露的物件呼叫方法來實現對應的功能

——>

<!——引入myModule檔案——>

myModule。doSomething1()

myModule。doOtherthing2()

上方兩個檔案中,我們在

myModule2。js

裡直接把兩個方法直接傳遞給window物件了。於是,在index。html中引入這個js檔案後,會立即執行裡面的匿名函式。在index。html中把myModule直接拿來用即可。

總結:

當然,方式一和方式二對比後,我們更建議採用方式二,因為很方便。

但無論如何,兩種方式都採用了閉包。

閉包的缺點及解決

缺點:函式執行完後, 函式內的區域性變數沒有釋放,佔用記憶體時間會變長,容易造成記憶體洩露。

解決:能不用閉包就不用,及時釋放。比如:

f = null; // 讓內部函式成為垃圾物件 ——>回收閉包

總而言之,你需要它,就是優點;你不需要它,就成了缺點。

記憶體溢位和記憶體洩露

記憶體溢位

記憶體溢位

:一種程式執行出現的錯誤。當程式執行

需要的記憶體

超過了剩餘的記憶體時, 就出丟擲記憶體溢位的錯誤。

程式碼舉例:

var obj = {};

for (var i = 0; i < 10000; i++) {

obj[i] = newArray(10000000); //把所有的陣列內容都放到obj裡儲存,導致obj佔用了很大的記憶體空間

console。log(“——-”);

}

記憶體洩漏

記憶體洩漏

佔用的記憶體

沒有及時釋放。

注意,記憶體洩露的次數積累多了,就容易導致記憶體溢位。

常見的記憶體洩露

1。意外的全域性變數

2。沒有及時清理的計時器或回撥函式

3。閉包

情況1舉例:

// 意外的全域性變數

functionfn() {

a = newArray(10000000);

console。log(a);

}

fn();

情況2舉例:

// 沒有及時清理的計時器或回撥函式

var intervalId = setInterval(function () { //啟動迴圈定時器後不清理

console。log(‘——’)

}, 1000)

// clearInterval(intervalId); //清理定時器

情況3舉例:

functionfn1() {

var a = 4;

functionfn2() {

console。log(++a)

}

return fn2

}

var f = fn1()

f()

// f = null //讓內部函式成為垃圾物件——>回收閉包

5。淺複製和深複製

淺複製

對於物件或陣列型別,當我們將a賦值給b,然後更改b中的屬性,a也會隨著變化。

也就是說,a和b指向了同一塊堆記憶體,所以修改其中任意的值,另一個值都會隨之變化,這就是淺複製。

深複製

那麼相應的,如果給b放到新的記憶體中,將a的各個屬性都複製到新記憶體裡,就是深複製。

也就是說,當b中的屬性有變化的時候,a內的屬性不會發生變化。

其他知識

Dom解析

事件events

渲染引擎的主要作用是,將網頁程式碼渲染為使用者視覺可以感知的平面文件。

不同的瀏覽器有不同的渲染引擎。

Firefox:Gecko 引擎

Safari:WebKit 引擎

Chrome:Blink 引擎

IE: Trident 引擎

Edge: EdgeHTML 引擎

渲染引擎處理網頁,通常分成四個階段。

解析程式碼:HTML 程式碼解析為 DOM,CSS 程式碼解析為 CSSOM(CSS Object Model)。

物件合成:將 DOM 和 CSSOM 合成一棵渲染樹(render tree)。

佈局:計算出渲染樹的佈局(layout)。

繪製:將渲染樹繪製到螢幕。

以上四步並非嚴格按順序執行,往往第一步還沒完成,第二步和第三步就已經開始了。所以,會看到這種情況:網頁的 HTML 程式碼還沒下載完,但瀏覽器已經顯示出內容了。

JavaScript 引擎

JavaScript 引擎的主要作用是,讀取網頁中的 JavaScript 程式碼,對其處理後執行。

JavaScript 是一種解釋型語言,也就是說,它不需要編譯,由直譯器實時執行。這樣的好處是執行和修改都比較方便,重新整理頁面就可以重新解釋;缺點是每次執行都要呼叫直譯器,系統開銷較大,執行速度慢於編譯型語言。

為了提高執行速度,目前的瀏覽器都將 JavaScript 進行一定程度的編譯,生成類似位元組碼(bytecode)的中間程式碼,以提高執行速度。

Top