理解Javascript_13_执行模型详解


Posted in Javascript onOctober 20, 2010

函数执行环境
简单的代码:

function say(msg,other){ 
var str = "nobody say:"; 
this.name = '笨蛋的座右铭'; 
function method(){};//var method = function(){}; 
alert(str+msg); 
} 
say('hello world'); 
alert(name);//笨蛋的座右铭

当调用say方法时,第一步是创建其执行环境,在创建执行环境的过程中,会按照定义的先后顺序完成一系列操作:
1.首先会创建一个'活动对象'(Activation Object)。活动对象是规范中规定的另外一种机制。之所以称之为对象,是因为它拥有可访问的命名属性,但是它又不像正常对象那样具有原型(至少没有预定义的原型),而且不能通过 JavaScript 代码直接引用活动对象。
2.为函数调用创建执行环境的下一步是创建一个 arguments 对象,这是一个类似数组的对象,它以整数索引的数组成员一一对应地保存着调用函数时所传递的参数。这个对象也有 length 和 callee 属性(更深入的内容,请参见《理解Javascript_14_函数形式参数与arguments 》)。然后,会为活动对象创建一个名为“arguments”的属性,该属性引用前面创建的 arguments对象。
3.接着,为执行环境分配作用域。作用域由对象列表(链)组成。(比较复杂,请参见:《理解Javascript_15_作用域分配与变量访问规则,再送个闭包》)
4.之后会发生由 ECMA 262 中所谓'活动对象'完成的'变量实例化'(Variable Instatiation)的过程。此时会将函数的形式参数创建为可变对象的命名属性,如果调用函数时传递的参数与形式参数一致,则将相应参数的值赋给这些命名属性(否则,会给命名属性赋 undefined 值)。对于定义的内部函数,会以其声明时所用名称为可变对象创建同名属性,而相应的内部函数则被创建为函数对象并指定给该属性。变量实例化的最后一步是将在函数内部声明的所有局部变量创建为可变对象的命名属性。注:在这个过程中,除了实际参数有值外和函数定义外,其它都被'预解析'为undefined值.
5.最后,要为使用 this 关键字而赋值。(此时的this指向的是全局对象,即window)

执行环境创建成功后,就进入第二步:在函数体内,从上到下执行代码。
1.当执行到var str='nobody say'会发生称之为'计算赋值表达式'的过程,此时,会将活动对象中key为str的值从undefined设置为'nobody say:'。
2.执行到this.name='笨蛋的座右铭'时,会为作为this的对象添加属性name,并赋值为'笨蛋的座右铭'.
3.然后是执行function innerMethod(){};最后执行'alert(str+msg),输出'nobody say:hello world'.

function method(){}与var method = function(){}的区别
简单的代码:

function say(){ 
method01();//method01 
method02();//error 
function method01(){ 
alert('method01'); 
} 
var method02 = function(){ 
alert('method02'); 
} 
} 
say();

为什么调用方法method01能正常运行,而调用方法method02却会报错呢?

首先,你要明确的知道method01为一个函数对象,而method02为一个变量,它指向于另一个函数对象。根据上一节的内容,在'活动对象'完成的'变量实例化'(Variable Instatiation)的过程中,对函数method01进行了正常的'预解析',而对于变量method02解析为undefined值,当进入到执行代码的环节时,因为method02的调用在其计算函数表达式之前,因此将undefined当作方法调来用,必然会报错。解决方案比较简单,就是将var method02=function(){...}放到其method02()的调用之前就可以了或者是用函数声明的方式定义函数(function method(){...})。
注:计算函数表达式就是指程序执行到var method02 = function(){...}。method02此时真正指向一个函数对象。因为在'预解析'时,method02只被赋于了undefined.

全局执行环境(《执行模型浅析》中已经讲过,不再深入)

在一个页面中,第一次载入JS代码时创建一个全局执行环境,全局执行环境的作用域链实际上只由一个对象,即全局对象(window),在开始JavaScript代码的执行之前,引擎会创建好这个Scope Chain结构。全局执行环境也会有变量实例化的过程,它的内部函数就是涉及大部分 JavaScript 代码的、常规的顶级函数声明。而且,在变量实例化过程中全局对象就是可变对象,这就是为什么全局性声明的函数是全局对象属性的原因。全局性声明的变量同样如此全局执行环境会使用 this 对象来引用全局对象。

注:区别'函数执行环境'中的活动对象(Activation Object)和全局执行环境中的可变对象(Vriable Object),Variable Object也叫做Activation Object(因为有一些差异存在,所以规范中重新取一个名字以示区别,全局执行环境/Eval执行环境中叫Variable Object,函数执行环境中就叫做Activation Object)。

Eval执行环境
构建Eval执行环境时的可变对象(Variable Object)就是调用eval时当前执行上下文中的可变对象(Variable Object)。在全局执行环境中调用eval函数,它的可变对象(Variable Object)就是全局对象;在函数中调用eval,它的可变对象(Variable Object)就是函数的活动对象(Activation Object)。

//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4 
function fn(arg){ 
var innerVar = "variable in function"; 
eval(' \ 
var evalVar = "variable in eval"; \ 
document.write(arg + "<br />"); \ 
document.write(innerVar + "<br />"); \ 
'); 
document.write(evalVar); 
} 
fn("arguments for function");

输出结果是:
arguments for function
variable in function
variable in eval
说明: eval调用中可以访问函数fn的参数、局部变量;在eval中定义的局部变量在函数fn中也可以访问,因为它们的Varible Object是同一个对象。
进入Eval Code执行时会创建一个新的Scope Chain,内容与当前执行上下文的Scope Chain完全一样。

最后的实例
代码如下:

var outerVar1="variable in global code"; 
function fn1(arg1, arg2){ 
var innerVar1="variable in function code"; 
function fn2() { return outerVar1+" - "+innerVar1+" - "+" - "+(arg1 + arg2); } 
return fn2(); 
} 
var outerVar2=fn1(10, 20);

执行处理过程大致如下:
1. 初始化Global Object即window对象,Variable Object为window对象本身。创建Scope Chain对象,假设为scope_1,其中只包含window对象。
2. 扫描JS源代码(读入源代码、可能有词法语法分析过程),从结果中可以得到定义的变量名、函数对象。按照扫描顺序:
2.1 发现变量outerVar1,在window对象上添加outerVar1属性,值为undefined;
2.2 发现函数fn1的定义,使用这个定义创建函数对象,传给创建过程的Scope Chain为scope_1。将结果添加到window的属性中,名字为fn1,值为返回的函数对象。注意fn1的内部[[Scope]]就是 scope_1。另外注意,创建过程并不会对函数体中的JS代码做特殊处理,可以理解为只是将函数体JS代码的扫描结果保存在函数对象的内部属性上,在函数执行时再做进一步处理。这对理解Function Code,尤其是嵌套函数定义中的Variable Instantiation很关键;
2.3 发现变量outerVar2,在window对象上添加outerVar2属性,值为undefined;
3. 执行outerVar1赋值语句,赋值为"variable in global code"。
4. 执行函数fn1,得到返回值:
4.1 创建Activation Object,假设为activation_1;创建一个新的Scope Chain,假设为scope_2,scope_2中第一个对象为activation_1,第二个对象为window对象(取自fn1的 [[Scope]],即scope_1中的内容);
4.2 处理参数列表。在activation_1上设置属性arg1、arg2,值分别为10、20。创建arguments对象并进行设置,将arguments设置为activation_1的属性;
4.3 对fn1的函数体执行类似步骤2的处理过程:
4.3.1 发现变量innerVar1,在activation_1对象上添加innerVar1属性,值为undefine;
4.3.2 发现函数fn2的定义,使用这个定义创建函数对象,传给创建过程的Scope Chain为scope_2(函数fn1的Scope Chain为当前执行上下文的内容)。将结果添加到activation_1的属性中,名字为fn2,值为返回的函数对象。注意fn2的内部 [[Scope]]就是scope_2;
4.4 执行innerVar1赋值语句,赋值为"variable in function code"。
4.5 执行fn2:
4.5.1 创建Activation Object,假设为activation_2;创建一个新的Scope Chain,假设为scope_3,scope_3中第一个对象为activation_2,接下来的对象依次为activation_1、window 对象(取自fn2的[[Scope]],即scope_2);
4.5.2 处理参数列表。因为fn2没有参数,所以只用创建arguments对象并设置为activation_2的属性。
4.5.3 对fn2的函数体执行类似步骤2的处理过程,没有发现变量定义和函数声明。
4.5.4 执行函数体。对任何一个变量引用,从scope_3上进行搜索,这个示例中,outerVar1将在window上找到;innerVar1、arg1、arg2将在activation_1上找到。
4.5.5 丢弃scope_3、activation_2(指它们可以被垃圾回收了)。
4.5.6 返回fn2的返回值。
4.6 丢弃activation_1、scope_2。
4.7 返回结果。
5. 将结果赋值给outerVar2。

参考:
http://www.cnblogs.com/RicCC/archive/2008/02/15/JavaScript-Object-Model-Execution-Model.html
http://www.cn-cuckoo.com/2007/08/01/understand-javascript-closures-72.html

Javascript 相关文章推荐
JS中批量给元素绑定事件过程中的相关问题使用闭包解决
Apr 15 Javascript
struts2+jquery组合验证注册用户是否存在
Apr 30 Javascript
IE10中flexigrid无法显示数据的解决方法
Jul 26 Javascript
最好用的Bootstrap fileinput.js文件上传组件
Dec 12 Javascript
微信小程序 详解Page中data数据操作和函数调用
Jan 12 Javascript
使用vue.js2.0 + ElementUI开发后台管理系统详细教程(二)
Jan 21 Javascript
AngularJS学习笔记之表单验证功能实例详解
Jul 06 Javascript
新版vue-cli模板下本地开发环境使用node服务器跨域的方法
Apr 03 Javascript
webpack proxy 使用(代理的使用)
Jan 10 Javascript
js实现弹幕飞机效果
Aug 27 Javascript
在vue中实现清除echarts上次保留的数据(亲测有效)
Sep 09 Javascript
vue cli4中mockjs在dev环境和build环境的配置详情
Apr 06 Vue.js
用jquery与css打造个性化的单选框和复选框
Oct 20 #Javascript
Jquery插件之多图片异步上传
Oct 20 #Javascript
jquery判断checkbox(复选框)是否被选中的代码
Oct 20 #Javascript
jQuery下扩展插件和拓展函数的写法(匿名函数使用的典型例子)
Oct 20 #Javascript
JQuery 拾色器插件发布-jquery.icolor.js
Oct 20 #Javascript
Javascript技巧之不要用for in语句对数组进行遍历
Oct 20 #Javascript
来自国外的14个图片放大编辑的jQuery插件整理
Oct 20 #Javascript
You might like
php Hex RGB颜色值互换的使用
2013/05/10 PHP
PHP函数strip_tags的一个bug浅析
2014/05/22 PHP
php实现文本数据导入SQL SERVER
2015/05/17 PHP
Linux安装配置php环境的方法
2016/01/14 PHP
Laravel模型间关系设置分表的方法示例
2018/04/21 PHP
JavaScript RegExp方法获取地址栏参数(面向对象)
2009/03/10 Javascript
javascript中window.event事件用法详解
2012/12/11 Javascript
jquery 实现上下滚动效果示例代码
2013/08/09 Javascript
jQuery文件上传插件Uploadify使用指南
2014/06/05 Javascript
Node.js模块加载详解
2014/08/16 Javascript
js实例属性和原型属性示例详解
2014/11/23 Javascript
Angularjs 实现一个幻灯片示例代码
2016/09/08 Javascript
angular中使用Socket.io实例代码
2017/06/03 Javascript
JQuery EasyUI的一些常用组件
2017/07/12 jQuery
vue组件中使用iframe元素的示例代码
2017/12/13 Javascript
javascript异常处理实现原理详解
2020/02/17 Javascript
Vue.js实现立体计算器
2020/02/22 Javascript
浅谈JS for循环中使用break和continue的区别
2020/07/21 Javascript
详解Python中使用base64模块来处理base64编码的方法
2016/07/01 Python
Python和C/C++交互的几种方法总结
2017/05/11 Python
python xpath获取页面注释的方法
2019/01/14 Python
Django REST framework 视图和路由详解
2019/07/19 Python
python垃圾回收机制(GC)原理解析
2019/12/30 Python
Django 用户认证Auth组件的使用
2020/11/30 Python
中国最大的名表商城:万表网
2016/08/29 全球购物
服装公司总经理岗位职责
2013/11/30 职场文书
车间统计员岗位职责
2014/01/05 职场文书
工作人员思想汇报
2014/01/09 职场文书
小学教研工作制度
2014/01/15 职场文书
中国梦主题教育活动总结
2014/05/05 职场文书
党员对照检查材料
2014/09/22 职场文书
乡镇干部党的群众路线教育实践活动个人对照检查材料
2014/09/24 职场文书
2014年依法行政工作总结
2014/11/19 职场文书
2014年学校团委工作总结
2014/12/20 职场文书
西双版纳导游词
2015/02/03 职场文书
Java集成swagger文档组件
2021/06/28 Java/Android