02-深入变量 与 闭包

6/22/2022

# 1. 加var的变量和不加var的变量

加var的变量和不加var的变量有什么区别:

  • 加var的变量在预编译期间会提升,不加var的变量在预编译期间不会提升。
<script>
    // 加var的变量在预编译期间会提升,不加var的变量在预编译期间不会提升。
    // console.log(a);  // undefined
    // var a = 110;

    console.log(b);  // ReferenceError: b is not defined
    b = 120;
</script>
1
2
3
4
5
6
7
8
  • 不管有没有加var,创建的全局变量,都会放到GO中的,也就是可以通过window.xx。
<script>
    // 不管有没有加var,创建的全局变量,都会放到GO中的,也就是可以通过window.xx。
    var a = 110;
    b = 120;
    console.log(window.a); // 110
    console.log(window.b); // 120
</script>
1
2
3
4
5
6
7
  • 加var的变量,可能是全局变量,也可能是局部变量,不加var的变量,只能是全局变量。

  • 加var的局部变量,不会作用window的属性

<script>
    function fn(){
        var a = 110;
    }
    fn();
    console.log(window.a);
</script>
1
2
3
4
5
6
7

不加var创建的变量:

  • 不建议使用。 项目中尽量不要使用全局变量。在项目中,不要使用没有加var的变量。

# 2. let声明的变量

使用let声明的变量的特点:

  • 使用let声明的变量没有提升。针对这个错误:ReferenceError: Cannot access 'a' before initialization有人是这样说的:let声明的变量也有提升。但是没有赋值(没有初始化),就记一种:使用let声明的变量也提升,只不过没有初始化,去使用的时候就报错了。
<script>
    // 使用let声明的变量没有提升
    // initialization  初始化的意思
    console.log(a); // ReferenceError: Cannot access 'a' before initialization  
    let a = 110; // let也可以声明变量
</script>
1
2
3
4
5
6
  • 使用let配合{}会形成块级作用域。 在C语言中,就有块级作用域。在JS中,原本是没有块级作用域。在块中声明的变量,只能在块中使用。
<script>
    // 在let之前,只有两种作用域 1)全局作用域  2)函数内部的局部作用域
    var a = 110;  // a处于全局作用域中
    console.log(a);
    function fn(){
        var b = 220; // b处于局部作用域中
        console.log(b);
    }
    fn();
    // 在let之前,是没有块级作用域  {}
    if(true){ // if 后面的 {} 在其它编程语言中会形成块级作用域
        // {}如果是块级作用域  c在{}外面是不能访问的
        var c = 330;  // c是全局变量
    }
    console.log(c);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
    if(true){
        // 此时 let + {} 就会形成块级作用域
        // 在此块定义的数据,只能在此块中访问
        let a = 110;
    }
    console.log(a);
</script>
1
2
3
4
5
6
7
8
  • 使用let声明的变量并不会挂载到GO中
<script>
    let a = 110;
    console.log(window.a);
</script>
1
2
3
4
  • 使用let声明的变量也不能重复声明
<script>
    var a = 1;
    var a = 2;
    var a = 3;
    console.log(a);
    let b = 1;
    let b = 2;
    let b = 3;
</script>
1
2
3
4
5
6
7
8
9

需要知道:项目中不要使用var来声明变量,你要声明变量,使用let。

使用var声明变量不足:

  • 提升
  • 重复声明 浏览器中有这样一个机制,如果一个变量提升过了,后面遇到同名变量,就不会提升了。

# 3. const声明变量(常量)

使用const声明的变量(常量)的特点:

  • 声明的变量不能修改(常量)
<script>
    const a = 110;
    a = 120;
    console.log(a);
</script>
1
2
3
4
5
  • 声明时必须赋值(初始化) 定义 = 声明+赋值(初始化)
<script>
    const a;
    a = 110;
    console.log(a);
</script>
1
2
3
4
5
  • const声明的常量也不会提升。
  • const声明的常量配合{} 也会形成块级作用域
  • const声明的常量也不会挂载到GO上。

总结:项目中不要使用var,如果声明变量使用let,如果声明常量,使用const。

# 4. 练习题(面试题)

<script>
    // 使用之前学的知识点来做
    // fn函数整体提升了
    console.log(fn)  // fn函数
    window.fn();  // fn...
    console.log(window.fn); // fn函数
    if("fn" in window){
        fn();  // fn...
        function fn(){
            console.log("fn...");
        }
    }
    fn();
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
    // 在很早的浏览器中,运行出来的结果,就是上面分析出来的结果
    // 但是现在的浏览器结果不一样了
    // 如果是全局函数  并且这个函数放到if中  它提升的不再是整体提升
    // 它提升的仅仅是函数名,整体函数体是没有提升的
    // 在预编译期间,如果函数声明放到if中,仅仅是提升函数名,不提升函数体
    console.log(fn)  // fn函数名提升了,默认值和变量是一样的,是und
    window.fn();  // und不能加()去调用 
    console.log(window.fn); // 
    if("fn" in window){
        fn();  // 
        function fn(){
            console.log("fn...");
        }
    }
    fn();
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
    // 在很早的浏览器中,运行出来的结果,就是上面分析出来的结果
    // 但是现在的浏览器结果不一样了
    // 如果是全局函数  并且这个函数放到if中  它提升的不再是整体提升
    // 它提升的仅仅是函数名,整体函数体是没有提升的
    // 在预编译期间,如果函数声明放到if中,仅仅是提升函数名,不提升函数体
    console.log(fn)  // und 
    console.log(window.fn); //  und 
    // 问:GO中有没有fn  答:有的,只不过它的值是und 
    if("fn" in window){
        // 如果if条件成立,进入到if中的第1件事,就是给fn赋值
        fn();  // fn...
        function fn(){
            console.log("fn...");
        }
    }
    fn();
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<script>
    // 预编译:加var变量和function声明的函数整体
    // function fn(){ console.log(1); } 要提升   fn是一个函数 log(1)
    // function fn(){ console.log(2); }  已经提升过一次fn了, fn要重新赋值  log(2)
    // var fn; 进提升  fn值又变成了und 
    // function fn(){ console.log(4); }  fn的又变成了函数  log(4)
    // function fn(){ console.log(5); }  fn的又要重新赋值  log(5)
    // fn  函数   log(3)
    fn(); // 5
    function fn(){ console.log(1); }
    fn();  // 5
    function fn(){ console.log(2); }
    fn(); // 5
    var fn = function(){ console.log(3); }
    fn(); // 3
    function fn(){ console.log(4); }
    fn();  // 3
    function fn(){ console.log(5); }
    fn();  // 3
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script>
    // var a = 12; var b = 13; var c = 14;
    var a = 12; b = 13; c = 14;
    function fn(a){
        // 形参a是函数内部的局部变量a  形参a的值是10
        console.log(a,b,c);  // 10   13   14
        a = 100;   // 找a  把100赋值给了局部变量a   
        b = 200;  // 找b   把200赋值给外面的b 是全局变量b 
        console.log(a,b,c); // 100  200  14
    }
    b = fn(10); // und
    console.log(a,b,c); // 12  und   14
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
    var a = 12; b = 13; c = 14;
    function fn(a){
        console.log(a,b,c); 
        a = 100;  
        b = 200; 
        console.log(a,b,c); 
    }
    b = fn(10); 
    console.log(a,b,c); 
</script>
1
2
3
4
5
6
7
8
9
10
11
<script>
    // var a = 1;
    // let a = 2;  // Identifier 'a' has already been declared
    function sum(a){
        // 形参是相当于函数内部局部变量  
        console.log(a); //  报错了
        let a = 100;  // 
        console.log(a); // 
    }
    sum(200);
</script>
1
2
3
4
5
6
7
8
9
10
11
<script>
    var ary = [12,13];
    function fn(ary){
        console.log(ary);
        ary[0] = 100;
        ary = [100];
        ary[0] = 0;
        console.log(ary);
    }
    fn(ary);
    console.log(ary);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
<script>
    function fn(){
        function gn(){
            console.log("gn...");
        }
        return gn;
    }
    var r = fn();
    r();
</script>
1
2
3
4
5
6
7
8
9
10
<script>
    function fn() {
        // 直接返回一个函数,返回的也是地址
        return function gn() {
            console.log("gn...");
        }
    }
    var r = fn();
    r();
</script>
1
2
3
4
5
6
7
8
9
10
<script>
    var i=0; 
    function A(){
        var i=10;
        function x(){
            console.log(i);
        }
        return x;
    }
    var y = A();
    y();
    function B(){
        var i=20;
        y();
    }
    B();
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script>
    var i = 5;
    function fn(i){
        return function(n){
            console.log(n+(++i))
        }
    }
    var f = fn(1);
    fn(2);
    fn(3)(4);
    fn(5)(6);
    f(7);
    console.log(i);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 5. JS中函数是一等公民

在JavaScript中,函数是非常重要的,并且是一等公民:

  • 那么就意味着函数的使用是非常灵活的;
  • 函数可以作为另外一个函数的参数,也可以作为另外一个函数的返回值来使用;

什么是高阶函数:

  • 一个函数它的参数是函数或它的返回值是函数,那么这个函数就是高阶函数。

  • 自己编写高阶函数

<script>
    // func是形参  把gn这个函数传递给func
    // gn中保存的是地址  地址传递给func
    function fn(func){
        func();
    }
    function gn(){
        console.log("gn...");
    }
    // gn叫实参
    fn(gn)
</script>
1
2
3
4
5
6
7
8
9
10
11
12
<script>
    function add(num1,num2){ return num1+num2 }
    function sub(num1,num2){ return num1-num2 }
    function mul(num1,num2){ return num1*num2 }
    // 封装一个计算器函数
    function calc(num1,num2,fn){
        console.log(fn(num1,num2));
    }
    calc(10,20,add);
    calc(10,20,sub);
    calc(10,20,mul);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
<script>
    // fn也是高阶函数
    function fn(){
        function gn(){
            console.log("gn");
        }
        return gn; // 返回一个地址
    }
    let kn = fn();
    kn();
</script>
1
2
3
4
5
6
7
8
9
10
11
<script>
    // 高阶函数:如果一个函数接受另外一个函数作为参数,或者该函数会返回另外一个函数作为返回值,
    // 那么这个函数就叫高阶函数
    // 闭包可以延长变量的生命周期
    function fn(count){
        function add(num){
            return count + num;
        }
        return add;
    }
    var gn5 = fn(5);
    console.log(gn5(6));
    console.log(gn5(10));
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 使用内置的高阶函数
<!-- 使用传统的方式过滤偶数,如下: -->
<script>
    let nums = [10,3,12,50,99,30];
    // 把上面数组中的偶数过滤出来
    let newArr = [];
    // 命令式编程
    for(var i=0; i<nums.length; i++){
        if(nums[i] % 2 == 0){
            newArr.push(nums[i])
        }
    }
    console.log(newArr);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 可以使用高阶函数,如下: -->
<script>
    // ---------------------- filter 过滤
    let nums = [10,3,12,50,99,30];
    // function(item){}  整体是实参
    // filter 是JS中内置好的高阶函数
    // item 这个item表示数组中的每一项
    // 不要问为什么,JS就是这规定
    // 声明式编程
    let newArr = nums.filter(function(item){
        // console.log(item);
        // return true; // 把item每一项都放到新数组中
        return item % 2 == 0;
    })
    console.log(newArr);  // filter返回值是一个数组
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
    // ---------------------- map 映射
    let nums = [10,3,12,50,99,30];
    let newArr = nums.map(function(item){
        // 可以对item每一项数据进行加工
        return item*10
    })
    console.log(newArr); // map返回值是一个新数组
</script>
1
2
3
4
5
6
7
8
9
<script>
    // ---------------------- forEach 遍历
    let nums = [10, 3, 12, 50, 99, 30];
    nums.forEach(function(item){
        console.log(item);
    })
</script>
1
2
3
4
5
6
7
<script>
    // ---------------------- find 查找
    let nums = [10, 3, 12, 50, 99, 30];
    var item = nums.find(function(item){
        return item === 30
    })
    console.log(item);
</script>
1
2
3
4
5
6
7
8
<script>
    // ---------------------- findIndex 
    let nums = [10, 3, 12, 50, 99, 30];
    // 返回找到元素的索引
    var item = nums.findIndex(function(item){
        return item === 30
    })
    console.log(item);
</script>
1
2
3
4
5
6
7
8
9
<script>
    // ---------------------- find 查找
    let names = [
        {name:"wc",age:10},
        {name:"xq",age:11},
        {name:"z3",age:12},
        {name:"L4",age:13},
    ];
    var res = names.find(function(item){
        // item表示数组中的每一项,是里面的对象
        return item.name === 'z3'
    })
    console.log(res);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 6. 什么是闭包

什么是闭包:

  • 函数执行后返回结果是一个内部函数,并被外部变量所引用,如果内部函数持有被执行函数作用域的变量,即形成了闭包。

闭包是一片不会被销毁的EC。它对EC中的数据有保护和保存的作用:

  • 保护:防止像全局变量那样被污染,数据在函数内部,外界访问不了。
  • 保存:可以像全局变量那样,延长变量的生命周期。
  • 闭包使用不当可能会导致内存泄露

应用场景:

  • 模块封装,在各模块规范(ES6)出现之前,都是用这样的方式防止变量污染全局
  • 数据私有化
  • 封装一些工具函数
  • 在循环中创建闭包,防止取到意外的值。
<script>
    var i = 20;
    function fn(){
        i -= 2;
        var i = 10;
        return function(n){
            console.log((++i)-n);
        }
    }
    var f = fn();
    f(1);
    f(2);
    fn()(3);
    console.log(i);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
    let a = 0;
    b = 0;
    function A(a){
        A = function(b){
            // b++ 优先级高于 + 
            alert(a+b++);
        }
        alert(a++)
    }
    A(1); // 1
    A(2); // 4
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
    var t = (function(i){
        return function(){
            alert(i *= 2)
        }
    })(2);

    t(5);
</script> 
1
2
3
4
5
6
7
8
9
<script>
    var n = 0;
    function a(){
        var n = 10;
        function b(){
            n++;
            console.log(n);
        }
        b();
        return b;
    }
    var c = a();
    c();
    console.log(n);
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 7. IIFE

Immediately Invoked Function Expression(立即调用函数表达式)

<script>
    function fn(){
        console.log("fn...");
    }
    // 之前:先定义函数  再去调用函数
    fn();

    // 思考:在定义函数时,能不能直接去调用
    // 答:IIFE
</script>
1
2
3
4
5
6
7
8
9
10
<script>
    function fn(){
        console.log("fn...");
    }()
</script>
1
2
3
4
5
  • 正确的IIFE,如下:
<script>
    // IIFE写法一
    (function fn() {
        console.log("fn...");
    })()
</script>
<script>
    // IIFE写法二
    (function fn() {
        console.log("fn...");
    }())
</script>
<script>
    // IIFE写法三
    +function fn() {
        console.log("fn...");
    }()
</script>
<script>
    // IIFE写法四
    -function fn() {
        console.log("fn...");
    }()
</script>
<script>
     // IIFE写法五
    !function fn() {
        console.log("fn...");
    }()
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  • 注意问题:
<script>

    let obj = {
        name:"wc",
        age:100
    }

    (function(){
        console.log("mn...");
    })();
</script>
1
2
3
4
5
6
7
8
9
10
11
<script>

    let obj = {
        name:"wc",
        age:100
    };
    
    (function(){
        console.log("mn...");
    })();
</script>
1
2
3
4
5
6
7
8
9
10
11
  • 为了防止,你前面的代码没有加分号,通常写一个IIFE,都会在前面加上分号,如下:
<script>
    let obj = {
        name:"wc",
        age:100
    }
    // 在写IIFE时,直接以分号开头
    ;(function(){
        console.log("mn...");
    })();
</script>
1
2
3
4
5
6
7
8
9
10
Last Updated: 12/25/2022, 10:02:14 PM