10、闭包、立即执行函数

闭包、立即执行函数

闭包的过程

闭包的概念

当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄露。

作用域链不释放是因为被子函数继承了,就会导致内存被占用。(内存泄露就是内存占用高的意思)

但凡是内部的函数被保存到了外部,一定生成闭包
function a() {
	function b() {
        var bbb = 234;
        console.log(aaa);
    }
    var aaa = 124;
    return b;  // 返回b之后,b函数被定义,继承a函数的指向,然后b函数被保存了出来赋值给demo(下图二),a函数销毁自己的执行期上下文(AO/GO)
}
var glob = 100;
var demo = a();  // demo这个变量是a执行的结果  也就是保存了b函数 此时的b函数被定义
demo();  // 在外部执行b函数  然后创建自己的执行期上下文AO 但是自己的执行期上下文里面没有AO  所以就找继承的a的AO中有没有aaa  没有的话继续找GO  然后在a的AO中找到了  所以执行b函数时是打印124

b被定义时,继承了a的劳动成果,直接链接a的AO和GO

scope

b指向AO/GO

闭包的响应

function a() {  
    var num = 100;
    function b() {
        num ++;  // num ++ 是先用后变 也就是当前行值不变 打印时会改变
        conole.log(num); // b函数执行完之后打印a的AO中的num101 因为b的AO中没有num
    }
    return b;  //返回b函数到demo中  b函数继承a的劳动成果
}
var demo = a();  //a函数赋值给demo demo保存的就是b函数
demo();  // 执行之后打印101
demo();  // 执行之后打印102
// 1. a函数执行时生成AO和继承全局的GO  aAO{num:100}
// 2. b函数执行时生成b的AO和继承a的AO和全局的GO bAO{num:100}

// 重点:b函数想要被定义,那就需要a函数被执行,因为a执行b才会被定义(return b),然后b继承a的劳动成果之后a销毁,也就是return b的时候函数b被定义,renturn b执行完之后相当于a函数执行完,然后a函数销毁AO和GO(b函数已经继承之后销毁)。
function a(){
    var aa = 345;
    function b(){
        var bb = 234;
        function c(){
            var cc = 123;  // c函数执行完也会销毁自己的执行期上下文
        }
        c();  // c函数被执行是b函数的最后一条语句 也就是把c函数内部读完 c函数被定义代表b函数被执行完 然后销毁函数b产生的执行期上下文
    }
    b();  // b函数被执行是a函数的最后一条语句 也就是把b函数内部读完 b函数被定义代表a函数被执行完  然后销毁函数c产生的执行期上下文
}
a();

// a defined a.[[scope]]   0 : GO

// a doing a.[[scope]]     0 : aAO
//                         1 : GO

// b defined a.[[scope]]   0 : aAO
//                         1 : GO

// b doing a.[[scope]]     0 : bAO
//                         1 : aAO
//                         2 : GO

// c defined a.[[scope]]   0 : bAO
//                         1 : aAO
//                         2 : GO

// c doing a.[[scope]]     0 : cAO
//                         1 : bAO
//                         2 : aAO
//                         3 : GO

闭包的作用

实现公有变量
函数累加器
function add() {
    var count = 0;
    function demo() {
        count ++;
        console.log(count); 
    }
    return demo;  // 返回函数b
}
var counter = add();  // add函数赋值给counter
counter();
counter();
counter();
counter();  //重复多次实现累加效果  调用一次累加一次 
// 这里用的是add函数的AO中的count 因为demo的AO没有count add中的AO执行一次累加一个1
可以做缓存(存储结构)
eater
// 存在存储结构,但是外部不可见。
function test() {
    var num = 100;
    function a() {
        num ++;
        console.log(num);
    }
    function b() {
        num --;
        console.log(num);
    }
    return [a,b]; // 返回数组存储a函数和b函数
}
var myArr = test(); // 把返回的a函数和b函数赋值给myArr
myArr[0]();  // 数组第一位是a函数  打印结果是101
myArr[1]();  // 数组第二位是b函数  打印结果是100
// 因为a函数和b函数都没有num  都共同指向a的AO中的num(同一个地址)
function eater() {
    var food = "";
    var obj = {  // 对象里面可以定义方法
        eat : function () {
            console.log("i am eating " + food);
            food = "";
        },
        push : function (myFood) {  // 传参函数
            food = myFood;
        }
    }
    return obj;  // 返回对象就意味着把对象里面的两个函数都返回了
}
var eater1 = eater();  // 函数赋值给eater1
eater1.push('banana');  // 函数传参 给food传值  food就相当于隐式的存储结构
eater1.eat();  // 执行函数就有了值
可以实现封装,属性私有化。
Person();
模块化开发,防止污染全局变量

立即执行函数

定义

此类函数没有声明,在一次执行过后即释放。适合做初始化工作。

立即执行函数也有预编译、执行期上下文等,立即执行函数与其他函数的区别只有执行完立即销毁,其他的完全一样。

// 只使用一次的函数又称作为 针对初始化功能的函数
function a() {  // 这种函数只运行一次 要求执行完就销毁 否则很占内存空间
    ....此处省略一万行代码
}

// 立即执行函数写法一 小括号包括了传参部分
(function (){}())  // W3C 建议第一种
// 立即执行函数写法二 小括号不包括传参部分
(function (){})()

// 针对初始化功能的函数写法(执行完立即被释放)
(function (){  // 函数外层包裹小括号  匿名函数(不需要取名字,实在要取个名字也可以)
    var a = 123
    var b = 234
    console.log(a + b)
}())  // 函数最后增加小括号

// 立即执行函数也可以进行传参
(function (a, b, c){
    console.log(a + b + c)
}(1, 2, 3))  // 参数写在最后的小括号里

// 立即执行函数也可以有返回值  用完再销毁
var num = (function (a, b, c){  // 这样写会直接把d返回给num 然后销毁函数
    var d = a + b + c * 2 - 2
    return d
}(1, 2, 3))

函数声明和函数表达式

// 函数声明和函数表达式都能定义函数
// 函数声明
// 只有表达式才能被执行符号执行  能被执行符号执行的表达式函数名会被忽略
function test (){
    var a = 123
}()  // 函数最后加括号会语法错误 无法执行  
// 因为括号前面需要表达式 而这里的括号前面是函数声明体

test();   // 写test能执行函数是因为test是表达式

// 函数表达式
// var test是变量声明 =后面的部分是表达式
var test = function() {  // test是表达式  所以这里能执行
    console.log('a')
}();   // 这里加了括号 函数名就会被忽略  执行一次之后 控制台输入test就找不到这个函数
// 这种写法和立即执行函数没什么区别  函数执行一次之后就被彻底放弃了

// 函数声明
+ function test() {  // +/-/! 加减或者!一个东西叫表达式 所以能执行 
    console.log('a')
}();  // 加一对括号 函数就被执行了 一个表达式被执行 天生的功能就是忽略函数名
// 这种写法和立即执行函数没什么区别  函数执行一次之后就被彻底放弃了

// 括号是数学运算符 把函数包起来 那函数也就变成表达式了
// 这也就是立即执行函数的第二种写法
(function test() {
    console.log('a')
})();  // 打印a之后就找不到test了  函数只执行一次

// 立即执行函数的第一种写法  (2+3*(7-5))  是先识别最外层的括号 但是是先运算最内层括号
// 由于函数识别是先识别最外层的括号 所以把括号放到最外层也是会把函数变成表达式
(function test() {
    console.log('a')
}()); 

练习

function test(a,b,c,d) {
    console.log(a + b + c + d)
}(1,2,3,4)
// 这个函数执行不会报错  如果最后只写一个() 系统会认定是立即执行函数 
// 但是如果有参数  系统为了不报错 会理解成下面这样  所以不会执行(因为不是立即执行函数)
function test(a,b,c,d) {
    console.log(a + b + c + d)
}


(1, 2, 3, 4) // 系统不会把这部分当运算符

闭包的防范

闭包的防范

function test() {
    var arr = [];  // 定义一个空数组
    for(var i = 0; i < 10; i ++) {
        arr[i] = function () { 
// 函数体赋值给数组,数组中存储了十个函数体(funcion(){}),i的值在AO中一直改变,最后是10,所以打印的时候获取testAO中的i就是10,可以理解为存的是框架,但框架里面的内容从AO中获取
            console.log(i);  
        }
    }
    return arr;  // 数组返回出去
}
var myArr = test();  // 返回给myArr  test()赋值给myArr就等价于arr赋值给myArr

// test执行完 i变成10了 
for(var j = 0; j < 10; j ++) {
    myArr[j]();  // 执行函数test 打印10个10 访问的是同一个i 因为这里打印的都是test的AO中定义的i 而i在循环结束之后变成10了
}
// 把i按顺序输出的方法   立即执行函数
function test() {
    var arr = [];
    for(var i = 0; i < 10; i ++) {
        (function (j) {  // 立即执行函数放到for循环里面循环几次就会有几次立即执行函数
          arr[j] = function () { 
            console.log(j + "")  // 这里是打印0-9 因为打印的j就是当前的i 因为此时打印的j是立即执行函数自己创建的AO中的j 只不过这个j的值是当前对应的i的值 而每次函数AO都不是同一个所以互不影响  如果打印i 就还是都是10 因为i只存在于test函数中 是子函数共用的爹
          }  
        }(i))  // 把i作为实参传给形参j
    }
    return arr; 
}
var myArr = test(); 
for(var j = 0; j < 10; j ++) {
    myArr[j](); 
}

练习

练习一
<ul>
    <li>a</li>
    <li>a</li>
    <li>a</li>
    <li>a</li>
</ul>
题目:使用原生js,addEventListener,给每个li元素绑定一个click事件,输出他们的顺序
css:
* {
	margin:0;
	padding:0;
}
ul {
	list-style: none;
}
li: nth-of-type(2n) {
	background-color: red;
}
li: nth-of-type(2n + 1) {
	background-color: green;
}
html:
<ul>
    <li>a</li>
    <li>a</li>
    <li>a</li>
    <li>a</li>
</ul>
<script type="text/javascript">
   function test() {
        var liCollection = document.getElementsByTagName('li') // 选li
        for(i = 0; i < liCollection.length; i ++) {
            liCollection[i].onclick = function () { // 把里面的函数绑定在外部(闭包)
                console.log(i)  
                // 打印的值都是4 因为打印的i都是test的AO中的值 循环结束后AO中的i值是4
            }
        }
   }
</script>

正确方法
<script type="text/javascript">
   function test() {
        var liCollection = document.getElementsByTagName('li') // 选li
        for(i = 0; i < liCollection.length; i ++) {function (j) {
                liCollection[j].onclick = function () {
                console.log(j)  //把i作为实参传入 这里的j就是打印当前的i值
                // 这样打印的就是0 1 2 3.....
             }
         }(i)}
   }
</script>
// 练习二  之前做过
// 题目一:注意AO和GO各提各的(有重复正常)
GO {
    a : 100,
    demo : function () {},  // 函数体
    f : 123  // 函数内部赋值 
}
a = 100;
function demo(e) {
    function e() {}
    arguments[0] = 2;  // 这里是给形参e赋值为2 arguments控制实参的位数和值 但是和实参是两个东西  一个变另外一个也变  这叫相互映射
    document.write(e);  // 打印出2
    if(a) {   // 循环是在执行之后才起作业  所以不看
        var b = 123;
        function c() {
            // if里面不能声明function(以前可以)
        }
    }
    var c;
    a = 10;  // 给a赋值10
    var a;
    document.write(b);  /// 打印AO中的b  undefined
    f = 123;    // 变量未声明  归GO所有
    document.write(c);  // undefined 
    document.write(a);  // 打印AO中的a 10
}
var a;
demo(1);
document.write(a);  // 打印全局的a 100
document.write(f);  // 123

AO {
    e : function () {},  // 执行时赋值2
    b : undefined,
    c : undefined,
    a : 10
}
// 练习三
写一个方法,求一个字符串的字节长度。(提示:字符串有一个方法charCodeAt();一个中文占两个字节,一个英文占一个字节。
定义和用法
charGodcAt()方法可返回指定位置的字符的Unicodc编码。这个返回值是0-65535之间的整数。(当返回值是<=255时,为英文,当返回值>255时为英文) // 输出指定字母对应的ASCII值
语法
stringObject.charCodeAt(index)
cg:
<script type="javascript/text”>
var str="Hello world!"
document.write(str.charCodeAt(1);,//输出101  这里是输出str第一个字符H的ASCII对应的值
</script>
               
// 解答过程  方法一 判断字符占位
<script type="javascript/text”>
function retByteslen(target) {
    var count = 0;
    for(var i = 0; i < target.length; i ++) {
        if(target.charCodeAt(i) <= 255) {
            count ++   // 小于等于255时只占一个字节
        }else if(target.charCodeAt(i) > 255 {
            count += 2  // 大于255时占两个字节
         })
    }
    console.log(count)   // 这里打印的就是count占位字符数
}
</script>

// 方法二  先按照每个占位1计算 遇到汉字加1
<script type="javascript/text”>
function retByteslen(target) {
    var count = target.length;
    for(var i = 0; i < target.length; i ++) {
        if(target.charCodeAt(i) > 255) {
            count ++   // 大于255的加一个字节
        }
    }
    console.log(count)   // 这里打印的就是count占位字符数
}
</script>

// 方法三  在方法二的基础上继续简化
<script type="javascript/text”>
function retByteslen(target) {
    var count,
    var len,
        count = len = target.length;
    for(var i = 0; i < len; i ++) {
        if(target.charCodeAt(i) > 255) {
            count ++   // 大于255的加一个字节
        }
    }
    console.log(count)   // 这里打印的就是count占位字符数
}
</script>

// 逗号操作符
var a = (2, 3);  // a是3
// 逗号表达式的意思是先算前面的表达式,前面的表达式需要计算的可以先计算,然后计算后面的表达式,都计算完之后把后面的表达式的结果返回到前面a
var a = (1 - 1, 1 + 1);  // a是2


// 练习四
// 写出下面程序的执行结果
// 立即执行函数 
// fAO {f:function(){}}
var f = (
    function f() {
        return "1";
    },
	function g() {
        return 2;  // 逗号运算符,会把后面的值返回,所以f返回的是2
    }
)();
typrof f;    // number
// 练习五
var x = 1;
if (function f() {}) { 
    // function被括号括起来就说明它已经不是表达式了 消失了(放弃函数名)
    x += typeof f; // 因为functionf消失了,所以f未定义 所以类型转换是"undefined"
}
console.log(x);   // 1undefined 这里的undefined是str类型 不然1+undefined=NaN
  • 1); // a是2

// 练习四
// 写出下面程序的执行结果
// 立即执行函数
// fAO {f:function(){}}
var f = (
function f() {
return “1”;
},
function g() {
return 2; // 逗号运算符,会把后面的值返回,所以f返回的是2
}
)();
typrof f; // number


```js
// 练习五
var x = 1;
if (function f() {}) { 
    // function被括号括起来就说明它已经不是表达式了 消失了(放弃函数名)
    x += typeof f; // 因为functionf消失了,所以f未定义 所以类型转换是"undefined"
}
console.log(x);   // 1undefined 这里的undefined是str类型 不然1+undefined=NaN
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

好好学习_fighting

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值