首頁 > web前端 > js教程 > 主體

什麼是JS變數物件? JS變數物件詳解以及注意事項

php是最好的语言
發布: 2018-07-23 11:10:50
原創
2667 人瀏覽過

在JavaScript中,變數物件是什麼?本文首先介紹了變數物件的概念,以及上下文中的變數物件怎麼執行的,變數物件中的程式碼是如何被處理的,最後又介紹了變數是什麼?

變數物件就是執行上下文作用域鏈中間的橋樑。
劇透一下,神秘的 this 就存在於執行情境環境之中!
當然,之後我會單獨用幾節來徹底講明白 this 到底是什麼(其實 this 很簡單)。

接下來,我們進入正文。

1. 執行上下文包含什麼

一個執行上下文我們可以抽象的理解為物件(object)。
每一個執行上下文都有一些屬性(又稱為上下文狀態),它們用來追蹤關聯程式碼的執行進度。

我用一個結構圖來說明:

执行上下文环境 object

Variable Object# 就代表變數物件。
Scope Chain 代表作用域鏈。
thisValue 代表神祕的 this 。

作用域鍊和 this 留到後面再講,今天我們先來弄清楚變數物件

2. 變數物件

A variable object is a scope of data related with the execution context. It's a special object associated with the context and which stores variables and faretion declarations are being defined within the context.

變數物件(variable object) 是與執行上下文相關的資料作用域(scope of data) 。它是與上下文關聯的特殊對象,用於儲存被定義在上下文中的 變數(variables) 和 函數宣告(function declarations) 。

變數物件(Variable Object -- 簡寫VO)是一個抽象的概念,指涉與執行上下文相關的特殊對象,它儲存著在上下文中聲明的:
變數(var)
函數宣告 (function declaration,簡稱FD)
函數的形參(arguments)

我們假設變數物件為一個普通ECMAScript 物件:

VO = {};
登入後複製

就像前面講過的,VO 是執行上下文的屬性:

activeExecutionContext = {
  VO: {
    // 上下文数据 (vars, FD, arguments)
  }
}
登入後複製

因為變數物件是一個抽象的概念,所以並不能透過變數物件的名稱直接訪問,但是卻可以透過別的方法來間接存取變數對象,例如在全域情境環境的變數物件會有一個屬性window (DOM 中) 可以引用變數物件自身,全域上下文環境的另一個屬性this 也指向全域上下文環境的變數物件。

舉例:

var a = 2;

function foo (num) {
   var b = 5;
}

(function exp () {
   console.log(111);
})

foo(10);
登入後複製

這裡對應的變數物件是:

// 全局上下文环境的变量对象
VO(globalContext) = {
   // 一些全局环境初始化时系统自动创建的属性: Math、String、Date、parseInt等等
   ···

   // 全局上下文的变量对象中有一个属性可以访问到自身,在浏览器中这个属性是 window ,在 node 中这个属性是 global
   window: global

   // 自己定义的属性
   a: 10,
   foo: <reference to function>
};

// foo 函数上下文的变量对象
VO(foo functionContext) = {
   num: 10,
   b: 5
};
登入後複製

注意:函數表達式並不包括在變數物件中。

3. 不同執行上下文中的變數物件

執行上下文包括:全域上下文、函數上下文和 eval() 上下文。

全域上下文中的變數物件

這裡我們先來了解什麼是全域物件:

全局对象(global object)是指在进入任何执行上下文之前就已经创建了的对象。
这个对象只有一份,它的属性在程序中的任何地方都可以访问,全局对象的生命周期终止于程序退出的那一刻。
登入後複製

全域物件初始化時系統將會建立並初始化一系列原始屬性,例如:Math、String、Date、parseInt、window等等,之後是我們在全域上下文中自己定義的全域變數。在 DOM 中,全域物件的 window 屬性可以引用全域物件自身,全域上下文環境的 this 屬性也可以引用全域物件。

// 全局执行上下文环境
EC(globalContext) = {
   // 全局对象(全局上下文环境的变量对象) 
   global: {
      Math: <...>,
      String: <...>,
      ...
      ...
      window: global     // 引用全局对象自身
   },

   // this 属性
   this: global

   // 作用域链
   ...
}
登入後複製

舉例:

var a = 10;

console.log(a);               // 10
console.log(window.a);        // 10
console.log(this.a);          // 10
登入後複製

因此,在全域上下文環境中,變數物件用全域物件來表示。

函數上下文中的變數物件

在函數上下文中,變數物件以活動物件 AO(Active Object)來表示。

VO(functionContext) = AO
登入後複製

活動物件是在進入函數上下文時刻被建立的,它是透過函數的 arguments 屬性進行初始化。 arguments 也是一個物件。

AO = {
   arguments: {
      ...
   }
}
登入後複製

arguments 是活動物件的屬性,它也是一個對象,包括以下屬性:
1. callee - 指向目前函數的參考
2. length - 真正傳遞的參數數量
3. properties-indexes - index 是字串類型的整數,例如"1": "aa",類似於數組類型,也可以透過arguments[1]來訪問,但是不能用數組的方法(push, pop等等)。另外,properties-indexes 的值和實際傳遞進來的參數之間是共享的,一個改變,另一個也隨之改變。

舉例:

function foo (x, y, z) {

   // 声明的函数参数数量
   console.log(foo.length);      // 3

   // 实际传递进来的参数数量
   console.log(arguments.length);      // 2

   // arguments 的 callee 属性指向当前函数
   console.log(arguments.callee === foo)   // true

   // 参数共享
   console.log(x === arguments[0]);      // true
   console.log(x);      // 10

   arguments[0] = 20;
   console.log(x);   // 20

   x = 30;
   console.log(arguments[0]);    // 30

   // 但是注意,没有传递进来的参数 z ,和第3个索引值是不共享的
   z = 40;
   console.log(arguments[2]);      // undefined

   arguments[2] = 50;
   console.log(z);      // 40
}

foo(10, 20);
登入後複製

4. 程式碼是如何被處理的

在第1節我們講過js 程式碼的編譯過程,其中有一步就叫作預編譯,是說在程式碼執行前的幾微秒會先對程式碼進行編譯,形成詞法作用域,然後執行。

那麼執行上下文的程式碼就可以分成兩個階段來處理:
1. 進入執行上下文(預編譯)
2. 執行程式碼

而变量对象的修改变化和这两个阶段是紧密相关的。
并且所有类型的执行上下文都会有这2个阶段。

进入执行上下文

当引擎进入执行上下文时(代码还未执行),VO 里已经包含了一些属性:
1. 函数的所有形参(如果是函数执行上下文)
由名称和对应值组成的一个变量对象的属性被创建,如果没有传递对应的实参,那么由名称和 undefined 组成的一种变量对象的属性也会被创建。

2.所有的函数声明(Function Declaration - FD)
由名称和对应值(函数对象 function object)组成的一个变量对象的属性被创建,如果变量对象已经存在相同名称函数的属性,则完全替换这个属性。

3.所有的变量声明(Variable Declaration - var)
由名称和对应值(在预编译阶段所有变量值都是 undefined)组成的一个变量对象的属性被创建,如果变量名和已经声明的形参或者函数相同,则变量名不会干扰已经存在的这类属性,如果已经存在相同的变量名,则跳过当前声明的变量名。

注意:变量碰到相同名称的变量是忽略,函数碰到相同名称的函数是覆盖。

举个例子:

function test(a, b, c) {
            
    console.log(a); // 函数体a
    console.log(b);  // 20
    function a() {
         console.log(1);
    }
    var a = 100;
    console.log(a);  // 100
    var b = 2;
    console.log(b); // 2

}
test(10,20,30);
登入後複製
function foo (a, b) {
   var c = 5;

   function bar () {};

   var d = function _d () {};

   (function f () {});
}

foo(10);
登入後複製

当进入带有实参10的 foo 函数上下文时(预编译时,此时代码还没有执行),AO 结构如下:

AO(foo) = {
   a: 10,
   b: undefined,

   c: undefined,
   bar: <reference to FunctionDelcaration "bar">,
   d: undefined 
};
登入後複製

注意,函数表达式 f 并不包含在活动对象 AO 内。
也就是说,只有函数声明会被包含在变量对象 VO 里面,函数表达式并不会影响变量对象。

行内函数表达式 _d 则只能在该函数内部可以使用, 也不会包含在 VO 内。

这之后,就会进入第2个阶段,代码执行阶段。

代码执行

在这个阶段,AO/VO 已经有了属性(并不是所有的属性都有值,大部分属性的值还是系统默认的初始值 undefined)。

AO 在代码执行阶段被修改如下:

AO[&#39;c&#39;] = 5;
AO[&#39;d&#39;] = <reference to FunctionDelcaration "_d">
登入後複製

再次要提醒大家,因为函数表达式 _d 已经保存到了声明的变量 d 上面,所以变量 d 仍然存在于 VO/AO 中。我们可以通 d() 来执行函数。但是函数表达式 f 却不存在于 VO/AO 中,也就是说,如果我们想尝试调用 f 函数,不管在函数定义前还是定义后,都会出现一个错误"f is not defined",未保存的函数表达式只有在它自己的定义或递归中才能被调用。

再来一个经典例子:

console.log(x);      // function

var x = 10;
console.log(x);      // 10

x = 20;

function x () {};

console.log(x);      // 20
登入後複製

这里为什么是这样的结果呢?

上边我们说过,在代码执行之前的预编译,会为变量对象生成一些属性,先是形参,再是函数声明,最后是变量,并且变量并不会影响同名的函数声明。

所以,在进入执行上下文时,AO/VO 结构如下:

AO = {
   x: <reference to FunctionDeclaration "x">

   // 在碰到变量声明 x 时,因为已经存在了函数声明 x ,所以会忽略
}
登入後複製

紧接着,在代码执行阶段,AO/VO 被修改如下:

AO[&#39;x&#39;] = 10;
AO[&#39;x&#39;] = 20;
登入後複製

希望大家可以好好理解变量对象,对于理解我们后边要讲的作用域链有很大的帮助。

5. 变量

有一些文章说过:

不管是使用 var 关键字(在全局上下文)还是不使用 var 关键字(在任何地方),都可以声明一个变量。

请记住,这是错误的观念。

任何时候,变量都只能通过使用 var 关键字来声明(ES6 之前)

a = 10;
登入後複製

上面的赋值语句,仅仅是给全局对象创建了一个新属性(在非严格模式,严格模式下会报错),但注意,它不是变量。“不是变量”并不是说它不能被改变,而是指它不符合ECMAScript 规范中变量的概念。

让我们通过一个例子来看一下两者的区别:

console.log(a);        // undefined
console.log(b);        // 报错,b is not defined

b = 10;
var a = 20;
登入後複製

只要我们很好的理解了:变量对象、预编译阶段和执行代码阶段,就可以迅速的给出答案。

预编译(进入上下文)阶段:

VO = {
   a: undefined
}
登入後複製

我们可以看到,因为 b 不是通过 var 声明的,所以这个阶段根本就没有 b ,b 只有在代码执行阶段才会出现。但是在这个例子中,还没有执行到 b 那就已经报错了。

我们稍微更改一下示例代码:

console.log(a);      // undefined

b = 10;
console.log(b);             // 10 代码执行阶段被创建
console.log(window.b);      // 10
console.log(this.b);        // 10

var a = 20;
console.log(a);      // 20 代码执行阶段被修改
登入後複製

关于变量,还有一个很重要的知识点。

变量不能用 delete 操作符来删除。

a = 10;

console.log(window.a);    // 10

console.log(delete a);    // true

console.log(window.a);    // undefined

var b = 20;
console.log(window.b);    // 20

console.log(delete b);    // false

console.log(window.b);    // 20
登入後複製

注意:这个规则在 eval() 上下文中不起作用。

eval(&#39;var a = 10;&#39;);
console.log(window.a);    // 10

console.log(delete a);    // true

console.log(window.a);    // undefined
登入後複製

 相关推荐:

 js高级面向对象和组件开发视频教程

js 多种变量定义(对象直接量,数组直接量和函数直接量)_javascript技巧

以上是什麼是JS變數物件? JS變數物件詳解以及注意事項的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板