Javascript 核心物件 執行上下文 Execution context 作用域鏈 Scope Chain 閉包 Closure

                                    

js 寫了好幾年,總是對其用法懵懂,恨其沒有標準語法書籍,這次下很心看了ECMA-262-3標準。

整理自己比較模糊的核心概念

 

Execution context 執行上下文,avascript 是單執行緒語言,同一時間只有一個任務被執行。當javascript 開始解析執行程式碼時,預設先進入全域性執行上下文(global execution context),然後在全域性上下文中每呼叫一次函式生成新的執行上下文。也可以呼叫eval 方法進入 eval 上下文 (eval execution context)。這個過程如下

將執行上下文棧(Execution Context Stack): 所有的執行上下文儲存到棧中如下

 

執行上下文可以抽象成一個物件

 

variable object :變數物件,該物件是一個與執行上下文相關的的容器物件。用於儲存 variable .function 宣告定義

注意函式表示式不儲存在 變數物件中

var foo = 10;

function bar() {} // function declaration, FD

(function baz() {}); // function expression, FE

console.log(

this.foo == foo, // true

window.bar == bar // true

);

console.log(baz); // ReferenceError, "baz" is not defined

以上式全域性上下文 global context

在全域性上下文中 Variable Object 是全域性物件本身

 

在eval context eval 上下文中 variable object 即可以用全域性的variable object 也可用呼叫它的function的variable object

 

Activation object: 函式上下文中 variable Object 叫做 Activation object,其除變數variable 與函式funciton的宣告定義外,還有形參及arguments 物件

function foo(x, y) {

var z = 30;

function bar() {} // FD

(function baz() {}); // FE

}

foo(10, 20);

函式宣告預先讀寫 hoisting of function variable

 

alert(sum(10,10));//20;因為預先讀取到了sum()函式的申明 alert(sum1(10,10))//報錯,因為找不到sum1()函式; function sum(num1,num2){ return num1 num2; } var sum1=function(num1,num2){ return num1 num2; }

 

scope chain 作用域鏈: 在當前執行上下文可訪問的變數。即一個可以內部函式可以訪問它父函式上下中變數variable object及全域性上下中的變數 global variable。

 

var 作用域 是 function scope

function f(shouldInitialize: boolean) 
{ 
if (shouldInitialize) { var x = 10; } return x;
 } f(true); // returns '10' 
f(false); // returns 'undefined'

var 作用域問題是相同變數名可以重複宣告

function sumMatrix(matrix) {

var sum = 0;

for (var i = 0; i < matrix.length; i  ) {

var currentRow = matrix[i];

for (var i = 0; i < currentRow.length; i  ) {

sum  = currentRow[i];

}

}

return sum;

}

sumMatrix([[1,2],[3,6]]) //return 3

var 作用域鏈

var x = 10;

(function foo(i) {

var y = 20;

(function bar() {

var z = 30;

// "x" and "y" and "i" are "free variables"

// and are found in the next (after

// bar's activation object) object

// of the bar's scope chain

console.log(x   y   z   i);

})();

})(40); //return 100

 

其外還有一些特殊的動態新增作用域

如 with-objects 或 catch-clauses.

Object.prototype.x = 10;

var w = 20;

var y = 30;

// in SpiderMonkey global object

// i.e. variable object of the global

// context inherits from "Object.prototype",

// so we may refer "not defined global

// variable x", which is found in

// the prototype chain

console.log(x); // 10

(function foo() {

// "foo" local variables

var w = 40;

var x = 100;

// "x" is found in the

// "Object.prototype", because

// {z: 50} inherits from it

with ({z: 50}) {

console.log(w, x, y , z); // 40, 10, 30, 50

}

// after "with" object is removed

// from the scope chain, "x" is

// again found in the AO of "foo" context;

// variable "w" is also local

console.log(x, w); // 100, 40

// and that's how we may refer

// shadowed global "w" variable in

// the browser host environment

console.log(window.w); // 20

})();

不建議使用with,使作用域鏈多了查詢維度

 

注意不是所有 global vo 都 prototype object

 

Closures 閉包: 由於javascript函式作為第一公民的語言。可以將函式作為引數,也可以作為返回值。函式在建立時[[scopes]] 屬性會儲存parent funtion 作用域鏈 scope chain,然後當函式啟用時,合併作用域鏈

Scope chain = Activation object [[Scope]]

下面是函式作為返回值,它的作用域鏈是向上尋找父函式作用域

function foo() {

var x = 10;

return function bar() {

console.log(x);

};

}

// "foo" returns also a function

// and this returned function uses

// free variable "x"

var returnedFunction = foo();

// global variable "x"

var x = 20;

// execution of the returned function

returnedFunction(); // 10, but not 20

 

上圖在作用域叫靜態作用域 static scope or lexical scope,其他語言也有dynamic scope 動態作用域

 

下面是函式作為實參,它的作用域鏈是向下尋找父函式作用域

// global "x"

var x = 10;

// global function

function foo() {

console.log(x);

}

(function (funArg) {

// local "x"

var x = 20;

// there is no ambiguity,

// because we use global "x",

// which was statically saved in

// [[Scope]] of the "foo" function,

// but not the "x" of the caller's scope,

// which activates the "funArg"

funArg(); // 10, but not 20

})(foo); // pass "down" foo as a "funarg"

 

上圖 foo 作用域是函式建立時生成[[scopes]]。

總結: 靜態作用域是作為語言擁有閉包closure 特性的一種必要條件。有些語言可以同時擁有靜態作用域,動態作用域,並可以開發的時候選擇。

 

閉包的定義 是程式碼塊作用域(var 是 function scope let {} scope )與靜態作用域的集合,改靜態作用域儲存父作用域鏈variable object 的引用。

共享父作用域鏈的情況

function baz() {

var x = 1;

return {

foo: function foo() { debugger; return   x; },

bar: function bar() { debugger; return --x; }

};

}

var closures = baz();

console.log(

closures.foo(), // 2

closures.bar() // 1

);

 

 

典型的問題迴圈中呼叫函式

var data = [];

for (var k = 0; k < 3; k  ) {

data[k] = function () {

console.log(k);

};

}

data[0](); // 3, but not 0

data[1](); // 3, but not 1

data[2](); // 3, but not 2

解決方法 使用function 隔離作用域

var data = [];

for (var k = 0; k < 3; k  ) {

data[k] = (function (x) {

return function () {

console.log(x);

};

})(k); // pass "k" value

}

// now it is correct

data[0](); // 0

data[1](); // 1

data[2](); // 2

但是ES6 let 塊作用域

let data = [];

for (let k = 0; k < 3; k  ) {

data[k] = function () {

console.log(k);

};

}

data[0](); // 0

data[1](); // 1

data[2](); // 2

context object 上下文 this物件的值,屬於執行上下文execute context的屬性,

與variable object相比this 不存在作用域鏈問題 scope chain,這個值是執行上下文啟用是被賦值即函式呼叫時,且只能被賦值一次

通常由函式的呼叫者決定, 一下三種不同context

// the code of the "foo" function

// never changes, but the "this" value

// differs in every activation

function foo() {

console.log(this);

}

// caller activates "foo" (callee) and

// provides "this" for the callee

foo(); // global object

foo.prototype.constructor(); // foo.prototype

var bar = {

baz: foo

};

bar.baz(); // bar

(bar.baz)(); // also bar

(bar.baz = bar.baz)(); // but here is global object

(bar.baz, bar.baz)(); // also global object

(false || bar.baz)(); // also global object

var otherFoo = bar.baz;

otherFoo(); // again global object

 

參考 http://dmitrysoshnikov.com/ecmascript/javascript-the-core/

關聯文章