JS函数表达式的应用

递归、闭包、模仿块级作用域、私有变量

作者 Trekerz 日期 2017-11-12
JS函数表达式的应用

前言

(1) 函数定义方式

函数声明、函数表达式。

(2) 声明提升

  • 函数声明在执行代码前就会先被读取,这意味着可以把调用放在声明前面;
  • 函数表达式则没有提升的过程。

所以,下面的做法是错误的,不同浏览器对它有不同处理方式:

img

一、递归用法

(1) 原理

在一个函数内通过名字调用自身。

(2) 应用

阶乘的递归算法。

(3) 改进

在一个函数内调用自身时,更好的实践是通过arguments.callee调用自己,这样就不会在改变指针变量后出错。

注意:在严格模式下不能通过脚本访问arguments.callee,此时可以使用命名函数表达式来解决:

img

二、闭包

(1) 定义

有权访问另一函数作用域中的变量的函数。

创建闭包的一种常见方式:在一个函数内部创建另一个函数。(原理是:内部函数的作用域链中包含外部函数的作用域。)

(2) 内存泄漏根源

外层函数返回后,其活动对象不会被销毁,因为内层匿名函数的作用域链仍然在引用这个活动对象,该活动对象仍驻留在内存中。直到内层函数被销毁后,外层活动对象才会被销毁。

解决:手动解除对匿名函数的引用。

(3) 与外层变量的问题

闭包只能取得包含函数中任何变量的最后一个值

解决:在闭包和外层函数中间再加一个接收参数的立即执行函数,如下图:

img

(4) this对象的问题

须知1:匿名函数的执行环境具有全局性,因此其this正常情况下是指向window的(callapply和一些编写技巧除外)。

须知2:每个函数在被调用时自动取得thisarguments两个对象,不同于普通对象,内部函数在搜索这两个对象时,只会搜索到其活动对象为止,永远不可能搜索到外部函数中的这两个对象。所以下图中就返回了全局作用域中的name:

img

如果要获得外层函数getNameFunc作用域object中的this,可以使用下图的技巧

img

注意:一个关于this的陷阱(最后一行,它是把getName单独作为一个函数来调用了):

img

(5) 内存泄漏

  • 闭包在<IE9会在引用DOM上产生一些问题。<IE9的版本中DOM是一种COM对象,垃圾回收使用的是引用计数,可能会有循环引用的问题。
  • 闭包会引用外层函数的整个活动对象。

img

这里,闭包永远在监听,并且闭包的作用域链中永远引用着外层函数的活动对象(其中包含element变量),其占用内存永远不会被回收。

通过以下改写可以解决这两个问题:

img

这里通过把element.id赋给变量id解决了循环引用,通过element=null解绑了对象。

三、模仿块级作用域

(1) 定义

块级作用域,也称私有作用域。

(2) 用匿名函数模拟块级作用域

img

放在括号里的函数声明表明它是一个函数表达式。

注意1:这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数

注意2:这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用

四、私有变量

(1) 定义

任何在函数中定义的变量,都可以认为是私有变量。(包括函数的参数、局部变量、在函数内定义的其它函数)

(2) 特权方法

有权访问私有变量和私有函数的公有方法称为特权方法。特权方法作为闭包有权访问在构造函数中欧定义的所有变量和函数,而私有变量和私有函数又只能通过特权函数来访问。

img

(3) 静态私有变量

自定义类型创建私有变量和方法。

如下图:在一个私有作用域内,初始化了一个未经声明的变量MyObject这就创建了一个全局变量,MyObject就能在私有作用域外被访问到

img

注意:这个模式与在构造函数内定义特权方法的区别在于:私有变量和函数是由实例共享的。由于特权方法定义在原型上,因此所有实例都使用同一个函数,并且特权方法总是保存着对包含作用域的引用。

(4) 模块模式

单例创建私有变量和方法。

注意:JavaScript的惯例是以对象字面量的方式来创建单例的,但利用模块模式来创建可以使单例的功能更丰富(私有变量、私有函数这些)。

img

用处:如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那就可以用模块模式。

(5) 增强的模块模式

改进点:在返回对象之前加入对其增强的代码,这个代码适合那些要求单例必须是某种类型的情况。

img

注意:这里的app实际上是application的局部变量版,实际上结果仍然是赋给全局变量application。


end -