JavaScript高级程序设计(第3版)学习笔记8——函数(中).doc_第1页
JavaScript高级程序设计(第3版)学习笔记8——函数(中).doc_第2页
JavaScript高级程序设计(第3版)学习笔记8——函数(中).doc_第3页
JavaScript高级程序设计(第3版)学习笔记8——函数(中).doc_第4页
JavaScript高级程序设计(第3版)学习笔记8——函数(中).doc_第5页
已阅读5页,还剩5页未读 继续免费阅读

下载本文档

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

文档简介

接着看函数这个具有魔幻色彩的对象。在上篇文章中说函数内部属性时,还遗留了一个this内部属性没有解释,不过在说this之前,我想先说一说执行环境和作用域的概念。6、执行环境和作用域(1)执行环境(execution context):所有的JavaScript代码都运行在一个执行环境中,当控制权转移至JavaScript的可执行代码时,就进入了一个执行环境。活动的执行环境从逻辑上形成了一个栈,全局执行环境永远是这个栈的栈底元素,栈顶元素就是当前正在运行的执行环境。每一个函数都有自己的执行环境,当执行流进入一个函数时,会将这个函数的执行环境压入栈顶,函数执行完之后再将这个执行环境弹出,控制权返回给之前的执行环境。(2)变量对象(variable object):每一个执行环境都有一个与之对应的变量对象,执行环境中定义的所有变量和函数就是保存在这个变量对象中。这个变量对象是后台实现中的一个对象,我们无法在代码中访问,但是这有助于我们理解执行环境和作用域相关概念。(3)作用域链(scope chain):当代码在一个执行环境中运行时,会创建由变量对象组成的一个作用域链。 这个链的前端,就是当前代码所在环境的变量对象,链的最末端,就是全局环境的变量对象。在一个执行环境中解析标识符时,会在当前执行环境相应的变量对象中搜索,找到就返回,没有找到就沿着作用域链一级一级往上搜索直至全局环境的变量对象,如果一直未找到,就抛出引用异常。(4)活动对象(activation object):如果一个执行环境是函数执行环境,也将变量对象称为活动对象。活动对象在最开始只包含一个变量,即arguments对象(这个对象在全局环境的变量对象中不存在)。这四个概念虽然有些抽象,但还是比较自然的,可以结合JavaScript高级程序设计(第3版)中的一个例子来细细体会一下:/ 进入到全局作用域,创建全局变量对象var color = blue;function changeColor() / 进入到changeColor作用域,创建changeColor相应变量对象 var anotherColor = red; function swapColors(color1, color2) / 进入到swapColors作用域,创建swapColors相应变量对象 var tempColor = anotherColor; anotherColor = color; color = tempColor; /* * swapColors作用域内可以访问的对象有: * 全局变量对象的color,changeColor * changeColor函数相应变量对象的anotherColor、swapColors * swapColors函数相应变量对象的tempColor */ swapColors(white); /* * changeColor作用域内可以访问的对象有: * 全局变量对象的color,changeColor * changeColor函数相应变量对象的anotherColor、swapColors */ changeColor();/* * 全局作用域内可以访问的对象有: * 全局变量对象的color,changeColor */这里的整个过程是:(1)进入全局环境,创建全局变量对象,将全局环境压入栈顶(这里也是栈底)。根据前面的关于声明提升的结论,这里创建全局变量对象可能的一个过程是,先创建全局变量对象,然后处理函数声明设置属性changeColor为相应函数,再处理变量声明设置属性color为undefined。(2)执行全局环境中的代码。先执行color变量初始化,赋值为blue,再调用changeColor()函数。(3)调用changeColor()函数,进入到changeColor函数执行环境,创建这个环境相应的变量对象(也就是活动对象),将这个环境压入栈顶。创建活动对象可能的一个过程是,先创建活动对象,处理内部函数声明设置属性swapColors为相应函数,处理函数参数创建活动对象的属性arguments对象,处理内部变量声明设置属性anotherColor为undefined。(4)执行changeColor()函数代码。先执行anotherColor初始化为red,再调用swapColors()函数。(5)调用swapColors()函数,进入到swapColors函数执行环境,创建相应的变量对象(活动对象),将swapColors执行环境压入栈顶。这里创建活动对象可能的一个过程是,先创建活动对象,处理函数参数,将形式参数作为活动对象的属性并赋值为undefined,创建活动对象的属性arguments对象,并根据实际参数初始化形式参数和arguments对应的值和属性(将属性color1和arguments0初始化为white,由于没有第二个实际参数,所以color2的值为undefined,而arguments的长度只为1了),处理完函数参数之后,再处理函数内部变量声明,将tempColor作为活动对象的属性并赋值为undefined。(6)执行swapColors()函数代码。先给tempColor初始化赋值,然后实现值交换功能(这里color和anotherColor的值都是沿着作用域链才读取到的)。(7)swapColors()函数代码执行完之后,返回undefined,将相应的执行环境弹出栈并销毁(注意,这里会销毁执行环境,但是执行环境相应的活动对象并不一定会被销毁),当前执行环境恢复成changeColor()函数的执行环境。随着swapColor()函数执行完并返回,changeColor()也就执行完了,同样返回undefined,并将changeColor()函数的执行环境弹出栈并销毁,当前执行环境恢复成全局环境。整个处理过程结束,全局环境直至页面退出再销毁。作用域链也解释了为什么函数可以在内部递归调用自身:函数名是函数定义所在执行环境相应变量对象的一个属性,然后在函数内部执行环境中,就可以沿着作用域链向外上溯一层访问函数名执行的函数对象了。如果在函数内部将函数名指向了一个新函数,递归调用时就会不正确了:function fn(num) if(1 = num) return 1; else fn = function() return 0; ; return num * fn(num - 1); (fn(5);/0关于作用域和声明提升,再看一个例子:1 var name = linjisong;2 function fn()3 (name);/undefined4 var name = oulinhai;5 (name);/oulinhai6 7 fn();8 (name);/linjisong这里最不直观的可能是第3行输出undefined,因为在全局中已经定义过name了,不过按照上面解析的步骤去解析一次,就可以得出正确的结果了。另外强调一下,在ECMAScript中只有全局执行环境和函数执行环境,相应的也只有全局作用域和函数作用域,没有块作用域虽然有块语句。function fn() var fnScope = a; var blockScope = b; blockScope += fnScope; (blockScope);/没有块作用域,所以可以在整个函数作用域内访问blockScope (fnScope);fn();/ba,(blockScope);/ReferenceError,函数作用域外,不能访问内部定义的变量(fnScope);/ReferenceError插了一段较为抽象的概念,希望不至于影响整个阅读的流畅,事实上,我在这里还悄悄的绕过了一个称为“闭包”的概念,关于函数与闭包,在下篇文章中再详细叙述。7、函数内部对象与this对于面向对象语言的使用者来说,this实在是再熟悉不过了,不就是指向构造函数新创建的对象吗!不过,在ECMAScript中,且别掉以轻心,事情没有那么简单,虽然在使用new操作符调用函数的情况下,this也的确是指向新创建的对象,但这只是指定this对象值的一种方式而已,还有更多的方式可以指定this对象的值,换句话说,this是动态的,是可以由我们自己自由指定的。(1)全局环境中的this在全局环境中,this指向全局对象本身,在浏览器中也就是window,这里也可以把全局环境中的this理解为全局执行环境相应的变量对象,在全局环境中定义的变量和函数都是这个变量对象的属性:var vo = a;vo2 = b;function fn() return fn;(this = window);/(this.vo);/(this.vo2);/(this.fn();/fn如果在自定义函数中要引用全局对象,虽然可以直接使用window,但更好的方式则是将全局对象作为参数传入函数,这是在JS库中非常通用的一种方式:(function(global) (global = window);/在内部可以使用global代替window了)(this);这种方式兼容性更好(ECMAScript的实现中全局对象未必都是window),在压缩时,也可以将global简化为g,而不用使用window了。(2)函数内部属性this在函数环境中,this是一个内部属性对象,可以理解成函数对应的活动对象的一个属性,而这个内部属性的值是动态的。那this值是怎么动态确定的呢? 使用new调用时,函数也称为构造函数,这个时候函数内部的this被指定为新创建的对象。 function fn() var name = oulinhai;/函数对应的活动对象的属性 = linjisong;/当使用new调用函数时,将this指定为新创建对象,也就是给新创建对象添加属性var person = new fn();();/linjisong需要注意区分一下函数执行环境中定义的属性(也即活动对象的属性)和this对象的属性。 作为一般函数调用时,this指向全局对象。 作为对象的方法调用时,this指向调用这个方法的对象。 看下面的例子:var name = oulinhai;var person = name:linjisong, getName:function() return ; ;(person.getName();/linjisongvar getName = person.getName;(getName();/oulinhai这里函数对象本身是匿名的,是作为person对象的一个属性,当作为对象属性调用时,this指向了对象,当把这个函数赋给另一个函数然后调用时,是作为一般函数调用的,this指向了全局对象。这个例子充分说明了“函数作为对象的方法调用时内部属性this指向这个调用对象,函数作为一般函数调用时内部属性this指向全局对象”,也说明了this的指定是动态的,是在调用时指定的,而不管函数是单独定义的还是作为对象方法定义的。也正是因为函数作为对象的方法调用时this指向这个调用对象,所以在函数内部返回this时才能够延续调用对象的下一个方法也就是链式操作(jQuery的一大特色)。 使用apply()、call()或bind()调用函数时,this指向第一个参数对象。如果没有传入参数或传入的是null和undefined,this指向全局对象(在ES5的严格模式下会设为null)。如果传入的第一个参数是一个简单类型,会将this设置为相应的简单类型包装对象。 var name = linjisong;function fn() return ;var person = name:oulinhai, getName:fn ;var person2 = name:hujinxing;var person3 = name:huanglanxue;(fn();/linjisong,一般函数调用,内部属性this指向全局对象,因此返回(person.getName();/oulinhai,作为对象方法调用,this指向这个对象,因此这里返回(fn.apply(person2);/hujinxing,使用apply、call或bind调用函数,执行传入的第一个参数对象,因此返回(fn.call(person2);/hujinxingvar newFn = fn.bind(person3);/ES5中新增方法,会创建一个新函数实例返回,内部this值被指定为传入的参数对象(newFn();/huanglanxue上面示例中列出的都是一些常见情况,没有列出第一个参数为null或undefined的情况,有兴趣的朋友可以自行测试。关于this值的确定,在原书中还有一个例子:var name = The Window;var object = name : My Object, getName:function() return ; ;(object.getName();/My O(object.getName)();/My O(object.getName = object.getName)();/The Window第1个是正常输出,第2个(object.getName)与object.getName的效果是相同的,而第3个(object.getName=object.getName)最终返回的是函数对象本身,也就是说第3个会作为一般函数来调用。8、函数属性和方法函数是一个对象,因此也可以有自己的属性和方法。不过函数属性和方法与函数内部属性很容易混淆,既然容易混淆,就把它们放一起对照着看,就好比一对双胞胎,不对照着看,不熟悉的人是区分不了的。先从概念上来区分一下:(1)函数内部属性:可以理解为函数相应的活动对象的属性,是只能从函数体内部访问的属性,函数每一次被调用,都会被重新指定,具有动态性。(2)函数属性和方法:这是函数作为对象所具有的特性,只要函数一定义,函数对象就被创建,相应的属性和方法就可以访问,并且除非你在代码中明确赋为另一个值,否则它们的值不会改变,因而具有静态性。有一个例外属性caller,表示调用当前函数的函数,也是在函数被调用时动态指定,在JavaScript高级程序设计(第3版)中也因此将caller属性和函数内部属性arguments、this一起讲解,事实上,在ES5的严格模式下,不能对具有动态特性的函数属性caller赋值。光从概念上区分是非常抽象的,也不是那么容易理解,再把这些属性列在一起比较一下:类别名称继承性说明备注函数内部属性this-函数据以执行的环境对象和一般面向对象语言有很大区别arguments-表示函数实际参数的类数组对象arguments本身也有自己的属性:length、callee和caller1、length属性表示实际接收到的参数个数2、callee属性指向函数对象本身,即有:fn.arguments.callee = fn3、caller属性主要和函数的caller相区分,值永远都是undefined函数属性caller否调用当前函数的函数虽然函数一定义就可访问,但是不在函数体内访问时永远为null,在函数体内访问时返回调用当前函数的函数,在全局作用域中调用函数也会返回nulllength否函数形式参数的长度就是定义函数时命名的参数个数prototype否函数原型对象原型对象是ECMAScript实现继承的基础constructor是继承自Object,表示创建函数实例的函数,也就是Function()值永远是Function,也就是内置的函数Function()函数方法apply否调用函数自身,以(类)数组方式接受参数这三个方法主要作用是动态绑定函数内部属性this1、apply和call在绑定之后会马上执行2、bind在绑定之后可以在需要的时候再调用执行call否调用函数自身,以列举方式接受参数bind否绑定函数作用域,ES5中新增toLocalString覆盖覆盖了Object类型中的方法,返回函

温馨提示

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

评论

0/150

提交评论