函数是Javascript的主要组建部分,函数定义了诸如闭包、“this”关键字、全局变量、局部变量等诸多的特性。理解函数是真正理解Javascript工作机制的第一步。
一、ExecutionContext的创建
总所周知,函数能够访问声明在当前函数作用域“之外”的变量、全局变量、声明在函数内部的变量以及通过参数传进来的变量和指向“容器对象”的"this"变量。以上所有这些变量为我们的函数形成了一个“环境”,该“环境”定义了哪些变量和它们的值是可以被当前函数访问的。一部分“环境”是随着函数的定义而定义的,其他一些是函数访问的时候才定义的。
当一个函数被访问时,一个ExecutionContext 被创建,ExecutionContext定义了函数“环境”大部分,接下来看一下ExecutionContext是怎么构建的(注意顺序):
利用伪代码演示例子:
function foo (a, b, c)
{
function z(){alert(‘Z!’);}
var d = 3;
}
foo(‘foo’,’bar’);
1. arguments属性被创建,arguments属性是一个类似数组的对象,该对象的整数类型的属性分别引用传递给函数的参数值,顺序和传参时顺序一致。arguments对象包含length(参数个数)和callee(引用被调用的函数本身)属性。
ExecutionContext:
{
arguments: {
0: ‘foo’, 1: ‘bar’,
length: 2, callee: function() //Points to foo function
}
}
2. 函数域被创建,[[scope]]属性和我们上面所说的ExecutionContext,更多的细节后面会讲到。
3. 变量实例化,分为3个子步骤(也是有顺序的):
3.1 ExecutionContext会为每一个定义在函数签名中的参数定义一个属性,如果在前面已经创建的arguments对象中对应的位置有一个值,这个值被分配给该属性,否则,该属性值为undefined。
ExecutionContext: {
arguments: {
0: ‘foo’, 1: ‘bar’,
length: 2, callee: function() //Points to foo function
},
a: ‘foo’, b: ‘bar’, c: undefined
}
3.2 扫描函数体检测其中声明的函数————FunctionDeclarations,这些声明的函数被创建并且作为属性分配给ExecutionContext,属性名就是该函数名称。
ExecutionContext: {
arguments: {
0: ‘foo’, 1: ‘bar’,
length: 2, callee: function() //Points to foo function
},
a: ‘foo’, b: ‘bar’, c: undefined,
z: function() //Created z() function
}
3.3 扫描函数体检测其中声明的变量,这些变量作为ExecutionContext的属性保存在ExecutionContext中并且被初始化成undefined。
ExecutionContext: {
arguments: {
0: ‘foo’, 1: ‘bar’,
length: 2, callee: function() //Points to foo function
},
a: ‘foo’, b: ‘bar’, c: undefined,
z: function(), //Created z() function,
d: undefined
}
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作为原型的空对象。
ExecutionContext: {
arguments: {
0: ‘foo’, 1: ‘bar’,
length: 2, callee: function() //Points to foo function
},
a: ‘foo’, b: ‘bar’, c: undefined,
z: function(), //Created z() function,
d: undefined,
this: window
}
当ExecutionContext创建完成之后,函数开始从代码第一行执行,直到遇到遇到return或者函数结束。代码每次尝试使用变量,都会从ExecutionContext对象中读取。
二、关于ExecutionContext栈
在Javascript中,每一个单一的指令都是在ExecutionContext对象中被执行。我们已经知道,任何函数中所有的代码都将有一个ExecutionContext与之关联,无论该函数怎么被创建,怎么被执行。因此,任何函数中的每一个单一的语句都是在该函数的ExecutionContext中被执行。那些不属于任何函数的全局代码(执行的内联代码、通过<script>标签装载的代码、通过eval()函数执行的代码)被关联到一个叫做GlobalExecutionContext的上下文中。GlobalExecutionContext
的工作机制非常类似ExecutionContext,但是没有方法参数,只包含2、3、4(this指向全局对象,常常是window对象)三步。通过以上得出结论:每一个Javascript语句运行都是在ExecutionContext中进行。程序运行中,有时候需要从一个函数跳转到另外一个函数(直接调用、DOM事件、定时器等)。由于每一个函数都有自己的ExecutionContext,所以这些函数的相互调用将形成一个上下文的栈,例如下面的代码:
<script>
function a() {
function b() {
var c = {
d: function() {
alert(1);
}
};
c.d();
}
b.call({});
}
a();
</script>
当Javascript引擎即将执行alert()方法的时候,形成的ExecutionContext栈如下:
d() Execution context.
b() Execution context.
a() Execution context.
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阶段被创建,所以一个遥不可及的函数声明有可能始终被创建,比如:
function foo() {
if (false) {
function bar() {alert(1);};
}
bar();
}
以上代码在浏览器(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会使这门语言释放更大的能量,确保尽量深层次的去理解他们。
分享到:
相关推荐
eval()函数可以执行字符串中的JavaScript代码,如果我们知道要调用的函数名的字符串,可以将其与括号“()”组合起来形成代码字符串,并用eval()函数执行。示例代码如下: ```javascript function targetFunction() {...
在 JavaScript 中,所有代码都是在一个执行环境中被执行的。执行环境可以分为三种类型:Global Code、Eval Code 和 Function Code。Global Code 是指不在任何函数里面的代码,例如一个 js 文件、嵌入在 HTML 页面中...
闭包是函数及其相关的变量组合,即使函数执行完毕,这些变量依然存在。闭包常用于实现私有变量,或者在异步操作中保存状态。 七、尾调用优化 JavaScript引擎支持尾调用优化,这意味着在函数的最后一步调用另一个...
此外,Function对象还具有一些内置的方法,如`apply()`、`call()`等,这些方法可以帮助我们更好地控制函数的执行环境。 #### 函数的执行上下文 当一个函数被调用时,JavaScript引擎会为该函数创建一个新的执行上...
### JavaScript函数中执行C#代码中的函数 在Web开发领域,尤其是混合开发环境中,有时候我们需要在客户端JavaScript中调用服务器端C#代码的功能。这种需求通常出现在ASP.NET Web应用程序中,其中JavaScript负责前端...
执行环境是JavaScript中一个核心概念,它定义了变量或函数有权访问的其他数据。在Web浏览器中,全局执行环境通常是指window对象。执行环境分为全局环境和函数环境。全局环境是最外围的执行环境,而每个函数调用时...
1. 全局执行上下文:当代码开始执行时首先创建一个全局上下文,全局变量和函数都在这个环境中定义,浏览器中的全局对象是 `window`。 2. 函数执行上下文:每当函数被调用时,都会创建一个新的执行上下文,用于函数的...
1. **跨语言通信**:JavaScript是一种广泛应用于Web开发的脚本语言,主要在浏览器环境中运行;而Delphi则是一种面向对象的编程语言,常用于开发Windows桌面应用。两者之间的通信需要借助特定的接口和桥接技术。 2. ...
因为闭包可以访问外部函数的变量,而这些变量通常会在外部函数执行完毕后立即从内存中清除,但由于闭包的引用,它们可能不会被垃圾回收机制回收,从而导致内存占用。 闭包的一个经典应用场景是定义工厂函数,这种...
- **模拟与测试**:在测试环境中替换某些复杂的依赖,简化测试逻辑。 在提供的标签"源码"和"工具"中,我们可以推测这篇博客可能涉及到使用函数劫持技术分析或优化JavaScript代码,或者是介绍某种工具利用了这一概念...
这个中间层通常是一个库或插件,它在JavaScript环境中暴露C++函数,使得JavaScript能够调用它们。 在MFC框架下,我们可以使用ActiveX控件或NPAPI(Netscape Plugin API)插件来创建这个中间层。ActiveX是微软开发的...
下面将详细讨论JavaScript函数式编程的核心概念、实践方法以及它在实际开发中的应用。 1. **纯函数**:纯函数是给定相同的输入时,始终返回相同输出,并且不会对外部环境产生任何影响的函数。在JavaScript中,我们...
这确保了函数执行环境中所有可访问的变量和对象都能被正确地查找和引用。 #### 示例分析 考虑以下示例代码: ```javascript function A() { var someVar; function B() { var someVar; } } ``` - 在定义...
在JavaScript环境中,函数式编程有着天然的优势。作为一门灵活的、多范式语言,JavaScript允许开发者在面向对象编程的同时,也能够运用函数式编程思想。JavaScript中的函数是第一类对象,这意味着它们可以像其他值...
在浏览器环境中,当函数作为全局函数调用时,`this`指向`window`对象。 2. **作为对象方法调用时**: ```javascript var o = { sayHi: function () { console.log('fn2' + this); } }; o.sayHi(); ``` 当...
在深入理解`eval()`之前,我们需要明白JavaScript的基本执行环境。 JavaScript是一种解释型的、基于原型的脚本语言,它广泛应用于Web开发中,用于创建交互式的网页和应用程序。在JavaScript中,`eval()`函数通常...
即时函数和初始化分支是JavaScript开发中的两个关键技巧,它们能帮助我们编写更加健壮、可维护的代码。即时函数提供了封装和隔离作用域的能力,而初始化分支则允许我们在程序启动时根据环境条件做出决策。理解并善用...
1. **安全性**: 使用`eval()`函数执行任意代码可能会导致安全漏洞,因为这相当于执行不受信任的输入。在现代Web开发中,应该避免使用`eval()`函数。 2. **ECMAScript3限制**: - 规定任何解释器都不允许对`eval()`...
setTimeout函数是JavaScript中的一种异步函数,它的执行环境是global对象,而不是当前函数的执行环境。因此,在setTimeout函数中,this关键字指向global对象,而不是当前函数的执行环境。 三、解决方法 为了解决这...
执行阶段分为全局执行上下文(Global Execution Context)和函数执行上下文。每当执行环境改变,如进入函数,就会创建一个新的执行上下文。每个执行上下文都有自己的变量对象,存储变量和函数声明。 在执行过程中,...