上一篇我们主要讲解了函数的执行过程和原理,本篇我们将介绍函数的另外两个特殊表现:闭包和立即执行函数。

一   闭包

  1,  闭包的形成

  之前我们提到,函数执行完毕,马上就会销毁自己的AO对象。但是如果遇到下面这种情况:有子函数的定义,并将子函数返回。它真的就完全销毁了自己的AO对象吗?

 function fn(){
var a = 1;
function son(){
console.log(a);
}
return son;
}
var test = fn();
test();//error ? 1

  这将打印什么呢?表面上看,son内并没有变量的声明,consol.log()访问a应该抛出错误。

  但事实上,test()将打印1,这是为什么呢?回忆上一篇文章函数的作用域链,不难发现:

    当fn执行时:fn.[[scope]] --- {0:AO(fn),1:GO};执行期间son被声明:son.[[scope]]  --- {0:AO(fn),1:GO};

  return son将保留该属性,这时fn已经执行完毕:

    fn.[[scope]] --- {0:GO}(AO(fn)被销毁?);

  直到test()执行时,test.[[csope]]  --- {0:AO(son),1:AO(fn),2:GO}( test是son的另一个引用,实际上他们是同一个函数)。

  这时test想要访问变量a,那么他将先在自己的AO内查找,没有,那么他将到fn的AO里去查找,刚好有,所以最终打印的是1。

这里看似已经被fn销毁的AO(fn),实际上还被son引用着,所以它并没有真正的被完全销毁,只是对于fn来说,已经丢弃了对这个对象的引用,看起来像被销毁了。这个还被son保留着的AO对象我们即称之为闭包。闭包能帮助一个函数读取另一个函数内部的变量,它起到了连接两个函数的桥梁作用。

总结一下,在JavaScripe中形成闭包需要三个要素:

    1,  父函数内定义了子函数。

    2,  子函数内访问了父函数的变量。

    3,  子函数被返回。

  2,闭包的应用

    a) 变量私有化,但可以实现全局变量的效果

 function add(){
var count = 0;
return function (){
count ++;
//some code
console.log(count);
}
}
var myAdd = add();
myAdd();//
myAdd();//
myAdd();//

    b) 用作(类似)缓存

 function person(){
var money = 0;
var obj = {
pay:function (){
if(money > 0){
console.log("I spent one yuan.");
money --;
}else{
console.log("I run out of my money.");
}
},
make:function (){
console.log("I made one yuan.");
money ++;
}
};
return obj;
}
var person1 = person();
person1.pay();//"I run out of my money."
person1.make();//"I made one yuan."
person1.pay();//"I spent one yuan."

    c) 模块化开发,防止全局变量污染

 var a = "Global";
function p0(){
console.log(a);
}
function p1(){
var a = "p1";
return function(){
console.log(a);
};
}
function p2(){
var a = "p2";
return function(){
console.log(a);
};
}
var myP1 = p1();
var myP2 = p2(); p0();//"Global"
myP1();//"p1"
myP2();//"p2"

  大型项目一般都是多人协同开发,每个人负责不同的模块,不可避免的,大家可能使用了相同的变量名,这将造成全局变量污染。使用闭包,即可解决这个问题题。了解了下一节的立即执行函数,这段代码还可以加以优化。

二     立即执行函数

在认识立即执行函数之前,让我们先来了解执行符()的两个特点

1)只有表达式才能被()执行。

 function test(){
console.log(1);
}();//error 这是函数声明
var test = function (){
console.log(1);
}();//1 这是函数表达式

         2)能被()执行的表达式会被系统忽略函数名称。

 var test = function (){
console.log(1);
}();//
console.log(test);//undefined
//这是一个有趣的现象:我们声明了变量test,并把一个函数赋值给它,紧接着使用()执行了这个表达式,随即打印出了1。
按理说,这时test的值应该是一个匿名函数的函数体才对,但实际上它是undefined,变量刚被声明的状态,即系统放弃了变量test对函数的引用。

  这很好的印证了()执行符的第二个特点。

  1,立即执行函数的形式

  我们知道"()"括号实际上也是一种数学运算符,表示运算优先级的。那么我们当然可以把函数声明用括号包起来,使它成为一个表达式。这样我们就可以使用()执行符马上执行它并得到函数执行的结果了。

 (function test(){
console.log(1);
});//函数声明变成了表达式
//结合()执行符的第二个特点,我们可以将它优化
(function (){
console.log(1);
})();//

  以上就是立即执行函数的最终形式。另外,把()执行符放在函数声明的括号里面其实也是可以的。

 (function (){
console.log(1);
}());//1

  2,立即执行函数的特点

  知道了()执行符的特点,其实我们不难发现:

    1)立即执行函数被声明后会马上执行函数体内的代码。

    2)执行完毕后立即销毁,不会被一直保存在内存中。

    3)只能被执行一次,不能起到代码块复用的功能。

  除了上述特点外,立即执行函数和普通函数的功能完全一样。

  3,立即执行函数结合闭包的经典应用

 function fn(){
var arr = [];
for(var i = 0; i < 10; i++){
arr[i] = function () {
console.log(i);
};
}
return arr;
}
var myArr = fn();
myArr.map(function (item){
item();
});//10 10 10 10 10 10 10 10 10 10

  我们是想依次输出1--9啊!为什么跑出来10个10呢?

  仔细想一想,不难发现,这是因为所有子函数和fn形成的是同一个闭包,所以最后都打印了10,那么要怎样才能实现我们想要的功能呢?

 function fn(){
var arr = [];
for(var i = 0; i < 10; i++){
(function (j){
arr[j] = function () {
console.log(j);
}
}(i));
}
return arr;
}
var myArr = fn();
myArr.map(function (item){
item();
});//0 1 2 3 4 5 6 7 8 9

  通过利用立即执行函数定义完即被执行的特点,每次循环的时候都把i的值当做立即执行函数的实参传递进去,使每个立即执行函数都和子函数形成单独的闭包,那么最终子函数在执行时访问到的其实都是各自闭包(立即执行函数的AO)里的i的值了。这样就得到了我们想要的结果了。

  虽然闭包能在很多地方发挥很大作用,但闭包也有它自身的缺陷:闭包将一直占用内存空间,严重时将导致内存泄漏,甚至系统崩溃。所以我们应该尽量避免使用闭包,如果别无他法,也应该在使用完后手动的解除它对内存的占用,比如把引用返回函数的变量赋值为null。

JavaScript之深入函数(二)的更多相关文章

  1. JavaScript正则表达式详解(二)JavaScript中正则表达式函数详解

    二.JavaScript中正则表达式函数详解(exec, test, match, replace, search, split) 1.使用正则表达式的方法去匹配查找字符串 1.1. exec方法详解 ...

  2. JavaScript入门学习之二——函数

    在前一章中讲了JavaScript的入门语法,在这一章要看看函数的使用. 函数的定义 JavaScript中的函数和Python中的非常类似,只不过定义的方式有些不同,下面看看是怎么定义的 //定义普 ...

  3. JavaScript权威指南 - 函数

    函数本身就是一段JavaScript代码,定义一次但可能被调用任意次.如果函数挂载在一个对象上,作为对象的一个属性,通常这种函数被称作对象的方法.用于初始化一个新创建的对象的函数被称作构造函数. 相对 ...

  4. JavaScript基础学习-函数及作用域

    函数和作用域是JavaScript的重要组成部分,我们在使用JavaScript编写程序的过程中经常要用到这两部分内容,作为初学者,我经常有困惑,借助写此博文来巩固下之前学习的内容. (一)JavaS ...

  5. Javascript模块化编程(二):AMD规范

    Javascript模块化编程(二):AMD规范   作者: 阮一峰 原文地址:http://www.ruanyifeng.com/blog/2012/10/asynchronous_module_d ...

  6. Javascript基础回顾 之(二) 作用域

    本来是要继续由浅入深表达式系列最后一篇的,但是最近团队突然就忙起来了,从来没有过的忙!不过喜欢表达式的朋友请放心,已经在写了:) 在工作当中发现大家对Javascript的一些基本原理普遍存在这里或者 ...

  7. javascript立即执行函数

    javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花;当然,能理解各型各色的写法也是对javascript语言特性更进一步的深入理解.  ( ...

  8. JavaScript 立即执行函数

    js中(function(){…})()立即执行函数写法理解 javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花,当然,能理解各型各色的写法 ...

  9. JavaScript作用域原理(二)——预编译

    JavaScript是一种脚本语言, 它的执行过程, 是一种翻译执行的过程.并且JavaScript是有预编译过程的,在执行每一段脚本代码之前, 都会首先处理var关键字和function定义式(函数 ...

  10. Javascript动画效果(二)

    Javascript动画效果(二) 在前面的博客中讲了简单的Javascript动画效果,这篇文章主要介绍我在改变之前代码时发现的一些问题及解决方法. 在前面的多物体宽度变化的例子中,我们给其增加代码 ...

随机推荐

  1. MinorGC和FullGC的触发条件

    前言 无论是日常工作,还是企业面试,我们都会经常接触到GC.我们都知道GC是java中的垃圾回收策略.GC帮我们省去了很多事.在GC中,我经常听到的就属于MinorGC和FullGC了.那么在什么情况 ...

  2. 如何编写高质量的 JS 函数(1) -- 敲山震虎篇

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/7lCK9cHmunvYlbm7Xi7JxQ作者:杨昆 一千个读者,有一千个哈姆雷特. 此系列文 ...

  3. java性能使用

    1.慎用异常 j写在for循环外面 2.使用局部变量 局部变量在栈(stack)里面,速度快;全局变量在堆(heap)里面 int a =0; public static int ta =0; 3.位 ...

  4. vue当图片加载失败时,用一个默认图片替换;

    原理就是给img绑定error事件,替换原有的src地址. 首先在data中创建一个defaultImg(随便起的),里面的值是该默认图片的地址: 在html页面的img标签上绑定该属性 这样默认图片 ...

  5. jvm系列(七):如何优化Java GC「译」

    本文由CrowHawk翻译,地址:如何优化Java GC「译」,是Java GC调优的经典佳作. Sangmin Lee发表在Cubrid上的”Become a Java GC Expert”系列文章 ...

  6. 浅谈jQuery中的Ajax

    浅谈jQuery中的Ajax 一.前言 jQuery 对 Ajax 操作进行了封装, 在 jQuery 中最底层的方法时 $.ajax(), 第二层是 load(), $.get() 和 $.post ...

  7. Java基础之Iterable与Iterator

    Java基础之Iterable与Iterator 一.前言: Iterable :故名思议,实现了这个接口的集合对象支持迭代,是可迭代的.able结尾的表示 能...样,可以做.... Iterato ...

  8. MSIL实用指南-加载int值

    这一篇讲的是怎样加载整数值到运算栈上.这一类的指令都是以Ldc_I4开头. Ldc_I4类OpCodes的Ldc_I4字段的功能是把一个int值压入运算栈上.它的使用方法是ilGenerator.Em ...

  9. vmware上安装的ubuntu连接不上网络

    解决方法如下: 1.右键点击ubuntu,选择“设置” 2.网络适配器设置为“仅主机模式”:与主机共享的专用网络.点击“确定”即可.

  10. F-maximum clique 1_2019牛客暑期多校训练营(第五场)

    题意 给出n个不同的数字\(a_i\),求出最大的子集,使得子集内任意两个数在二进制下至少有两位不同. 题解 先对任意两个二进制位只有一个不同的两个数连边,那么问题就转化成找出最多的点集,任意两点没有 ...