JavaScript--我发现,原来你是这样的JS:函数表达式和闭包

1、介绍

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

2、函数表达式

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

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

区别:
1.函数声明是用function后面有函数名,函数表达式是赋值形式给一个变量。
2.函数声明能够提高函数,而函数表达式不会提高github

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

//例子:
//先调用运行
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');
    }
}

3、闭包

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

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

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

重点理解这里:

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 欢迎转载,转载请标明出处,保留该字段。

相关文章
相关标签/搜索