javascript中的函数表达式
Firefox、Safari、Chrome和Opera 都给函数定义了一个非标准的name
属性,通过这个属性可以访问到给函数指定的名字。这个属性的值永远等于跟在function
关键字后面的标识符。
function func(){}
console.info(func.name) // 输出func,即function关键字后面的标识符
对于函数表达式
var func = function(){}
console.info(func.name) // 空字符串
是没有名字的,也就是说函数表达式是拉姆达函数.关于函数声明和函数表达式最大的区别就是函数声明具有提升,函数表达式必须先定义,否则会报错TypeError xxx is not a function
递归arguments.callee
是一个指向正在执行的函数的指针,因此可以用它来实现对函数的递归调用.
function fac(num) {
return num <= 1 ? 1 : num * arguments.callee(num - 1)
}
Tips:
通过使用
arguments.callee
代替函数名,可以确保无论怎样调用函数都不会出问题。在严格模式下不能访问
arguments.callee
,可以使用命名函数表达式来达成相同的结果。
'use strict'
var fac = (function f(num) {
return num <= 1 ? 1 : num * f(num - 1)
})
以上代码创建了一个名为f()的命名函数表达式,然后将它赋值给变量fac。即便把函数赋值给了另一个变量,函数的名字f仍然有效,所以递归调用照样能正确完成。
闭包闭包和匿名函数经常被混淆.实际上闭包是指有权访问另一个函数作用域中的变量的函数。.创建闭包的常见方式,就是在一个函数内部创建并返回另一个函数.
function makeFunc() {
var name = "Mozilla";
function displayName() {
console.info(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc(); // Mozilla,访问的函数内部的成员
关与闭包,阮一峰老师的这篇文章将的很清楚,对新手很有用.现将该文章结尾处的两个思考题分析下:
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
console.log('getNameFunc#this => ', this, 'this.name => ', this.name);
return function() {
console.info('lambdaFunction#this => ', this, 'this.name => ', this.name);
return this.name;
};
}
};
console.log('#demo1', object.getNameFunc()()); // The Window
var name = "The Window";
var object = {
name: "My Object",
getNameFunc: function() {
var that = this;
return function() {
return that.name;
};
}
};
console.log('#demo2', object.getNameFunc()()); // My Object
我们在Chrome控制台执行下以上程序:
其中由于匿名函数的执行环境具有全局性,所以匿名函数的this绑定的是全局对象,其他的也就不难理解了,demo2中使用that变量保存了外部的this指向.同理如果想要保存arguments也应该采用这种方式.
闭包和变量来看下面的一个例子:
function createFunctions() {
'use strict'
var result = []
for (var i = 0; i < 5; i++) {
result[i] = function() {
return i
}
}
for (var j = 0; j < 5; j++) {
console.info(result[j]()) // 5 5 5 5 5
}
for (var i = 0; i < 5; i++) {
console.info(result[i]()) // 0 1 2 3 4
}
for (let i = 0; i < 5; i++) {
console.info(result[i]()) // 5 5 5 5 5
}
return result
}
createFunctions()
其中result数组中存放了5个函数,每个函数的返回值都是5,而不是我们期待的0,1,2,3,4,其中第二个循环输出的0,1,2,3,4给我们造成了一个错觉:仅仅是因为i是一个函数作用域,可以转化为以下的更容易理解的代码:
function createFunctions() {
var result = []
var i
for (i = 0; i < 5; i++) {
result[i] = function() {
return i
}
}
for (i = 0; i < 5; i++) {
console.info(result[i]()) // 0 1 2 3 4
}
return result
}
createFunctions()
如果我们使用ES5的严格模式或者是更换一个变量就可以看到真是的函数绑定了!再来分析下为什么每个函数都是返回5.
因为每个函数的作用域链中都保存着
createFunctions()
函数的活动对象, 所以它们引用的都是同一个变量i 。 当createFunctions()
函数返回后,变量 i 的值是 5,此时每个函数都引用着保存变量 i的同一个变量对象,所以在每个函数内部 i 的值都是 5。但是,我们可以通过创建另一个匿名函数强制让闭包的行为符合预期,如下所示。
function createFunctions() {
'use strict'
var result = []
var i
for (i = 0; i < 5; i++) {
result[i] = function(num) {
return num
}(i)
}
for (let i = 0; i < 5; i++) {
console.info(result[i]) // 0 1 2 3 4
}
return result
}
createFunctions()
我们没有直接把闭包赋值给数组,而是定义了一个匿名函数,并将立即执行该匿名函数的结果赋给数组。
闭包带来的内存泄露问题由于IE9 之前的版本对 JScript 对象和 COM 对象使用不同的垃圾收集例程,所以IE的低版本存在内存泄露问题,具体来说就是如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。请看下面的例子:
function assignHandler() {
var element = document.getElementById('someElement')
element.onclick = function() {
alert(element.id)
}
}
以上代码创建了一个作为element
元素事件处理程序的闭包,而这个闭包则又创建了一个循环引用.由于匿名函数保存了一个对assignHandler()
的活动对象的引用,因此就会导致无法减少element
的引用数。只要匿名函数存在, element的引用数至少也是 1,因此它所占用的内存就永远不会被回收。这个问题可以通过稍微改变下代码来解决:
function assignHandler() {
var element = document.getElementById('someElement')
var id = element.id
element.onclick = function() {
alert(id)
}
element = null
}
模仿块级作用域通过把element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用。但仅仅做到这一步,还是不能解决内存泄漏的问题。必须要记住:闭包会引用包含函数的整个活动对象,而其中包含着element。即使闭包不直接引用element,包含函数的活动对象中也仍然会保存一个引用。因此,有必要把 element 变量设置为 null 。这样就能够解除对 DOM 对象的引用,顺利地减少其引用数,确保正常回收其占用的内存。
function ouputNumbers (count) {
for(var i = 0;i < count;i++)
console.info(i) // 0...9
var i // 重新声明变量
console.info(i) // 结果仍然是10
}
ouputNumbers(10)
通过匿名立即执行函数(IIFE)可以解决上述问题.
(function () {
// 这是块级作用域
})()
下面的方式可能更好理解:
var someFunction = function() {
// 这是块级作用域
}
someFunction()
使用这个思想我们可以实现for循环中变量的块级作用域:
(function() {
for (var i = 0; i < 10; i++)
console.info(i) // 0...9
})()
console.info(i) // i is not defined
我们在for循环外部插入了一个私有作用域。在匿名函数中定义的任何变量,都会在执行结束时被销毁。因此,变量i只能在循环中使用,使用后即被销毁。
这种技术经常在全局作用域中被用在函数外部,从而限制向全局作用域中添加过多的变量和函数。一般来说,我们都应该尽量少向全局作用域中添加变量和函数。在一个由很多开发人员共同参与的大型应用程序中,过多的全局变量和函数很容易导致命名冲突。而通过创建私有作用域,每个开发人员既可以使用自己的变量,又不必担心搞乱全局作用域。
实际上ES6提供了
let
关键字,使用它声明的变量就是块级作用域了.
(function() {
var now = new Date()
if (now.getMonth() == 0 && now.getDate() == 1) {
alert("Happy new year!")
}
})()
把上面这段代码放在全局作用域中,可以用来确定哪一天是 1 月 1 日;如果到了这一天,就会向用户显示一条祝贺新年的消息。其中的变量 now 现在是匿名函数中的局部变量,而我们不必在全局作用域中创建它。这种做法可以减少闭包占用的内存问题,因为没有指向匿名函数的引用。只要函数执行完毕,就可以立即销毁其作用域链了。例如jQuery的大闭包
js中的单例js中的对象字面量就是单例:
var singleton = {
name: value,
method: function() {
// body...
}
}
模块模式通过为单例添加私有变量和特权方法能够使其得到增强.如下所示:
var singleton = function(){
// 私有属性和方法
var privateVarible = 10
function privateFunction() {
return false
}
// 共有属性和方法
return {
publicProperty: true,
publicMethod: function() {
privateVarible++
return privateFunction()
}
}
}()
这个模块模式使用了一个返回对象的匿名函数。在这个匿名函数内部,首先定义了私有变量和函数。然后,将一个对象字面量作为函数的值返回。返回的对象字面量中只包含可以公开的属性和方法。由于这个对象是在匿名函数内部定义的,因此它的公有方法有权访问私有变量和函数。从本质上来讲,这个对象字面量定义的是单例的公共接口。这种模式在需要对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的
var application = function() {
//私有变量和函数
var components = []
//初始化
components.push(new BaseComponent())
//公共
return {
getComponentCount: function() {
return components.length
},
registerComponent: function(component) {
if (typeof component == "object") {
components.push(component)
}
}
}
}()
在 Web 应用程序中,经常需要使用一个单例来管理应用程序级的信息。这个简单的例子创建了一个用于管理组件的 application对象。在创建这个对象的过程中,首先声明了一个私有的 components数组,并向数组中添加了一个 BaseComponent的新实例(在这里不需要关心 BaseComponent 的代码,我们只是用它来展示初始化操作)。而返回对象的getComponentCount() 和 registerComponent() 方法,都是有权访问数组 components的特权方法。前者只是返回已注册的组件数目,后者用于注册新组件。简言之,如果必须创建一个对象并以某些数据对其进行初始化,同时还要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。以这种模式创建的每个单例都是Object的实例,因为最终要通过一个对象字面量来表示它。事实上,这也没有什么;毕竟,单例通常都是作为全局对象存在的,我们不会将它传递给一个函数。因此,也就没有什么必要使用instanceof 操作符来检查其对象类型了。
共同学习,写下你的评论
评论加载中...
作者其他优质文章