blog-md icon indicating copy to clipboard operation
blog-md copied to clipboard

函数表达式

Open jiangjiu opened this issue 8 years ago • 0 comments

函数表达式

递归

  • 经典递归阶乘函数,但是是强耦合,一旦赋给其他函数并对原函数销毁,就会失效
  //递归经典函数,但是是强耦合
    function factorial(num) {
      if (num <= 1) {
        return 1
      } else {
        return num * factorial(num - 1)
      }
    }
  • 非严格模式下使用arguments.callee可以指向函数自身,从而解耦
 // 使用 arguments.callee解耦,严格模式下报错
    function factorial1(num) {
      if (num <= 1) {
        return 1
      } else {
        return num * arguments.callee(num - 1)
      }
    }
  • 严格模式下,使用命名函数表达式来解决
 //严格模式和非严格模式均可使用
  var factorial2 = (function f(num) {
    if (num <= 1) {
      return 1
    } else {
      return num * f(num - 1)
    }
  })

闭包

  • 闭包和匿名函数容易混用,实际闭包是指有权访问另一个函数作用域的变量的函数。创建闭包的常见方式,就是函数之中创建另一个函数。
  • 当某个函数被调用时,会创建一个执行环境以及相应的作用域链。然后使用 arguments 和其他参数的值来初始化函数的活动对象。外部函数的活动对象始终处于第二位,外层的外层是第三位直至找到全局执行环境。
  • 作用域链本质上是一个指向变量对象的指针列表,只引用但不实际包含变量对象。
  • 无论何时在函数中访问一个变量时,就会在作用域链中搜索相应变量,函数执行完毕后,局部活动对象会被销毁,内存中仅保存全局作用域,但闭包的情况有所不同
var compare = createComparisonFunction('name')
var result = compare({name: 'Nicholas'}, {name: 'James'})
compare = null //手动解除引用
  • createComparisionFunction()函数执行完毕后,其执行环境的作用域链会被销毁,但活动对象并不会,因为compare作用域链还保留着对活动对象的引用。直到 compare 设置为 null 后,才会释放内存。
  • 由于闭包会懈怠包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多,建议只在绝对必要的时候使用闭包。

闭包和变量

  • 闭包只能取得包含函数中的任何变量的最后一个值,因为闭包保存的是整个变量对象,而不是某一个特殊的变量值。
  //闭包与变量,实际是保存了整个变量对象,并非某个特殊的值
    function createFunctions() {
      var result = []
      for (var i = 0; i < 10; i++) {
        result[i] = function () {
          return i
        }
      }
      return result
    }
  • 例子中的result[i]调用后取得的值都是10,因为当函数执行时 i 已经变成了10,给每一个数组元素都执行了一遍。通过匿名函数赋值就可以解决这个问题。

关于this 对象

  • 在全局函数中,this 指向 window。
  • 当作为某个对象的方法调用时,this 指向对象本身。
  • 匿名函数执行具有全局性,因此 this 对象通常指向 window。
 //this 对象
    var name = 'window'
    var obj = {
      name: 'obj',
      getName: function () {
        return function () {
        return this.name
        }
      }

    }
    obj.getName()()
  • 在通过call()和 apply()方式调用时,this 会指向调用的对象。

内存泄露

  • 如果闭包的作用域链中保存着一个 HTML 元素,那么意味着该元素无法被销毁。
function assignHandler() {
    var el = document.getElementById('ddd') //dom 元素
    el.onclick = function() {
        alert(el.id)    //在匿名函数中引用了 el 元素
    }
}
  • 由于匿名函数保存了一个对assignHandler()方法中的活动对象的引用,因此会导致无法减少 el 的引用数。只要匿名函数存在,el 的引用次数最少是1,占用的内存永远不会回收。
function assignHandler() {
    var el = document.getElementById('ddd') //dom 元素
    var id = el.id
    el.onclick = function() {
        alert(id)    //在匿名函数中引用了局部变量 id
    }
    el = null  //解除引用
}
  • 通过把 el.id 的副本保存在变量中,最后解除引用,从而顺利回收内存。

私有变量

  • 私有变量包含的是函数的参数,局部变量和函数内部的其他函数。
  • 有权访问私有变量和私有函数的公有方法称为特权方法,有两种在对向上创建特权方法的方式:构造函数定义特权方法,静态私有变量。

构造函数定义特权方法

 //私有变量,在构造函数中定义特权方法
  function MyObject() {
    var privateVal = 10

    function privateFunc() {
      return false
    }

    this.publicMethod = function () {
      privateVal++
      return privateFunc()
    }
  }
  • 特权方法作为闭包有权访问在构造函数中定义的所有变量和函数,而且可以隐藏不该直接修改的数据。在函数外部无法访问内部私有变量。
  • 构造函数模式无法复用函数,每个实例都会创建相同方法,造成浪费。

静态私有变量


  (function(){

    var privateVal = 10
    function privateFunc() {
      return false
    }

    MyObject = function(){} //不使用 var 命名,成为全局变量

    MyObject.prototype.publicMethod = function(){
      privateVal++
      return privateFunc()
    }
  }())
  • 在私有作用域定义私有变量或函数,并通过不实用 var 关键字使构造函数成为全局变量,通过原型模式实现私有变量共享。
  • 这个模式与在构造函数中定义特权方法的区别,就在于私有变量和函数是被实例共享的。一旦属性被修改,所有实例都会收到影响。
  • 而且使用闭包和私有变量,会影响查找作用域链的速度,影响性能。
  • 这种方法增进代码复用,但每个实例都没有自己的私有变量,所以具体和上面的方法要视情况。

模块模式

  • 为单例模式创建私有变量和特权方法。所谓单例,就是指只有一个实例的对象。
 var singleton = function() {
    var privateVal = 10
    function privateFuncion() {
      return false
    }

    return {  //返回一个对象字面量,包含公有方法和属性
      publicProperty: true,
      publicMethod: function(){
        privateVal++
        return privateFuncion()
      }
    }
  }
  • 这种模式对于需要对单例进行某些初始化,同时又需要维护私有变量时是非常有用的。
  • 在 web 程序中,经常需要一个单例来管理应用程序级的信息。这个单例可以作为 app 的入口,又比如有些 jquery 插件也会使用模块模式,对插件进行初始化和增强。

增强的模块模式

  • 有人进一步改进了模块模式,即在返回对象之前加入对其增强的代码。这种增强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性和方法对其进行增强的情况。
 var application = function () {
    var components = []

    components.push(new BaseComponent())

    var app = new BaseComponent()  //BaseComponent()的实例

    app.getComponentCount = function () {
      return components.length
    }

    app.registerComponent = function (component) {
      if (typeof component === 'object') {
        components.push(component)
      }
    }

    return app //返回这个 app 实例,包括了两个共有方法
  };
  • 实际与模块模式的区别就在于,创建了一个实例,作为另一个构造器的实例,添加公有方法并返回这个对象。

jiangjiu avatar May 27 '16 03:05 jiangjiu