遇到了个小问题,问大家都说是作用域的问题.我觉得不对啊.于是做个笔记记录下来.
本文涉及到 JavaScript执行机制 / 作用域 / 作用域链 / Js预解析 / this指向 / 改变this指向 / var重复声明.菜比较多.
首先先牵扯到的是JavaScript是单线程语言.代码的执行顺序是自上而下的执行代码.
我的理解就像是高速公路收费站一样,一次只能过一辆车.但是如果真的完全是这样的话,就会出问题.好比说:如果调用函数在函数声明前执行,那么就会报错.
以上执行机制内容来自JavaScript闭包 - Web前端工程师面试题讲解
下图来自掘金:

两个内容的差异是名词叫法的不同.
为了简短文字.下文描述JavaScript预解析阶段,只描述创建步骤只描述创建变量对象这一步.
    // console.log(a); 这个时候b是等于undefined的.并没有进行赋值.
        var a = 66;
        function test1(a) {
            console.log(a); // function a(){};
            function a() { };
            var a = 5;
        }
        test1(a);
这段代码的console.log(a)的结果是 function a(){};
        var b = 66;
        function test2(b) {
            console.log(b); // 输出 66
            var b = 5;
        }
        test2(b);
这段代码的console.log(b)的结果是 66.
而 :
  var b = 66;
function test2(b) {
    var b = 5;
    console.log(b); // 输出 5
}
test2(b);
输出的结果就会是5.因为 var b = 5 把 局部变量给覆盖掉了.进行了一次重新赋值
重复声明时:首先编译器对代码进行分析拆解,从左至右遇见var a,则编译器会询问作用域是否已经存在叫a的变量了。如果不存在,则招呼作用域声明一个新的变量a;若已经存在,则忽略 var 继续向下编译,这时 a = 2被编译成可执行的代码供引擎使用。
参考博文 : 为什么var可以重复声明
        function test3(b) {
            console.log(b); // 输出 undefined
            var b = 5;
        }
        test3();
这段代码的console.log(a)的结果是 undefined
        var a = 100;
        function fns() {
            console.log(a); //undefined
            var a = 200;
            console.log(a); //200
        }
        fns();
        console.log(a); //100
        var a;
        console.log(a); //100
        var a = 300;
        console.log(a); //300
输出结果见上面的注释.
        var a = 12;
        var a; // 因为值没有覆盖
        console.log(a); // 12
赋值时:引擎遇见a=2时同样会询问在当前的作用域下是否有变量a。若存在,则将a赋值为2(由于第一步编译器忽略了重复声明的var,且作用域中已经有a,所以重复声明会发生值的覆盖而不会报错);若不存在,则顺着作用域链向上查找,若最终找到了变量a则将其赋值2,若没有找到,则招呼作用域声明一个变量a并赋值为2(这就是为什么第二段代码可以正确执行且a变量为全局变量的原因,当然,在严格模式下JS会直接抛出异常:a is not defined)。
重复声明时:首先编译器对代码进行分析拆解,从左至右遇见var a,则编译器会询问作用域是否已经存在叫a的变量了。如果不存在,则招呼作用域声明一个新的变量a;若已经存在,则忽略 var 继续向下编译,这时 a = 2被编译成可执行的代码供引擎使用。
来自 https://blog.csdn.net/DurianPudding/article/details/87953939
    
        var num1 = 55;
        var num2 = 66;
        function fn_2(num, num2) {
            console.log(num);// 66
            num = 100; // 预解析阶段,并不会执行此行代码.但是在代码执行到这一行的时候.会寻找此局部作用域中是否存在num.num在此作用域中存在(函数的参数).所以将100赋值给局部作用域中的num变量.
            num1 = 100; // 预解析阶段,并不会执行此行代码.但是在代码执行到这一行的时候.会寻找此局部作用域中是否存在num1.num1在此作用域中不存在.顺着作用域链在全局作用域中找到了num1.所以将100赋值给全局作用域中的num变量.
            num2 = 100;// 预解析阶段,并不会执行此行代码.但是在代码执行到这一行的时候.会寻找此局部作用域中是否存在num2.num2在此作用域中存在.所以将100赋值给局部作用域中的num2变量.
            numall = 888; // 预解析阶段,并不会执行此行代码.但是在代码执行到这一行的时候.会寻找此局部作用域中是否存在numall.numall在此作用域中不存在.顺着作用域链在全局作用域中寻找是否存在numall.全局作用域中也不存在numall.所以会在全局作用域中创建一个属性.也就是window.numall.
            console.log(num);// 100
            console.log(num1); //100
            console.log(num2); // 100
        }
        fn_2(num1, num2);
        console.log(num1); // 100
        console.log(num2); // 66
        //console.log(num); // 报错 这行代码会报错.因为全局作用域中不存在num
        console.log(numall); // 100
        console.log(window)
      
首先执行全局作用域的预解析. var num1 ; var num2 ; function fn_2; 此刻函数表达式是可以立即执行的,但是变量是等于undefined.
读取到fn_2(num1, num2); 函数fn_2()需要两个局部函数 num和num2. 在调用fn_2的时候(fn_2(num1,num2)). 传递进去(赋值)的参数实际上是全局变量的num1与num2.
        而 num = 1;
        事实上是对属性赋值操作。首先,它会尝试在当前作用域链(如在方法中声明,则当前作用域链代表全局作用域和方法局部作用域etc。。。)中解析 num; 如果在任何当前作用域链中找到num,则会执行对num属性赋值; 如果没有找到num,它才会在全局对象(即当前作用域链的最顶层对象,如window对象)中创造num属性并赋值。
        []()
        fn3();
        console.log(c); // 9
        console.log(b); // 9
        console.log(a); // 报错
        function fn3() {
            var a = b = c = 9;
            /*
            以上代码相当于
                c = 9;
                b = c;
                var a = b;
            */
            console.log(a);//9
            console.log(b);//9
            console.log(c);//9
        }
      
js赋值语句执行顺序 // js赋值语句执行:自右向左(仅指简单的赋值.如上).
       var n = 0;
        function a() {
            var n = 10;
            function b() {
                n++;
                console.log(n);
                return n;
            }
            b();
            return b;
        }
        var c = a(); // 11
        var now = c(); //12
        console.log(n); // n == 0 上图是一个闭包
        console.log(now) // 12
这是一个闭包. var c 在引用 function b(){...}.
概念性的东西写起来太长.推荐看书去理解.
  var x = 10;
        function fn() {
            console.log(x); // 答案是10
           
            console.log(this); //window
        }
        function show(f) {
            var obj = { x: 20 };
            f();
        }
        show(fn);
全局作用域之外,每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时
作用域与作用域链
        var x1 = 10;
       function fn1() {
           console.log(this.x1);
       }
       function show1(f) {
           let obj = { x1: 66 };
           var x1 = 20;
           f();//10
           f.call(obj); // 使用call改变了this指向 =>66
       }
       show1(fn1);
        var fn9 = function () {
            console.log(fn9);
        }
        var objs = {
            fnn: function () {
                console.log(fn9);
                console.log(this.fn9);
                console.log(this); // this指向obj.
            }
        }
        objs.fnn();
        console.log(test_haha); // 函数声明的优先级是高于var的
        function test_haha() { };
        var test_haha = 66;
经过测试,在预解析阶段,函数声明的优先级是高于var变量的. console.log(test_haha);输出的是函数体,尽管var在最下面.
var objs = {
            fn9: function () {
                console.log(fn9); // 输出      var fn9 = function () {console.log(fn9);}
                console.log(this.fn9); // 输出 objs.fn9
                console.log(this); // this指向obj.
            },
        }
        objs.fn9();
谁调用this,this指向谁.
js里只有全局作用域与函数作用域.在ES6中出现了块级作用域.还没有学到.
深入理解JavaScript作用域和作用域链
        var a;
        if (true) {
            a = 5;
            function a() {
                console.log(‘我没有被楼下的骚a给霸占了‘);
            };
            a = 0
            console.log(a);
        }
        console.log(a)
这段代码在chrome与ie下的输出结果是不一致的.
不要在判断语句里定义函数.
原文:https://www.cnblogs.com/gtscool/p/12636616.html