var 、let 声明变量、 const声明常量(02-深入变量和闭包--JS进阶)

一、var 变量

  1. 加var的变量在预编译期间会提升,不加var的变量在预编译期间不会提升。
  2. 不管是否加var只要是全局变量,在非严格模式下,都会挂在GO上。
  3. 加var的变量可以做全局变量,也可以做局部变量,没有加var只能做全局变量。
<script>
    console.log(a); //und
    var a = 110;
    console.log(a); //110
    console.log(window.a); //110

    // console.log(b); //引用错误 以下不执行

    b = 666;
    console.log(window.b); //666
</script>

二、let 声明变量

<script>
    console.log(a); //Uncaught ReferenceError: Cannot access 'a' before initialization 
    // a没有初始化(赋值),是不能访问的
    // 理解成:使用let 声明的变量没有提升
    // 理解成:使用let 声明的变量提升了,没有赋值,没有赋值的变量是不能访问的
    //使用let 声明的变量不会挂在GO上

    let a = 110;
<script>
    //  {let } 可以形成块级作用域
    // 块级作用域中定义的变量,只能在块级作用域中使用,出了块级作用域就是用不了
    if (true) {

        let c = 10;
    }
    console.log(c); //引用报错
</script>

<script>
    // 使用let 声明的变量不会挂在GO上
    let a = 110;
    console.log(a); //110
    console.log(window.a); //undefined
</script>
<script>
    // let 在一个作用域上重复声明一个a,汇报错
    let a = 110;
    let a = 110;// 报错 Uncaught SyntaxError: Identifier 'a' has already been declared
    console.log(a); 
</script>
<script>
    function fn(a) {
        // 形参相当于函数内部定义的局部变量
        // VO:AO 已经有a了
        let a = 110; //Uncaught SyntaxError: Identifier 'a' has already been declared
    }
    fn();
    // let ES6中提出的,弥补了var声明变量的缺点
</script>

三、const声明常量

使用const 声明常量的特点:

1) 声明的常量不能修改

2) 使用const 声明常量时,必须赋值,不然会报语法错误

3) const 声明的常量也不会提升

4) const和{}也形成块级作用域

5) const 声明的常量也不会挂到GO上

↓练习题 感受let const 与var的区别↓

<script>
    const PI;
    //const 声明的常量不能被修改
    PI = 3.14;//  Uncaught SyntaxError: Missing initializer in const declaration
    console.log(PI); 
</script>
<script>
    console.log(fn); //undefined
    // window.fn(); // und()  (注释掉 window.fn())
    console.log(window.fn); //Uncaught TypeError: window.fn is not a function
    if ('fn' in window) {
        // 如果条件成立,进来第一件事句式给fn赋值 (注释掉 window.fn())
        fn();
        // 函数位于if 条件中 
        // 在最新版浏览器中,不会提升整体,仅仅提升fn函数名,提升至代码段最前面
        function fn() {
            console.log('fn....'); //fn...(注释掉 window.fn())
        }
    }
    fn(); //fn...(注释掉 window.fn())
</script>
<script>
    fn();

    function fn() {
        console.log(1);

    }
    fn();

    function fn() {
        console.log(2);

    }
    fn();
    var fn = function() { //执行到此处,fn指向改变log(3)
        console.log(3);
    }
    fn();

    function fn() {
        console.log(4);
    }
    fn();

    function fn() {
        console.log(5);
    }
    fn();

    // 控制台
    // 5
    // 5
    // 5
    // 3
    // 3
    // 3
</script>
<script>
    var a = 12;
    b = 13; //(3)b=200  (5)b=und
    c = 14;

    function fn(a) { //(2) a=100
        console.log(a, b, c); //(1)  10  13  14
        a = 100;
        b = 200;
        console.log(a, b, c); //(4) 100   200  14
    }
    b = fn(10); //(5)  没有return ==》 (5)
    console.log(a, b, c); //(6)  12  und  14
</script>
<script>
    function sum(a) { //预解析   (1)局部有个a ==>und
        console.log(a);
        let a = 100; //预解析  已经存在a let a 报错Uncaught SyntaxError: Identifier 'a' has already been declared ,
        console.log(a);
    }
    sum(200);
</script>
<script>
    var ary = [12, 13]; //ECG中的 ary的地址 指向堆中的 数组[12, 13]

    function fn(ary) { //(1)fn执行上下文  ary被赋值为全局执行上下文 ary的地址,所以指向堆中的同一个数组[12, 13]
        console.log(ary); //(2) [12,13]
        ary[0] = 100; //(3) 通过地址修改堆中的数组[12,13]==>[100,13]
        ary = [100]; //(4) fn执行上下文的ary 地址发生改变,指向堆中新的数组[100]
        ary[0] = 0; //(5)fn 执行上下文中ary通过地址,修改堆中数组[100]==>[0]
        console.log(ary); //(6) [0]
    }
    fn(ary);
    console.log(ary); //(7) [100,13]
</script>
<script>
    function fn() {
        function gn() {
            console.log('gn...');
        }
        // return 了gn的地址
        return gn;
    }
    let res = fn(); //(1) 这里分两步:1)调用fn() 获得gn 函数地址。2) 将函数地址赋值给res ,使得指向堆中的数据 function gn() {console.log('gn...');}
    res(); //(2) 调用res 通过地址指向堆中数据  打印得出gn...

    // ---------------上部代码等价于下部------------

    function fn() {
        return function() { //直接返回地址
            console.log('gn...');
        }
    }
    let res = fn(); //调用函数 fn(),返回地址 给res
    res();
</script>

↓此段代码从执行上下文理解闭包↓

<script>
    //栈中有个ECG   堆中为空
    var i = 0; //(1.1) ECG中 产生 i 预解析时值为und
    //(2) 代码从此步开始执行 : ECG中 i赋值为0;

    function A() { //(1.2) ECG中 产生 函数A 的地址 
        var i = 10;

        function x() {
            console.log(i);
        }

        return x; //返回函数的地址
    }
    var y = A(); //(1.3)  ECG中产生 y 预解析时值为und
    // (3.1) 调用A(), ECA 入栈 ,ECA里面包含 i ==>und==>1 ,包含 函数x 地址 , 该地址指向堆中的  x函数体;函数执行完毕返回 x的地址。
    // (3.2) 将函数x地址赋值给ECG中的y y==>und==>x的地址,指向x的函数体。
    // (3.3)注意此时 ECG中的y的地址  指向了 函数x预解析时存放在堆中的数据。(函数A执行完毕本该进行ECA出栈,堆内存释放空间,但是通过调用A函数,修改了y的值,等同ECA中同于x地址,都指向了x的堆,使得此堆中的值一直被引用,此时的ECA不能出栈回收,把这个不能被回收的栈空间叫做闭包)。

    y(); //(4) 调用函数y ECy入栈,函数y的值此时为x函数地址,通过地址找到堆中函数体,执行打印出log(i),Ey中找不到i ,通过作用域链找到函数x 的父级 函数A所产生的ECA中的i=10,控制台打印log(i) 结果为10, 函数y()调用完毕,ECy出栈,释放堆空间。

    function B() { //(1.4)  ECG中产生 B的地址  
        var i = 20;
        y(); //(5.2)调用 函数y ECy2入栈,ECy2 通过地址(函数x)指向堆中函数x,要求打印i,但是ECy2提供不了i的数据,就找指向的函数的父级A函数,打印出i=10
    }
    B(); //(5.1)调用函数B ,ECB入栈,ECB中 包含i==>und==>20
    // 个人总结:
    // 1)函数指向是在代码执行时开始指向的

    // 控制台显示:
    // 10
    // 10

    // 闭包的作用:
    // 1)保护  保护EC中的变量,不被外界直接访问
    // 2)保存  可以让我们像使用全局变量那样使用局部变量,延长了变量的使用周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IsJwT4YU-1667889392424)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0df3348078bb42cab8241c2b65c8cb50~tplv-k3u1fbpfcp-watermark.image?)]

<script>
    console.log(fn); //und
    if (1 == 1) {
        // 进来第一件事是给fn赋值(地址)
        console.log(fn); //fn(){}

        function fn() { //在代码块里面,声明函数名提升到最前面
            console.log('ok');
        }
    }
    console.log(fn); //fn(){console.log('ok');}
</script>

<script>
    console.log(num); //und
    console.log(fn); //und
    if ([]) {
        // 进来第一件事是给fn赋值(地址)
        fn() //'a'
        var num = 100;

        function fn() {
            console.log('a');
        }
    }
    console.log(fn); //fn(){console.log('ok');};
</script>

<script>
    function fn(i) {
        return function(n) {
            console.log(n + (++i));
        }
    }
    var f = fn(2);
    f(3); //6
    f(5)(6); //9
    f(7)(8);
    f(4);
    // 控制台:
    // 6
    // 9
    // Uncaught TypeError: f(...) is not a function
</script>
<script>
    var foo = 'hello';
    // 立即执行函数  没有函数名 
    // 即使有函数名  在函数在外面也不能使用 
    (function(foo) { //foo形参  相当于函数内部的局部变量
        console.log(foo) || 'world'; //hello
        console.log(foo); //hello
    })(foo); //实参oo="hello"
    console.log(foo); //hello
</script>
<!-- <script>
    var a = 9;

    function fn() {
        a = 0;
        return function(b) {
            return b + a++;
        }
    }
    var f = fn();
    console.log(f()(5)); // )(5 不能作为一个参数  Uncaught TypeError: f(...) is not a function
    console.log(f(5)); //
    console.log(a);
</script> -->
<script>
    // let a = {
    //     num: 0,
    //     valueOf: function() {
    //         console.log('valueOf...');
    //         return ++a.num;
    //     }
    // };

    // --------
    let a = {
        num: 0,
        toString: function() {
            console.log('valueOf...');
            return ++a.num;

        }
    };
    // a和别人作比较时,会自动调用valueof和toString
    // 如果a是一个对象,就会调用自己的valueof 或toString,就会调用自己的valueof 或toString,
    if (a == 1 && a == 2 && a == 3) {

        console.log('ok'); //
    }
    console.log(++a.num);
    // 控制台:
    // valueOf...
    // valueOf...
    //valueOf...
    //4
</script>

推荐阅读

深入理解JavaScript作用域和作用域链 - 掘金 (juejin.cn)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值