JavaScript-中级:2 函数高级内幕-❤2.执行上下文

1.

EC(Execution Context):函数执行环境(或执行上下文),执行上下文中存放的是当前函数所需要的一些信息(如:变量等)。

ECS(Execution Context Stack):执行环境栈。

每个函数执行时,js引擎都会为其创建一个EC,而这个EC就存放在ECS中。

示例代码:
 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>函数执行上下文</title>
</head>
<body>
    
    <script>
    // 代码执行前,就会立即创建一个全局的执行上下文Global Execution Context,并压栈(ECS)
    
    function f1(){
        console.log("f1:");
    }

    function f2(){
        console.log("f2:");
        f3(); // 调用函数f3
    }

    function f3(){
        console.log("f3:");
        f4(); // 调用函数f4
    }

    function f4(){
        console.log("f4:");
    }
    
    f1(); // 代码进入执行f1函数,函数内的代码在执行前,js执行引擎立即创建一个f1的执行环境(f1 Execution Context),并压栈(ECS)
    // f1函数执行完后,出栈 

    // f2函数执行前,创建f2 EC,并压栈(ECS)
    f2(); // f2函数内部调用了f3(在f3函数执行前,创建f3 EC,并压栈(ECS)),f3函数内部调用了f4 (在f4函数执行前,创建f4 EC,并压栈(ECS))
    //  f4执行完,f4的EC出栈;f3执行完,f3的EC出栈;f2执行完,f2的EC出栈。

    </script>
</body>
</html>

 

讲解:

 

2.执行上下文的生命周期

  1. 创建阶段:
    1. 创建变量对象

      ps:VO和AO就是一回事,白话:VO全局的。
    2. 创建作用域链(Scope Chain)
  2. 执行阶段:执行变量赋值、代码执行
  3. 回收阶段:执行上下文出栈等待虚拟机垃圾回收。

 示例代码:
 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>函数执行上下文的讲解</title>
</head>
<body>
    <script>
    // 变量声明    
    var a1 = 19, 
        a2 = 20,
        a3 = 'sss',
        b1 = {name: 'lili'}; 
    
    // 函数调用
    a1 = f1(a1, a2); // 函数为什么可以先调用后声明:因为整个代码在执行前,有一个全局EC被创建,其中有一步就是扫描声明的函数。
    // f1函数执行完毕,出栈,垃圾回收
    console.log(a1); // 打印a1的值

    // 函数声明
    function f1(a, b){
        // f1函数的执行上下文的创建阶段:
        // step1:扫描参数, a = 19, b = 20
        // step2:扫描函数声明 f2 = function(){}
        // step3:扫描变量声明 t = undefined, m = undefined,i = undefined

        // 执行阶段:变量赋值,代码执行。
        var t = 0,
            m = 10;
        for(var i = 0; i<a; i++){
            console.log(i);
        }

        function f2(){
            console.log("f2");
        }

        return a + b;
    }
    </script>
</body>
</html>

讲解:

ps:

  1. 创建阶段:this:(待后续)(全局的this 指向的是window)
  2. 创建时第一步扫描申明:先扫描函数,再扫描变量,变量都是先赋值为undefined。执行阶段才有变量赋值。

 

3.JavaScript作用域

1. JavaScript的解释和执行

2.函数变量的作用域

ps:

  • 变量声明没有带var就是全局变量!! 
  • js只有函数作用域!
  • 变量声明和函数声明,同时拥有一个名字时,函数优先级高。

    (声明阶段函数优先级高,执行时先执行的第一个打印,输出为function;继续执行b为9,覆盖前面的,故第二次执行输出9)

示例代码:
 

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>变量的作用域</title>
</head>

<body>
    <script>
        // alt+shift+f 快速格式化我们的代码
    var t = 9; // 全局作用域,在js中的任何地方都可以访问

    function f1(){ // f1 函数全局作用域
        var t2 = 10; // t2是f1函数内部的变量,只有f1内部可以访问。f1局部作用域
        console.log(t);

        function f2(){ // f2 f1函数作用域
            var t3 = 200; // t3 只能在f2中访问。f2局部作用域
            console.log(t2);
            return t2 * t3; // f2函数可以访问f1函数的作用域的变量及f2自己内部的变量。
        }
        return f2(); // 调用f2,并返回f2的返回值
    }

    var m = f1(); // 调用f1
    console.log(m);
    </script>
</body>

</html>

3.变量提升(白话:遵守js作用域规则)

示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>函数变量提升</title>
</head>
<body>
    <script>
    var a = 10;  // 全局变量

    // !js代码解释阶段:已然确定了作用域规则
    // 所以js好的编程习惯:把函数内部所有的变量声明都放在函数的头部。
    
   function f1(){
        // 函数变量提升,因为在函数执行之前,先创建了函数的EC,
        // 复习:在创建EC的时候已经把函数里面声明的变量都已经初始化为undefined (即:EC创建阶段的扫描变量声明!)
         console.log(a); // undefined
        var a = 19;      // EC创建阶段,a为局部变量。给局部变量a赋值 
        console.log(a)   // 局部变量a的值:19
    }
    f1(); 
    console.log(a); // 全局变量啊的值:10

    console.log("******");

    function f2(){
        console.log(a); // 10
        a = 29; // EC创建阶段,a为全局变量。
        console.log(a); // 29
    }
    f2(); 
    console.log(a); // 29
    </script>
</body>
</html>

4.作用域链

 

4.函数的四种调用模式与this

1.方法调用模式

示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>this 方法调用模式</title>
</head>
<body>
    <script>
    // js的灵活性足见一般!

    // 在ES6中有class关键字了。
    // 此处是ES3,函数当做构造函数来使用。
    function Dog(dogName){
        this.name = dogName;
        this.age = 0;

        this.run = function(){
            console.log(this.name + "is running...");
        };
        // 如果函数当做构造函数调用,并没有返回任何数据,默认就会返回 新对象(this)
    }

    var d = new Dog('gorg'); // !创建一个实例对象
    d.run(); // 实例对象调用run方法
    // 在方法调用模式中,方法内部的this指向当前调用者对象
    // 故此处 this 指向d 
    </script>
</body>
</html>

 

2.构造器调用模式

示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>构造器调用模式</title>
</head>
<body>
    
    <script>
    // 构造器调用模式
    // 关键字:new

    function Cat(){
        // step1:创建一个空对象(新对象)
        // step2:给函数上下文赋值 新对象 this = 新对象
        this.age = 19;
        this.name = "cat"; // 在构造函数内部定义的this的所有属性,都会返回给新对象
        this.run = function(){
            console.log(this.name + "is running...");
        }

        // 如果构造函数没有返回值,那么就返回this(新对象)
        // return 3; // 如果返回值类型为简单类型,那么会被忽略!

        return {  // 注意 别用!
            name: "aliex",
            run: function(){
                console.log("aliex is singing!");
            }
        }; // 但如果返回一个对象,即一个引用类型,那么就返回这个引用类型,(注意!即this的相关被覆盖了,故下面cat的输出为"aliex is singing!",但是cat的age输出还是20,被覆盖的只有name和run。)请避免使用这种方式!
    }

    var cat = new Cat(); // 构造函数调用模式
    // 如果使用new关键字+构造函数,就触发了构造函数执行模式
    cat.age = 20;
    cat.name = "serty";
    cat.run(); // 方法调用模式 
    // console.log(cat.age);

    </script>
</body>
</html>

3.函数调用模式

示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>函数调用模式</title>
</head>
<body>
    
    <script>
    function f(a, b){
        console.log(a + " "+ b);
        this.a = 19; // 这里的this指向window
        console.log('a' in window); // 故这里输出为true
        console.log(this); // 这里输出window 但如果是严格模式:undefined
    }

    f(1, 2); // 直接调用函数:f():函数调用模式

    //小记:
    console.log("********");
    function Dog(){
        this.age = 1;
        console.log(this);
    }
    Dog(); // 函数调用模式,this为window
    var d = new Dog(); // 构造器调用模式, this 为d
    </script>

</body>
</html>

4.apply/call调用模式(借用方法模式)

示例代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>apply/call调用模式</title>
</head>
<body>

    <script>
    function sum(a, b){
        console.log(this);
        console.log(arguments);
        return a + b;
    }

    sum(1, 2) // 函数调用模式, this 为window

    console.log("*****");
    // call调用模式
    var t = {
        name: "lili"
    } ;
    // f.call(fContext, p1, p2, ....)
    var m = sum.call(t, 2, 3) // 第一个参数t相当于给了sum内部的this, 2和3 分别给了a和b。注意:除了第一个参数,后面的参数都是放到arguments里
    console.log(m);

    console.log("*****");
    // apply调用模式
    var m2 = sum.apply(t, [2, 3]); // apply的第二个参数,是传的一个数组
    console.log(m2);

    console.log("*****");
    // 注意:如果传递的参数是简单类型,那么
    //          如果传递的第一个参数是:null、undefined 则转为window
    //          如果是number、string、boolean 则转为包装类型
    sum.call(null, 3, 5); 
    console.log("*****");
    sum.call(undefined, 3, 5);
    console.log("*****");
    sum.call(3, 3, 5);
    </script>
</body>
</html>

运行结果:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值