一、介绍

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

二、函数表达式

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


//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. log4j 和slf4j的比较

    log4j 和slf4j的比较 slf4j 官网:https://www.slf4j.org/manual.html slf4j(simple logging facade for java)是Jav ...

  2. jQuery源码的一个坑

    纯吐槽 大半夜也真是够了,想学着jQ造个小轮子巩固下js,结果一开始就卡住了. 虽然之前也看过源码,但是主要是研究方法实现什么的,对于框架主函数和入口结构不怎么熟悉,于是想着一步一步调试看看. $(' ...

  3. angular-utils-ui-breadcrumbs使用心得

    angular-utils-ui-breadcrumbs是一个用来自动生成面包屑导航栏的一个插件,需要依赖angular.UIRouter和bootstrap3.css.生成的界面截图如下,点击相应的 ...

  4. CodeForces - 385C Bear and Prime Numbers (埃氏筛的美妙用法)

    Recently, the bear started studying data structures and faced the following problem. You are given a ...

  5. JDBC(MySQL)一周学习总结(二)

    上一篇文章我们总结了获取数据库连接以及操作数据表的一些知识点,本篇将继续上次的文章给大家分享! 1. 上一篇文章我们可以对数据表进行增删改查的操作了,对与一些小项目的部分功能我们也足以胜任.但现在有一 ...

  6. 【DG】利用闪回数据库(flashback)修复Failover后的DG环境

    利用闪回数据库(flashback)修复Failover后的DG环境 1.1  BLOG文档结构图 1.2  前言部分 1.2.1  导读和注意事项 各位技术爱好者,看完本文后,你可以掌握如下的技能, ...

  7. oracle数据库显示所有用户方法

    sql>show user查看自己的是哪个用户sql>select * from all_users; 查询所有用户sql>select * from user_users;查询当前 ...

  8. RPC 调用简述

    首先了解什么叫RPC,为什么要RPC,RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网 ...

  9. 【译】Asp.Net Identity Cookies 格式化-中英对照版

    原文出处 Trailmax Tech Max Vasilyev: ASP.Net MVC development in Aberdeen, Scotland I've been reached out ...

  10. Do you kown Asp.Net Core -- 配置Kestrel端口

    Kestrel介绍 在Asp.Net Core中,我们的web application 其实是运行在Kestrel服务上,它是一个基于libuv开源的跨平台可运行 Asp.Net Core 的web服 ...