闭包


闭包

一、作用域初探

作用域定义:变量(变量作用于又称上下文)和函数生效(能被访问)的区域
互相嵌套的函数,里面可以访问外面的,外面不能访问里面

外面不访问里面演示:

var a = 123;
function test() {
  var b = 123;
}
test(); //
document.write(b);

里面能访问外面演示:

var a = 123;
function test() {
    var b = 0;
    function demo() {
        var c = 234;
        console.log(b);//0
        console.log(a);//123
    }
    demo();
    // document.write(c);//报错
}
test();

二、js 运行三部曲

JS 逼格:单线程;解释性语言

语法分析,通篇扫描—–>预编译——->解释执行

1.预编译

引入 demo

var a = 123;
console.log(a);//123

console.log(a);
var a = 123;//undefined

函数声明整体提升

function test(){}

变量 声明提升

var a;
document.write(a);
a = 123;
//var a  = 123;相当于var a;a = 123

两句话不能解决的问题

console.log(a);
function a(a){
    var a = 234;
    var a = function(){
    }
    a();
}
var a = 123;

预编译前奏:

1.imply global 暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局对象 window所有。

function test(){
    var a = b =1;
    //先b赋值1(未声明),在声明a,在赋值a
}
test();
//var a = 123;先声明a,后赋值
console.log(window.a);
console.log(window.b);

2.一切声明的全局变量,全是 window 的属性。
window 就是全局的域;window 就是全局:

var a = 123;
console.log(a);//---->window.a等价
var a = 123;
window {
    a : 123 //相当
}

经典 demo

function test() {
    var a = b = 123;
}
test();
console.log(window.b);//undefined,局部,不是全局
console.log(a);

声明局部变量不行 demo

function test(){
    var b = 123;
}
test();
console.log(window.b);
console.log(b);

预编译四部曲

预编译发生在函数执行的前一刻

1.创建 AO 对象(Activation object)执行期上下文 2.找形参和变量声明,将变量和形参名作为 AO 属性名,值为 undefined 3.将实参值和形参统一(GO 没有) 4.在函数体里面找函数声明,值赋予函数体

例子 1

function fn(a){
    console.log(a);
    // function a(){}
    var a = 123;
    // var a看过了不看了 直接a=123;
    console.log(a);
    // 123
    function a(){}
    // 早已看过不看了
    console.log(a);
    // 123
    var b=function(){}//叫函数表达式
    // var b不用看了,看过了,直接b=function(){}
    console.log(b);
    // function(){}
    function d(){}
}
fn(1);

例子 2

function test(a,b) {
    console.log(a);
    c = 0;
    var c;
    a = 3;
    b = 2;
    console.log(b);
    function b() {}
    function d() {}
    console.log(b);
}
test(1);// 答案122

例子 3

function test(a, b) {
    console.log(a);
    console.log(b);
    var b = 234;
    console.log(b);
    a = 123;
    console.log(a);
    function a(){}
    var a;
    b = 234;
    var b = function () {}
    console.log(a);
    console.log(b);
}
test();

预编译不止发生在函数体系里面,还发生在全局,全局叫生成了一个 GO 对象,名字不同,步骤一样。 window 就是 GO

// GO{
// 	b : 123;
// }
function test(){
    var a = b = 123;
    console.log(window.b);//能访问
}
test();
//AO {
    // a:undefiend
    // 对b不起作用
//}
// 但是GO里没有a,so window.a没有

先生成 GO(全局),后 AO

// GO{
//     test:function(){...}
// }
console.log(test);
function test(test) {
    console.log(test);
    var test = 234;
    console.log(test);
    function test() {
    }
}
test(1);
var test = 123;
// AO{
//     //test执行的前一刻
//     // GO,AO里面都有test,要AO的
// }

难度 1:

// GO{
// 	global : undefined---->100
// 	fn : function (){...}
// }
var global = 100;
function fn(){
    console.log(global);
}
// AO{
// 	没东西了,就打印把,global自己里面没有,再去GO里面找到了
// }
fn();

难度 2:

global = 100;
function fn(){
    console.log(global);
    global = 200;
    console.log(global);
    var global = 300;
}
fn();
var global;
// AO{
//  global:undefined
// }
//答案:undefined 200 有自己的先用自己的

难度 3

// GO{
//     //GO里面才有a
//     a : undefined
//     c : 234暗示全局变量
// }
function test() {
    console.log(b);//undefined
    if(a) {
        var b = 100;
    }
    console.log(b);//undefined,因为if不走
    c = 234;//暗示全局变量
    console.log(c);//234
}
var  a;
test();
// AO{
//  b : undefined
// }
a = 10;
console.log(c);//234

百度题

function bar(){
    return foo;
    foo = 10;
    function foo(){
    }
    var foo = 11;
}
console.log(bar());
//答案:function foo(){}return 下面有函数,返回函数

百度题

console.log(bar());
function bar(){
    foo = 10;
    function foo(){}
    var foo = 11;
    return foo;
    //return 前面覆过值,就11
}

变量 声明提升

console.log(b);//undefined
var b = function () {
}

顶级难度

// GO{
// 	a : undefined,
// 	demo : function () {}
// 	然后a:100;在demo执行的前一刻,产生AO
// f :123
// }
a = 100;
function demo(e){
    function e() {}
    arguments[0] = 2;
    console.log(e);//2
    if(a) {//如今if里面不能放函数声明了//a是undefined,里面语句不走了
        var b = 123;
        function c() {
            //猪都能做出来
        }
    }
    var c;
    a = 10;
    var a;
    console.log(b);//undefined
    f = 123;//f AO里面没有,扔给GO 暗示全局变量
    console.log(c);//func  unde
    console.log(a);//10
}
var a;
AO{
    //形参
    e : undefined--->1---->function e(){}--->2
    b : undefined
    c : undefined--->(function (){})
    a : undefined--->10
}
demo(1);
// 以下为函数外面了,全局了,GO
console.log(a);//100
console.log(f);//123

姬成题目

//typeof(null);----object
var str = false + 1;
document.write(str);//1
var demo = false == 1;
document.write(demo);//false
if(typeof(a)&&-true + (+undefined) + ""){
    document.write('基础扎实');
}//"undefined"&&-1 + "NAN"字符串类型 +""
if(11 + "11" * 2 == 33) {
    document.write('基础扎实');
}
!!" " + !!"" - !!false||document.write('你觉得能打印,你就是猪');
// " "空格字符串true
// ""空串 false
// true + false - false == 1就停了
// 1||(被省略了)

笔试题:

1.css 中,display 属性几种,列出来
display : none/block/inline-block/inline……
2.css 中 list-style 的属性有几种,分别是什么

3.

<div class="box">
    <div class="box_1"></div>
    <div class="box_r"></div>
</div>

平行排列,并且均分父级,并且没有间距//两栏布局 4.使用 CSS HTML 三角形 5.水平垂直居中 6.写出 window.foo 值

(window.foo || window.foo = 'bar');//这样报错,这样先计算或,优先级高
(window.foo || (window.foo = 'bar'));//先看括号,bar

三、作用域精解

1.单线程 2.解释性语言(翻译一句解释一句)
[[scope]]:每个 javascript 函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供 javascript 引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们所说的作用域,其中存储了运行期上下文的集合。
作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。 3.运行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象 AO。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,执行上下文被销毁。
查找变量:从作用域链的顶端依次向下查找。

var a = 100;
function test() {
    var a = 234;
    console.log(a);//自己里面有,就不要GO,无论如何说明AO与GO有某种联系
}
test();

小例子

function a(){
    function b() {
        var b = 234;
    }
    var a = 123;
    b();
    console.log(a);
}
var glob = 100;
a();
// a defined a.[[scope]]---> 0:GO{}
// a.doing   a.[[scope]]---> 0:AO{}
//                           1:GO{}

两个 AO 是引用,指向同一个房间

function a(){
    function b() {
        var bb = 234;
        aa = 0;
    }
    var a = 123;
    b();
    console.log(aa);
}
var glob = 100;
a();

例子

function a() {
    function b() {
        function c(){
        }
        c();
    }
    b();
}
a();
a defined a.[[scope]] --->
0:GO
a doing a.[[scope]] --->
0 : aAO
1 : GO
b defined b.[[scope]] --->
0:aAO
1:GO
b doing   b.[[scope]] --->
0:bAO
1:aAO
2:GO
c defind c.[[scope]] --->
0:bAO
1:aAO
2:GO
c doing c.[[scope]] --->
0:cAO
1:bAO
2:aAO
3:GO

四、闭包

1.定义:

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

2.闭包例子

例子 1

function a() {
    function b(){
        var b = 234;
        console.log(aaa);//123
    }
    var aaa = 123;
    return b;
}
var glob = 100;
var demo = a();
demo();

例子 2

function a() {
    var num = 100;
    function b() {
        num ++;
        console.log(num);
    }
    return b;
}
var demo = a();
demo();//101
demo();//102

3.闭包的作用

实现公有变量
eg:函数累加器,之前依赖外部变量

function add() {
    var count = 0;
    function demo(){
        count++;
        console.log(count);
    }
    return demo;
}
var counter = add();
counter();
counter();
counter();
counter();
counter();

可以做缓存
eg:eater

function test() {
    var num = 100;
    function a(){
        num++;
        console.log(num);
    }
    // a defined  a.[[scope]] 0 : testAO
    // 					   1 : GO
    function b(){
        num--;
        console.log(num);
    }
    // b defined b.[[scope]] 0 : testAO
    // 					   1 : GO
    return [a,b];
}
var MyArr = test();
MyArr[0]();
// a doing a.[[scope]] 0 : aAO
// 						1 : testAO  *
// 						2. GO
MyArr[1]();
// b doing b.[[scope]]  0 : bAO
// 						1 : testAO	*
// 						2. GO
// 因为a,b是一个爹,环境一样,a=b

缓存演示 demo

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.push('banana');
eater1.eat();

可以实现封装,属性私有化
eg: Person();

模块化开发,防止污染全局变量

函数声明&&函数表达式区别

4.闭包的防范

闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生

五、立即执行函数

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

针对初始化功能的函数。立即执行函数用完销毁和函数没区别

var num = (function (a, b, c){
    var d = a + b + c;
    return d;
}(1, 2, 3))

立即执行函数的两种写法:

  1. (function (){}()); W3C 建议第一种
  2. (function (){})();

现象

function test() {//函数声明
    var a = 123;
}();//语法解析错误
function test() {
    var a = 123;
}
test();//test是表达式,就可以

以上,只有表达式才能被执行符号执行

function test() {
    var a = 123;
}//函数声明
123;//函数表达式
test();//函数表达式

// 函数表达式可以
var test = function() {
    console.log('a');
}();

// 能被执行符号执行的表达式,这个函数名字就会被自动忽略
var test = function() {
    console.log('a');
}
var test = function() {
    console.log('a');
}();//立即执行

+ function test(){
    console.log('a');
}();//减号,叹号,乘除不行(加减代表正负),&&,||

// 惊悚的
(function test() {
    console.log('a');
})()
(function test {
    console.log('a');
}())
// 因为被执行符号执行的表达式,这个函数名字就会被自动忽略
(function () {
    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 () {
            document.write(i + ' ');
        }
    }
    return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++){
    myArr[j]();
}

为什么是 10
i++
为什么全是 10

arr[i] = function () {
    document.write(i + ' ');
}

是一个赋值语句,把一个函数引用赋给数组当前位,函数体 document.write(i + ‘ ‘);没调用前不执行,直到调用,才执行,恰好执行的时候全是十。
执行位置!=定义位置
怎么解决(唯一方法) 十个小立即执行函数吧

function test () {
    var arr = [];//里面存了十个函数
    for( var i = 0; i < 10; i++) {
        (function (j){
            arr[j] =  function () {
                document.write(j + " ");
            }
        }(i));
    }
    return arr;
}
var myArr = test();
for(var j = 0; j < 10; j++){
    myArr[j]();
}//打印i依旧是10,
//立即执行函数是读到马上执行

六、闭包

var demo;
function test() {
    var abc = 100;
    function a(){
        console.log(abc);
    }
    demo = a;
}
test();
demo();
//里面的保存到外部,生成闭包

阿里巴巴笔试题(UC 移动事业群)社招题

function test() {
    var liCollection = document.getElementsByTagName('li');
    for(var i  = 0; i < liCollection.length; i++){
        liCollection[i].onclick = function(){
            console.log(i);
        }
    }
}
test();
<!DOCTYPE html>
<html>
  <head>
    <title>hehe</title>
    <style type="text/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;
      }
    </style>
  </head>
  <body>
    <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");
        for (var i = 0; i < liCollection.length; i++) {
          (function (j) {
            liCollection[i].onclick = function () {
              console.log(j);
            };
          })(i);
        }
      }
      test();
    </script>
  </body>
</html>

笔试题

var x = 1;
if(function f() {}){
    x += typeof f;
}
console.log(x);//1undefined

文章作者: Sunny
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Sunny !
  目录