关于闭包的知识总结下:

一、闭包

1、定义

闭包的关键是作用域,概念是:能有读取其他函数内部的函数

使用的场景有很多,最常见的是函数封装的时候,再就是在使用定时器的时候,会经常用到;

//闭包:有参数的加载事件(空参数形式)

(function($){

alert("123");

})(jQuery);

//有参数的加载事件

(function(\(){
alert(\));

})(456);

(function($){

$("div p").click(function(){alert("cssrain!")});

})(jQuery); //一个闭包

这里面的$只是形参,但jquery是全局变量,所以不需要调用该函数就会自动执行,或者分两步

就是转化成正常的函数,先写函数,后调用。

2、实例
(1)
function Test()
{
var str="Test里面的局部变量"; retrun function()
{
alert(str);
} } var func=Test(); func();
//运行后将打印"Test里面的局部变量" 按照变量的生命周期 Test函数运行完毕 str变量就会被销毁 但是在这里它并没有 因为它被匿名函数给捕获了 延长了它的生命周期 这就是闭包
(2)
(function($){
$("div p").click(。。。);
})(jQuery);
就是等于
function tempFunction($){ //创建一个以$为形参的函数
$("div p").click(....);
}
TempFunction(jQuery); //传入实参jQuery执行函数. $(function(cssrain){
cssrain("div p").click(.... );
})(jQuery); //一个闭包 闭包的基本写法:
$(function(){do someting})();
//这个你就理解为定义一个匿名函数并立即执行
带参数的话就这样:
$(function(形参){do someting})(实参);
另外
$(function(){var upc="i am upc"})();
alert(upc);
会提示undefined。
因为闭包后,里面的变量就相当于局部了。 闭包的好处:
不增加额外的全局变量,
执行过程中所有变量都是在匿名函数内部。
3、闭包的优点和缺点
(1)、优点:不增加额外的全局变量,

执行过程中所有变量都是在匿名函数内部。

(2)、缺点:可能造成内存泄漏。
关于js闭包这个总结的很好,地址:https://www.cnblogs.com/darrenji/p/3804993.html

本篇的标题虽然是"jQuery闭包之浅见...",但实际上本篇涉及的多半是javascript"闭包"相关内容,之所以写这个标题,完全是因为自己平常用jQuery写前端习惯了。还有一个原因是:javascript"闭包"很容易造成"内存泄漏", 而jQuery已经自动为我们规避、处理了由"闭包"引发的"内存泄漏"。

javascript的"闭包"是这样定义的:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

定义高度提炼,还是从代码实例来体验"闭包"的来龙去脉吧。本篇主要包括:

  • 从面向对象的角度来理解"闭包"
  • 调用内部函数的几种方式
  • 变量的作用域
  • jQuery中的"闭包"
  • 内存泄漏
从面向对象的角度来理解"闭包"

在面向对象中,我们是这样定义类:

public class SomeClass
{
private string _someVariable; public void SomeMethod()
{
//TODO:
}
}

在javascript中,有一种情况是,一个函数中包含了另外一个内部函数:

function OutFunction()
{
var temp = 0;
function innerFunction(){
}
}

我们把OutFunction称为外部函数,把innerFunction称为内部函数。

这里的外部函数OutFunction相当于一个类。

外部函数变量temp相当于类的私有字段。

内部函数innerFunction相当于类的方法。

就像我们需要实例化类来调用类的实例方法一样,在javasript中,当我们在外部函数之外调用内部函数的时候,我们把此时的内部函数叫做"闭包",相当于面向对象的实例方法。

接下来,从"如何调用内部函数"这个角度,来循序渐进地体会何谓"闭包"。

调用内部函数的几种方式

在外部函数之外直接调用内部函数

 <script type="text/javascript">
function outerFunction() {
console.log('外部函数执行了');
function innerFunction() {
console.log('内部函数执行了');
}
} $(function() {
innerFunction();
});
</script>

结果报错:Uncaught ReferenceError: innerFunction is not defined

以上情况,内部函数的作用域是在外部函数之内,当在外部函数之外调用内部函数的时候,自然就报错。

在外部函数之内调用内部函数:

 <script type="text/javascript">
function outerFunction() {
console.log('外部函数执行了');
function innerFunction() {
console.log('内部函数执行了');
} innerFunction();
} $(function() {
outerFunction();
});
</script>

结果:

外部函数执行了

内部函数执行了

以上情况,内部函数的作用域是在外部函数之内,当在外部函数之内调用内部函数,自然不会报错。

□ 在外部函数之外调用内部函数,把内部函数赋值给一个全局变量

<script type="text/javascript">

        //全局变量
var globalInner; function outerFunction() {
console.log('外部函数执行了');
function innerFunction() {
console.log('内部函数执行了');
} globalInner = innerFunction;
} $(function() {
outerFunction();
console.log('以下是全局变量调用内部方法:');
globalInner();
});
</script>

结果:

外部函数执行了

以下是全局变量调用内部方法:

内部函数执行了

以上情况,全局变量globalInner相当于面向对象的委托,当把内部函数赋值给全局变量,调用委托方法就会调用内部函数。

□ 在外部函数之外调用内部函数,把内部函数赋值给一个变量:

 <script type="text/javascript">

        function outerFunction() {
console.log('外部函数执行了');
function innerFunction() {
console.log('内部函数执行了');
} return innerFunction;
} $(function() {
console.log('先把外部函数赋值给变量');
var temp = outerFunction();
console.log('再执行外部函数变量');
temp();
});
</script>

结果:

先把外部函数赋值给变量

外部函数执行了

再执行外部函数变量

内部函数执行了

以上情况,我们可以看到,内部函数不仅可以赋值给全局变量,还可以赋值给局部变量。

就像面向对象的方法会用到类的字段,内部函数也会用到变量,接下来体验变量的作用域。

变量的作用域

内部函数变量

 <script type="text/javascript">

        function outerFunction() {
function innerFunction() {
var temp = 0;
temp++;
console.log('内部函数的变量temp的值为:' + temp);
}
return innerFunction;
} $(function() {
var out1 = outerFunction();
out1();
out1(); var out2 = outerFunction();
out2();
out2();
});
</script>

结果:

内部函数的变量temp的值为:1

内部函数的变量temp的值为:1

内部函数的变量temp的值为:1

内部函数的变量temp的值为:1

从中我们可以看出内部函数变量temp的生命周期:

→当第一次执行内部函数,JavaScript运行时创建temp变量

→当第二次执行内部函数,JavaScript垃圾回收机制把先前的temp回收,并释放与该temp对应的内存,再创建一个新的内部函数变量temp

.....

所以,每次调用内部函数,内部函数的变量是全新的。也就是说,内部函数的变量与内部函数同生共灭。

□ 全局变量

 <script type="text/javascript">
//全局变量
var temp = 0;
function outerFunction() {
function innerFunction() {
temp++;
console.log('全局变量temp的值为:' + temp);
}
return innerFunction;
} $(function() {
var out1 = outerFunction();
out1();
out1(); var out2 = outerFunction();
out2();
out2();
});
</script>

结果:

全局变量temp的值为:1

全局变量temp的值为:2

全局变量temp的值为:3

全局变量temp的值为:4

可见,全局变量供外部函数和内部函数共享。

□ 外部函数变量

<script type="text/javascript">
function outerFunction() {
var temp = 0;
function innerFunction() {
temp++;
console.log('外部函数变量temp的值为:' + temp);
}
return innerFunction;
} $(function() {
var out1 = outerFunction();
out1();
out1(); var out2 = outerFunction();
out2();
out2();
});
</script>

结果:

外部函数变量temp的值为:1

外部函数变量temp的值为:2

外部函数变量temp的值为:1

外部函数变量temp的值为:2

可见,外部函数的变量与外部函数同生共灭。

以上情况,更接近与"闭包"的原型。有如下几个要素:

1、外部函数

2、外部函数变量

3、内部函数

当我们在外部函数之外调用内部函数的时候,这时的内部函数就叫做"闭包",可以理解为面向对象的实例方法。"闭包"与外部函数变量的"外部环境"是外部函数,他俩与外部函数同生共灭。

一个外部函数中可以有多个内部函数,当调用"闭包"的时候,多个"闭包"共享外部函数变量:

  <script type="text/javascript">
function outerFunction() {
var temp = 0;
function innerFunction1() {
temp++;
console.log('经innerFunction1,外部函数变量temp的值为:' + temp);
} function innerFunction2() {
temp += 2;
console.log('经innerFunction2,外部函数变量temp的值为:' + temp);
} return {'fn1' : innerFunction1, 'fn2' : innerFunction2};
} $(function() {
var out1 = outerFunction();
out1.fn1();
out1.fn2();
out1.fn1(); var out2 = outerFunction();
out2.fn1();
out2.fn2();
out2.fn1();
});
</script>

结果:

经innerFunction1,外部函数变量temp的值为:1

经innerFunction2,外部函数变量temp的值为:3

经innerFunction1,外部函数变量temp的值为:4

经innerFunction1,外部函数变量temp的值为:1

经innerFunction2,外部函数变量temp的值为:3

经innerFunction1,外部函数变量temp的值为:4

jQuery中的"闭包"

"闭包"在jQuery中最常见的应用是,当Document加载完毕再执行jQuery部分:

<script type="text/javascript">
$(document).ready(function() {
var temp = 0; function innerFunction() {
temp++;
console.log(temp);
} innerFunction();
innerFunction();
});
</script>

结果:

1

2

可见,$(document).ready()的参数就是一个匿名外部函数,匿名函数内的函数是内部函数。

把元素的事件也可看作是内部函数:

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
<script src="../Scripts/jquery-2.1.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var counter = 0; $('#btn1').click(function(event) {
counter++;
console.log(counter);
}); $('#btn2').click(function(event) {
counter--;
console.log(counter);
});
});
</script>
</head>
<body>
<input id="btn1" type="button" value="递增"/>
<input id="btn2" type="button" value="递减"/>
</body>

可见,2个input元素的click事件看作是内部函数,共享同一个外部函数变量counter。

在循环体遍历中,把每次遍历的元素事件也可看作是内部函数:

<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
<script src="../Scripts/jquery-2.1.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
for (var i = 0; i < 3; i++) {
$('<div>Print ' + i + '</div>').click(function() {
console.log(i);
}).insertBefore('#results');
}
});
</script>
</head>
<body>
<div id="results"></div>
</body>

页面呈现的结果如预期:

Print 0

Print 1

Print 2

可当点击每个div的时候,原本以为控制器台应该显示:0, 1, 2,但实际显示的始终是3,为什么?

--i看作是匿名外部函数的"自由变量",当页面加载完毕后,i就变成了3。div的每次点击看作是内部函数的闭环,而所有的闭环都共享了值为3的这个变量。

我们可以使用jQuery的each()方法来解决以上问题,遍历一个数组,每一次遍历元素值都不同:

<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
<script src="../Scripts/jquery-2.1.1.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$.each([0, 1, 2], function(index, value) {
$('<div>Print ' + value + '</div>').click(function() {
console.log(value);
}).insertBefore('#results');
});
});
</script>
</head>
<body>
<div id="results"></div>
</body>
内存泄漏

内存泄漏可以狭隘地理解为:应用程序中变量对应一块内存,由于某些原因(比如代码上的漏洞),始终没有对变量及时实施手动或自动垃圾回收,内存没有得到及时释放,造成内存泄漏。

在JavaScript中,如果对象间的关系比较简单:

1

以上,A有一个属性指向B,B有一个属性指向C,当A被回收的时候,没有依赖B的对象,B随之被自动回收,C也被最终回收。

当对象间的关系比较复杂,比如存在循环引用的时候,如下:

2

以上,A有一个属性指向B, B有一个属性指向C,而C又有一个属性指向B,B和C之间存在循环引用。当A被回收之后,B和C是无法自动被回收的,在JavaScript中应该手动处理回收。

JavaScript闭包有可能会造成内存泄漏:

→内部函数闭包引发的内存泄漏

   $(function() {
var outerVal = {}; function innerFunction() {
console.log(outerVal);
} outerVal.fn = innerFunction;
return innerFunction;
});

以上,outVal是在内存中的一个对象,而内部函数innerFunction同样是内存中的一个对象。对象outVal的属性fn指向内部函数,而内部函数通过console.log(outerVal)引用outVal对象,这样outVal和内部函数存在循环引用的情况,如果不手动处理,就会发生"内存泄漏"。

如果,我们在内部函数中不显式引用outerVal对象变量,会造成"内存泄漏"吗?

$(function() {
var outerVal = {}; function innerFunction() {
console.log('hello');
} outerVal.fn = innerFunction;
return innerFunction;
});

答案是:会的。因为,当内部函数被引用、调用的时候,即使内部函数没有显式引用外部函数的变量,也会隐式引用外部函数变量。

→元素事件引发的内存泄漏

在IE中,如下写法会造成内存泄漏:

$(document).ready(function(){
var button = document.getElementById("btn");
button.onclick = function(){
console.log('hello');
return false;
}
});

而如下JavaScript写法不会造成内存泄漏:

function hello(){
console.log('hello');
return false;
} $(document).ready(function(){
var button = docuemtn.getElementById('btn');
button.onclick = hello;
});

而在jQuery中,类似的写法就不用担心内存泄漏了,因为jQuery为我们做了自动处理来规避内存泄漏。

$(document).ready(function(){
var $button = $('#btn');
$button.click(function(event){
event.preventDefault();
console.log('hello');
});
});
总结

与"闭包"相关的包括:变量的作用域、javascript垃圾回收、内存泄漏,需在实践多体会。

jQuery中的闭包和js中的闭包总结的更多相关文章

  1. MVC中处理Json和JS中处理Json对象

    MVC中处理Json和JS中处理Json对象 ASP.NET MVC 很好的封装了Json,本文介绍MVC中处理Json和JS中处理Json对象,并提供详细的示例代码供参考. MVC中已经很好的封装了 ...

  2. 【转】MVC中处理Json和JS中处理Json对象

    事实上,MVC中已经很好的封装了Json,让我们很方便的进行操作,而不像JS中那么复杂了. MVC中: public JsonResult Test() { JsonResult json = new ...

  3. JavaScript 基础——使用js的三种方式,js中的变量,js中的输出语句,js中的运算符;js中的分支结构

    JavaScript 1.是什么:基于浏览器 基于(面向)对象 事件驱动 脚本语言 2.作用:表单验证,减轻服务器压力 添加野面动画效果 动态更改页面内容 Ajax网络请求 () 3.组成部分:ECM ...

  4. JS中的运算符和JS中的分支结构

    JS中的运算符 1.算术运算(单目运算符) + .-.*. /. %取余.++自增 .--自减 +:两种作用,链接字符串/加法运算.当+两边全为数字时,进行加法运算:当+两边有任意一边为字符串时,起链 ...

  5. bug日记之---------js中调用另一个js中的有ajax的方法, 返回值为undefind

    今天做一个OCR授权的需求, 需要开发一个OCR弹框, 让用户选择是否授权给第三方识别公司(旷世科技)保存和识别用户个人信息, 照片等. 其中用到了在一个js的方法中调用另外一个js的方法, 其中有一 ...

  6. 转:el表达式获取map对象的内容 & js中使用el表达式 & js 中使用jstl 实现 session.removeattribute

    原文链接: ①EL表达式取Map,List值的总结 ②在jsp中使用el表达式通过键获得后台的一个map<Long,String>的值 ③在javascript中使用el表达式(有图有真相 ...

  7. java:JavaScript3(innerHTML,post和get,单选框,多选框,下拉列表值得获取,JS中的数组,JS中的正则)

    1.innerHTML用户登录验证: <!DOCTYPE> <html> <head> <meta charset="UTF-8"> ...

  8. vue cli 中关于vue.config.js中chainWebpack的配置

    Vue CLI  的官方文档上写:调整webpack配置最简单的方式就是在vue.config.js中的configureWebpack选项提供一个对象. Vue CLI 内部的 webpack 配置 ...

  9. JS高级面试题思路(装箱和拆箱、栈和堆、js中sort()方法、.js中Date对象中的getMounth() 需要注意的、开发中编码和解码使用场景有哪些)

    1.装箱和拆箱: 装箱:把基本数据类型转化为对应的引用数据类型的操作: var num = 123 // num var objNum = new Num(123) // object console ...

随机推荐

  1. solve update pip 10.0.0

    The bug is found in pip 10.0.0. In linux you need to modify file: /usr/bin/pip from: from pip import ...

  2. Jade学习(四)之结合node如何编译执行

    1.首先安装node 2.新建一个文件夹并进入该文件夹 3.安装jade 4.在新建的文件夹下新建js文件,写nodejs代码 5.在vscode中利用插件code runner直接执行js文件,输出 ...

  3. luogu P5328 [ZJOI2019]浙江省选

    传送门 每个人都可以看成一条直线\(y=ax+b\),所以我们要求的是每条线在整点处,上方线的数量的最小值(注意多条直线如果交于同一整点互不影响) 如果\(m=1\),其实只要求出半平面交,然后在半平 ...

  4. 表格强制换行 table-layout:fixed

    如果想要一个table固定大小,里面的文字强制换行(尤其是在一长串英文文本,并且中间无空格分隔的情况下),以达到使过长的文字不撑破表格的目的,一般是使用样式:table-layout:fixed.

  5. 81. Search in Rotated Sorted Array II (JAVA)

    Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e. ...

  6. 为什么对华为不拍Arm?

    华为可以靠着现有的 ARMv8 授权坚持很长一段时间,足以等到这波科技禁运结束. 今天,华为在美国遭遇的科技禁运上升到了全球新高度. 据 BBC 报道,由软银全资拥有的英国技术公司 Arm 向员工发出 ...

  7. Laravel - 验证码(captcha)

    首先,登录网址 packagist.org 查找 laravel captcha,找到mews/captcha ,根据 packagist 上的使用方法一步步来实现验证码的安装.配置composer. ...

  8. CentOS 7.6 下载和安装

    一. CentOS 7.6 下载 官网下载地址:https://www.centos.org/download/ 选择Minimal ISO 选择适合自己的下载路径即可. 二.CentOS 7.6 安 ...

  9. Codeforces 989 P循环节01构造 ABCD连通块构造 思维对云遮月参考系坐标轴转换

    A 直接判存不存在连续的三个包含A,B,C就行 /*Huyyt*/ #include<bits/stdc++.h> #define mem(a,b) memset(a,b,sizeof(a ...

  10. Linux 安装 nginx 安装PCRE库

    PCRE(Perl Compatible Regular Expressions)是一个Perl库,包括 perl 兼容的正则表达式库.这些在执行正规表达式模式匹配时用与Perl 5同样的语法和语义是 ...