# 函数定义
# 函数声明
关于函数声明,一个重要的特性就是函数声明提升。意思是在执行代码之前会先读取函数声明。
// 可以执行
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
}
评 论:
← 继承 promise与异步函数 →