內容是對暢喵大佬和凍伢大佬所寫博客的總結和自己的一點點體會哈哈哈哈 沖!
作用域
定義:
1.作用域是指程式源代碼中定義變數的區域
2.作用域規定了如何查找變數,也就是確定當前執行代碼對變數的訪問權限
詞法作用域和動態作用域
JavaScript采用的是詞法作用域 (也稱為靜態作用域),函式的作用域在函式定義的時候就確定了,
與詞法作用域相對立的是動態作用域,函式的作用域是在函式呼叫時才確定的,
一個例子:
var a = 1;
function foo() {
console.log(a);
}
function bar() {
var a = 2;
foo();
}
bar(); //其結果是1
1.如果JavaScript采用的是詞法作用域時,起執行程序為:
執行foo函式,先在foo函式內部查找是否有區域變數a,沒有的話則根據書寫的位置,查找其上面一層的代碼即 (var a = 1) , 所以會列印1
2.如果JavaScript采用的是動態作用域時,其執行程序為:
執行foo函式,先在foo函式內部查找是否有區域變數a,沒有的話則從呼叫foo函式的bar函式作用域中去尋找a變數,查找其上面一層的代碼即 (var a = 1) , 所以會列印2
結論:JavaScript采用的詞法作用域
執行背景關系
定義:執行背景關系是評估和執行JavaScript代碼的環境的抽象概念,每當JavaScript代碼在運行的時候,它都是在執行背景關系中運行
JavaScript執行順序
JavaScript代碼在執行時,JavaScript引擎并非一行一行地分析和執行程式,而是一段一段地分析執行,當執行一段代碼的時候,會進行一個“準備作業”,那么會進行“準備作業”的每段代碼是如何劃分的?
可執行代碼
JavaScript的可執行代碼一共有三種,分別是:
1.全域代碼
2.函式代碼
3.eval代碼 (不常用)
舉個例子,當執行到一個函式時,就會進行“準備作業”,這里的“準備作業”更專業一點來說,就叫做“執行背景關系(execution context)” 這里即叫做函式執行背景關系,
舉個例子來說明一下全域代碼和可執行代碼 ~ :
全域代碼
let str = 'hello world';
function foo() {
// 函式體中的代碼不算全域代碼
}
console.log(str); // hello world
函式代碼
function foo() {
console.log('I like JavaScript'); // 函式代碼
console.log('hello world'); // 函式代碼
}
執行背景關系堆疊
代碼中的函式有很多,那么我們應該如何管理這么多的執行背景關系?
JavaScript引擎創建了執行背景關系堆疊(Execution context stack,ECS)來管理所創造的執行背景關系
下面將舉例子來模擬執行背景關系堆疊的行為 ~ :
1.為了模擬執行背景關系堆疊的行為,定執行背景關系堆疊是一個陣列:
ECStack = [];
2.當JavaScript開始執行解釋代碼的時候最先遇到是全域代碼,所以在初始化的時候會向執行背景關系堆疊中壓入一個全域執行背景關系,我們用globalContext來表示它,并且在程式結束之前,這個全域執行背景關系將一直存在,
ECStack = [
globalContext
];
現在我們將就下面這段代碼塊來模擬執行背景關系的行為 ~ :
function fun3() {
console.log('好困')
}
function fun2() {
fun3();
}
function fun1() {
fun2();
}
fun1();
當執行一個函式的時候,就會創造一個背景關系并壓入堆疊中,并且當函式執行完畢后,函式的執行背景關系將從堆疊中剔除,
// fun1()
ECStack.push(<fun1> functionContext);
// fun1中呼叫了fun2 還需要創造fun2的執行背景關系
ECStack.push(<fun2> functionContext);
// fun2中呼叫了fun3 還需要創造fun3的執行背景關系
ECStack.push(<fun3> functionContext);
// fun3執行完畢
ECStack.pop();
// fun2執行完畢
ECStack.pop();
// fun1執行完畢
ECStack.pop();
創建執行背景關系
對于每個執行背景關系,都有三個重要屬性:
1.變數物件(Variable object,VO)
2.作用域鏈(Scope chain)
3.this
變數物件
定義:變數物件是與執行背景關系相關的資料作用域,存盤了在背景關系中定義的變數和函式宣告
全域背景關系
全域背景關系的變數物件就是全域物件window(瀏覽器)
函式背景關系
在函式背景關系中,我們用活動物件(activation object,AO)來表示變數物件(VO)
活動物件(activation object,AO):未進入執行階段之前,變數物件(VO)中的屬性都不能訪問,但是進入執行階段之后,變數物件(VO)轉變為活動物件(AO),里面的屬性都能被訪問,然后開始進行執行階段的操作,VO和AO是同一個物件在不同生命周期時的不同的名字
執行背景關系的執行程序
執行背景關系的代碼會分為兩個階段進行處理:
1.進入執行背景關系
2.代碼執行
舉個例子來說明AO在不同階段的變化:
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
}
foo(1);
1.進入執行背景關系
變數物件會包括:
1.函式的所有形參
2.函式宣告
3.變數宣告
此時的AO:
AO = {
arguments: { // aruguments是一個對應傳給函式的引數的類陣列物件
0: 1, //arguments[0] = 1
length: 1 // arguments.length = 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
2.代碼執行
此時的AO:
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: 3,
c: reference to function c() {},
d: reference to FunctionExpression "d"
}
練習
function foo() {
console.log(a);
a = 2;
}
foo(); // a is not defined
function bar() {
a = 2;
console.log(a);
}
bar(); // 2
//兩端代碼執行console的時候 AO:
AO = {
arguments: {
length: 0
}
}
函式中的 "a" 并沒有通過 var 關鍵字宣告,所有不會被存放在 AO 中,
但是由于第二段代碼 a = 2; 位于 consol.log(a)之前,所以在執行console的時候全域變數a = 2
作用域鏈
定義:當查找變數的時候,會先從當前背景關系的變數物件中查找,如果沒有找到,就會從父級執行背景關系的變數物件中查找,一直找到全域背景關系的變數物件,也就是全域物件,這樣由多個執行背景關系的變數物件構成的鏈表就叫做作用域鏈,
[[scope]]內部屬性,當函式創建的時候,就會保存所有父變數到其中,可以理解為一個函式的[[scope]]就是所有父級變數物件的層級鏈
舉個例子 ~ :
function foo() {
function bar() {
...
}
}
上面各自的[[scope]]為:
foo.[[scope]] = [
globalContexts.VO
];
bar.[[scope]] = [
fooContexts.AO,
globalContexts.VO
];
讓我們舉個例子來細細捋一下函式執行背景關系中作用域鏈和變數物件的創建程序:
var scope = "global scope";
function checkscope() {
var scope2 = "local scope";
return scope2;
}
checkscop();
其執行程序如下:
1.創建checkscope函式,保存其作用域鏈到內部屬性[[scope]]
checkscope.[[scope]] = [
globalContext.VO // 全域代碼的變數物件
];
2.執行checkscope函式,創建checkscope函式的執行背景關系,并將checkscope函式的執行背景關系壓入堆疊中
ECStack = [
checkscopeContext, //checkscope函式執行背景關系
globalContext //全域執行背景關系
];
3.checkscope函式并不立即執行,開始做準備作業,第一步:復制函式scope屬性創造作用域鏈
checkscopeContext = {
Scope: checkscope.[[scope]], //將第一步函式的scope屬性復制,創造作用域鏈
}
4.第二步:用arguments創建活動物件,隨后初始化活動物件,加入形參、函式宣告、變數宣告 ( 即背景關系執行程序中的進入執行背景關系 )
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}
5.將活動物件壓入checkscope作用域頂端( 即函式激活 )
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, globalConext.[[Scope]]]
}
6.開始執行函式,并修改AO的屬性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, globalContext.[[Scope]]]
}
7.函式執行完畢 從執行背景關系堆疊中彈出
ECStack = [
globalContext
];
閉包
定義:閉包是指有權訪問另一個函式作用域中的變數的函式
舉例說明 ~
var data = [];
for (var i = 0; i < 3; i++) {
data[i] = function () {
console.log(i);
};
}
data[0]();
data[1]();
data[2]();
在講閉包之前首先讓我們分析一下這一段代碼
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
var foo = checkscope();
foo();
執行程序如下:
1.遇到全域代碼,創建全域背景關系,并將全域背景關系壓入背景關系堆疊
ECStack = [
globalContext
];
2.全域背景關系初始化
globalContext = {
VO: [global],
Scope: [globalContext.VO],
}
3.初始化的同時,創建checkscope函式,保存其作用域鏈到函式的內部屬性[[scope]]
checkscope.[[scope]] = {
globalContext.VO
}
4.執行checkscope函式,創建checkscope函式執行背景關系,checkscope執行背景關系被壓入執行背景關系堆疊
ECSstack = [
checkscopeContext,
globalContext
]
5.checkscope函式執行背景關系初始化
- 復制函式[[scope]]屬性創建作用域鏈
checkscopeContext = {
scope:checkscope.[[scope]]
}
- 用arguments創建活動物件,并初始化活動物件,加入形參,函式宣告,變數宣告
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope: undefined,
f: reference to function f(){}
},
}
- 將活動物件壓入checkscope作用域頂端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope: undefined,
f: reference to function f(){}
},
Scope: [AO, globalContext.VO],
this: undefined
}
6.f函式被創建,保存作用域到f函式的內部屬性[[scope]]
fscope.[[scope]] = {
checkscopeContext.AO, globalContext.VO
}
7.f函式初始化
- 復制函式[[scope]]屬性創建作用域
- 用arguments創建活動物件,同時初始化活動物件,加入形參,變數宣告,函式宣告
- 將活動物件呀如f作用域鏈頂端
fContext = {
AO: {
arguments: {
length: 0
}
},
Scope: [AO, checkscopeContext.AO, globalContext.VO],
}
8.f函式執行,沿著作用域鏈尋找scope
9.f函式執行完畢,從執行背景關系堆疊中彈出
ECSstack = [
checkscopeContext,
globalContext
]
10.checkscope函式執行完畢,從執行背景關系堆疊中彈出
ECSstack = [
globalContext
]
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/231014.html
標籤:其他
上一篇:spring+tomcat的web專案一開始是怎么啟動的
下一篇:5句話輕松搞定js原型鏈
