zoukankan      html  css  js  c++  java
  • 作用域,上下文,闭包

    作用域

    作用域决定了你的代码里的变量和其他资源在各个区域中的可见性,为代码提供了一个安全层级,用户只能访问他们当前需要的东西。

    在 JavaScript 中有两种作用域:

    全局作用域:定义在函数之外的变量会被保存在全局作用域中,在代码的任何地方都是可访问的。

    局部作用域函数作用域):在函数内定义的变量(局部变量)或者函数的参数,只在当前函数体内以及这个函数体嵌套的任意函数体可访问,函数外部不能访问。

    访问当前作用域的变量速度比访问其他作用域的快,因为会顺着作用域链查找,直到找到你要的或者没有结果。

    在一个函数中,如果局部变量和全局变量同名,局部变量会覆盖全局变量。在函数体内访问这个变量,是局部变量的值。所以,在不同的作用域,可以命名相同的变量而不导致冲突,解决了不同范围的同名变量命名问题

      var num = 1;            //声明一个全局变量
       function func() {
          var num = 2;        //声明一个局部变量
           return num;
       }
       console.log(func());    //输出:2 

    局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。(当通过闭包在函数外面引用了局部变量,当函数执行完,局部变量不会被销毁)

    一个应用中全局作用域的生存周期与该应用相同。局部作用域只在该函数调用执行期间存在。 

    变量声明提前

    变量在整个函数体内都是有定义的,所以在赋值前是能访问的,即变量声明提升到当前函数体顶部(要是当前函数嵌套了其他函数,其他函数中声明的变量,由于作用域链的关系,其他函数体的变量,在当前函数体内是不可访问的,不存在声明提升到当前函数体的说法)

     function func() {
                console.log(num);           //输出:undefined,而非报错,因为变量num在整个函数体内都是有定义的
                var num = 1;                //声明num 在整个函数体func内都有定义
                console.log(num); //输出:1 
    } func();

    作用域链

    从当前作用域出发,决定了哪些数据能被访问,不讲大道理,直接画图吧。

    比如下图:想在函数3里访问变量a,过程是:先在函数3里找,没有就在函数2里找,再没有就在函数1里找,直到找到全局作用域,看是否有这么个变量a,是不是像顺着一根链子找呢,这个就是作用域链。(上一层的作用域也叫做父级作用域)

    ECMAScript 6 引入了let和const关键字。这些关键字可以代替var。

    let likes = 'Coding';
    const skills = 'Javascript and PHP';

    和var关键字不同,let和const关键字支持在块级声明中创建使用局部作用域

    if (true) {
    var name = 'Hammad';
    let likes = 'Coding';、
    const skills = 'JavaScript and PHP';
    }
    console.log(name); // logs 'Hammad'
    console.log(likes); // Uncaught ReferenceError: likes is not defined
    console.log(skills); // Uncaught ReferenceError: skills is not defined

    上下文

    上下文指的是在相同的作用域中的this的值。可以使用函数方法改变上下文,上下文是执行的时候确定的

    在全局作用域中,上下文总是 Window 对象。

    作为一个对象的方法,上下文就是这个方法所在的那个对象。

    使用new关键字调用函数时上下文,上下文会设置为被调用的函数的实例

    当在严格模式(strict mode)中调用函数时,上下文默认是 undefined。

    使用 .call(), .apply() 和 .bind() 改变上下文

    Call 和 Apply 函数来改变函数调用时的上下文。
    context={a:1,y:2}
    function hello(a,b) {
    alert(this.a);
    alert(a);
    alert(b);
    }
    hello();
    hello.call(context,"cc","dd"); //1,cc,dd
    hello.apply(context,["cc","dd"]); //

    Bind 并不是自己调用函数,它只是在函数调用之前绑定上下文和其他参数。

    (function introduce(name, interest) {
    console.log('Hi! I'm '+ name +' and I like '+ interest +'.');
    console.log('The value of this is '+ this +'.')
    }).bind(window, 'Hammad', 'Cosmology')();

    函数的运行过程

    第一阶段是创建阶段,是函数刚被调用但代码并未执行的时候。创建阶段主要发生了 3 件事。

    创建变量对象

    创建作用域链

    设置上下文(this)的值

    第二个阶段就是代码执行阶段,进行其他赋值操作并且代码最终被执行。

    词法作用域

    词法作用域(静态作用域)的意思是在函数嵌套中,内层函数可以访问父级作用域的变量等资源,在定义时就确定。

    闭包

    当内部函数试着访问外部函数的作用域链(词法作用域之外的变量)时产生闭包。闭包包括它们自己的作用域链、父级作用域链和全局作用域。

    闭包不仅能访问外部函数的变量,也能访问外部函数的参数。

    即使函数已经return,闭包仍然能访问外部函数的变量。这意味着return的函数允许持续访问外部函数的所有资源

    当你的外部函数return一个内部函数,调用外部函数时return的函数并不会被调用。你必须先用一个单独的变量保存外部函数的调用,然后将这个变量当做函数来调用。

    用闭包实现共有作用域和私有作用域

    (function () {
    // private scope
    })();

    函数结尾的括号告诉解析器立即执行此函数。我们可以在其中加入变量和函数,外部无法访问。但如果我们想在外部访问它们,也就是说我们希望它们一部分是公开的,一部分是私有的。我们可以使用闭包的一种形式,称为模块模式(Module Pattern),它允许我们用一个对象中的公有作用域和私有作用域来划分函数。

    模块模式

    var Module = (function() {
    function privateMethod() {
    // do something
    }
    return {
    publicMethod: function() {
    // can call privateMethod();
    }
    };
    })();
    Module.publicMethod(); // works
    Module.privateMethod(); // Uncaught ReferenceError: privateMethod is not defined

    Module 的return语句包含了我们的公共函数。私有函数并没有被return。函数没有被return确保了它们在 Module 命名空间无法访问。但我们的共有函数可以访问我们的私有函数,方便它们使用有用的函数、AJAX 调用或其他东西。

    一种习惯是以下划线作为开始命名私有函数,并返回包含共有函数的匿名对象。这使它们在很长的对象中很容易被管理。向下面这样:

    var Module = (function () {
    function _privateMethod() {
    // do something
    }
    function publicMethod() {
    // do something
    }
    return {
    publicMethod: publicMethod,
    }
    })();

    立即执行函数表达式(IIFE)

    另一种形式的闭包是立即执行函数表达式(Immediately-Invoked Function Expression,IIFE)。这是一种在 window 上下文中自调用的匿名函数,也就是说this的值是window。它暴露了一个单一全局接口用来交互。如下所示:

    (function(window) {
    // do anything
    })(this);

     两段好玩的闭包代码

    1、返回一个函数

    function createComparisonFunction(propertyName) {
       return function(object1, object2){
      var value1 = object1[propertyName];
      var value2 = object2[propertyName];
      if (value1 < value2){
        return -1;
      } else if (value1 > value2){
        return 1;
      } else {
        return 0;
      }
      };
    }
    var compare = createComparisonFunction("name");
    //createComparisonFunction函数返回后,闭包的作用域链开始初始化,包含了Comparison作用链上的对象;Comparison返回后作用域链被销毁,但是上面的活动对象内存得不到释放,直到匿名函数被销毁
    var result = compare({ name: "Nicholas" }, { name: "Greg" }); compare=' ';//解除对匿名函数的引用,释放内存。此时result已经达到目的指向了需要引用的函数

    2,循环引用造成的内存泄漏

    var el = document.getElementById('MyElement');
            var func = function () {
                //
            }
            el.func = func;
            func.element = el;
    
    //通常循环引用发生在为dom元素添加闭包作为expendo的时候。
    function init() {
                var el = document.getElementById('MyElement');
                el.onclick = function () {
                    //……
                }
            }
            init();
    
    解除引用:
     function init() {
                var el = document.getElementById('MyElement');
                el.onclick = function () {
                    //……
                }
                el = null;
            }
            init();
  • 相关阅读:
    装饰器
    目录和文件操作
    网络通信过程
    epoll并发
    laravel(包含lumen)框架中的跨域函数实例
    windows使用nginx
    nginx反向代理配置 其实很简单
    前端html页面使用marked点亮你的代码
    PHPWAMP开启SSL,PHPWAMP配置ssl证书
    php接收并存储base64位字符串图片
  • 原文地址:https://www.cnblogs.com/yaoyao-sun/p/10365422.html
Copyright © 2011-2022 走看看