为了账号安全,请及时绑定邮箱和手机立即绑定

javascript中的函数表达式

标签:
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 操作符来检查其对象类型了。

点击查看更多内容
25人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消