深入理解javascript的作用域--函数声明为什么会前置

标签: javascript函数对象

这篇博文解决了以下迷惑

  • 函数声明为什么前置
  • 函数声明前置和变量前置优先级问题
  • 为什么js文件开头就可以使用Math,String等库,而不需要导入头文件

1.变量对象VO

变量对象(Variable Object, 缩写为VO)是一个抽象 
概念中的“对象”,它用于存储执行上下文中的: 
1. 变量 
2. 函数声明 
3. 函数参数

js解释器就是通过变量对象(VO)来找到我们定义的变量和函数的。

举个例子:

var a = 10;
function test(x) {
var b = 20;
}
test(30);

针对例子的浏览器js引擎的VO记录:
//全局作用域
VO(globalContext) = {
a : 10,//变量
test : <ref to function>//函数声明
};
//test函数的函数作用域
VO(test functionContext) = {
x : 30,//函数参数
b: 20//函数声明
};

2.js开头不需要加入头文件谜解

在js代码执行前,js引擎为我们初始化了如下全局作用域的VO。

在全局上下文中

//在全局上下文中 ,vo===this===global

VO(globalContext) === [[global]];
[[global]] = {
Math : <...>,
String : <...>,
isNaN : function() {[Native Code]}
...
...
window : global // applied by browser(host)
};

那么String(10)就相当于[[global]].String(10);了。这就是js代码执行前就可以调用Math等库的原因。

String(10); //[[global]].String(10);
window.a = 10; // [[global]].window.a = 10
this.b = 20; // [[global]].b = 20;
GlobalContextVO (VO === this === global)

其中有趣的是,我们发现global的window指向global自身,所以我们可以在浏览器中尝试window.window.window…一直循环调用下去,可以证明window是一个无限循环调用。

3.函数中的VO–AO

在函数上下文中,我们在进入函数上下文的时候创建vo,这时候称呼他为ao(activation object)。 
AO可以看作是VO的激活对象。

VO(functionContext) === AO;
AO = {
arguments : <Arg0>
};
arguments = {
callee,
length,
properties-indexes
};

函数的AO经历两个阶段

  1. 变量的初始化阶段
  2. 代码执行阶段

3.1变量初始化阶段

VO按照如下顺序填充: 
函数参数(若未传?入,初始化该参数值为undefined) 
函数声明(若发?生命名冲突,会覆盖) 
变量声明(初始化变量值为undefined,若发?生命名冲突,会忽略。)

举个例子:

function test(a, b) {
var c = 10;
function d() {}
var e = function _e() {};
(function x() {});
b = 20;
}
test(10);

VO扫描创建过程: 
添加所有传入参数:a:undefined,b:undefined 
添加所有函数声明,名字重复就覆盖:d:func 
添加所有变量声明,名字重复就忽略:c:undefined,e:undefined

因此它的VO:

AO(test) = {
a: 10,
b: undefined,
c: undefined,
d: <ref to func "d">
e: undefined
};

再来个例子:

function foo(x,y,z){
    function x(){};
    alert(x);
}
foo(100);

结果alert:function x(){}

分析:扫描传入参数:x:undefined,y:undefined,z:undefined 
扫描函数声明:x:func覆盖undefined 
扫描变量声明:无

最终x是func。

function foo(x,y,z){
    function func(){};
    var func;
    consoel.log(func);
}
foo(100);

结果console.log:func(){}

分析:扫描传入参数:x:undefined,y:undefined,z:undefined 
扫描函数声明:x:func覆盖undefined 
扫描变量声明:x名字冲突不更改,直接忽略。

最终x还是func。

3.2代码执行阶段

还是这个例子

function test(a, b) {
var c = 10;
function d() {}
var e = function _e() {};
(function x() {});
b = 20;
}
test(10);

之前分析之后的VO:

AO(test) = {
a: 10,
b: undefined,
c: undefined,
d: <ref to func "d">
e: undefined
};

十分好理解的为每个VO变量添加上数值

VO[‘c‘] = 10;
VO[‘e‘] = function _e() {};
VO[‘b‘] = 20;

结果是:

AO(test) = {
a: 10,
b: 20,
c: 10,
d: <reference to FunctionDeclaration "d">
e: function _e() {};
};

3.3练习

题目:

alert(x); // function
var x = 10;
alert(x); // 10
x = 20;
function x() {}
alert(x); // 20
if (true) {
var a = 1;
} else {
var b = true;
}
alert(a); // 1
alert(b); // undefined

解析: 
变量初始化阶段: 
寻找函数声明:x:func 
寻找变量声明:x不覆盖(还是func) a:undefined,b:undefined

所以第一行alert(x)返回function

代码执行阶段: 
x:10 
alert结果就是10 
x:20 
alert结果就是20

if语句没有产生新作用域,true永远为真,赋值a为1

最后两句打印a为1,b永远得不到执行,打印为undefined

时间: 2024-10-14 15:08:59

深入理解javascript的作用域--函数声明为什么会前置的相关文章

【JavaScript】Javascript中的函数声明和函数表达式

Javascript有很多有趣的用法,在Google Code Search里能找到不少,举一个例子: <script> ~function() { alert("hello, world."); }(); </script> 试一下就知道这段代码的意思就是声明一个函数,然后立刻执行,因为Javascript中的变量作用域是基于函数的,所以这样可以避免变量污染,但这里的位运算符『~』乍一看让人摸不到头脑,如果去掉它再运行则会报错:SyntaxError. 在阐述

深入理解javascript:揭秘命名函数表达式

这是一篇转自汤姆大叔的文章:http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html 前言 网上还没用发现有人对命名函数表达式进去重复深入的讨论,正因为如此,网上出现了各种各样的误解,本文将从原理和实践两个方面来探讨JavaScript关于命名函数表达式的优缺点. 简单的说,命名函数表达式只有一个用户,那就是在Debug或者Profiler分析的时候来描述函数的名称,也可以使用函数名实现递归,但很快你 就会发现其实是不切实际的.当然

深入理解javascript之作用域

简单地说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期.在javascript中,变量的作用域分为全局和局部两种. 拥有全局作用域就是在代码任何地方都能够访问到,叫做全局变量,以下三种情况可以拥有 全局作用域 最外层函数和最外层函数外面定义的变量: var name="brizer"; function doSomething(){ var realname="lf"; function innerSay(){ alert(realn

Javascript进阶(6)---函数声明

------------- JS中的函数也是一种对象 函数的返回值是return来决定的,没有则返回undefined 1.函数声明法 function add(a, b) { a = a + a; b = b + b; } 2.函数表达式法 var add = function (a , b){ ......}; (function(){......}()); return function (){......} var add =function foo(a,b){......}; //命名

JavaScript中的函数声明和函数表达式

JavaScript 中定义函数的方式有两种,一种是函数声明,另一种是函数表达式.这两种定义方式之间有一些细微的差别. 1.函数声明: function 关键字 + 函数名字 + 函数体构成了函数声明,具体形式如下: function functionName(arg0, arg1, arg2) {   // function body } Firefox. Safari. Chrome 和 Opera 都给函数定义了一个非标准的 name 属性,通过这个属性可以访问到给函数指定的名字: ale

深入理解JavaScript变量作用域

JS变量作用域特点: a.JS变量作用域是基于其特有的作用域链的. b.JavaScript没有块级作用域. c.函数中声明的变量在整个函数中都有定义. 1.作用域链 var rain = 1 ; function rainman(){ var man = 2 ; function inner(){ var innerVar = 4; alert(rain); } inner(); // 调用inner函数 } rainman(); // 调用rainman函数 观察alert(rain);这句

理解JavaScript中作用域链的关系

javascript里的关系又多又乱.作用域链是一种单向的链式关系,还算简单清晰:this机制的调用关系,稍微有些复杂:而关于原型,则是prototype.proto和constructor的三角关系.本文先用一张图开宗明义,然后详细解释原型的三角关系 概念 上图中的复杂关系,实际上来源就两行代码 function Foo(){}; var f1 = new Foo; [构造函数] 用来初始化新创建的对象的函数是构造函数.在例子中,Foo()函数是构造函数 [实例对象] 通过构造函数的new操作

理解JavaScript中回调函数的使用

首先要理解function 对象,在JavaScript中function和array object,number一样作为一个对象,因此function也可以像普通对象一样可以作为一个参数传递个另一个函数; function parentF(callback){//callback可以任意定义名称,它只是个参数,此时并不能确定callback的类型(是number,object,function都不确定) var a=1; var b=2;  console.log("parent")

深入理解JavaScript中的函数操作

匿名函数 对于什么是匿名函数,这里就不做过多介绍了.我们需要知道的是,对于JavaScript而言,匿名函数是一个很重要且具有逻辑性的特性.通常,匿名函数的使用情况是:创建一个供以后使用的函数. 简单的举个例子如下: window.onload = function() { alert('hello'); } var templateObj = { shout:function() { alert('作为方法的匿名函数') } } templateObj.shout(); setTimeout(