blog-md
blog-md copied to clipboard
函数表达式
函数表达式
递归
- 经典递归阶乘函数,但是是强耦合,一旦赋给其他函数并对原函数销毁,就会失效
//递归经典函数,但是是强耦合
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 实例,包括了两个共有方法
};
- 实际与模块模式的区别就在于,创建了一个实例,作为另一个构造器的实例,添加公有方法并返回这个对象。