函数表达式和闭包
1. 函数声明的一个重要特征是函数声明提升
如:
sayHi()
function sayHi () {
console.log('hi')
}
2. 递归
递归函数是在一个函数通过名字调用自身的情况下构成的。
如:
function factorial (num) {
if (num <= 1) {
return 1
} else {
return num * factorial(num - 1) // 这里存在强耦合,不太好
}
}
比如下面的代码会导致它出错
var anotherFactorial = factorial
factorial = null
anotherFactorial(3) // 出错:factorial is not a function
arguments.callee 是一个指向正在执行函数的指针,可以做如下改善:
function factorial (num) {
if (num <= 1) {
return 1
} else {
return num * arguments.callee(num - 1)
}
}
严格模式下使用 arguments.callee 会出错。可以做以下改善
var factorial = (function f (num) {
if (num <= 1) {
return 1
} else {
return num * f(num - 1)
}
})
我的理解:这里的 f 只在函数内部有效,所以不会受到外界影响,外部得不到这个变量。
(function test () {
console.log('this is test')
})
test() // 报错。
用小括号括起来的函数声明在外部是得不到的,test()只有函数内部可以用
3. 闭包
闭包指有权访问另一个函数作用域中的变量的函数。
创建闭包的常用方式,就是在一个函数内部创建另一个函数。
如:
function createComparisonFunction (propertyName) {
return function(obj1, obj2) {
var value1 = obj1[propertyName]
var value2 = obj2[propertyName]
return value1 - value2
}
}
var obj = [{age: 13}, {age: 29}, {age: 18}, {age: 37}, {age: 5}, {age: 14}]
var compare = createComparisonFunction('age')
var obj2 = obj.sort(function (a, b) {
compare(a, b)
})
这个例子中,内部函数访问了外部函数的变量 propertyName,而且即使被返回了在其它地方调用,仍然可以访问。
是因为内部函数的作用域链中包含了 createComparisonFunction 的作用域链。
由于闭包会携带包含它的函数的作用域,因此会比其它函数占用更多的内存。
4. 闭包与变量
闭包作用域链的配置机制有一个副作用,闭包只能取得包含函数中任何变量的最后一个值,因为闭包保存的是整个变量对象,不是某个特殊的变量。
如:
function createFunction () {
var result = []
for (var i = 0; i < 10; i++) {
result[i] = function () {
return i
}
}
return result
}
// 返回数组中每个函数的执行结果都是10
改善版:
function createFunction () {
var result = []
for (var i = 0; i < 10; i++) {
result[i] = (function (num) {
return function () {
return num
}
}(i))
}
return result
}
这样就按预期来返回值了,这里通过立即执行函数把变量 i 的当前值作为参数传递到了内部闭包函数里,由于函数的基本类型参数是按值传递,
所以每个内部闭包函数保存的值都是当前 i 值。
5. 关于闭包的this对象
var name = 'window';
(function () {
this.name = 'fn'
console.log(this.name) // 'fn'
var obj = {
name: 'obj',
getFunc: function () {
return function () {
console.log(this.name)
}
}
}
obj.getFunc()() // 'fn'
}())
闭包函数的this指向它被调用时的作用域对象。
每个函数被调用时都会自动取得两个特殊的变量:this 和 arguments。在调用这两个变量时,只会搜索到其活动对象为止。
this 的改变,如:
var name = 'the Window'
var obj = {
name: 'obj',
getName: function () {
console.log(this.name)
}
}
obj.getName(); // 'obj'
(obj.getName)(); // 'obj'
(obj.getName = obj.getName)(); // 'the Window'
var obj2 = {
name: 'obj2'
}
(obj2.getName = obj.getName)(); // 'the Window'
// 这里我的理解是相当于一个立即执行函数,这个立即执行函数是在window环境下被执行的,所以返回window下的变量值。
obj2.getName = obj.getName
obj2.getName() // 'obj2'
6. 通过构造函数创建私有变量和特权方法
如:
function Person (name) {
this.getName = function () {
return name
}
this.setName = function (value) {
name = value
}
}
var ming = new Person('ming')
ming.name // undefined
ming.getName() // 'ming'
ming.setName('ming2')
ming.getName() // 'ming2'
说明:getName() setName() 在构造函数外部使用,只有这两个方法有权访问私有变量name,没有其它办法,因为它们作为闭包能够通过作用域链访问name
这种方法创建私有变量和特权方法有构造函数所共有的缺点——函数没有复用。
7. 静态私有变量
(function () {
var name = ''
Person = function (value) {
name = value
}
Person.prototype.getName = function () {
return name
}
Person.prototype.setName = function (value) {
name = value
}
}())
var ming = new Person('ming')
ming.getName() // 'ming'
ming.setName('ming2')
ming.getName() // 'ming2'
var li = new Person('li')
li.getName() // 'li'
ming.getName() // 'li' // 说明所有实例都是对作用域链中的 name 的引用
说明:这里立即执行函数里Person使用函数表达式来定义,而且没有使用 var , 是为了形成全局变量。
或者先在全局定义一下 Person也可以。
var Person = function () {}
注意:es6 中已经是块级作用域了,所以这些东西感觉实际用途没有那么大,但是对理解闭包对作用域链中的属性的引用,这一点还是有作用的。
8. 模块模式
指为单例创建私有变量和特权方法。所谓单例,指的是只有一个实例的对象。
如:
var application = function () {
var components = []
components.push(new BaseComponent()) // BaseComponent 指初始化组建
return {
getComponentCount: function () {
return components.length
},
registerComponent: function (component) {
if (typeof component === 'object') {
components.push(component)
}
}
}
}()
增强版,适合那种单例必须是某种类型的实例:
var application = function () {
var components = []
components.push(new BaseComponent())
var app = new Base()
app.getComponentCount = function () {
return components.length
}
app.registerComponent = function (component) {
if (typeof component === 'object') {
components.push(component)
}
}
return app
}()
// 函数表达式和闭包部分结束
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。