• 作用域详解


    1.作用域是什么?

    作用域,也叫做静态作用域。是变量存在的范围,或者说查找变量的范围。

    作用域之所以是静态作用域,是因为一旦声明完成,作用域就不再变化(eval除外)。

    js运行时,查找变量是通过作用域链查找,从声明时所在的作用域开始查找。

    2.如何作用?

    在引擎运行时,通过编译器的结果协助引擎查询变量。

    代码编译通常有 词法分析,语法分析(AST), 生成可执行代码

    引擎查询时分为两种,

    一种是LHS查询。主要是查询在赋值操作符左侧的变量,等待赋值,其中函数传参是隐形的LHS查询;

    另一种是RHS查询。主要是查询并获取变量的值。

    区别:

    RHS查询失败,直接抛出ReferenceError;

    LHS非严格模式下,自动创建全局变量;严格模式下,抛出ReferenceError异常。

    function foo(a) {
        var b = a;
        return a + b;
    }
    var a = foo(2);
    
    // 含有3个LHS查询,4个RHS查询
    // 3个LHS: a = , a参数(隐式), b=..
    // 4个RHS:foo, =a, a +, +b

    3. 作用域嵌套

    查找遍历嵌套作用域的规则是引擎从声明时所在作用域开始查找,如果找不到,向上一级作用域请求,直到全局作用域。

    如果找不到,引擎抛出异常ReferenceError。

    下图是个嵌套作用域。

    1是全局作用域,包含标识符foo;

    2是foo创建的作用域,包含标识符a, b,bar;

    3是bar创建的作用域, 包含标志符c;

    4.词法作用域

    词法作用域也就是词法阶段的作用域。由写代码时的位置决定。一般不变。

    无论函数在哪里被调用,如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。

    词法作用域只能查找一级标识符,如a,b,c

    对于访问foo.bar,baz。词法作用域只能查找foo,剩下的通过对象属性访问规则访问。

    欺骗词法作用域

    对于大多数情况来说,代码写完,词法作用域就不再变化。但也有例外,可以在运行时修改(欺骗)词法作用域。

    ⚠️最好不使用。会导致性能下降。并且在严格模式下失效。

    实现机制有两种:

    1)eval()

    eval(str)函数接收一个字符串作为参数,并将其中的内容看作书写时就存在的代码。

    作用域是当前作用域;但是非直接调用eval时,作用域就是widow;

    但是不能是'return;'这种和其他代码配合的命令。

    function foo(str, a) {
        eval(str); //  欺骗--相当于var b = 3;
        console.log(a, b);
    }
    var b = 2;
    foo("var b = 3;", 1); // 1, 3
    
    function foo(str, a) {
        "use strict"
        eval(str); // 严格模式下,eval有自己的作用域
        console.log(a, b);
    }
    var b = 2;
    foo("var b = 3;", 1); // 1, 2

    2) with(已被禁止不做考虑)

    性能下降原因:

    js引擎在编译阶段会对作用域查找进行优化。预先确定变量和函数的定义位置,快速找到标志符。

    但是如果使用了eval(), 引擎会默认所有标识符的位置都不确定,则所作的优化无效。

    尽量不要使用eval()

    5. 函数作用域

    函数作用域是函数定义时所在的作用域,而不是函数调用时所在的作用域。

    RHS查询从当前作用域(定义时所在的作用域)开始查找。

    // 函数在外部声明的,作用域绑定在外部
    var x = function () {
      console.log(a);
    };
    function y(f) {
      var a = 2;
      f(); 
    }
    y(x); // ReferenceError: a is not defined
    // 函数在内容声明的,作用域绑定在内部
    var a = 2;
    function foo() {
      var a = 1;
      function bar(){
        console.log(a);
      }
      return bar;
    }
    foo()();

    函数作用域中属于这个函数的所有标志符(变量、函数)都能在函数范围内使用和复用。但是函数外部不能使用。

    function a() {
      function b(){}
    }
    b(); // ReferenceError: b is not defined

    隐藏内部实现

    根据函数作用域的规定,可以将一些代码包裹在一个声明的函数中,使外部无法访问,达到“隐藏”代码的目的。

    必要性:

    依据软件设计的最小授权(最小暴露)原则,将一些代码隐藏在函数作用域内部,不影响功能和实现,

    但是可以将代码私有化,不过多的暴露变量或者函数。

    而且可以避免命名重复的冲突。

    ⚠️为了避免全局变量污染和冲突,自定义的插件后者第三方库,最好定义自己的命名空间,将所有的变量和方法挂载到一个对象上。

    如jquery。(单例模式)

    缺点:

    1)需要声明一个函数,可能导致污染全局作用域

    2)需要调用声明的函数

    解决方案:

    //改善前:
    var a= 2;
    function foo() { // 函数声明,foo处于全局作用域
        var a= 3;
        console.log(a); // 3 
    }
    console.log(a); // 2
    //改善后
    var a= 2;
    (function foo(){ // 不是以function开始,是函数表达式,作用域在foo内部
       var a = 3;
       console.log(a);//3
    })();
    console.log(a); // 2        
    foo() // ReferenceError: foo is not defined

    具名和匿名

    匿名函数在栈追踪中没有有意义的函数名,会使得调试困难;不能引用自身;没有可读性;

    所以建议在所有使用匿名函数的地方都改为具名函数

    setTimeout(function time(){
        // 有名字的函数表达式
    },100)

    立即执行函数表达式IIFE(;是必要的)

    (function IIFE(){
    //...
    })(params); // ;必要!第一个括号表示函数表达式,作用域被绑定在函数表达式自身的函数中,意味着IIFE只能在...的位置中被访问;第二个括号立即执行;
    等同于
    (function IIFE(){
    }(params));

    立即执行函数表达式可以传递参数,参数类型可以是变量,如window;也可以是函数。

    // 传参是变量
    var a = 2;
    (function IIFE(global){
        var a = 3;
        console.log(a); //3
        console.log(global.a); //2
    })(window); // 注意node环境中没有window;在console中可以测试

    // 传参是函数 var a = 2; (function IIFE(def){ def(window); })(function def(global) { var a = 3; console.log(a); //3 console.log(global.a); //2 })

    6. 块作用域

    try/cacth

    try/catch的catch分句中具有块作用域。

    let

    1)将变量绑定到块作用域

    for(let i= 0; i< 10; i++){  
    }
    // 相当于将变量绑定到一个块作用域中
    {
        let j;
        for(j =0; i<10;j++){
           let i=j;
        }
    }    
    console.log(i); // Uncaught ReferenceError: i is not defined
    
    for(var i = 0; i< 10;i++){
    }
    console.log(i); // 10

    2)不能变量提升

    {
        console.log(a); // ReferenceError
        let a = 2;
    }

    const

    const也是块作用域变量,但是不可更改

    7. 变量(声明)提升

    原理: 变量和函数的声明提升是在代码被执行前(编译阶段)处理。提升阶段所有的代码处于待执行阶段!!不执行!!

    var a = 2;
    // js引擎将其分为两块:
    var a;
    //var a;定义声明在编译阶段进行
    a = 2;
    // 赋值声明等待执行阶段被执行

    示例

    1)变量声明提升

    console.log(a);
    var a = 2;
    // 相当于
    var a;
    console.log(a);
    a = 2;

    2)函数声明提升

    foo();
    function foo() {
        console.log(a); // undefined
        var a= 2;
    }
    // 相当于
    function foo() { // 函数声明提升;整个声明的函数都上移
       var a;   
       console.log(a); 
       a = 2;
    }
    foo();
    ⚠️ //var foo = function(){} 不是函数声明,是函数表达式。函数表达式的函数不会被提升。只有foo作为变量被提升!!!
    foo(); // TypeError: foo is not a function
    var foo = function(){}

    ⚠️函数重复声明的时候,后面的会覆盖前面的。而且因为函数声明提升,不管在任何位置访问,都以最后一个为主。

    a(); // 2
    function a() {
      console.log(1)
    }
    a(); // 2
    function a() {
      console.log(2)
    }
    a(); // 2
     

    3)当同时存在函数声明和变量声明提升时,函数声明提升优先于变量声明提升;

    ⚠️!!!提升时不是将变量或者函数提升到整个程序的最上方!!!是当前作用域的最上方

    1)变量提升
    function foo() {
       var a;    // ⚠️变量提升到foo创建的作用域的顶部,不是全局作用域!!!
       console.log(a); 
       a = 2;
    }
    /*****在条件判断语句中变量声明提升和函数声明提升不同
    变量声明在条件判断语句中会出现变量提升,而函数声明不会*******
    */ console.log(b, c);// undefined undefine var a = true; if (a) { var b = 1; } else { var c = 2; }
    2)函数声明提升(条件判断)
    foo(); // ❌UnCaught TypeError: foo is not a function
    // js代码的规则是,如果有未捕捉异常,程序崩溃,且不再往下执行
    var a = true;
    if (a) {
        function foo() { 
            console.log('a');
        } // 函数声明在代码块内部,在编译阶段,声明提升只在块作用域中提升
    } else {
        function foo() {
            console.log('b');
        }
    }
    /****************不抛出异常******************/
    console.log(foo); 
    var a = true;
    if (a) { // 判断是在执行阶段进行判断的
        function foo() {
            console.log('a');
        }
    } else {
        function foo() {
            console.log('b');
        }
    }
    /**运行结果如下***/
    // undefined
    // function foo() {       不抛出异常,代码正常执行,判断执行后,在全局
    //    console.log('a');    声明函数
    // }   
  • 相关阅读:
    读《万历十五年》和《一个广告人的自白》
    Windows Phone开发(26):启动器与选择器之MediaPlayerLauncher和SearchTask
    Windows Phone开发(24):启动器与选择器之发送短信
    Windows Phone开发(25):启动器与选择器之WebBrowserTask
    linux服务器,svn认证失败,配置问题,防火墙等等
    nginx 防火墙、权限问题
    本地phpstudy开发中apache可以用,nginx不可用,
    nginx https配置后无法访问,可能防火墙在捣鬼
    为了解决linux配置Nginx 只能关闭防火墙才能访问的问题
    搜集点shell资料 规格严格
  • 原文地址:https://www.cnblogs.com/lyraLee/p/11428325.html
Copyright © 2020-2023  润新知