JavaScript--我发现,原来你是这样的JS:函数表达式和闭包
一、介绍
本次博客主要介绍函数表达式的内容,主要是闭包。
二、函数表达式
定义函数的两种方式:一个是函数声明,另一个就是函数表达式。
//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:函数表达式和闭包的更多相关文章
- JavaScript函数表达式、闭包、模仿块级作用域、私有变量
函数表达式是一种非常有用的技术,使用函数表达式可以无需对函数命名,从而实现动态编程.匿名函数,是一种强大的方式,一下总结了函数表达式的特点: 1.函数表达式不同于函数声明,函数声明要求有名字,但函数表 ...
- 深入理解javascript系列(4):立即调用的函数表达式
本文来自汤姆大叔 前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行. 在详细了解这个之前,我们来谈了解一下“自执行”这个叫法,本文对这个功能的叫法 ...
- [JS]深入理解JavaScript系列(4):立即调用的函数表达式
转自:汤姆大叔的博客 前言 大家学JavaScript的时候,经常遇到自执行匿名函数的代码,今天我们主要就来想想说一下自执行.在详细了解这个之前,我们来谈了解一下"自执行"这个叫法 ...
- 浅谈JavaScript的函数表达式(闭包)
前文已经简单的介绍了函数的闭包.函数的闭包就是有权访问另一个函数作用域的函数,也就是函数内部又定义了一个函数. var Super=function(num){ var count=num; retu ...
- javascript高级程序设计第3版——第7章 函数表达式
此张内容的难点在于闭包.而闭包又涉及到原型,原型链,执行上下环境,this的取值等知识点.(此章节对于闭包的内容篇幅较少,且写的很是艰涩难懂,推荐一位大牛的博客,对于闭包的前因后果以及作用机制写的很明 ...
- JavaScript高级程序设计(读书笔记)之函数表达式
定义函数的方式有两种:一种是函数声明,另一种就是函数表达式. 函数声明的一个重要特征就是函数声明提升(function declaration hoisting),意思是在执行代码前会先读取函数声明. ...
- 一步步学习javascript基础篇(6):函数表达式之【闭包】
回顾前面介绍过的三种定义函数方式 1. function sum (num1, num2) { return num1 + num2; } //函数声明语法定义 2. var sum = funct ...
- JavaScript函数表达式
函数表达式的基本语法形式 var functionName = function(arguments){ //函数体 } 递归建议 我们通过例子来一步步说明,递归的最佳实现方式.下面是普通递归调用的例 ...
- 详解Javascript 函数声明和函数表达式的区别
Javascript Function无处不在,而且功能强大!通过Javascript函数可以让JS具有面向对象的一些特征,实现封装.继承等,也可以让代码得到复用.但事物都有两面性,Javascrip ...
随机推荐
- C# 使用OpenCV在一张图片里寻找人脸
先上个效果图 相关库的下载 例程中用到一个库叫做emgucv,是opencv\的net封装 编译打包好的稳定版,在这:https://sourceforge.net/projects/emgucv/f ...
- JavaScript数组去重方法汇总
1.运用数组的特性 1.遍历数组,也遍历辅助数组,找出两个数组中是否有相同的项,若有则break,没有的话就push进去. //第一版本数组去重 function unique(arr){ var r ...
- 通过对DAO层的封装减少数据库操作的代码量
在学框架之前,写项目时总是要花大量的时间去写数据库操作层代码,这样会大大降低我们的效率,为了解决这个问题,我花了两天时间利用反射机制和泛型将DAO层进行了封装,这样我们只需要写sql语句,不需要再写 ...
- h5 动画页面
伪元素上就不要做动画了,页面果然应该做一个测试一个啊 拿到设计稿一开始就先看看这个设计稿的布局,有一些是从页面顶部到底部都有效果的,这个时候就要考虑在 iPhone4 这样屏幕不够高的设备上如何保 ...
- mybatis映射异常
今天写项目突然遇到了这么个问题: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no ...
- 几种常用的ajax 跨域请求
前 言 首先,我们要明白,什么是跨域,为什么要跨域. 由于JS中存在同源策略.当请求不同协议名不同端口号下面的文件时,将会违背同源策略,无法请求成功!需要进行跨域处理! 这篇文章就为大家详细介绍一 ...
- 向TRichEdit插入图片的单元
很简单, 就3个函数, 直接看代码吧 unit RichEditBmp; { 2005-03-04 LiChengbin Added: Insert bitmap or gif into RichEd ...
- OGEngine_2.x中BitmapFont加载后黑屏问题的解决办法
在我使用OGEngine_2.x进行消灭圈圈(星星)游戏的实践的时候,使用BitmapFont对自定义字体进行调用. 原文字体教程如下:http://blog.csdn.net/OrangeGame/ ...
- 利用C#转换图片格式及转换为ico
注意:转换为ICO后效果不好. 源代码: using System;using System.Collections.Generic;using System.Text;using System.Dr ...
- MVC 小案例 -- 信息管理
前几次更新博客都是每次周日晚上到周一,这次是周一晚上开始写,肯定也是有原因的!那就是我的 Tomact 忽然报错,无法启动,错误信息如下!同时我的 win10 也崩了,重启之后连 WIFI 的标志也不 ...