js作用域链.doc_第1页
js作用域链.doc_第2页
js作用域链.doc_第3页
js作用域链.doc_第4页
js作用域链.doc_第5页
已阅读5页,还剩2页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

关于js的作用域链,早有耳闻,也曾看过几篇介绍性的博文,但一直都理解的模棱两可。近日又精心翻看了一下悟透Javascript这本书,觉得写得太深刻,在“代码的时空”一节里有一段介绍作用域链的地方寥寥数语,回味无穷(其实还是理解的模棱两可_)。现在整理下自己的读书笔记,顺便借鉴网上资源,写下来。 一、从一个简单的问题说起 下面的js代码在页面中运行显示什么结果: JavaScript代码 1. var arg = 1; 2. function fucTest(arg) 3. alert(arg); 4. var arg = 2; 5. /alert(arg); 6. 7. fucTest(10); 您的答案是什么?没错,就是弹出10。我的理解是这样的,funTest函数有一个形参arg,funTest函数传入实参10,alert方法把 10弹出就是了,囧。 好,问题又来了: JavaScript代码 1. var arg = 1; 2. function funcTest() 3. alert(arg); 4. var arg = 2; 5. 6. arg = 10; 7. funcTest(); 答案是什么?如果是5年前的我,肯定不会再往下想了,还是10!这么简单的问题还用想什么呀?我的理解是这样的:funTest函数是一个无参数的函数,函数内部通过alert方法,调用外部(全局)的变量arg,在函数执行前,arg赋值为10,弹出arg值后改变arg值为2,所以弹出值为 10。 真的是10吗?是还是不是? 测试的结果:弹出“undefined”,瀑布汗. 二、理解作用域链,从javascript运行机制说起 1、js的运行顺序 如果一个文档流中包含多个script代码段(用script标签分隔的js代码或引入的js文件),它们的运行顺序是: 步骤1. 读入第一个代码段(js执行引擎并非一行一行地执行程序,而是一段一段地分析执行的) 步骤2. 做语法分析,有错则报语法错误(比如括号不匹配等),并跳转到步骤5 步骤3. 对var变量和function定义做“预解析”(永远不会报错的,因为只解析正确的声明) 步骤4. 执行代码段,有错则报错(比如变量未定义) 步骤5. 如果还有下一个代码段,则读入下一个代码段,重复步骤2 步骤6. 结束 上面的分析已经足够清楚,步骤二、三和步骤四里的红色字体可能是我们新手理解上的一个盲点,尤其是步骤三的“预解析”,如果不清楚什么叫预解析,总觉得不踏实。而步骤四的“有错则报错”也是经常碰到的。举例来说: JavaScript代码 1. function funcTest() 2. alert(arg); 3. var arg = 2; 4. 5. funcTest(); 上面这段代码执行时,弹出“undefined”,也就是说arg没有定义,js的变量不是不用定义也可以吗? 2、语法分析和“预解析” (1)、从解释型语言的编译过程说起 众所周知,javascript是解释型语言,它不同于c#和java等编译型语言。对于传统编译型语言来说,编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成;但对于解释型语言来说,通过词法分析和语法分析得到语法树后,就可以开始解释执行了。 a、词法分析 简单地说,词法分析是将字符流(char stream)转换为记号流(token stream)。 但是这个转换过程并不是可以用一句话就可以概括的那么简单,我们可以试着用伪代码理解一段简单的程序: 代码var result=x-y;的转换大致可以表示如下: NAME result EQUALS NAME x MINUS NAME y SEMICOLON b、语法分析 简单地说,语法分析就是为了构造合法的语法分析树,而语法分析树可以直观地表示出推导的过程。 那么什么是语法分析树?简单地说,就是程序推导过程的描述。但是到底什么是语法树,请参考专业文章,本篇略过。 c、其他 通过语法分析,构造出语法分析树后,接下来还可能需要进一步的语义检查。对于传统强类型语言来说,语义检查的主要部分是类型检查,比如函数的实参和形参类型是否匹配等等。 结论:通过上面的分析可以看出,对于javascript引擎来说,肯定有词法分析和语法分析,之后可能还有语义检查、代码优化等步骤,等这些编译步骤完成之后(任何语言都有编译过程,只是解释型语言没有编译成二进制代码),才会开始执行代码。 (2)、执行过程 a、javascript的作用域机制 通过编译,javascript代码已经翻译成了语法树,然后会立刻按照语法树执行。 进一步的执行过程,需要理解javascript的作用域机制:词法作用域(lexcical scope)。通俗地讲,就是javascript变量的作用域是在定义时决定而不是执行时决定,也就是说词法作用域取决于源码,编译器通过静态分析就能确定,因此词法作用域也叫做静态作用域(static scope)。但需要注意,with和eval的语义无法仅通过静态技术实现,所以只能说javascript的作用域机制非常接近词法作用域(lexical scope). javascript引擎在执行每个函数实例时,都会创建一个执行环境(execution context)。执行环境中包含一个调用对象(call object), 调用对象是一个scriptObject结构(scriptObject是与函数相关的一套静态系统,与函数实例的生命周期保持一致),用来保存内部变量表varDecls、内嵌函数表funDecls、父级引用列表upvalue等语法分析结构(注意varDecls和 funDecls等信息是在语法分析阶段就已经得到,并保存在语法树中。函数实例执行时,会将这些信息从语法树复制到scriptObject上)。 b、javascript作用域机制的实现方法 词法作用域(lexical scope)是javascript的作用域机制,还需要理解它的实现方法,就是作用域链(scope chain)。作用域链是一个name lookup机制,首先在当前执行环境的scriptObject中寻找,没找到,则顺着upvalue到父scriptObject中寻找,一直 lookup到全局调用对象(global object)。 现在回过头来分析第二个问题: JavaScript代码 1. var arg = 1; 2. function funcTest() 3. alert(arg); 4. var arg = 2; 5. 6. arg = 10; 7. funcTest(); 在执行funcTest函数时,也即进入了funcTest对应的作用域,js引擎在执行时,当遇到对变量名或者函数名的使用时,会首先在当前作用域(也即funcTest对应的作用域)查找变量或者函数(显然,arg变量在funcTest对应的作用域里被定义为var arg=2 所以alert方法的参数采用的是当前作用域的arg,但是因为arg被定义在alert方法后,所以arg变量默认值为undefined)。当然,如果没有找到就到上层作用域查找,依此类推(作用域范围可以持续到javascript运行环境的根:window对象)。 最后,让你看的更清楚,上面的代码其实可以等价于: JavaScript代码 1. var arg = 1; 2. function funcTest() 3. var arg; /默认值undefined 4. alert(arg); 5. arg = 2; 6. 7. arg = 10; 8. funcTest(); c、闭包(closure) 当一个函数实例执行时,会创建或关联到一个闭包。 (关于闭包,打算另写一篇学习笔记) scriptObject用来静态保存与函数相关的变量表,闭包则在执行期动态保存这些变量表及其运行值; 闭包的生命周期有可能比函数实例长。函数实例在活动引用为空后会自动销毁; 闭包则要等要数据引用为空后,由javascript引擎回收(有些情况下不会自动回收,就导致了内存泄漏)。 ps:关于“执行过程”这一段比较拗口,名词很多,不过别被它们吓住,一旦理解了执行环境(execution context)、调用对象(call object)、词法作用域(lexical scope)、作用域链(scope chain)、闭包(closure)等这些概念,javascript的很多现象都能迎刃而解。 JavaScript 中的作用域链Scope Chain /* 作用域链(Scope Chain) JavaScript中的一种重要机制,JS中所有的标识符(Identifier)都是通过Scope Chain来查找值的。 */ var i = w i; /* 词法作用域(Lexical Scoping) JavaScript中的函数运行在它们被定义的作用域里,而不是它们被执行的作用域里. 这个作用域我们称之为scope 非内嵌函数的scope都是宿主对象,不同的宿主会有不同的内容 */ var a =function() alert(i); var b = function() var i = b i; a(); var c = function() a(); a(); /* 函数在运行的时候,会先创建一个call Object, 将函数的形参、变量和函数声明的结果都添加到call Object作为属性; 把call Object添加到该函数的作用域链的最前面; 然后,再将函数定义时的scope添加到它的作用域链中; 函数体内的标识符查找 先从Scope Chain中第一个对象找,存在则返回,否则 继续查找下一个对象,存在则返回,否则(重复此步骤) 最后找不到则返回undefined */ b(); c(); /* JavaScript中的scope Chain的机制使得闭包得以实现 */ var d = function() var i = d i; var e = function() alert(i); return e; var f = d(); f(); /* f是function e的引用 call Object部分:没有形参、变量声明和内部函数; scope部分:e定义在函数d体内,e的scope中可以访问到d,i,e,然后d也会有一个scope,又可以接着往上访问,这样,便形成一条链 */ 具名函数表达式与匿名函数表达式和函数申明的区别 /* 具名函数表达式与匿名函数表达式和函数申明的区别 winter: 正常的函数定义时是直接把scope设为定义时的scope chain 但是具名函数表达式是先给scope chain加入一个new Object 之后再将scope设为新的scope chain 设完后再将scope还原到正常函数定义时的scope 等于是在正常函数定义时的scope添加了一个new Object到最前面 在不同浏览器中对具名函数表达式的处理方式都太一样,都没太按ECMAScript中的规定去实现 ECMAScript中关于具名函数表达式实现的规定大致如下: 1、创建一个new Object 2、将这个new Object插入到scope chain 3、创建一个new Function,将scope chain设为scope 4、为new Object添加一个属性,function name:function body(readOnly、dont

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论