一、介绍

本次博客主要介绍函数表达式的内容,主要是闭包。

二、函数表达式

定义函数的两种方式:一个是函数声明,另一个就是函数表达式。


//1.函数声明写法
function fn2(){
console.log('函数声明');
}
//2.函数表达式写法
var fn1 = function(){
console.log('函数表达式');
}

区别:

1.函数声明是用function后面有函数名,函数表达式是赋值形式给一个变量。

2.函数声明可以提升函数,而函数表达式不会提升

函数提升就是函数会被自动提升到最前方,以至于再调用函数后再声明函数也不会有错:

//例子:
//先调用运行
sayName();
//再声明函数
function sayName(){
console.log('ry');
} //运行结果
'ry'

函数表达式就不会被提升:

//先调用
sayBye();
//函数表达式
var sayBye = function(){
console.log('bye bye');
} //运行报错

但是下面的写法很危险:因为存在函数声明的提升

//书上代码
if(condition){
function sayHi(){
console.log('hi');
}
}
else{
function sayHi(){
console.log('yo');
}
}

解说一下: 这段代码想表达在condition为true时声明sayHi,不然就另一函数sayHi,但是运行结果往往出乎意料,在当前chrome或firefox可能能做到,但是在IE10以下的浏览器(我测试过)往往不会遵循你的意愿,不管condition是true还是false都会输出yo。

这时函数表达式能派上用场了:

//换成函数表达式,没问题因为不会被提升,只有当执行时才赋值
var sayHi = null;
if(condition){
sayHi = function (){
console.log('hi');
}
}
else{
sayHi = function sayHi(){
console.log('yo');
}
}

三、闭包

闭包的定义:有权访问另一个函数作用域中的变量的函数

有人觉得闭包很难理解,一开始我也是这样的,我认为那是对一些概念还不够了解。

定义中说明了什么是闭包,最常见的形式就是在函数中再声明一个函数。

重点理解这里:

1.闭包能访问外围函数的变量是因为其作用域链中有外围函数的活动对象(这个活动对象即使在外围函数执行完还会存在,不会被销毁,因为被闭包引用着)。

2.闭包是函数中的函数,也就是说其被调用时也创建执行上下文,对于执行上下文这部分可以看看这篇:深入理解js执行--创建执行上下文这篇博客。

理解了上面之后我们再来看闭包的例子:

function a(){
//a函数的变量
var val_a = "我是a函数里的变量";
//声明函数b,b能访问函数a的变量
function b(){
console.log(val_a);
}
//a函数将b返回
return b;
} //全局变量fn,a执行返回了b给fn
var fn = a();
//调用fn,能够在全局作用域访问a函数里的变量
fn(); //我是a函数里的变量

这里fn能够访问到a的变量,因为b中引用着a的活动对象,所以即使a函数执行完了,a的活动对象还是不会被销毁的。这也说明过度使用闭包会导致内存泄漏。

再来个常见的例子(给多个li添加点击事件,返回对于li的下标):

<body>
<ul id="list">
<li>red</li>
<li>green</li>
<li>yellow</li>
<li>black</li>
<li>blue</li>
</ul>
</body>
//获得li标签组
var li_group = document.getElementsByTagName('li'); //错误例子:每个li都会跳出5
function fn(){
//为每一个li添加点击事件
var i = 0;
//使用for来给每个li添加事件
for(;i<li_group.length;i++){
//添加点击事件
li_group[i].addEventListener('click',function(){
// 输出对应得下标
console.log(i);
});
}
}
fn(); //正确的例子:
//在加一层的函数,作为闭包,用来保存每一次循环的i变量,就可以达到目的
function correctFn(){
var i = 0;
for(;i<li_group.length;i++){
//在外面套一层函数,这层函数会保存每次循环的i变量,也就是闭包了。
(function(num){
li_group[num].addEventListener('click',function(){
console.log(num);
});
}(i));
}
}
correctFn();

看下面解释之前我默认你已经知道活动对象是什么了,如果不懂可以看这篇:深入理解js执行--创建执行上下文

解释一下:

1.错误的例子:

屡一下思路,每个li都有click执行的函数,每个函数点击后才会执行是吧,那每个click的函数的外层函数是fn这个函数,那这5个click函数都会保存着fn的活动对象,那这个输出的i变量在fn这函数里面,所以当fn执行完后,i的值是5了,因此当每个里触发click的函数的时候输出的也就是5了。

再简单的说:每个li的click事件触发的函数引用的i在同一个活动对象中,所以值都一样。

2.正确执行的例子:

我们就在外层加了一层匿名函数并立即执行(虽然函数被执行了,如果有函数引用着它的活动对象,那其活动对象将不灭),这时每个li的click函数外层函数是每次循环产生的不同的匿名函数,这匿名也是有活动对象,每个li的click的函数保存着各自的匿名函数的活动对象,num这变量也根据每次循环产生不同的匿名函数传入的i的不同而不同,所以能够输出对应不同的值。

上面说的可能有点啰嗦,请原谅我[捂脸.jpg],我是希望尽可能的表达清楚。如果你看懂了,那对闭包的理解也更深一层了哦。

小结:

1.本篇主要讲的是闭包,闭包是有权访问另一个函数作用域中的变量的函数,主要是函数中的函数,因为能引用外层函数的活动对象所以能够访问其外层的变量。

2.我本篇主要讲的是原理,如果对一些东西不懂,可以看下面几篇。

相关的几篇:

深入理解js执行--单线程的JS

深入学习JS执行--创建执行上下文(变量对象,作用域链,this)

我发现,原来你是这样的JS全部文章汇总(点击此处)

本文出自博客园:http://www.cnblogs.com/Ry-yuan/

作者:Ry(渊源远愿)

欢迎访问我的个人首页:我的首页

欢迎访问我的github:https://github.com/Ry-yuan/demoFiles

欢迎转载,转载请标明出处,保留该字段。

JavaScript--我发现,原来你是这样的JS:函数表达式和闭包的更多相关文章

  1. JavaScript函数表达式、闭包、模仿块级作用域、私有变量

    函数表达式是一种非常有用的技术,使用函数表达式可以无需对函数命名,从而实现动态编程.匿名函数,是一种强大的方式,一下总结了函数表达式的特点: 1.函数表达式不同于函数声明,函数声明要求有名字,但函数表 ...

  2. 深入理解javascript系列(4):立即调用的函数表达式

    本文来自汤姆大叔 前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行. 在详细了解这个之前,我们来谈了解一下“自执行”这个叫法,本文对这个功能的叫法 ...

  3. [JS]深入理解JavaScript系列(4):立即调用的函数表达式

    转自:汤姆大叔的博客 前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行.在详细了解这个之前,我们来谈了解一下"自执行"这个叫法 ...

  4. 浅谈JavaScript的函数表达式(闭包)

    前文已经简单的介绍了函数的闭包.函数的闭包就是有权访问另一个函数作用域的函数,也就是函数内部又定义了一个函数. var Super=function(num){ var count=num; retu ...

  5. javascript高级程序设计第3版——第7章 函数表达式

    此张内容的难点在于闭包.而闭包又涉及到原型,原型链,执行上下环境,this的取值等知识点.(此章节对于闭包的内容篇幅较少,且写的很是艰涩难懂,推荐一位大牛的博客,对于闭包的前因后果以及作用机制写的很明 ...

  6. JavaScript高级程序设计(读书笔记)之函数表达式

    定义函数的方式有两种:一种是函数声明,另一种就是函数表达式. 函数声明的一个重要特征就是函数声明提升(function declaration hoisting),意思是在执行代码前会先读取函数声明. ...

  7. 一步步学习javascript基础篇(6):函数表达式之【闭包】

    回顾前面介绍过的三种定义函数方式 1. function sum (num1, num2) { return num1 + num2; }  //函数声明语法定义 2. var sum = funct ...

  8. JavaScript函数表达式

    函数表达式的基本语法形式 var functionName = function(arguments){ //函数体 } 递归建议 我们通过例子来一步步说明,递归的最佳实现方式.下面是普通递归调用的例 ...

  9. 详解Javascript 函数声明和函数表达式的区别

    Javascript Function无处不在,而且功能强大!通过Javascript函数可以让JS具有面向对象的一些特征,实现封装.继承等,也可以让代码得到复用.但事物都有两面性,Javascrip ...

随机推荐

  1. highcharts框架使用总结

    Highcharts官网地址:https://www.hcharts.cn/products/highcharts首先需要引入jQuery框架,然后包含Highcharts框架需要使用到的js文件,最 ...

  2. 已安装Fedora的U盘无法格式化,要求reinitialize layout

    错误提示:This partition cannot be modified because it contains a partition table; >please reinitializ ...

  3. .12-Vue源码之patch(2)

    快完事咯! 简单看了下patch函数,虽然不长,但是实际上很长很长,慢慢来吧, 首先来个总览: // line-5250 // oldVnode => 原生DOM节点 // vnode => ...

  4. struts2(四)之输入校验

    前言 这个本来是昨天就写好的,但是不知道为什么没有保存成功!但是今天起来再写一遍就当巩固一下知识吧. 一.输入校验概述 在以前我们写一个登录页面时,并没有限制用户的输入,不管用户输入什么,我们都存入数 ...

  5. T9

    T9 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) Total Submission ...

  6. POJ 3468 A Simple Problem with Integers(树状数组区间更新)

    A Simple Problem with Integers Time Limit: 5000MS   Memory Limit: 131072K Total Submissions: 97217   ...

  7. Linux分区规划与xshell使用排错

    1.1 没有重要数据 /boot   200M    存放系统的引导信息 内核 swap   交换分区   防止内存用光了 临时的一个内存 如果你的内存小于8G swap是内存的1.5倍   如果你的 ...

  8. git使用教程之git基础

    1 获取Git仓库 在现有目录中初始化仓库 git init 克隆现有的仓库 git clone https://github.com/yangwang12345/node_test.git Git ...

  9. php缓存模块apc可能导致php-fpm终止

    如果你的网站出现502错误.同时你网站中又使用了apc模块来做缓存处理.那么这篇文章兴许能帮到你. 首先,查看了php-fpm 的进程数. 发现php-fpm的进程数已经到达了php-fpm.conf ...

  10. JS中的this的应用总结

    简述this的用法 "this是由被调用的方式确定"这个事实,使得this可以被改变,从而为函数增加了动态性,可变性,使得变成更加灵活.目前因为工作经验有限,暂时总结一下五种情况下 ...