# 函数定义

# 函数声明

关于函数声明,一个重要的特性就是函数声明提升。意思是在执行代码之前会先读取函数声明。

// 可以执行
sayHi()
function sayHi() {
  console.log('hi')
}

# 函数表达式

这种创建的函数也叫匿名函数;函数表达式与其他表达式一样,在使用前必须先赋值。

functionName() // 错误:函数还不存在
var functionName = function(arg0, arg1, arg2) {
  // 函数体
}

# 递归

就是在一个函数中通过函数名调用自身,下面代码实现一个经典的阶乘:

function factorial(num) {
  if (num <= 1) {
    return 1
  } else {
    return num * factorial(num - 1)
  }
}

var anotherFactorial = factorial
factorial = null
console.log(anotherFactorial(4)) // anotherFactorial 中的引用函数 factorial 已经变成了 null

函数中的 arguments.callee 是一个指向正在执行的函数的指针,因此可以用它来实现对函数的递归调用:

function factorial(num) {
  if (num <= 1) {
    return 1
  } else {
    return num * arguments.callee(num - 1)
  }
}

var anotherFactorial = factorial
factorial = null
console.log(anotherFactorial(4)) // 24

在严格模式下不能通过脚本访问 arguments.callee,访问这个属性会导致错误,可以使用一个命名函数表达式来达成相同的效果:

var factorial = (function f(num) {
  if (num <= 1) {
    return num
  } else {
    return num * f(num - 1)
  }
})

上面代码创建了一个名为 f() 的命名函数表达式,然后将它赋值给变量 factorial,即便把函数赋值给另一个变量,函数的名字 f 仍然有效,所以递归调用照样能正确完成。

# 闭包

# 创建闭包

  • 概念 闭包是指有权访问另一个函数作用域中的变量的函数;
  • 创建闭包的常见方式 就是在一个函数内部创建另一个函数:

如下代码:

function createComparisonFunction(propertyName) {
  return function(object1, object2) {
    // 访问另一个函数作用域中的变量
    var value1 = object1[propertyName]
    var value2 = object2[propertyName]

    if (value1 < value2) {
      return -1
    } else if (value1 > value2) {
      return 1
    } else {
      return 0
    }
  }
}
// 创建函数
var compareNames = createComparisonFunction('name')

// 调用函数
var result = compareNames({name: 'Nicholas'}, {name: 'Greg'})

// 解除对匿名函数的引用(以便释放内存)
compareNames = null

createComparisonFunction 函数在执行完成后,他的活动对象(arguments 以及其他命名参数的值)不会被销毁,因为匿名函数的作用域链仍然在引用这个活动对象,也就是说,当 createComparisonFunction 函数返回后,其执行环境的作用域链会被销毁,但他的活动对象仍然会留在内存中,直到匿名函数被销毁后, createComparisonFunction 函数的活动对象才会被销毁。

由于闭包会携带包含他的函数的作用域,因此会比其他函数占用更过的内存。过度使用闭包可能会导致内存占用过多。

# 闭包与变量

作用域链的这种配置机制引出了一个值得注意的副作用,即闭包只能取得包含函数中任何变量的最后一个值。闭包所保存的是整个变量对象,而不是某个特殊的变量;如下代码:

function createFunction() {
  var result = new Array()
  for (var i = 0; i < 10; i ++) {
    result[i] = function () {
      return i
    }
  }
  return result
}

上面的代码中 result 数组存储的函数并没有都返回自己的索引值,而是都返回 10;因为每个函数的作用域链中都保存着 createFunctions() 函数的活动对象,所以他们引用的都是同一个变量 i,当 createFunctions() 函数执行返回后,变量 i 的值就变成了 10,此时每个函数都引用着保存变量 i 的同一个变量对象,所以每个函数内部 i 的值都是 10。

有两种方法解决上面的问题,第一种是创建另一个匿名函数强制让闭包的行为符合预期: 由于函数参数是按值传递的,所以就会将变量 i 的当前值复制给参数 num;

function createFunctions() {
  var result = new Array()
  for (var i = 0; i < 10; i ++) {
    result[i] = function(num) {
      return function() {
        return num
      }
    }(i)
  }
  return result
}

第二种是使用 let :

function createFunction() {
  var result = new Array()
  for (let i = 0; i < 10; i ++) {
    result[i] = function () {
      return i
    }
  }
  return result
}

# 关于 this 对象

this 对象是在运行时基于函数的执行环境绑定的:

  • 在全局函数中:this 等于 window;
  • 函数被作为某个对象的方法调用时:this 等于那个对象;
  • 匿名函数的执行环境具有全局性,因此其 this 对象通常执向 windos,当然,在通过 call() 或者 apply() 改变函数的执行环境的情况下,this 就会指向其他对象;

# 内存泄漏

如下代码:

function assignHandler() {
  var element = document.getElementById('someElement')
  element.onclick = function() {
    console.log(element.id)
  }
}

上面代码创建了一个作为 element 元素事件处理程序的闭包;由于匿名函数存在对 assignHandler() 的活动对象的引用,所有他所占用的内存就永远不会被回收,可以像下面这样改一下代码:

function assignHandler() {
  var element = document.getElementById('someElement')
  var id = element.id

  element.onclick = function() {
    console.log(id)
  }
  element = null
}

这样减少其引用次数,确保正常回收其占用的内存。

# 模仿块级作用域

匿名函数可以用来模仿块级作用域:

(function() {
  // 这里是块级作用域
})()

如下代码:

(function() {
  var now = new Date()
  if (now.getMonth() === 0 && now.getDate() === 1) {
    console.log('Happy new year!')
  }
})()

这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链;

# 私有变量

任何在函数中定义的变量,都可以认为是私有变量,因为不能再函数的外部访问这些变量;我们可以通过在函数内部创建一个闭包,那么闭包通过自己的作用域链可以访问这些变量:

function MyObject() {
  // 私有变量和私有函数
  var privateVariable = 10
  function privateFunction() {
    return false
  }
  // 访问私有变量以及私有函数
  this.publicMethod = function() {
    privateVariable++
    return privateFunction
  }
}

利用这种方法可以隐藏那些不应该被直接修改的数据,例如:

function Person(name) {
  this.getName = function() {
    return name
  }
  this.setName = function(value) {
    name = value
  }
}

var person = new Person('jiegiser')
console.log(person.getName()) // jiegiser
person.setName('Grey')
console.log(person.getName()) // Grey

在函数中定义一个闭包来访问私有变量,有一个缺点,那就是你必须使用构造函数模式来打到这个目的,构造函数模式的缺点是针对每个实例都会创建同样一组新方法,使用静态私有变量来实现访问私有变量就可以避免这个问题;

# 静态私有变量

通过在私有作用域中定义私有变量或函数,同样也可以访问函数中的变量,如下代码:

(function() {
  // 私有变量和私有函数
  var privateVariable = 10
  function privateFunction() {
    return false
  }

  // 构造函数
  MyObject = function() {}

  // 访问私有变量
  MyObject.prototype.publicMethod = function() {
    privateVariable++
    return privateFunction()
  }
})()

上面代码由于访问私有变量的方法是在原型上定义的,因此所有实例都使用同一个函数,有一个缺点是 MyObject 构造函数成为了全局变量,而这个访问私有变量的方法 publicMethod 作为一个闭包,总是保存着对包含作用域的引用,如下代码:

(function () {
  var name = ''
  Person = function(value) {
    name = value
  }
  Person.prototype.getName = function() {
    return name
  }
  Person.prototype.setName = function(value) {
    name = value
  }
})()

var person1 = new Person('Nocholas')
console.log(person1.getName()) // Nocholas
person1.setName('Grey')
console.log(person1.getName()) // Grey

var person2 = new Person('Michale')
console.log(person1.getName()) // Michale
console.log(person2.getName()) // Michale

上面的方式创建静态私有变量会因为使用原型而增进代码复用,但是每个实例都没有自己的私有变量。

# 模块模式

也是单例模式,这种模式的应用场景就是,如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就使用这个模式, 以这种模式创建的每个单例都是 Object 的实例,因为最终要通过一个对象字面量来表示他;这种单例通常作为全局对象存在;

如下例子:

(function() {
  // 私有变量和函数
  var components = new Array()

  // 初始化
  components.push(new BaseComponent())

  // 公共
  return {
    getComponentCount: function() {
      return components.length
    },
    registerComponent: function(component) {
      if (typeof component === 'object') {
        components.push(component)
      }
    }
  }
})()

# 增强的模块模式

可以在返回对象之前加入对其增强的代码。这种增强的模块模式适合那些单例必须是某种类型的实例,同时还必须添加某些属性或方法对其加以增强的情况,比如下面例子:

var singleton = function() {
  // 私有变量和私有函数
  var privateVariable = 10
  function privateFunction() {
    return false
  }

  // 创建对象
  var object = new CustomType()

  // 添加公有属性以及访问私有变量方法
  object.publicProperty = true
  object.publicMethod = function() {
    privateVariable++
    return privateFunction()
  }
  // 返回这个对象
  return object
}

评 论:

更新: 12/27/2020, 4:59:16 PM