`
footman265
  • 浏览: 118411 次
  • 性别: Icon_minigender_1
  • 来自: 宁波
社区版块
存档分类
最新评论

JavaScript对象模型-执行模型(笔记3)

阅读更多

执行模型
执行上下文(Execution Context)简介
JavaScript代码运行的地方都存在执行上下文,它是一个概念,一种机制,用来完成JavaScript运行时作用域、生存期等方面的处理。执行上下文包括Variable Object、Variable Instatiation、Scope/Scope Chain等概念,在不同的场景/执行环境下,处理上存在一些差异,下面先对这些场景进行说明。

函数对象分为用户自定义函数对象和系统内置函数对象,对于用户自定义函数对象将按照下面描述的机制进行处理,但内置函数对象与具体实现相关,ECMA规范对它们执行上下文的处理没有要求,即它们基本不适合本节描述的内容。

执行的JavaScript代码分三种类型,后面会对这三种类型处理上不同的地方进行说明:
1. Global Code,即全局的、不在任何函数里面的代码,例如一个js文件、嵌入在HTML页面中的js代码等。
2. Eval Code,即使用eval()函数动态执行的JS代码。
3. Function Code,即用户自定义函数中的函数体JS代码。

基本原理
在用户自定义函数中,可以传入参数、在函数中定义局部变量,函数体代码可以使用这些入参、局部变量。背后的机制是什么样呢?
当JS执行流进入函数时,JavaScript引擎在内部创建一个对象,叫做Variable Object。对应函数的每一个参数,在Variable Object上添加一个属性,属性的名字、值与参数的名字、值相同。函数中每声明一个变量,也会在Variable Object上添加一个属性,名字就是变量名,因此为变量赋值就是给Variable Object对应的属性赋值。在函数中访问参数或者局部变量时,就是在variable Object上搜索相应的属性,返回其值
一般情况下Variable Object是一个内部对象,JS代码中无法直接访问。规范中对其实现方式也不做要求,因此它可能只是引擎内部的一种数据结构。

大致处理方式就这样,但作用域的概念不只这么简单,例如函数体中可以使用全局变量、函数嵌套定义时情况更复杂点。这些情况下怎样处理?JavaScript引擎将不同执行位置上的Variable Object按照规则构建一个链表,在访问一个变量时,先在链表的第一个Variable Object上查找,如果没有找到则继续在第二个Variable Object上查找,直到搜索结束。这就是Scope/Scope Chain的大致概念。//岂不是效率很低?  还是我火星了.....

下面是各个方面详细的处理。

Global Object
JavaScript的运行环境都必须存在一个唯一的全局对象-Global Object,例如HTML中的window对象。Global Object是一个宿主对象,除了作为JavaScript运行时的全局容器应具备的职责外,ECMA规范对它没有额外要求。它包含Math、String、Date、parseInt等JavaScript中内置的全局对象、函数(都作为Global Object的属性),还可以包含其它宿主环境需要的一些属性。

Variable Object
上面简述了Variable Object的基本概念。创建Variable Object,将参数、局部变量设置为Variable Object属性的处理过程叫做Variable Instatiation-变量实例化,后面结合Scope Chain再进行详细说明。

Global Code
Variable Object就是Global Object,这是Variable Object唯一特殊的地方(指它是内部的无法访问的对象而言)。

var globalVariable = "WWW";
document.write(window.globalVariable); 
//result: WWW

上面代码在Global Code方式下运行,根据对Variable Object的处理,定义变量globalVariable时就会在Global Object(即window)对象上添加这个属性,所以输出是WWW这个值。

Function Code
Variable Object也叫做Activation Object(因为有一些差异存在,所以规范中重新取一个名字以示区别,Global Code/Eval Code中叫Variable Object,Function Code中就叫做Activation Object)。
每次进入函数执行都会创建一个新的Activation Object对象,然后创建一个arguments对象并设置为Activation Object的属性,再进行Variable Instantiation处理
退出函数时,Activation Object会被丢弃(并不是内存释放,只是可以被垃圾回收了)

附arguments对象的属性:
length: 为实际传入参数的个数。注意,参考函数对象创建过程,函数对象上的length为函数定义时要求的参数个数
callee: 为执行的函数对象本身。目的是使函数对象能够引用自己,例如需要递归调用的地方。
function fnName(...) { ... }这样定义函数,它的递归调用可以在函数体内使用fnName完成。var fn=function(...) { ... }这样定义匿名函数,在函数体内无法使用名字引用自己,通过arguments.callee就可以引用自己而实现递归调用
参数列表: 调用者实际传入的参数列表。这个参数列表提供一个使用索引访问实际参数的方法。Variable Instantiation处理时会在Activation Object对象上添加属性,前提是函数声明时有指定参数列表。如果函数声明中不给出参数列表,或者实际调用参数个数与声明时的不一样,可以通过arguments访问各个参数。

arguments中的参数列表与Activation Object上的参数属性引用的是相同的参数对象(如果修改,在两处都会反映出来)。规范并不要求arguments是一个数组对象,下面是一个测试:

//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
var argumentsLike = { 0"aaa"12222"WWW", length: 3, callee: function() { } };
document.write(argumentsLike[
2+ "<br />"); //result: WWW
document.write(argumentsLike[1+ "<br />"); //result: 222
//
convert the argumentsLike to an Array object, just as we can do this for the arguments property
var array = [].slice.apply(argumentsLike);
document.write(array 
instanceof Array); //result: true
document.write("<br />");
document.write(array.reverse().join(
"|")); //result: WWW|222|aaa


Eval Code
Variable Object就是调用eval时当前执行上下文中的Variable Object。在Global Code中调用eval函数,它的Variable Object就是Global 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是同一个对象。

Scope/Scope Chain
首先Scope Chain是一个类似链表/堆栈的结构,里面每个元素基本都是Variable Object/Activation Object。
其次存在执行上下文的地方都有当前Scope Chain,可以理解为Scope Chain就是执行上下文的具体表现形式。

Global Code
Scope Chain只包含一个对象,即Global Object。在开始JavaScript代码的执行之前,引擎会创建好这个Scope Chain结构。

Function Code
函数对象在内部都有一个[[Scope]]属性,用来记录该函数所处位置的Scope Chain。
创建函数对象时,引擎会将当前执行环境的Scope Chain传给Function的[[Construct]]方法。[[Construct]]会创建一个新的Scope Chain,内容与传入的Scope Chain完全一样,并赋给被创建函数的内部[[Scope]]属性。在前面函数对象创建过程一节中,这个处理位于步骤4和5之间。
进入函数调用时,也会创建一个新的Scope Chain,包括同一个函数的递归调用,退出函数时这个Scope Chain被丢弃。新建的Scope Chain第一个对象是Activation Object,接下来的内容与内部[[Scope]]上存储的Scope Chain内容完全一样。

Eval Code
进入Eval Code执行时会创建一个新的Scope Chain,内容与当前执行上下文的Scope Chain完全一样。

实例说明
Scope Chain的原理就上面这些,必须结合JS代码的执行、Variable Instantiation的细节处理,才能理解上面这些如何产生作用,下面用一个简单的场景来综合说明。假设下面是一段JavaScript的Global Code:

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(1020);

执行处理过程大致如下:
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。

其它情况下Scope Chain、Variable Instantiation处理类似上面的过程进行分析就行了。

根据上面的实例说明,就可以解释下面这个测试代码的结果:

//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
function fn(obj){
    
return {
        
//test whether exists a local variable "outerVar" on obj
        exists: Object.prototype.hasOwnProperty.call(obj, "outerVar"),
        
//test the value of the variable "outerVar"
        value: obj.outerVar
    };
}
var result1 = fn(window);
var outerVar = "WWW"
var result2 = fn(window);

document.write(result1.exists 
+ " " + result1.value); //result: true undefined
document.write("<br />");
document.write(result2.exists 
+ " " + result2.value); //result: true WWW

result1调用的地方,outerVar声明和赋值的语句还没有被执行,但是测试结果window对象已经拥有一个本地属性outerVar,其值为undefined。result2的地方outerVar已经赋值,所以window.outerVar的值已经有了。实际使用中不要出现这种先使用,后定义的情况,否则某些情况下会有问题,因为会涉及到一些规范中没有提及,不同厂商实现方式上不一致的地方。

一些特殊处理
1. with(obj) { ... }这个语法的实现方式,是在当前的Scope Chain最前面位置插入obj这个对象,这样就会先在obj上搜索是否有相应名字的属性存在。其它类似的还有catch语句。
2. 前面对arguments对象的详细说明中,提到了对函数递归调用的支持问题,了解到了匿名函数使用arguments.callee来实现引用自己,而命名函数可以在函数体内引用自己,根据上面Scope Chain的工作原理我们还无法解释这个现象,因为这里有个特殊处理。
任何时候创建一个命名函数对象时,JavaScript引擎会在当前执行上下文Scope Chain的最前面插入一个对象,这个对象使用new Object()方式创建,并将这个Scope Chain传给Function的构造函数[[Construct]],最终创建出来的函数对象内部[[Scope]]上将包含这个object对象。创建过程返回之后,JavaScript引擎在object上添加一个属性,名字为函数名,值为返回的函数对象,然后从当前执行上下文的Scope Chain中移除它。这样函数对象的Scope Chain中第一个对象就是对自己的引用,而移除操作则确保了对函数对象创建处Scope Chain的恢复。

this关键字处理
执行上下文包含的另一个概念是this关键字。
Global Code中this关键字为Global Object;函数调用时this关键字为调用者,例如obj1.fn1(),在fn1中this对象为obj1;Eval Code中this关键字为当前执行上下文的Variable Object。

在函数调用时,JavaScript提供一个让用户自己指定this关键字值的机会,即每个函数都有的call、apply方法。例如:
fn1.call(obj1, arg1, arg2, ...)或者fn1.apply(obj1, argArray),都是将obj1作为this关键字,调用执行fn1函数,后面的参数都作为函数fn1的参数。如果obj1为null或undefined,则Global Object将作为this关键字的值;如果obj1不是Object类型,则转化为Object类型。它们之间的唯一区别在于,apply允许以数组的方式提供各个参数,而call方法必须一个一个参数的给。
前面的测试示例代码中有多处运用到了这个方法。例如window对象并没有hasOwnProperty方法,使用Object.prototype.hasOwnProperty.call(window, "propertyName")也可以测试它是否拥有某个本地属性

 

分享到:
评论

相关推荐

    JavaScript-学习笔记.pdf

    以上是JavaScript学习笔记中提到的一些核心知识点,通过对这些知识点的理解和熟练应用,可以为进一步学习和掌握JavaScript打下坚实的基础。在实际开发过程中,结合具体的项目需求,这些知识会得到更深入的拓展和应用...

    JavaScript基础笔记-尚硅谷视频自己总结

    - **DOM (Document Object Model)**: 描述了文档对象模型的接口,允许程序和脚本动态地访问和更新文档的内容、结构和样式。 - **BOM (Browser Object Model)**: 提供了一组与浏览器交互的对象,如window、navigator...

    李炎恢JavaScript-pdf文档笔记

    3. **DOM操作**:文档对象模型(DOM)是JavaScript操作HTML和XML文档的标准接口。学习者将了解到如何通过JavaScript选择、添加、修改和删除DOM元素,以及事件处理机制。 4. **AJAX与异步编程**:AJAX(异步...

    JavaScript学习笔记-适合初学者

    本学习笔记专为初学者设计,旨在帮助新接触JavaScript的人快速掌握这门语言的核心概念和实用技巧。 首先,"JavaScript特效.chm"可能是一份关于JavaScript实现的各种网页特效的教程。这些特效可能包括图片轮播、下拉...

    JavaScript核心笔记

    ### JavaScript核心笔记精要 #### 一、定义变量与类型转换 **1.1 定义变量的方法** 在JavaScript中,定义变量有两种方法:显示定义和隐式定义。 - **显示定义**: 使用`var`关键字定义变量。这种方法直到变量首次...

    Javascript权威指南学习笔记二

    ### JavaScript权威指南学习笔记二:客户端JavaScript #### 第十二章:Web浏览器中的JavaScript ##### 一、Web浏览器环境 在客户端JavaScript中,浏览器提供了一个特定的执行环境,其中`window`对象扮演着至关...

    javascript笔记 javascript笔记

    JavaScript可以用来动态地修改文档对象模型(DOM),从而改变网页的内容或布局。 **示例6:** ```html ('Down!')"&gt;Click ('Click!')"&gt;Click ``` - **解释:** 第一个链接在鼠标按下的时候会在页面上输出`Down!`;第二...

    JavaScript.-Extjs基础学习笔记

    最后,`GridPanel`对象被创建,指定了渲染目标、数据源、列模型、选择模型等属性。 ### 总结 Extjs的Tab Panel和Grid组件提供了丰富的特性和自定义选项,使得开发者能够构建出既美观又功能强大的用户界面。通过...

    JavaScript-学习笔记.docx

    - JavaScript可以操作DOM(文档对象模型),实现对网页元素的增删改查,如`getElementById`、`appendChild`等。 以上就是JavaScript学习笔记中的主要知识点,理解和掌握这些内容对于深入学习JavaScript至关重要。...

    狂神说系列 JavaScript笔记

    【狂神说系列 JavaScript笔记】是一份全面且深入的JavaScript学习资源,旨在帮助开发者和初学者深入理解这门广泛应用于Web开发的脚本语言。这份笔记涵盖了JavaScript的基础语法、核心概念以及高级特性,旨在构建一个...

    JavaScript_DOM_编程艺术读书笔记

    ### JavaScript DOM 编程艺术读书笔记关键知识点解析 #### 一、JavaScript简史与相关技术简介 - **XHTML(可扩展的超文本标记语言)**:这是一种更加严格、更加强大的HTML版本,旨在提高网页的可读性和可扩展性。 ...

    JavaScript基础笔记.md

    3. **DOM(Document Object Model)**:文档对象模型,允许JavaScript操作HTML文档结构。 #### 四、基本语法 ##### 1. HTML与JavaScript的结合方式 - **内部JS**:通过`&lt;script&gt;`标签直接在HTML文件中嵌入...

    JavaScript教程--从入门到精通.zip

    DOM(文档对象模型)是JavaScript操作网页内容的主要接口。学会选择元素(如getElementById、querySelector、querySelectorAll等)、修改元素属性、插入和删除节点,是进行网页动态更新的基础。 异步编程是...

    蜗牛学院-第三阶段笔记.zip

    【压缩包子文件的文件名称列表】: "第三阶段笔记" 没有给出具体的文件详细信息,但可以推测这可能包含一系列按主题或模块划分的笔记文档,如HTML、CSS、JavaScript的基础知识,数据库管理,面向对象设计原则,类和...

    JavaWeb02-JavaScript学习笔记

    JavaScript基础知识点 JavaScript是一种脚本语言...本笔记涵盖了JavaScript的基础知识点,包括变量、数据类型、流程控制语句、函数、数组、对象、JSON、BOM和Location等概念,为学习JavaScript提供了一个良好的基础。

    yolo开发t-JavaWeb-m笔记

    【标题】"yolo开发t-JavaWeb-m笔记"揭示了这是一个关于快速开发JavaWeb项目的实践笔记。在JavaWeb开发中,"yolo"(You Only Live Once)常常被用来象征快速行动、勇于尝试的精神,而"t-JavaWeb-m"可能是项目代号或者...

    bootstrap响应式网页作业tion-model-for-network-开发笔记

    在开发过程中,笔记可能记录了如何将这些机器学习模型与前端Bootstrap界面整合,比如创建图表来展示模型的预测结果,或者利用Bootstrap的表单组件来输入和处理数据。这涉及到前后端交互,可能使用了Ajax异步请求,...

    web--bootstrap入门基础笔记

    总的来说,这份笔记将引导学习者了解Bootstrap的基础知识,包括栅格系统、组件使用、样式定制和JavaScript插件的应用。通过学习和实践,无论是新手还是有经验的开发者,都能快速上手并提升网页开发效率。

    javascript入门学习笔记

    在Web开发中,JavaScript常用于操作文档对象模型(DOM),通过DOM可以改变HTML元素的样式、内容或位置。熟悉DOM API,如getElementById、appendChild等,是前端开发的基本技能。 四、事件处理 JavaScript通过事件...

    javascript笔记 超级详细 入门

    **文档对象模型(Document Object Model, DOM)**是HTML或XML文档的标准模型,它提供了对文档结构的访问和修改的方式。 1. **DOM树的概念**: - HTML文档被解析成一个节点树,每个节点代表文档中的一个元素。 - ...

Global site tag (gtag.js) - Google Analytics