JavaScript中的闭包与匿名函数

时间:2022-03-07 16:22:20

知识内容:

1.预备知识 - 函数表达式

2.匿名函数

3.闭包

一、函数表达式

1.定义函数的两种方式

函数声明:

1 function func(arg0, arg1, arg2){
2 // 函数体
3 }

函数表达式:

1 var func = function (arg0, arg1, arg2){
2 // 函数体
3 }

2.注意事项

函数表达式使用前必须赋值!像下面的代码是错误的:

1 say()
2 var say = function(){
3 console.log("123")
4 }

函数表达式可以作为一个普通变量在分支中根据条件来赋值:

 1 // 下面的代码将根据不同的condition 将不同的函数表达式赋给sayHi
2 var sayHi
3 var condition = true // Hi!
4 // var condition = false // Hello!
5
6 if(condition){
7 sayHi = function(){
8 console.log("Hi!")
9 }
10 }
11 else{
12 sayHi = function(){
13 console.log("Hello!")
14 }
15 }
16
17 sayHi()

上述代码不同的运行效果:

JavaScript中的闭包与匿名函数

能创建函数赋值给变量,当然也可以把函数作为其他函数的返回值返回

二、匿名函数

1.什么是匿名函数

匿名函数的基本形式为(function(){...})();
前面的括号包含函数体,后面的括号就是给匿名函数传递参数并立即执行之

一般用到匿名函数的时候都是立即执行的。通常叫做自执行匿名函数或者自调用匿名函数。常用来构建沙箱模式,作用是开辟封闭的变量作用域环境,避免全局变量的污染,在多人联合工作中,合并js代码后,不会出现相同变量互相冲突的问题

2.匿名函数的详细写法

匿名函数的详细写法有以下两种,推荐使用第一种:

1 (function(){
2 console.log("我是匿名方式1");
3 })(); // 我是匿名方式1
4
5 (function(){
6 console.log("我是匿名方式2");
7 }()); // 我是匿名方式2
8
9 console.log((function(){}).name); // name为空

注:实际上,立即执行的匿名函数并不是函数,因为已经执行过了,所以它是一个结果,这个结果是对当前这个匿名函数执行结果的一个引用(函数执行默认return undefined)。这个结果可以是一个字符串、数字或者null/false/true,也可以是对象、数组或者一个函数(对象和数组都可以包含函数),当返回的结果包含函数时,这个立即执行的匿名函数所返回的结果就是典型的闭包了

3.匿名函数的作用

  • 函数表达式可以存储在变量中,并且可以赋值,可以作为其他函数的参数
  • 可以通过匿名函数执行某些一次性的任务
  • 避免全局变量的污染以及函数名的冲突

三、闭包

1.什么是闭包

什么是闭包:闭包指有权访问另一个函数作用域中的变量的函数,官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分

  • 作为一个函数变量的一个引用,当函数返回时,其处于激活状态
  • 一个闭包就是当一个函数返回时,一个没有释放资源的栈区

简单说Javascript允许使用内部函数---即函数定义和函数表达式位于另一个函数的函数体内。而且这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包

2.闭包的写法

1
2
3
4
5
6
7
8
9
10
function a(){
  var n = 0;
  function inc() {
    n++;
    console.log(n);
  }
  inc();
  inc();
}
a(); //控制台输出1,再输出2

简单吧。再来看一段代码:

1
2
3
4
5
6
7
8
9
10
function a(){
  var n = 0;
  this.inc = function () {
    n++;
    console.log(n);
  };
}
var c = new a();
c.inc();  //控制台输出1
c.inc();  //控制台输出2

简单吧。

什么是闭包?这就是闭包!

有权访问另一个函数作用域内变量的函数都是闭包。这里 inc 函数访问了构造函数 a 里面的变量 n,所以形成了一个闭包。

再来看一段代码:

1
2
3
4
5
6
7
8
9
10
11
function a(){
  var n = 0;
  function inc(){
    n++;
    console.log(n);
  }
  return inc;
}
var c = a();
c();  //控制台输出1
c();  //控制台输出2

看看是怎么执行的:

var c = couter(),这一句 couter()返回的是函数 inc,那这句等同于 var c = inc;

c(),这一句等同于 inc();  注意,函数名只是一个标识(指向函数的指针),而()才是执行函数。

后面三句翻译过来就是:  var c = inc;  inc();  inc();,跟第一段代码有区别吗? 没有

这样写的原因:
我们知道,js的每个函数都是一个个小黑屋,它可以获取外界信息,但是外界却无法直接看到里面的内容。将变量 n 放进小黑屋里,除了 inc 函数之外,没有其他办法能接触到变量 n,而且在函数 a 外定义同名的变量 n 也是互不影响的,这就是所谓的增强“封装性”。

而之所以要用 return 返回函数标识 inc,是因为在 a 函数外部无法直接调用 inc 函数,所以 return inc 与外部联系起来,代码 2 中的 this 也是将 inc 与外部联系起来而已

3.闭包的作用

  • 实现匿名自执行函数
  • 实现封装
  • 实现类和继承
  • 将变量的值一直保存在内存中

(1)闭包实现匿名自执行函数见上面匿名函数中的详细介绍

(2)闭包实现封装

JavaScript中没有私有成员的概念,所有对象属性都是公有的,不过却有一个私有变量的概念。简单说任何定义在函数中的变量都可以认为是私有变量,因为不能在函数的外部访问这些变量

私有变量包括:

  • 函数的参数
  • 函数的局部变量
  • 在函数内部定义的其他函数

另外利用闭包原理,我们可以创建用于访问私有变量的公有方法:在函数内部创建一个闭包,闭包通过作用域链访问这些变量,从而实现外部无法直接访问内部变量只能通过某些方法来访问,这样就实现了封装

 var person = function(){
// 变量作用域为函数内部,外部无法访问
var name = "default"; return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}(); print(person.name); // 直接访问,结果为undefined
print(person.getName());
person.setName("wyb");
print(person.getName());

(3)闭包实现类和继承

 function Person(){
var name = "default"; return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}; var p = new Person();
p.setName("Tom");
alert(p.getName()); var s= function(){};
// 继承自Person
s.prototype = new Person();
// 添加私有方法
s.prototype.Say = function(){
alert("Hello,my name is s");
};
var j = new s();
j.setName("s");
j.Say();
alert(j.getName());

(4)将变量的值一直保存在内存中

 // 闭包将变量的值一直保存在内存中
var f1 = function () {
var n = 1;
test = function () {
n += 1;
};
var f2 = function () {
alert(n);
};
return f2;
}; var res = f1();
alert(res()); // 1 undefined
test();
alert(res()); // 2 undefined

 

闭包的应用:

 // 上述闭包应用 - 迭代器
var f = function (x) {
var i = 0;
return function () {
return x[i++];
}
};
var next = f([11, 22, 33, 44]);
alert(next()); //
alert(next()); //
alert(next()); //
alert(next()); //

4.闭包注意事项

(1)闭包与变量

闭包只能取得包含函数中任何变量的最后一个值,闭包所保存的是整个变量对象,而不是某个特殊的变量

 1 function createFunctions(){
2 var result = new Array()
3
4 for (var i=0; i < 10; i++){
5 result[i] = function(){
6 return i;
7 }
8 }
9
10 return result
11 }
12
13 var foo = createFunction()

结果foo中并不是预料中的那样,而是10,这是为什么呢?仅仅声明某一个函数,引擎并不会对函数内部的任何变量进行查找或赋值操作。只会对函数内部的语法错误进行检查(如果往内部函数加上非法语句,那么不用调用也会报错),也就是说在返回result之前

result并未执行,返回之后再执行时匿名函数中的所有i引用的都是同一个变量(最后一个值10),故每个函数内部i的值为10

详细说明看这一篇文章:https://www.cnblogs.com/kindofblue/p/4907847.html

通过创建另一个匿名函数强制让闭包的行为符合预期:

 1 function createFunctions(){
2 var result = new Array()
3
4 for (var i=0; i < 10; i++){
5 result[i] = function(num){
6 return function(){
7 return num
8 }
9 }(i)
10 }
11
12 return result
13 }

(2)闭包中的this对象

关于this对象:

  • this对象是在运行时基于函数的执行环境绑定的
  • 在全局函数中this等价于window,当函数被作为某个对象的方法调用时this等价于那个对象
  • 在闭包中使用this对象可能会导致一些问题
 1 var name = "The Window";
2 var object = {
3 name: "My object",
4 getNameFunc: function() {
5 return function() {
6 return this.name;
7 };
8 }
9 }
10 alert(object.getNameFunc()()); // "The Window"

为什么最后的结果是"The Window"而不是object里面的name"My object"呢?

首先,要理解函数作为函数调用和函数作为方法调用,我们把最后的一句拆成两个步骤执行:

var first = object.getNameFunc();
var second = first();

其中第一步,获得的first为返回的匿名函数,此时的getNameFunc()作为object的方法调用,如果在getNameFunc()中使用this,此时的this指向的是object对象。

第二步,调用first函数,可以很清楚的发现,此时调用first函数,first函数没有在对象中调用,因此是作为函数调用的,是在全局作用域下,因此first函数中的this指向的是window

再看下面这句话:

为什么匿名函数没有取得其包含作用域(外部作用域)的this对象呢?

每个函数被调用时,其活动对象都会自动取得两个特殊变量:this和arguments。内部函数在搜索这两个变量时,只会搜索到其活动对象为止,因此永远不可能直接访问外部函数中的这两个变量。  《Javascript高级程序设计》

那么,如何获得外部作用域中的this呢?可以把外部作用域中的this保存在闭包可以访问到的变量里。如下:

 1 var name = "The Window";
2 var object = {
3 name: "My object",
4 getNameFunc: function() {
5 var that = this; // 将getNameFunc()的this保存在that变量中
6 var age = 15;
7 return function() {
8 return that.name;
9 };
10 }
11 }
12 alert(object.getNameFunc()()); // "My object"

(3)模仿块级作用域

在JavaScript中没有块级作用域,JavaScript从来不会告诉你是否多次同时声明同一个变量,遇到这种情况它只会对后续的声明视而不见,不过我们可以使用匿名函数和闭包来实现块级作用域

用作块级作用域的匿名函数如下:

1 (function(){
2 // 这里是块级作用域
3 }) ();

上述匿名函数也等价于这种形式:

1 var someFunction = function(){
2 // 这里是块级作用域
3 }
4 someFunction()

关于匿名函数及模仿块级作用域的注意事项:

  • 在匿名函数中定义的任何变量都会在执行结束时被销毁
  • 在私有作用域(匿名函数)中可以访问外层函数的变量是因为这个匿名函数是一个闭包,它能够访问包含作用域中的所有变量

关于以上两点,示例代码如下:

1 function func(count){
2 (function (){
3 for(var i=0; i < count; i++){
4 console.log(i)
5 }
6 })();
7
8 console.log(i) // 导致一个错误!
9 }

(4)闭包保存的是函数的作用域而不是变量本身的值

 // 闭包保存的是函数的作用域而不是变量本身的值
var f = function (param) {
var n = function () {
return param;
};
param++;
return n;
};
var test = f(456);
console.log(test()); //

注:通过闭包可以实现将变量一直保存在内存中

(5)闭包会使得函数中的变量都保存在内存中,内存消耗很大,不能滥用闭包,否则会造成网页的性能问题,在IE中可能会导致内存泄露,尽量在退出函数之前,将不使用的局部变量全部删除

 // 闭包将变量的值一直保存在内存中
var f1 = function () {
var n = 1;
test = function () {
n += 1;
};
var f2 = function () {
alert(n);
};
return f2;
}; var res = f1();
alert(res()); // 1 undefined
test();
alert(res()); // 2 undefined

JavaScript中的闭包与匿名函数的更多相关文章

  1. JavaScript中的闭包和匿名函数

    JavaScript中的匿名函数及函数的闭包   1.匿名函数 2.闭包 3.举例 4.注意 1.匿名函数 函数是JavaScript中最灵活的一种对象,这里只是讲解其匿名函数的用途.匿名函数:就是没 ...

  2. 浅析PHP中的闭包和匿名函数

    PHP闭包和匿名函数使用的句法与普通函数相同,但闭包和匿名函数其实是伪装成函数的对象(Closure类的实例) .下面给大家介绍PHP中的闭包和匿名函数知识,需要的朋友参考下吧   闭包是指在创建时封 ...

  3. 一篇文章把你带入到JavaScript中的闭包与高级函数

    在JavaScript中,函数是一等公民.JavaScript是一门面向对象的编程语言,但是同时也有很多函数式编程的特性,如Lambda表达式,闭包,高阶函数等,函数式编程时一种编程范式. funct ...

  4. PHP中的闭包和匿名函数

    闭包的概念是指在创建闭包时,闭包会封装周围的状态的函数.即便闭包所在环境不在了.但闭包中封装的状态依然存在. 匿名函数就是没有名称的函数. 它们看似很函数一样,实际上它们属于Closure类的实例 P ...

  5. js闭包中的this(匿名函数中的this指向的是windows)

    js闭包中的this(匿名函数中的this指向的是windows) 一.总结 1.普通函数中的this指向的是对象,匿名函数中的this指向的是windows,和全局变量一样 2.让匿名函数中的thi ...

  6. Java 终于在 Java 8 中引入了 Lambda 表达式。也称之为闭包或者匿名函数。

    本文首发于 blog.zhaochunqi.com 转载请注明 blog.zhaochunqi.com 根据JSR 335, Java 终于在 Java 8 中引入了 Lambda 表达式.也称之为闭 ...

  7. 让你分分钟学会Javascript中的闭包

    Javascript中的闭包 前面的话: 闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它 ...

  8. 浅谈JavaScript中的闭包

    浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...

  9. Javascript 中的闭包和引用

    Javascript 中一个最重要的特性就是闭包的使用.因为闭包的使用,当前作用域总可以访问外部的作用域.因为Javascript 没有块级作用域,只有函数作用域,所以闭包的使用与函数是紧密相关的. ...

随机推荐

  1. &period;Container与&period;container&lowbar;fluid区别

    .Container与.container_fluid是bootstrap中的两种不同类型的外层容器,两者的区别是:.container 类用于固定宽度并支持响应式布局的容器..container-f ...

  2. HDU5977 Garden of Eden(树的点分治)

    题目 Source http://acm.hdu.edu.cn/showproblem.php?pid=5977 Description When God made the first man, he ...

  3. div盒子垂直水平居中

    div盒子,水平垂直居中. <!DOCTYPE html><html> <head> <meta charset="utf-8"> ...

  4. Android程序进行混淆,在导出签名apk包时出错!

    今天终于完成了近一个月的App开发工作,对程序进行混淆导出签名apk包时,却出现了如下的错误: Proguard returned with error code 1. See console Not ...

  5. gulp任务

    项目使用的gulp自动化任务 //定义输出文件夹名称 var distFolderH5 = "distH5"; var distFolderMofang = "distM ...

  6. Android(java)学习笔记142:使用Sqlite基本流程

  7. YII 1&period;0 小功能总结

    1.操作成功提示 只能使用一次,getFlash()取值以后,值就删除了 控制器中: Yii::app()->user->setFlash('success','修改成功'); 视图中: ...

  8. centos实现永久修改hostname

    前言 介绍一下centos的两种修改hostname的方式. 查看hostname [root@slave02 ~]# hostname slave02 临时性修改 [root@slave02 ~]# ...

  9. springboot-24-restTemplate的使用

    项目中使用的是HttpClient, 后来改成springboot, 偶然间发现restTemplate { "Author": "tomcat and jerry&qu ...

  10. Django model转字典的几种方法

    平常的开发过程中不免遇到需要把model转成字典的需求,尤其是现在流行前后端分离架构,Json格式几乎成了前后端之间数据交换的标准,这种model转dict的需求就更多了,本文介绍几种日常使用的方法以 ...