`

【转】Javascript中的函数和执行环境

阅读更多

转自:http://zhangbo-peipei-163-com.iteye.com/blog/1773959

 

函数是Javascript的主要组建部分,函数定义了诸如闭包、“this”关键字、全局变量、局部变量等诸多的特性。理解函数是真正理解Javascript工作机制的第一步。 

一、ExecutionContext的创建 
总所周知,函数能够访问声明在当前函数作用域“之外”的变量、全局变量、声明在函数内部的变量以及通过参数传进来的变量和指向“容器对象”的"this"变量。以上所有这些变量为我们的函数形成了一个“环境”,该“环境”定义了哪些变量和它们的值是可以被当前函数访问的。一部分“环境”是随着函数的定义而定义的,其他一些是函数访问的时候才定义的。 
当一个函数被访问时,一个ExecutionContext 被创建,ExecutionContext定义了函数“环境”大部分,接下来看一下ExecutionContext是怎么构建的(注意顺序): 

利用伪代码演示例子: 

Js代码  收藏代码
  1. function foo (a, b, c)   
  2. {  
  3.     function z(){alert(‘Z!’);}  
  4.     var d = 3;  
  5. }  
  6. foo(‘foo’,’bar’);  


1.    arguments属性被创建,arguments属性是一个类似数组的对象,该对象的整数类型的属性分别引用传递给函数的参数值,顺序和传参时顺序一致。arguments对象包含length(参数个数)和callee(引用被调用的函数本身)属性。 

Js代码  收藏代码
  1. ExecutionContext:   
  2. {  
  3.     arguments: {  
  4.         0: ‘foo’, 1: ‘bar’,  
  5.         length: 2, callee: function() //Points to foo function  
  6.     }  
  7. }  


2.    函数域被创建,[[scope]]属性和我们上面所说的ExecutionContext,更多的细节后面会讲到。 

3.    变量实例化,分为3个子步骤(也是有顺序的): 
3.1    ExecutionContext会为每一个定义在函数签名中的参数定义一个属性,如果在前面已经创建的arguments对象中对应的位置有一个值,这个值被分配给该属性,否则,该属性值为undefined。 

Js代码  收藏代码
  1. ExecutionContext: {  
  2.     arguments: {  
  3.         0: ‘foo’, 1: ‘bar’,  
  4.         length: 2, callee: function() //Points to foo function  
  5.     },  
  6.     a: ‘foo’, b: ‘bar’, c: undefined  
  7. }  


3.2    扫描函数体检测其中声明的函数————FunctionDeclarations,这些声明的函数被创建并且作为属性分配给ExecutionContext,属性名就是该函数名称。 

Js代码  收藏代码
  1. ExecutionContext: {  
  2.     arguments: {  
  3.         0: ‘foo’, 1: ‘bar’,  
  4.         length: 2, callee: function() //Points to foo function   
  5.     },  
  6.     a: ‘foo’, b: ‘bar’, c: undefined,  
  7.     z: function() //Created z() function  
  8. }  


3.3    扫描函数体检测其中声明的变量,这些变量作为ExecutionContext的属性保存在ExecutionContext中并且被初始化成undefined。 

Js代码  收藏代码
  1. ExecutionContext: {  
  2.     arguments: {  
  3.         0: ‘foo’, 1: ‘bar’,  
  4.         length: 2, callee: function() //Points to foo function  
  5.     },  
  6.     a: ‘foo’, b: ‘bar’, c: undefined,  
  7.     z: function(), //Created z() function,  
  8.     d: undefined  
  9. }  


4.    “this”属性被创建,它的值依赖于函数的访问方式。 
a.    正常函数(myFunction(1,2,3))。“this”指向全局对象(i.e. window)。 
b.    对象方法(myObject.myFunction(1,2,3))。“this”指向包含该函数的对象,例中的myObject对象。 
c.    类似于setTimeout() 或者 setInterval()的回调函数,“this”指向全局对象(i.e. window)。 
d.    call()或者apply()函数,“this”指向call()/apply()函数的第一个参数。 
e.    作为构造函数(new myFunction(1,2,3))。“this”是一个以myFunction.prototype作为原型的空对象。 

Js代码  收藏代码
  1. ExecutionContext: {  
  2.     arguments: {  
  3.         0: ‘foo’, 1: ‘bar’,  
  4.         length: 2,  callee: function() //Points to foo function  
  5.     },  
  6.     a: ‘foo’, b: ‘bar’, c: undefined,  
  7.     z: function(), //Created z() function,  
  8.     d: undefined,  
  9.     this: window  
  10. }  


当ExecutionContext创建完成之后,函数开始从代码第一行执行,直到遇到遇到return或者函数结束。代码每次尝试使用变量,都会从ExecutionContext对象中读取。 

二、关于ExecutionContext栈 
在Javascript中,每一个单一的指令都是在ExecutionContext对象中被执行。我们已经知道,任何函数中所有的代码都将有一个ExecutionContext与之关联,无论该函数怎么被创建,怎么被执行。因此,任何函数中的每一个单一的语句都是在该函数的ExecutionContext中被执行。那些不属于任何函数的全局代码(执行的内联代码、通过<script>标签装载的代码、通过eval()函数执行的代码)被关联到一个叫做GlobalExecutionContext的上下文中。GlobalExecutionContext 
的工作机制非常类似ExecutionContext,但是没有方法参数,只包含2、3、4(this指向全局对象,常常是window对象)三步。通过以上得出结论:每一个Javascript语句运行都是在ExecutionContext中进行。程序运行中,有时候需要从一个函数跳转到另外一个函数(直接调用、DOM事件、定时器等)。由于每一个函数都有自己的ExecutionContext,所以这些函数的相互调用将形成一个上下文的栈,例如下面的代码: 

Js代码  收藏代码
  1. <script>  
  2.     function a() {  
  3.         function b() {  
  4.             var c = {  
  5.                 d: function() {  
  6.                     alert(1);  
  7.                 }  
  8.             };  
  9.             c.d();  
  10.         }  
  11.         b.call({});  
  12.     }  
  13.     a();  
  14. </script>  


当Javascript引擎即将执行alert()方法的时候,形成的ExecutionContext栈如下: 

Js代码  收藏代码
  1. d() Execution context.  
  2. b() Execution context.  
  3. a() Execution context.  
  4. Global Execution context.  


ExecutionContext栈中最重的部分发生在函数被定义的时候,要充分认识JavaScript的上下文的关键点是每一个声明的函数都是在ExecutionContext中被执行(前面的例子中:b()函数被创建和执行都是在a()函数的ExeuctionContext中)。每一次一个函数被创建,当前的 ExecutionContext栈就被保存到该函数自己的[[scope]]属性中,这个过程全部发生在函数创建的过程中,这个栈被保存和绑定到新创建的函数中,尽管以前的函数已经执行完(例如原来的函数将创建的函数作为返回值返回)。 

现在回头看一下第2步中函数域的创建: 
当函数被访问时,一个新的ExecutionContext栈被创建,然后将函数的ExecutionContext压入到前面提到的[[scope]]属性顶。这个栈我们也称之为 
“scope chain”.注意ExecutionContext栈可以并且常常是和calling stack不同的,后者是函数调用时被定义,前者是函数定义的时候就被定义。例如,函数a()调用函数b(),函数b()调用函数c(),那么这种“calling stack”可能在任何调试工具中被检查到,进一步,函数c()可能在函数d()中被创建,那么函数d()关联的ExecutionContext是“scope chain”的一部分,但是不属于“calling stack”。当函数内的代码查找一个变量,“scope chain”将被检查,引擎将在“scope chain”的第一个ExecutionContext中搜索该变量,这个ExecutionContext也是函数自己的ExecutionContext,通过搜索函数参数,变量等进行匹配,如果没有找到,引擎将继续在“scope chain”的下一个ExecutionContext中查找,一次类推直到“scope chain”的最后一个ExecutionContext,如果始终没有找到,就返回undefined作为该变量的值,如果在中间某个ExecutionContext中找到,就直接返回其中的值赋给变量。 

三、总结函数和其执行上下文的要点 
1.    this与函数不耦合也不是一个特殊的属性,更像一个普通的参数。它在函数调用的时候被定义,所以说相同的函数执行"this"是可以不一样的。 
2.    arguments不是一个数组,而是一个以数字作为属性名称的普通对象,所以它没有继承像push()、concat()和slice()的数组方法。 
3.    变量实际上在第3.3步被定义,无论变量定义到函数的什么地方,都要等到执行流到达该变量初始化指令代码的时候才初始化它们。这就是为什么我们的例子中d一开始指向undefined,当代码执行到函数第2行的时候d才指向3。函数及变量定义的提升 
4.    你能在一个函数被定义之前调用它,这依赖于3.2中ExecutionContext的创建(函数表达式不成立) 
5.    所有的内部声明函数都将在ExecutionContext阶段被创建,所以一个遥不可及的函数声明有可能始终被创建,比如: 

Js代码  收藏代码
  1. function foo() {  
  2.     if (false) {  
  3.         function bar() {alert(1);};  
  4.     }  
  5.     bar();  
  6. }  


以上代码在浏览器(IE8、Chrome和Safari5,Firefox不可以)中将正常运行,因为函数bar()在ExecutionContext阶段被创建,此时函数代码还没有开始执行,if条也没有被评估。 
6.    变量可以被隐藏。因为所有的步骤发生都是有顺序的,后发生的步骤有可能覆盖之前发生的步骤,比如:如果我们在函数签名中定义一个叫foo的参数,然后在函数体中声明一个函数也叫foo,那么当ExecutionContext创建完之后后面的foo变量将覆盖前面的foo变量。 
7.    闭包:一个函数能访问其“父函数”的变量。当访问一个变量在当前的 ExecutionContext中没有找到,在其“父函数”的ExecutionContext中找到,就形成了闭包。你甚至可以建立很多复杂的闭包通过使用当前函数的"父函数"、"祖父函数"等等中的数据。返回函数和闭包 
8.    Javascript中的全局变量。一个变量可以一直被查到“scope chain”的最后一项,即GlobalExecutionContext(这就是为什么全局变量访问相对较慢的原因,因为引擎将搜索完所有的关联的Context直到最后才访问GlobalExecutionContext)。同样你能使用类似全局变量:如果不用的函数拥有一个相同的ExecutionContext,那么声明在该ExecutionContext中的所有的变量都可以像全局变量一样被不同的函数访问 

四、总结 
Javascript代码实际上就是ExecutionContext和scope chains,该语言的大多数功能都可以从上下文的行为得到中提升。如果你习惯于在一个交互的上下文中设计你的项目,你的代码将更加的简单而且自然。例如,如果你心中有上下文的概念,一个mixin-based继承是很容易实现的;大部分加载的Javascript库都只依赖自身的管理模块上下文而不会污染全局环境。归纳起来,我们通过ExecutionContext和scope chains(而不是函数或者对象)来思考Javascript会使这门语言释放更大的能量,确保尽量深层次的去理解他们。 

分享到:
评论

相关推荐

    【JavaScript源代码】详解JavaScript中的执行上下文及调用堆栈.docx

    详解JavaScript中的执行上下文及调用堆栈  目录 一、执行上下文是什么二、执行上下文栈是什么三、执行上下文栈的过程...函数执行环境:每当执行流程进入到一个函数体内部的时候 Eval执行环境:当eval函数内部的文本

    JavaScript 函数的执行过程

    1. 每一个JavaScript函数都是Function对象的一个实例, 它有一个仅供JavaScript引擎存取的内部属性[[Scope]]....3. 当函数被执行时, 函数的执行环境会被推入一个环境栈中: 1.此时进入函数的变量初始化阶段, 此阶段会确

    深入Javascript函数、递归与闭包(执行环境、变量对象与作用域链)使用详解

    1、JavaScript中定义函数有2钟方法:  1-1.函数声明: 代码如下:function funcName(arg1,arg2,arg3){ //函数体} ①name属性:可读取函数名。非标准,浏览器支持:FF、Chrome、safari、Opera。 ②函数声明提升:指...

    详解JavaScript函数绑定

    在JavaScript与DOM交互中经常需要使用函数绑定,定义一个函数然后将其绑定到特定DOM元素或集合的某个事件触发程序上,绑定函数经常和回调函数及事件处理程序一起使用,以便把函数作为变量传递的同时保留代码执行环境

    JavaScript执行环境及作用域链实例分析

    执行环境包括全局执行环境和函数执行环境。 全局执行环境是最外围的一个执行环境,在浏览器中,全局执行环境被认为是是window对象,所有全局变量和属性都是作为window对象的属性和方法创建的。 函数执行环境是指函数...

    详释JavaScript执行环境与执行栈

    执行环境 ( 也称”执行上下文” ) 可以说是 JavaScript 最重要的一个概念。那么执行环境到底是什么呢?一句话就可以概括:代码 ( 包括函数 ) 执行时所需要的所有信息就是执行环境。由于 ES 历经多个版本,所以执行...

    javascript中eval函数用法分析

    本文实例分析了javascript中eval函数用法。分享给大家供大家参考。...最为重要的是,它使用了调用它的变量作用域环境,即它查找变量的值和定义新变量和函数的操作和局部作用域的代码完全一样。 eval(va

    JS匿名函数、闭包

    在后台执行环境中,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域; 通常,函数的作用域及其所有变量都会在函数执行结束后被销毁; 但是,当函数返回了一个闭包时,这个函数的作用域将会...

    javascript中关于执行环境的杂谈

    ——这就要从JAVASCRIPT的解释器开始说起了:每当JAVASCRIPT解释器开始执行一个函数的时候,都会创建一个执行环境,并且还会产生一个和这个函数息息相关的变量对象,在这个执行环境中定义的一切变量或者函数都会被他...

    JavaScript闭包和立即执行函数的个人笔记

    闭包和立即执行函数一、闭包二、闭包的作用三、闭包形式四、立即执行函数五、使用环境六、小练习 一、闭包 当a函数已经执行完了,b函数才开始。 原创文章 10获赞 3访问量 453 关注 私信 展开阅读全文 作者:...

    JavaScript中的执行环境和作用域链

    执行环境定义了变量或者函数有权访问的数据集合,每一个执行环境都有一个与之关联的变量对象,该执行环境中定义的所有变量和函数都保存在这个对象中。我们无法直接访问这个对象,这个对象只是在解析器处理数据的时候...

    javascript执行环境及作用域详解

    环境中定义的所有变量和函数都保存在这个对象中。虽然我们在编写代码的时候无法访问这个对象,但解析器在处理数据时会在后台用到它。  执行环境是一个概念,一种机制,它定义了变量或函数是否有权访问其他数据  在...

    浅谈javascript中执行环境(作用域)与作用域链

    一般情况下,我们把执行环境分为全局执行环境和局部执行环境,其中局部执行环境我们又可以称之为函数执行环境。那么究竟什么使执行环境呢?通俗的说,执行环境即为代码执行时所处的环境。我们下来看一看如下代码,再...

    浅谈JavaScript 执行环境、作用域及垃圾回收

    变量对象:环境中定义的所有变量和函数都保存在这个对象中。 作用域链:当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的...

    JavaScript高级程序设计(第3版)学习笔记8 js函数(中)

    6、执行环境和作用域 (1)执行环境(execution context):所有的JavaScript代码都运行在一个执行环境中,当控制权转移至JavaScript的可执行代码时,就进入了一个执行环境。活动的执行环境从逻辑上形成了一个栈,...

    javascript作用域链与执行环境详解

    作用域、作用域链、执行环境、执行环境栈以及this的概念在javascript中非常重要,本人经常弄混淆,这里梳理一下; 局部作用域函数内部的区域,全局作用域就是window; 作用域链取决于函数被声明时的位置,解析...

    javascript学习笔记.docx

    2) 一个应用程序出现的每个窗口或框架都对应一个Window对象,而且都为客户端JavaScript代码定义了一个唯一的执行环境。 3) 脚本执行过程是Web浏览器的HTML解析过程的一部分。脚本按照它们的出现顺序执行。 4) 简单的...

    JavaScript 语言基础教程

    轻量级:JavaScript 是一种轻量级的编程语言,不需要昂贵的开发环境,只需一个文本编辑器和浏览器即可开始编程。 解释型语言:JavaScript 代码在运行时被解释执行,无需编译。 动态类型:JavaScript 是一种动态类型...

    JavaScript经典实例

     第6章使用JavaScript函数构建重用性  6.0简介  6.1创建一段可重用的代码  6.2把单个数据值传递到函数  6.3把复杂的数据对象传递给函数  6.4创建一个动态运行时函数  6.5把一个函数当做参数传递给另一个函数 ...

Global site tag (gtag.js) - Google Analytics