关闭

js基础_究竟什么是变量对象,什么是活动对象?

时间: 2018-12-02阅读: 1276标签: 对象

1.什么是变量对象(variable Object)

在写程序的时候会定义很多变量和函数,那js解析器是如何找到这些变量和函数的?

变量对象是与执行上下文对应的概念,在执行上下文的创建阶段,它依次存储着在上下文中定义的以下内容:


1.1 函数的所有形参(如果是函数上下文中):

建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值。没有实参的话,属性值为undefined。


1.2. 所有函数声明:(FunctionDeclaration, FD)

检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果变量对象已经存在相同名称的属性,则完全替换这个属性。


1.3. 所有变量声明:(var, VariableDeclaration)

检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。


2.什么是活动对象?(activation object, AO)

只有全局上下文的变量对象允许通过VO的属性名称来间接访问,在其他上下文(后面干脆直接讲函数上下文吧,我们并没有分析eval上下文)中是不能直接访问VO对象的。

在函数上下文中,VO是不能直接访问的,此时由活动对象AO继续扮演VO的角色。

未进入执行阶段前,变量对象中的属性都不能访问!但是进入到执行阶段之后,变量对象转变成了活动对象,里面的属性都能被访问了,然后开始进行执行阶段的操作。

因此,对于函数上下文来讲,活动对象与变量对象其实都是同一个对象,只是处于执行上下文的不同生命周期。不过只有处于执行上下文栈栈顶的函数执行上下文中的变量对象,才会变成活动对象。


3.举个例子

说了一堆概念,有点懵,对吗?请看这个例子:

var a = 10;
function b () {
    console.log('全局的b函数')
};
function bar(a, b) {
    console.log('1', a, b) 
    var a = 1
    function b() {
        console.log('bar下的b函数')
    }
    console.log('2', a, b) 
}
bar(2, 3)
console.log('3', a, b)

要想知道为什么会这样打印,首先,从执行上下文的创建阶段来分析变量对象:

// 创建阶段:
// 第一步,遇到了全局代码,进入全局上下文,此时的执行上下文栈是这样
ECStack = [
    globalContext: {
        VO: {
            // 根据1.2,会优先处理全局下的b函数声明,值为该函数所在内存地址的引用
            b: <reference to function>,
            // 紧接着,按顺序再处理bar函数声明,此时根据1.1,因为是在全局上下文中,并不会分析bar函数的参数
            bar: <refernce to function>,
            // 根据1.3,再处理变量,并赋值为undefined
            a: undefined
        }
    }
];
// 第二步,发现bar函数被调用,就又创建了一个函数上下文,此时的执行上下文栈是这样
ECStack = [
    globalContext: {
        VO: {
            b: <reference to function b() {}>, 
            bar: <refernce to function bar() {}>,
            a: undefined
        }
    },
    <bar>functionContext: {
        VO: {
            // 根据1.1,优先分析函数的形参
            arguments: {
                0: 2,
                1: 3,
                length: 2,
                callee: bar
            },
            a: 2,
            // b: 3,
            // 根据1.2, 再分析bar函数中的函数声明b,并且赋值为b函数所在内存地址的引用, 它发现VO中已经有b:3了,就会覆盖掉它。因此上面一行中的b:3实际上不存在了。
            b: <refernce to function b() {}>
            // 根据1.3,接着分析bar函数中的变量声明a,并且赋值为undefined, 但是发现VO中已经有a:2了,因此下面一行中的a:undefined也是会不存在的。
            // a: undefined
        }
    }
]

以上就是执行上下文中的代码分析阶段,也就是执行上下文的创建阶段。再看看执行上下文的代码执行阶又发生了什么。

// 执行阶段:
// 第三步:首先,执行了bar(2, 3)函数,紧接着,在bar函数里执行了console.log('1', a, b)。全局上下文中依然还是VO,但是函数上下文中VO就变成了AO。并且代码执行到这,就已经修改了全局上下文中的变量a.
ECStack = [
    globalContext: {
        VO: {
            b: <reference to function b() {}>, 
            bar: <refernce to function bar() {}>,
            a: 10,
        }
    },
    <bar>functionContext: {
        AO: {
            arguments: {
                0: 2,
                1: 3,
                length: 2,
                callee: bar
            },
            a: 2,
            b: <refernce to function b() {}>
        }
    }
]

// 因此会输出结果: '1', 2, function b() {console.log('bar下的b函数')};

// 第四步:执行console.log('2', a, b)的时候, 发现里面的变量a被重新赋值为1了。
ECStack = [
    globalContext: {
        VO: {
            b: <reference to function b() {}>, 
            bar: <refernce to function bar() {}>,
            a: 10,
        }
    },
    <bar>functionContext: {
        AO: {
            arguments: {
                0: 2,
                1: 3,
                length: 2,
                callee: bar
            },
            a: 1,
            b: <refernce to function b() {}>
        }
    }
]
// 因此会输出结果: '2', 1, function b() {console.log('bar下的b函数')};

// 第五步,执行到console.log('3', a, b)的时候,ECStack发现bar函数已经执行完了,就把bar从ECStack给弹出去了。此时的执行上下文栈是这样的。

ECStack = [
    globalContext: {
        VO: {
            b: <reference to function b() {}>, 
            bar: <refernce to function bar() {}>,
            a: 10,
        }
    }
]

// 因此会输出结果: '3', 10, function b() {console.log('全局的b函数')}

总结一下,变量对象会有以下四种特性:

  1. 全局上下文的变量对象初始化是全局对象(其实这篇文章并没有介绍这个特性,不过它也很简单就这么一句话而已)
  2. 函数上下文的变量对象初始化只包括Arguments对象
  3. 在进入执行上下文的时候会给变量对象添加形参,函数声明,变量声明等初始的属性值
  4. 在代码执行阶段,会再次修改变量对象的属性值。


理解了这些,是不是发现再有一些函数提升,变量提升什么的是不是都很简单了。例如,你可以思考下这三段代码分别发生了什么。

foo() 
var foo = function() {console.log(1)}
function foo() {console.log(2)}
foo() 
function foo() {console.log(2)}
var foo = function() {console.log(1)}
var foo = function() {console.log(1)}
function foo() {console.log(2)}
foo() 
站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/1473

JS构造函数、原型对象、隐含参数this

解析器再调用函数每次都会向函数内部传递一个隐含的参数this,this指向的是一个对象(函数执行的上下文对象),使用工厂的方法创建对象:通过该方法可以大批量的创建对象,这样创建的对象使用的构造函数都是Object,无法区分多种不同类型的对象。

JavaScript中创建对象的7种模式

ECMA-262把对象定义为:”无需属性的集合,其属性可以包含基本值、对象或者函数。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。正因为这样,我们可以把ECMAScript的对象想象成散列表:无非就是一组名对值,其中值可以是数据或函数。

Js ES6 Promiss对象

Node.js中,以异步(Async)回调著称,使用了异步,提高了程序的执行效率,但是,代码可读性较差的。假如有几个异步操作,后一个操作需要前一个操作的执行完毕之后返回的数据才能执行下去,如果使用Node.js,就需要一层层嵌套下去

js中arguments对象_理解arguments参数

在js中万物皆对象,甚至数组字符串函数都是对象。所以这个叫做arguments的东西也是个对象,而且是一个特殊的对象,它的属性名是按照传入参数的序列来的,第1个参数的属性名是0,第2个参数的属性名是1,以此类推,并且它还有length属性,

如何在JavaScript中访问暂未存在的嵌套对象

JavaScript 是个很神奇的东西。但是 JavaScript中的一些东西确实很奇怪,让人摸不着头脑。其中之一就是当你试图访问嵌套对象时,会遇到这个错误

JS对象的创建方式

组合模式(构造函数+原型模式)这是常用的创建方式。通过构造函数模式定义实例属性,通过原型模式定义方法和共享的属性。

深入理解js对象中数据属性和访问器属性

创建对象的方式有两种:第一种,通过new操作符后面跟Object构造函数,第二种,对象字面量方式。两种方式创建出来的对象是一样的,有相同的属性和方法。这些属性内部都有描述其行为的属性描述符。

javascript中内置对象和浏览器对象的区别是什么?

JavaScript对象是包含相关属性和方法的集合体。在 JavaScript 中,对象是数据(变量),拥有属性和方法。在javascript中对象通常包括两种类型:内置对象和浏览器对象,此外,用户还可以自定义对象。

可以迭代大部分数据类型的 for…of 为什么不能遍历普通对象?

我们知道,ES6 中引入 for...of 循环,很多时候用以替代 for...in 和 forEach() ,并支持新的迭代协议。for...of 允许你遍历 Array(数组), String(字符串), Map(映射), Set(集合),TypedArray(类型化数组)、arguments、NodeList对象

JS声明对象时属性名加引号与不加引号的问题

JS声明对象时属性名加引号与不加引号的问题,一般情况下属性名加引号和不加引号是都可以的,效果是一样的。如果属性名是数字,则必须用“”包围,并且用 [] 方括号访问。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!