Maison > interface Web > js tutoriel > le corps du texte

Explication détaillée du champ d'application et de la clôture

php中世界最好的语言
Libérer: 2018-03-19 16:36:58
original
1197 Les gens l'ont consulté

Cette fois, je vous apporte une explication détaillée de la portée et de la fermeture Quelles sont les précautions d'utilisation de la portée et de la fermeture Voici des cas pratiques, jetons un coup d'oeil.

Contexte d'exécution

Il existe deux situations principales de contexte d'exécution :

  • Code global : Dans une section de balise <script>, il y a un contexte global d'exécution. Ce qui est fait, c'est : définition de variable, déclaration de fonction

  • Code fonction : Il y a un contexte dans chaque fonction. Ce qui est fait c'est : définition de variable, déclaration de fonction, ceci, arguments

PS : Faites attention à la différence entre "déclaration de fonction" et "expression de fonction".

Contexte d'exécution global

Déterminez la fenêtre comme contexte d'exécution global avant d'exécuter le code global.

(1) Prétraiter les données globales : (pas d'attribution de valeur)

  • Variables globales définies par var==>non défini, ajoutées comme attributs de window

  • function déclare la fonction globale ==> affectation (fun), et l'ajoute comme méthode de fenêtre

  • this==> affectation (window )

(2) Commencer à exécuter le code global

Avant d'appeler la fonction et de préparer l'exécution du corps de la fonction, créez l'objet de contexte d'exécution de la fonction correspondant (virtuel, existant sur le milieu de la pile ).

(1) Prétraiter les données locales :

  • Variable de paramètre formel ==> Affectation (paramètre réel) ==> Ajouter comme attribut du contexte d'exécution

  • arguments==> Assignation (liste de paramètres réelle), ajouter des variables locales définies comme attributs de contexte d'exécution

  • var ==> ; non défini, ajouté comme un attribut du contexte d'exécution

  • fonction déclarée fonction ==> affectation (fun), ajoutée comme méthode du contexte d'exécution

  • this ==>Affectation (l'objet de la fonction appelante)

(2) Commencez à exécuter le code du corps de la fonction

Pile de contexte d'exécution

  • 1. Avant l'exécution du code global, le moteur JS créera une pile pour stocker et gérer tous les objets de contexte d'exécution

  • 2. fenêtre) Après cela, ajoutez-le à la pile (push)

  • 3 Une fois le contexte d'exécution de la fonction créé, ajoutez-le à la pile (push)

  • <.>
  • 4. Une fois la fonction actuelle exécutée, supprimez (pop) l'objet en haut de la pile

  • 5. be La fenêtre restante

this

this fait référence à l'objet

qui appelle la fonction. cela pointe toujours vers l'objet sur lequel la fonction est exécutée.

L'analyseur transmettra un paramètre implicite à la fonction à chaque fois qu'il appelle la fonction. Ce paramètre implicite est le suivant.

Selon la façon dont la fonction est appelée, cela pointera vers différents objets : [Important]

  • 1 Lorsqu'elle est appelée en tant que fonction, ce sera toujours window . Par exemple,

    équivaut à fun();window.fun();

  • 2 Lorsqu'il est appelé en tant que méthode, c'est l'objet qui appelle la méthode

  • <.>3 .Lorsqu'il est appelé en tant que constructeur, il s'agit de l'objet nouvellement créé
  • 4. Lorsqu'il est appelé à l'aide de call et apply, il s'agit de l'objet spécifié
  • Un rappel spécial est que ce point ne peut pas être confirmé lorsque la fonction est définie et ne peut être déterminé que lorsque la fonction est exécutée. Plusieurs scénarios de ceci :

    1. Exécuté en tant que constructeur
  • Par exemple :

    function Foo(name) {
        //this = {};
        this.name = name;
        //return this;
    }
    var foo = new Foo();
Copier après la connexion
    2. Exécuter comme un attribut de l'objet
    var obj = {
        name: 'A',
        printName: function () {
            console.log(this.name);
        }
    }
    obj.printName();
Copier après la connexion
    3. Exécuter comme une fonction ordinaire
    function fn() {
        console.log(this); //this === window
    }
    fn();
Copier après la connexion
    4. call apply bind
  • scope

scope fait référence à la

portée

d'une variable. Il est statique (par rapport à l'objet contextuel) et est déterminé lors de l'écriture du code.

作用:隔离变量,不同作用域下同名变量不会有冲突。

作用域的分类:

if (true) {
    var name = 'smyhvae';}console.log(name);
Copier après la connexion

上方代码中,并不会报错,因为:虽然 name 是在块里面定义的,但是 name 是全局变量。

全局作用域

直接编写在script标签中的JS代码,都在全局作用域。

在全局作用域中:

  • 在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口,它由浏览器创建我们可以直接使用。

  • 创建的变量都会作为window对象的属性保存。

  • 创建的函数都会作为window对象的方法保存。

全局作用域中的变量都是全局变量,在页面的任意的部分都可以访问到。

变量的声明提前:

使用var关键字声明的变量( 比如 var a = 1),会在所有的代码执行之前被声明(但是不会赋值),但是如果声明变量时不是用var关键字(比如直接写a = 1),则变量不会被声明提前。

举例1:

    console.log(a);
    var a = 123;
Copier après la connexion

打印结果:undefined

举例2:

    console.log(a);
    a = 123;   //此时a相当于window.a
Copier après la connexion

程序会报错:

Explication détaillée du champ d'application et de la clôture

函数的声明提前:

  • 使用函数声明的形式创建的函数function foo(){}会被声明提前

也就是说,它会在所有的代码执行之前就被创建,所以我们可以在函数声明之前,调用函数。

  • 使用函数表达式创建的函数var foo = function(){}不会被声明提前,所以不能在声明前调用。

很好理解,因为此时foo被声明了,且为undefined,并没有给其赋值function(){}

所以说,下面的例子,会报错:

Explication détaillée du champ d'application et de la clôture

函数作用域

调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁。

每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的。

在函数作用域中可以访问到全局作用域的变量,在全局作用域中无法访问到函数作用域的变量。

在函数中要访问全局变量可以使用window对象。(比如说,全局作用域和函数作用域都定义了变量a,如果想访问全局变量,可以使用window.a

提醒1:

在函数作用域也有声明提前的特性:

  • 使用var关键字声明的变量,会在函数中所有的代码执行之前被声明

  • 函数声明也会在函数中所有的代码执行之前执行

因此,在函数中,没有var声明的变量都会成为全局变量,而且并不会提前声明。

举例1:

        var a = 1;
        function foo() {
            console.log(a);
            a = 2;     // 此处的a相当于window.a
        }
        foo();
        console.log(a);   //打印结果是2
Copier après la connexion

上方代码中,foo()的打印结果是1。如果去掉第一行代码,打印结果是Uncaught ReferenceError: a is not defined

提醒2:定义形参就相当于在函数作用域中声明了变量。

        function fun6(e) {
            console.log(e);
        }
        fun6();  //打印结果为 undefined
        fun6(123);//打印结果为123
Copier après la connexion

作用域与执行上下文的区别

区别1:

  • 全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时

  • 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建

  • 函数执行上下文是在调用函数时, 函数体代码执行之前创建

区别2:

  • 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化

  • 执行上下文是动态的, 调用函数时创建, 函数调用结束时就会自动释放

联系:

  • 执行上下文(对象)是从属于所在的作用域

  • 全局上下文环境==>全局作用域

  • 函数上下文环境==>对应的函数使用域

作用域链

当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用(就近原则)。如果没有则向上一级作用域中寻找,直到找到全局作用域;如果全局作用域中依然没有找到,则会报错ReferenceError。

外部函数定义的变量可以被内部函数所使用,反之则不行。

<!DOCTYPE html><html lang="en"><head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script>
        //只要是函数就可以创造作用域
        //函数中又可以再创建函数
        //函数内部的作用域可以访问函数外部的作用域
        //如果有多个函数嵌套,那么就会构成一个链式访问结构,这就是作用域链
        //f1--->全局
        function f1(){
            //f2--->f1--->全局
            function f2(){
                //f3---->f2--->f1--->全局
                function f3(){
                }
                //f4--->f2--->f1---->全局
                function f4(){
                }
            }
            //f5--->f1---->全局
            function f5(){
            }
        }
    </script></head><body></body></html>
Copier après la connexion

理解:

  • 多个上下级关系的作用域形成的链, 它的方向是从下向上的(从内到外)

  • 查找变量时就是沿着作用域链来查找的

查找一个变量的查找规则:

    var a = 1
    function fn1() {
      var b = 2
      function fn2() {
        var c = 3
        console.log(c)        console.log(b)        console.log(a)        console.log(d)      }
      fn2()    }
    fn1()
Copier après la connexion
  • 在当前作用域下的执行上下文中查找对应的属性, 如果有直接返回, 否则进入2

  • 在上一级作用域的执行上下文中查找对应的属性, 如果有直接返回, 否则进入3

  • 再次执行2的相同操作, 直到全局作用域, 如果还找不到就抛出找不到的异常

闭包

闭包就是能够读取其他函数内部数据(变量/函数)的函数。

只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。

上面这两句话,是阮一峰的文章里的,你不一定能理解,来看下面的讲解和举例。

如何产生闭包

当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量或函数时, 就产生了闭包。

闭包到底是什么?

使用chrome调试查看

  • 理解一: 闭包是嵌套的内部函数(绝大部分人)

  • 理解二: 包含被引用变量 or 函数的对象(极少数人)

注意: 闭包存在于嵌套的内部函数中。

产生闭包的条件

  • 1.函数嵌套

  • 2.内部函数引用了外部函数的数据(变量/函数)。

来看看条件2:

    function fn1() {
        function fn2() {
        }
        return fn2;
    }
    fn1();
Copier après la connexion

上面的代码不会产生闭包,因为内部函数fn2并没有引用外部函数fn1的变量。

PS:还有一个条件是外部函数被调用,内部函数被声明。比如:

    function fn1() {
        var a = 2
        var b = 'abc'
        function fn2() { //fn2内部函数被提前声明,就会产生闭包(不用调用内部函数)
            console.log(a)        }
    }
    fn1();
    function fn3() {
        var a = 3
        var fun4 = function () {  //fun4采用的是“函数表达式”创建的函数,此时内部函数的声明并没有提前
            console.log(a)        }
    }
    fn3();
Copier après la connexion

常见的闭包


  1. 将一个函数作为另一个函数的返回值


    1. 将函数作为实参传递给另一个函数调用。

    闭包1:将一个函数作为另一个函数的返回值

        function fn1() {
          var a = 2
          function fn2() {
            a++
            console.log(a)      }
          return fn2    }
        var f = fn1();   //执行外部函数fn1,返回的是内部函数fn2
        f() // 3       //执行fn2
        f() // 4       //再次执行fn2
    Copier après la connexion
    Copier après la connexion

    当f()第二次执行的时候,a加1了,也就说明了:闭包里的数据没有消失,而是保存在了内存中。如果没有闭包,代码执行完倒数第三行后,变量a就消失了。

    上面的代码中,虽然调用了内部函数两次,但是,闭包对象只创建了一个。

    也就是说,要看闭包对象创建了一个,就看:外部函数执行了几次(与内部函数执行几次无关)。

    闭包2. 将函数作为实参传递给另一个函数调用

        function showDelay(msg, time) {
          setTimeout(function() {  //这个function是闭包,因为是嵌套的子函数,而且引用了外部函数的变量msg
            alert(msg)      }, time)    }
        showDelay('atguigu', 2000)
    Copier après la connexion

    上面的代码中,闭包是里面的funciton,因为它是嵌套的子函数,而且引用了外部函数的变量msg。

    闭包的作用

    • 作用1. 使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)

    • 作用2. 让函数外部可以操作(读写)到函数内部的数据(变量/函数)

    我们让然拿这段代码来分析:

        function fn1() {
          var a = 2
          function fn2() {
            a++
            console.log(a)      }
          return fn2    }
        var f = fn1();   //执行外部函数fn1,返回的是内部函数fn2
        f() // 3       //执行fn2
        f() // 4       //再次执行fn2
    Copier après la connexion
    Copier après la connexion

    作用1分析

    上方代码中,外部函数fn1执行完毕后,变量a并没有立即消失,而是保存在内存当中。

    作用2分析:

    函数fn1中的变量a,是在fn1这个函数作用域内,因此外部无法访问。但是通过闭包,外部就可以操作到变量a。

    达到的效果是:外界看不到变量a,但可以操作a

    比如上面达到的效果是:我看不到变量a,但是每次执行函数后,让a加1。当然,如果我真想看到a,我可以在fn2中将a返回即可。

    回答几个问题:

    • 问题1. 函数执行完后, 函数内部声明的局部变量是否还存在?

    答案:一般是不存在, 存在于闭中的变量才可能存在。

    闭包能够一直存在的根本原因是f,因为f接收了fn1(),这个是闭包,闭包里有a。注意,此时,fn2并不存在了,但是里面的对象(即闭包)依然存在,因为用f接收了。

    • 问题2. 在函数外部能直接访问函数内部的局部变量吗?

    不能,但我们可以通过闭包让外部操作它。

    闭包的生命周期

    1. 产生: 嵌套内部函数fn2被声明时就产生了(不是在调用)

    2. 死亡: 嵌套的内部函数成为垃圾对象时。(比如f = null,就可以让f成为垃圾对象。意思是,此时f不再引用闭包这个对象了)

    闭包的应用:定义具有特定功能的js模块

    • 将所有的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包含n个方法的对象或函数。

    • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能。

    方式一

    (1)myModule.js:(定义一个模块,向外暴露多个函数,供外界调用)

    function myModule() {
        //私有数据
        var msg = 'Smyhvae Haha'
        //操作私有数据的函数
        function doSomething() {
            console.log('doSomething() ' + msg.toUpperCase()); //字符串大写
        }
        function doOtherthing() {
            console.log('doOtherthing() ' + msg.toLowerCase()) //字符串小写
        }
        //通过【对象字面量】的形式进行包裹,向外暴露多个函数
        return {
            doSomething1: doSomething,
            doOtherthing2: doOtherthing    }}
    Copier après la connexion

    上方代码中,外界可以通过doSomething1和doOtherthing2来操作里面的数据,但不让外界看到。

    (2)index.html:

    <!DOCTYPE html><html lang="en"><head>
        <meta charset="UTF-8">
        <title>05_闭包的应用_自定义JS模块</title></head><body><!--闭包的应用 : 定义JS模块  * 具有特定功能的js文件  * 将所有的数据和功能都封装在一个函数内部(私有的)  * 【重要】只向外暴露一个包含n个方法的对象或函数  * 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能--><script type="text/javascript" src="myModule.js"></script><script type="text/javascript">
        var module = myModule();
        module.doSomething1();
        module.doOtherthing2();</script></body></html>
    Copier après la connexion

    方式二

    同样是实现方式一种的功能,这里我们采取另外一种方式。

    (1)myModule2.js:(是一个立即执行的匿名函数)

    (function () {
        //私有数据
        var msg = 'Smyhvae Haha'
        //操作私有数据的函数
        function doSomething() {
            console.log('doSomething() ' + msg.toUpperCase())    }
        function doOtherthing() {
            console.log('doOtherthing() ' + msg.toLowerCase())    }
        //外部函数是即使运行的匿名函数,我们可以把两个方法直接传给window对象
        window.myModule = {
            doSomething1: doSomething,
            doOtherthing2: doOtherthing    }})()
    Copier après la connexion

    (2)index.html:

    <!DOCTYPE html><html lang="en"><head>
        <meta charset="UTF-8">
        <title>05_闭包的应用_自定义JS模块2</title></head><body><!--闭包的应用2 : 定义JS模块  * 具有特定功能的js文件  * 将所有的数据和功能都封装在一个函数内部(私有的)  * 只向外暴露一个包信n个方法的对象或函数  * 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能--><!--引入myModule文件--><script type="text/javascript" src="myModule2.js"></script><script type="text/javascript">
        myModule.doSomething1()    myModule.doOtherthing2()</script></body></html>
    Copier après la connexion

    上方两个文件中,我们在myModule2.js里直接把两个方法直接传递给window对象了。于是,在index.html中引入这个js文件后,会立即执行里面的匿名函数。在index.html中把myModule直接拿来用即可。

    总结:

    当然,方式一和方式二对比后,我们更建议采用方式二,因为很方便。

    但无论如何,两种方式都采用了闭包。

    闭包的缺点及解决

    缺点:函数执行完后, 函数内的局部变量没有释放,占用内存时间会变长,容易造成内存泄露。

    解决:能不用闭包就不用,及时释放。比如:

        f = null;  // 让内部函数成为垃圾对象 -->回收闭包
    Copier après la connexion

    总而言之,你需要它,就是优点;你不需要它,就成了缺点。

    内存泄漏内存溢出

    内存泄漏

    内存泄漏:占用的内存没有及时释放。内存泄露积累多了就容易导致内存溢出。

    常见的内存泄露:

    • 1.意外的全局变量

    • 2.没有及时清理的计时器或回调函数

    • 3.闭包

    情况1举例:

        // 意外的全局变量
        function fn() {
            a = new Array(10000000);
            console.log(a);
        }
        fn();
    Copier après la connexion

    情况2举例:

        // 没有及时清理的计时器或回调函数
        var intervalId = setInterval(function () { //启动循环定时器后不清理
            console.log('----')    }, 1000)    // clearInterval(intervalId);  //清理定时器
    Copier après la connexion

    情况3举例:

    <script type="text/javascript">
      function fn1() {
        var arr = new Array[100000];   //这个数组占用了很大的内存空间
        function fn2() {
          console.log(arr.length)    }
        return fn2  }
      var f = fn1()  f()
      f = null //让内部函数成为垃圾对象-->回收闭包</script>
    Copier après la connexion

    内存溢出(一种程序运行出现的错误)

    内存溢出:当程序运行需要的内存超过了剩余的内存时,就出抛出内存溢出的错误。

      //内存溢出
      var obj = {}
      for (var i = 0; i < 10000; i++) {
        obj[i] = new Array(10000000);   //把所有的数组内容都放到obj里保存,导致obj占用了很大的内存空间
        console.log('-----')  }
    Copier après la connexion

    相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

    推荐阅读:

    Vue指令的使用

    JS闭包的使用

    JavaScript的字符串怎样使用

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

    Étiquettes associées:
    source:php.cn
    Déclaration de ce site Web
    Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
    Tutoriels populaires
    Plus>
    Derniers téléchargements
    Plus>
    effets Web
    Code source du site Web
    Matériel du site Web
    Modèle frontal
    À propos de nous Clause de non-responsabilité Sitemap
    Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!