第7章函数表达式
?????? 定义函数的方式有两种:一种是函数声明,另一种就是函数表达式。
?????? 关于函数声明,它的一个重要特征就是函数声明提升(function declaration hoisting),意思是在执行代码之前会先读取函数声明。这就意味着可以把函数声明放在调用它的语句后面。
?????? 第二种创建函数的方式是使用函数表达式。函数表达式有几种不同的语法形式。下面是最常见的一种形式。
?????? varfunctionName=function(arg0,arg1,arg2)
?????? {
????????????? //函数体
?????? }
?????? 这种形式看起来好像是常规的变量赋值语句,即创建一个函数并将它赋值给变量functionName。这种情况下创建的函数叫做匿名函数(anonymous function),因为function关键字后面没有标识符。(匿名函数有时候也叫拉姆达函数。)匿名函数的name属性是空字符串。
7.1递归
?????? arguments.callee是一个指向正在执行的函数的指针,因此可以用它来实现对函数的递归调用。
?????? function factorial(num)
?????? {
????????????? if(num<=1) {
???????????????????? return 1;
????????????? }else{
???????????????????? returnnum*arguments.callee(num-1);
????????????? }
?????? }
7.2 闭包
?????? 闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。
?????? 当某个函数第一次被调用时,会创建一个执行环境(execution context)及相应的作用域链,并把作用域链赋值给一个特殊的内部属性(即[[Scope]])。然后,使用this、arguments和其他命名参数的值来初始化函数的活动对象(activation object)。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作为作用域链终点的全局执行环境。
?????? 后台的每个执行环境都有一个表示变量的对象――变量对象。
?????? 作用链本质上是一个执行变量对象的指针列表,它只引用但不实际包含变量对象。
?????? 在另一个函数内部定义的函数会将包含函数(即外部函数)的活动对象添加到它的作用域链中。
7.2.1 闭包与变量
?????? 作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。别忘了闭包所保存的是整个变量对象,而不是某个特殊的变量。
7.2.2 关于this对象
?????? this对象是在运行时基于函数的执行环境绑定的:在全局函数中,this等于window,而当函数被作为某个对象的方法调用时,this等于那个对象。不过匿名函数的执行环境具有全局性,因此其this对象通常指向window(通过call()或apply()改变函数执行环境的情况下,this就会指向其他对象。)
7.2.3 内存泄漏
7.3 模仿块级作用域
?????? JavaScript从来不会告诉你是否多次声明了同一个变量;遇到这种情况,它只会对后续的声明视而不见(不过,它会执行后续声明中的变量初始化)。匿名函数可以用来模仿块级作用域并避免这个问题。
?????? 用作块级作用域(通常称为私有作用域)的匿名函数的语法如下所示:
?????? (function(){
?????? ? //这里是块级作用域
???? }
?????? )
?????? 以上代码定义并立即调用了一个匿名函数。将函数声明包含着一对圆括号中,表示它实际上是一个函数表达式。而紧随其后的另一对圆括号会立即调用这个函数。
?????? 在匿名函数中定义的任何变量,都会在执行结束时被销毁。
?????? 这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。
7.4 私有变量
?????? 严格来讲,JavaScript中没有私有成员的概念;所有的对象属性都是公有的。不过,倒是有一个私有变量的概念。任何在函数中定义的变量,都可以认为是私有变量,因为不能再函数的外部访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
?????? 有权访问私有变量和私有函数的公有方法称为特权方法(privileged? method)。
7.4.1 静态私有变量
?????? 通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法。
7.4.2 模块模式
?????? 模块模式(module pattern)则是为单例创建私有变量和特权方法。所谓单例(singleton),指的就是只有一个实例的对象。按照惯例,JavaScript是以字面量的方式来创建单例对象的。
?????? 如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。
7.4.3 增强的模块模式
?????? 有人进一步改进了模块模式,即在返回对象之前加入对其增强的代码。这种增强的模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和(或)方法对其加以增强的情况。
7.5小结
?????? 函数表达式的特点:
?????? (1)函数表达式不同于函数声明。函数声明要求有名字,但函数表达式不需要。没有名字的函数表达式也叫做匿名函数。
?????? (2)在无法确定如何引用函数的情况下,递归函数就会变得比较复杂。
?????? (3)递归函数应该始终使用arguments.callee来递归调用本身,不要使用函数名――函数名可能会发生变化。
?????? 当函数内部定义了其他函数时,就创建了闭包。闭包有权访问包含函数内部的所有变量,原理如下:
?????? (1)在后头执行环境中,闭包的作用链包含着它自己的作用域、包含函数的作用域和全局作用域。
?????? (2)通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。
?????? (3)但是,当函数返回了一个闭包时,这个函数的作用域将会一直在内存在保存到闭包不存在为止。
第8章 BOM
?????? BOM(浏览器对象模型)提供了很多对象,用于访问浏览器的功能,这些功能与任何网页内容无关。
8.1 window对象
?????? BOM的核心对象是window,它表示浏览器的一个实例。在浏览器中,window对象有双重角色,它既是通过JavaScript访问浏览器窗口的一个接口,又是ECMAScript规定的Global对象。这意味着网页中定义的任何一个对象、变量和函数,都以window作为其Global对象,因此有权访问parseInt()等方法。
8.1.1 全局作用域
?????? 抛开全局变量会成为window对象的属性不谈,定义全局变量与在window对象上直接定义属性还是有一点差别:全局变量不能通过delete操作符删除,而直接在window对象上定义的属性可以。
?????? 尝试访问未声明的变量会抛出错误,但是通过查询window对象,可以直达某个可能未声明的变量是否存在。
8.1.2 窗口关系及框架
?????? 如果页面中包含框架,则每个框架都拥有自己的window对象,并且保存在frames集合中。在frames集合中,可以通过数值索引(从0开始,从左至右,从上到下)或者用框架名称来访问相应的window对象。每个window都有一个name属性,其中包含框架名称。
?????? top对象始终指向最高(最外)层的框架,也就是浏览器窗口。使用它可以确保在一个框架中正确地访问另一个框架。因为对于一个在框架中编写的任何代码来说,其中的window对象指向的都是那个框架的特定实例,而非最高层框架。
?????? 与top相对的另一个window对象是parent。parent(父)对象始终指向当前框架的直接上层框架。在某些情况下,parent有可能等于top;但在没有框架的情况下,parent一定等于top(此时它们都等于window)。
?????? 注意,除非最高层窗口时通过window.open()打开的,否则其window对象的name属性不会包含任何值。
?????? 与框架有关的最后一个对象是self,它始终指向window;实际上,self和window对象可以互换使用。引入self对象的目的只是为了与top和parent对象对应起来,因此它不格外包含其他值。
8.1.3 窗口位置
?????? 使用下列代码可以跨浏览器取得窗口左边和上边的位置。
?????? varleftPos=(typeofwindow.screenLeft=="number")?window.screenLeft
??????????????????????????? :window.screenX;
?????? vartopPos=(typeofwindow.screenTop=="number")?window.screenTop
??????????????????????????? :window.screenY;
?????? 使用moveTo()和moveBy()方法倒是有可能将窗口精确地移动到一个新位置。这两个方法都接收两个参数,其中moveTo()接收的是新位置的x和y坐标值,而moveBy()接收的是在水平和垂直方向上移动的像素数。
8.1.4 窗口大小
8.1.5 导航和打开窗口
?????? 使用window.open方法既可以导航到一个特定的URL,也可以打开一个新的浏览器窗口。这个方法可以接收4个参数:要加载的URL、窗口目标、一个特性字符串以及一个表示新页面是否取代浏览器历史记录中当前加载页面的布尔值。通常只须传递第一个参数,最后一个参数只在不打开新窗口的情况下使用。
8.1.6 间歇调用和超时调用
?????? JavaScript是单线程语言,但它允许通过设置超时值和间歇时间来调度代码在特定的时刻执行。前者是在指定的时间过后执行代码,而后者则是每隔指定的时间就执行一次代码。
?????? 调用setTimeout()之后,该方法会返回一个数值ID,表示超市调用。这个超时调用ID是计划执行代码的唯一标识符,可以通过它来取消超时调用。
?????? 超时调用的代码都是在全局作用域中执行的,因此函数中this的值在非严格模式下指向window对象,在严格模式下是undefined。
?????? 一般认为,使用超时调用来模拟间歇调用的是一种最佳模式。在开发环境下,很少使用真正的间歇调用,原因是后一个间歇调用可能会在前一个间歇调用结束之前启动。
8.1.7 系统对话框
?????? 浏览器通过alert()、confirm()和prompt()方法可以调用系统对话框向用户显示消息。
?????? //显示“打印”对话框
?????? window.print();
//显示“查找”对话框
window.find();
8.2 location对象
?????? location是最有用的BOM对象之一,它提供了当前窗口中加载文档有关的信息,还提供了一些导航功能。
8.2.1 查询字符串参数
8.2.2 位置操作
?????? 使用location对象可以通过很多方式来改变浏览器的位置。最常见的方式就是使用assign()方法并为其传递一个URL。如果是将location.href或window.location设置为一个URL值,也会调用assign()方法。
replace()只接受一个参数,即要导航到的URL;结果虽然会导致浏览器位置改变,但不会在历史记录中生成新记录。在调用replace()方法之后,用户不能回到前一个页面。
location.reload();//重新加载(有可能从缓存中加载)
location.reload(true);//重新加载(从服务器重新加载)
8.3 navigator对象
8.3.1 检测插件
?????? 检测浏览器中是否按照了特定的插件是一种最常见的检测例程。对于非IE浏览器,可以使用plugins数组来达到这个目的。该数组中的每一项都包含下列属性。
(1)name:插件的名字。
(2)description:插件的文件名。
(3)filename:插件的文件名。
(4)length:插件所处理的MIME类型数量。
plugins集合有一个名叫refresh()的方法,用于刷新plugins以反映最新安装的插件。这个方法接收一个参数:表示是否应该重新加载页面的一个布尔值。如果将这个值设置为true,则会重新加载包含插件的所有页面;否则,只更新plugins集合,不重新加载页面。
8.3.2 注册处理程序
?????? Firefox2为navigator对象新增了registerContentHandler()和registerProtocolHandler()方法(这两个方法是在HTML5中定义的)。这两个方法可以让一个站点指明它可以处理特定类型的信息。
?????? 其中,registerContentHandler()方法接收三个参数:要处理的MIME类型、可以处理该MIME类型的页面的URL以及应用程序的名称。
?????? registerProtocolHandler()方法,它也接收三个参数:要处理的协议、处理该协议的页面的URL和应用程序的名称。
8.4? screen对象
?????? screen对象基本上只用来表明客户端的能力,其中包括浏览器窗口外部的显示器信息,如像素宽度和高度等。
8.5 history对象
?????? history对象保存着用户上网的历史记录,从窗口被打开的那一刻算起。
?????? 使用go()方法可以在用户的历史记录中任意跳转,可以向后也可以向前。这个方法接受一个参数,表示向后或向前跳转的页面数的一个整数值。负数表示向后跳转,正数表示向前跳转。
?????? 也可以给go()方法传递一个字符串参数,此时浏览器会跳转到历史记录中包含该字符串的第一个位置。
?????? 还可以使用两个简写的方法back()和forward()来代替go()。
?????? history对象还有一个length属性,保存这历史记录的数量。