# 全局作用域

作用域,是指变量的生命周期(一个变量在哪些范围内保持一定值)。

全局变量:

生命周期将存在于整个程序之内。

能被程序中任何函数或者方法访问。

在 JavaScript 内默认是可以被修改的。

全局变量,虽然好用,但是是非常可怕的,这是所有程序员公认的事实。

# 显式声明:

带有关键字 var 的声明;

<script type="text/javascript">

    var testValue = 123;

    var testFunc = function () { console.log('just test') };

    /**---------全局变量会挂载到 window 对象上------------**/

    console.log(window.testFunc)		// ƒ () { console.log('just test') }

    console.log(window.testValue)		// 123
    
</script>
复制代码

其实,我们写的函数如果不经过封装,也会是全局变量,他的生命周期也就是全局作用域;

# 隐式声明:

不带有声明关键字的变量,JS 会默认帮你声明一个全局变量!!!

<script type="text/javascript">

    function foo(value) {

      result = value + 1;	 // 没有用 var 修饰

      return result;
    };

    foo(123);				// 124
    
    console.log(window.result);			// 124 <=  挂在了 window全局对象上 
    
</script>
复制代码

现在,变量 result 被挂载到 window 对象上了!!!

# 函数作用域

函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域!!!

function bar() {
  var testValue = 'inner';
}

console.log(testValue);		// 报错:ReferenceError: testValue is not defined
# 通过 return 访问函数内部变量:
function bar(value) {
  var testValue = 'inner';
  
  return testValue + value;
}

console.log(bar('fun'));		// "innerfun"
复制代码

函数就像一个工厂,我们输入一些东西,它在内部加工,然后给我们一个加工产物;

# 通过 闭包 访问函数内部变量:
function bar(value) {
  var testValue = 'inner';
  
  var rusult = testValue + value;
  
  function innser() {
     return rusult;
  };
  
  return innser();
}

console.log(bar('fun'));		// "innerfun"

关于闭包,我不会在这篇文章过多描述,因为,想要描述闭包,本身需要跟本文章一样的长度;

# 立即执行函数:

这是个很实用的函数,很多库都用它分离全局作用域,形成一个单独的函数作用域;

<script type="text/javascript">

    (function() {

      var testValue = 123;

      var testFunc = function () { console.log('just test'); };

    })();

    console.log(window.testValue);		// undefined
    
    console.log(window.testFunc);		// undefined
    
</script>

它能够自动执行 (function() { //... })() 里面包裹的内容,能够很好地消除全局变量的影响;

# 块级作用域

在 ES6 之前,是没有块级作用域的概念的。如果你有 C++ 或者 Java 经验,想必你对块级作用域并不陌生;

for(var i = 0; i < 5; i++) {
  // ...
}

console.log(i)				// 5

很明显,用 var 关键字声明的变量,在 for 循环之后仍然被保存这个作用域里;

这可以说明: for() { }仍然在,全局作用域里,并没有产生像函数作用域一样的封闭效果;

如果想要实现 块级作用域 那么我们需要用 let 关键字声明!!!

for(let i = 0; i < 5; i++) {
  // ...
}

console.log(i)				// 报错:ReferenceError: i is not defined

在 for 循环执行完毕之后 i 变量就被释放了,它已经消失了!!!

同样能形成块级作用域的还有 const 关键字:

if (true) {
  const a = 'inner';
}

console.log(a);				// 报错:ReferenceError: a is not defined

letconst 关键字,创建块级作用域的条件是必须有一个 { } 包裹:

{
  let a = 'inner';
}
  
if (true) {
   let b = 'inner'; 
}

var i = 0;

// ......

不要小看块级作用域,它能帮你做很多事情,举个栗子:

举一个面试中常见的例子:

for(var i = 0; i < 5; i++) {
  setTimeout(function() {
     console.log(i);			// 5 5 5 5 5
  }, 200);
};

这几乎是作用域的必考题目,你会觉得这种结果很奇怪,但是事实就是这么发生了;

这里的 i 是在全局作用域里面的,只存在 1 个值,等到回调函数执行时,用词法作用域捕获的 i 就只能是 5;

因为这个循环计算的 i 值在回调函数结束之前就已经执行到 5 了;我们应该如何让它恢复正常呢???

解法1:调用函数,创建函数作用域:

for(var i = 0; i < 5; i++) {
  abc(i);
};

function abc(i) {
  setTimeout(function() {
     console.log(i);			// 0 1 2 3 4 
  }, 200); 
}

这里相当于创建了5个函数作用域来保存,我们的 i 值;

解法2:采用立即执行函数,创建函数作用域;

for(var i = 0; i < 5; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j);
    }, 200);
  })(i);
};

原理同上,只不过换成了自动执行这个函数罢了,这里保存了 5 次 i 的值;

**解法3:let 创建块级作用域,可以将块级作用域理解为: 使用let和const声明的变量, 只在当前大括号内生效 **

for(let i = 0; i < 5; i++) {
    setTimeout(function() {
      console.log(i);
    }, 200);
};

这里的大括号内主要指的是一下几种情况

// 条件语句
if () {}

// switch语句
switch () {}

// for / while循环语句
for () {}
while () {}

// try...catch语句
try () catch (err) {}

// 单大括号
{}

for循环问题的解决

<ul>
    <li>第一个</li>
    <li>第二个</li>
    <li>第三个</li>
    <li>第四个</li>
</ul>

<script>
    let oli = document.getElementsByTagName("li")
    for(var i=0;i<4;i++){
        oli[i].onclick = function(){
            console.log(i)
        }
    }
    // //相当于
    var i
    i=0
    oli[0].onclick = function(){
            console.log(i)
    }
    i=1
    oli[1].onclick = function(){
            console.log(i)
    }
    i=2
    oli[2].onclick = function(){
            console.log(i)
    }
    i=3
    oli[3].onclick = function(){
            console.log(i)
    }
 //按照词法作用域查找,会每次都找到全局的i


    //方法一:改为let
    for(let i=0;i<4;i++){
        oli[i].onclick = function(){
            console.log(i)
        }
    }
    // 《深入理解ES6》上面说循环中的let声明在循环内部是标准中专门定义的
    // 而是每次let声明都会创建一个新的变量i,并将其初始化为i的当前值
    for(let i=0;i<4;i++){
        let i = //i,暂存的思想
        oli[i].onclick = function(){
            console.log(i)
        }
    }
    //这个时候在执行点击事件时,i会在for循环的第一行找到i的值

    //方法二:闭包
    for(var i=0;i<4;i++){
        (function(i){
            oli[i].onclick = function(){
                console.log(i)
            }
        })(i)
    }
    
    //方法三:暂存数据,和let原理类似
    for(var i=0;i<4;i++){
        oli[i].index = i
        oli[i].onclick = function(){
            console.log(this.index)
        }
    }

    //方法四:事件委托
    var oul = document.getElementsByTagName("ul")[0];
    oul.onclick = function(e){
        var e = e || window.event
        var target = e.target || e.srcElement
        if(target.nodeName.toLowerCase() == 'li'){
            var li=this.querySelectorAll("li");
            var index = Array.prototype.indexOf.call(li,target);
            console.log(index);
        }
    }
</script>

# 词法作用域

词法作用域是指一个变量的可见性,及其文本表述的模拟值(《JavaScript函数式编程》);

听起来,十分地晦涩,不过将代码拿来分析就非常浅显易懂了;

testValue = 'outer';

function afun() {
  var testValue = 'middle';
  
  console.log(testValue);		// "middle"
  
  function innerFun() {
    var testValue = 'inner';
    
    console.log(testValue);		// "inner"
  }
  
  return innerFun();
}

afun();

console.log(testValue);			// "outer"

当我们要使用声明的变量时:JS引擎总会从最近的一个域,向外层域查找;

img

再举一个一个实际的例子:

var testValue = 'outer';

function foo() {
  console.log(testValue);		// "outer"
}

function bar() {
  var testValue = 'inner';
  
  foo();
}

bar();
// 个人理解词法作用于是基于代码定义的

显然,当 JS 引擎查找这个变量时,发现全局的 testValue 离得更近一些,这恰好和 动态作用域 相反;

img

如上图所示,下面将讲述与 词法作用域相反的动态作用域;

# 动态作用域

在编程中,最容易被低估和滥用的概念就是动态作用域(《JavaScript函数式编程》)。

在 JavaScript 中的仅存的应用动态作用域的地方:this 引用,所以这是一个大坑!!!!!

动态作用域,作用域是基于调用栈的,而不是代码中的作用域嵌套;

作用域嵌套,有词法作用域一样的特性,查找变量时,总是寻找最近的作用域;

同样是,词法作用域,例子2,同一份代码,如果 是动态作用域:

var testValue = 'outer';

function foo() {
  console.log(testValue);		// "inner"
}

function bar() {
  var testValue = 'inner';
  
  foo();
}

bar();

当然,JavaScript 除了this之外,其他,都是根据词法作用域查找!!!

img

为什么要理解动态作用域呢?因为,这能让你更好地学习 this 引用!!!

要清楚,JavaScript 实际上没有动态作用域。它拥有词法作用域。就这么简单。但是 this 机制有些像动态作用域。

关键的差异:词法作用域是编写时的,而动态作用域(和 this)是运行时的。词法作用域关心的是 函数在何处被声明,但是动态作用域关心的是函数 从何处 被调用。

作者:leiting1998 链接:https://juejin.im/post/5abb99e9f265da2392366824 来源:掘金

最后更新时间: 9/3/2021, 7:07:07 PM