重学JavaScript之匿名函数
1. 什么是匿名函数?
匿名函数就是没有名字的函数,有时候也称为《 拉姆达函数》。匿名函数是一种强大的令人难以置信的工具。如下:
function a(a1, a2, a3) {
// 函数体
}
其他函数表达式
var a = function(a1, a2, a3) {
// 函数体
}
以上两个例子在逻辑上等价,其主要的区别是: 前者会在代码执行前被加载到作用域中,而后者则是在代码执行到那一行的时候才会有定义。另一个重要的区别就是:函数声明会给函数一个指定的名字,而函数表达式则是:创建一个匿名函数,然后将这个匿名函数赋给一个变量。
function(a1, a2, a3) {
// 函数体
}
上面例子也是完全可以的,但是却无法调用这个函数,因为没有指向这个函数的指针,但是可以将这个函数作为参数传入另外一个函数,或者从一个函数中返回另一个函数时就可以使用这种形式来定义匿名函数。
2. 递归
递归函数是在一个函数通过名字调用自身的情况下构成的
function f(num) {
if (num <= 1) {
retrun 1
} else {
return num * f(num - 1)
}
}
以上,这是一个经典的递归阶乘函数,表面上没有任何问题,但是却会被以下代码导致出错:
var a = f
f = null
console.log(a(4) // 报错
以上代码先把 f() 函数保存在变量 a 中,然后将f变量设置为 null ,结果指向原始函数的引用只剩下一个。但在接下来调用 a() 时,由于必须执行 f(),但 f 已经不是函数,所有就会报错。这个时候可以使用 arguments.callee
function f(num) {
if (num <= 1) {
return 1
} else {
return num * arguments.callee(num - 1)
// 通过 arguments.callee 代替函数名,可以保证不会出问题
}
}
var a = f
a = null
a(4) // 24
3. 闭包
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的方式:在一个函数内部创建另一个函数。
function c(p) {
retrun function(o1,o2){
// var v1 = o1[p]
// var v2 = o2[p]
if (v1 < v2) {
return -1
} else if (v1 > v2) {
retrun 1
}else {
retrun 0
}
}
}
在上面代码中,有标记的两行是匿名函数中的代码。这两行代码访问了外部函数中的变量 p。即使这个内部函数被返回了,而且被其他地方调用了,但它仍然可以访问变量 p。之所以还能够访问这个变量,是因为函数的作用域链中包含了c()的作用域。
当某个函数第一次被调用时,会创建一个执行环境及相应的作用域链,并把作用域链赋值给一个特殊的内部属性([Scope])。然后,使用 this、arguments和其他命名参数的值来初始化函数的活动对象。但在作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部活动对象处于第三位。直到作为作用域链重点的全局执行环境。
- 在函数执行过程中,为读取和写入变量的值,就需要在作用域链中查找变量。后台的每个执行环境都有一个表示变量的对象--变量对象。
- 全局环境的变量对象始终存在,而局部环境的变量对象,则只在函数执行的过程中存在。我们在创建函数的时候会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[Scope]属性中,当调用函数时,会为函数创建一个执行环境,然后通过赋值函数的[Scope]属性中的对象构建起执行环境的作用域链。
- 如果这时候有一个变量对象被创建并被推入执行环境作用域链的前端,对于一开始创建的函数的执行环境而言,其作用域链中包含两个变量:本地活动对象和全局变量对象。所以,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
无论什么时候函数在访问一个变量时,就会从作用域链中搜索具有相同名字的变量,函数执行完成后,局部活动对象将被销毁,内存中仅保存全局作用域。但是由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。
在一个函数内部定义的函数会将外部函数的活动对象添加到它的作用域链中。内部函数在外部函数中被返回后,它的作用域链被初始化为包含外部函数的活动对象和全局变量对象,这样内部函数就可以访问外部函数中定义的所有的变量。所以在外部函数执行结束后,它并不会被销毁,因为内部函数的作用域链还在引用这个活动对象。也就是说外部函数执行结束后,它的作用域链会被销毁,但是活动对象还在内存中,直到内部函数被销毁后。
3.1 闭包与变量
作用域链的这种配置引出了一个副作用,闭包只能取得包含函数中任何变量的最后一个值。
3.2 关于 this 对象
在闭包中使用this 也可能会导致一些问题。因为this对象是在运行时基于函数的执行环境绑定的。在全局函数中 this === window,函数被作为某个对象的方法调用时,this就等于那个对象。匿名函数的执行环境具有全局性,因此其this 对象通常指向window。但是这并不是绝对的。
在函数被调用的时候,其活动对象都会自动获得两个特殊变量:this 和 arguments。 内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。如果把外部作用域中的this对象保存在一个闭包能够访问的变量里,就可以让闭包访问该对象了。
3.3 内存泄露
由于IE对JS对象和 COM对象使用不同的垃圾收集例程,因此闭包在IE中会导致一些特殊的问题。也就是说,如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁。
注意:闭包会引用包含函数的整个活动对象,而其中包含着变量,即使闭包不直接引用变量,包含函数的活动对象中也仍然会保存一个引用。因此把变量设置为 null ,这样就能够解除对DOM对象的引用,减少其引用数,确保正常回收其占用的内存。
4、 模仿块级作用域
vJS没有块级作用域的概念,这意味着在块语句中定义的变量,实际上是在包含函数中而非语句中创建的。JS从来不会告诉你是否多次声明了同一个变量,它总是对后续的声明视而不见。我们可以通过匿名函数来模仿块级作用域从而避免这个问题。
(function () {
// 块级作用域
})()
5、私有变量
严格来说在JS中并没有私有成员的概念:所有对象属性都是公有的。不过倒是有一个私有变量的概念。任何在函数中定义的变量都可以认为是私有变量,因为不能在函数的外部访问这些变量。私有变量包括函数的参数、局部变量和在函数内部定义的其他函数。
在函数内部如果有私有变量,那么在函数内部可以访问这个变量,但在函数外部则不能访问它们。如果在这个函数内部创建一个闭包,那么闭包通过自己的作用域链也可以访问这些变量。
我们把有权访问私有变量和私有函数的公有方法称为特权方法。有两种在对象上创建特权方法的方式,
第一种:在构造函数中定义
function m (){
let p = 10
function p (){
retrun false
}
// 特权方法
this.pb = function () {
p++
retrun p()
}
}
第二种:静态私有变量
通过在私有作用域中定义私有变量或函数,同样也可以创建特权方法。和在构造函数中定义特权方法的区别在于私有变量和函数是由实例共享的,由于特权方法是在原型上定义的,因此所有实例都使用同一个函数。
多查找作用域链中的一个层次,就会在一定程度上影响查找速度。这正是闭包和私有变量一个不足之处。
5.1 模块模式
指的是为单例创建私有变量和特权方法。所谓单例,指的就是只有一个实例对象,按照惯例,JS是以对象字面量的方式来创建单例对象的:
var s = {
name : v,
method: function(){
// 方法的代码
}
}
6、总结
匿名函数,也称为拉姆达函数,是一种使用JS函数的强大方式。有如下特点:
- 任何函数表达式从技术上说都是匿名函数,因为没有引用它们的确定的方式
- 在无法确定如何引用函数的情况下,递归函数就会变得比较复杂
- 递归函数应该始终使用 argument.callee来递归地调用自身,不要使用函数名,因为函数名可能会发生变化。
当函数内部定义了其他函数时,就创建s了闭包,闭包有权访问包含函数内部的所有变量。
- 在后台执行环境汇总,闭包的作用域链包含着它自己的作用域、包含函数的作用域和全局作用域;
- 通常,函数的作用域及所有变量都会在函数执行结束后被销毁
- 但是,如果函数返回了一个闭包时, 这个函数的作用域将会一直在内存中保存到闭包不存在为止
使用闭包可以在JS中模仿块级作用域
- 创建并立即调用一个函数,这样即可以执行其中的代码,又不会在内存中留下对该函数的引用
- 结果就是函数内部的所有变量都会被立即销毁--除非将某些变量赋值给了包含作用域中的变量
闭包可以用于对象中创建私有变量
- 即使JS中没有正式的私有对象属性概念,但可以使用闭包来实现公有方法,而通过公有方法可以访问在包含作用域中定义的变量。
- 有权访问私有变量的公有方法叫做 特权方法
- 可以使用构造函数、原型模式来实现自定义类型的特权方法,也可以使用模块模式、增强的模块模式来实现单例的特权方法。
JS中的匿名函数和闭包都是非常的特性,但是要注意使用场景和方法。
欢迎关注 前端公众号【小夭同学】
重学JavaScript之匿名函数的更多相关文章
- Javascript之匿名函数
分析: 1.所谓匿名函数,从字面意思理解,就是没有名字的函数,js 用()来代替(注意,是英文状态下的括号) 2.定义形式: function (){ //to add codes that you ...
- javascript 利用匿名函数对象给你异步回调方法传参数
先来创建一个匿名函数对象: /*** * 匿名函数 */ var callChangeBtn=new function(bugBtn){ this.chage=function(json){ bugB ...
- Javascript的匿名函数与自执行
1.匿名函数 函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数:就是没有函数名的函数. 1.1 函数的定义,首先简单介绍一下函数的定义,大致可分为三种方式 第一种: ...
- JavaScript的匿名函数和模块化的使用方法
对于开发人员来说,很多时候我们都会涉及到JavaScript的使用,而在使用过程中,最令人沮丧的就是变量没有相应的使用范围. 在开发中,对于任何变量.数组.函数.对象等,只要不在函数的内部,都会被默认 ...
- Javascript的匿名函数
一.什么是匿名函数?在Javascript定义一个函数一般有如下三种方式:函数关键字(function)语句:function fnMethodName(x){alert(x);}函数字面量(Func ...
- JavaScript 作用域 匿名函数 模仿块级作用域(私有作用域)
作用域 对于有块级作用域的语言来说,for语句中定义并初始化的变量i在循环外是无法访问的. 而javascript没有块级作用域,for语句中定义的变量i在循环结束后,依旧会存在于循环外部的执行环境( ...
- javascript . 04 匿名函数、递归、回调函数、对象、基于对象的javascript、状态和行为、New、This、构造函数/自定义对象、属性绑定、进制转换
匿名函数: 没有名字的函数,函数整体加小括号不报错, 函数调用 : a:直接调用 (function (){函数体}) ( ) ; b:事件绑定 document.onlick = functio ...
- 谈谈Javascript的匿名函数
JQuery 里面有这么一种代码: (function(){ // code here })(); 当一个匿名函数被括起来,然后再在后面加一个括号,这个匿名函数就能立即运行起来,神奇吧! 要说匿名函数 ...
- (转载)JavaScript中匿名函数,函数直接量和闭包
首先,我们先看看下面几种写法:1.function f(x){return x*x;};f(x);2.(function(x){return x*x;})(x);3.(function(x){retu ...
随机推荐
- 一、基础篇--1.2Java集合-HashMap和ConcurrentHashMap的区别【转】
http://www.importnew.com/28263.html 今天发一篇”水文”,可能很多读者都会表示不理解,不过我想把它作为并发序列文章中不可缺少的一块来介绍.本来以为花不了多少时间的,不 ...
- LC 562. Longest Line of Consecutive One in Matrix
Given a 01 matrix M, find the longest line of consecutive one in the matrix. The line could be horiz ...
- vue 登录路由判断
router.beforeEach((to, from, next) => { // alert(sessionStorage.getItem('accessToken')) // consol ...
- re 模块, 正则表达式 \w+\d+ 的重复问题引发的题目解析
题目 计算以下代码的结果 s = "?!.18)dajslj$12.15613sdadw.123sdasda35615.168sndsda$15.6sdasd.sdfsdgw123.156s ...
- 阶段3 3.SpringMVC·_06.异常处理及拦截器_7 SpringMVC拦截器之拦截器接口方法演示
返回值改成false 就是不放行 没有方形,控制台只有一个输出 转发到error页面 新建error.jsp页面 控制台只有拦截器的输出.controller根本就没有执行 把代码改回来 重写第二个方 ...
- MySQL 按照数据库表字段动态排序 查询列表信息
MySQL 按照数据库表字段动态排序 查询列表信息 背景描述 项目中数据列表分页展示的时候,前端使用的Table组件,每列自带对当前页的数据进行升序或者降序的排序. 但是客户期望:随机点击某一列的时候 ...
- 【JVM学习笔记】类加载过程
在Java代码中,类型的加载.连接与初始化过程都是在程序运行期间完成的:提供了更大的灵活性,增加了更多的可能性 JVM启动过程包括:加载.连接.初始化 加载:就是将class文件加载到内存.详细的说是 ...
- MathType 6.0中MT Extra(TrueType)问题
问题 MathType 6.0中MT Extra(TrueType)字体问题在打开MathType6.0时,有时会提示MathType需要安装一个较新版本的MT Extra(TrueType)字体,这 ...
- AE调用GP工具(创建缓冲区和相交为例)
引用 Geoprocessing是ArcGIS提供的一个非常实用的工具,借由Geoprocessing工具可以方便的调用ArcToolBox中提供的各类工具,本文在ArcEngine9.2平台环境下总 ...
- 我和CMS的往事
CMS(内容管理系统)放在web中理解,也就是我们常说的后台了,用于网站的日常管理.又到期末了,我们的课程——web程序设计,需要提交一份期末大作业,最近需要开发出一个基于JSP的简单web,所以呢, ...