谈谈 JavaScript 的做用域

学了很久的 Javascript 惭愧仍然没有总结彻底做用域,今天老夫就来总结一番:javascript

涉及内容:java

  1. 全局做用域
  2. 函数做用域
  3. 块级做用域
  4. 词法做用域
  5. 动态做用域 动态做用域跟 this 引用机制相关

全局做用域:

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

全局变量:面试

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

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

在 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 建立块级做用域

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

词法做用域:

词法做用域是指一个变量的可见性,及其文本表述的模拟值(《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引擎总会从最近的一个域,向外层域查找;

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

var testValue = 'outer';

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

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

bar();
复制代码

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

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

动态做用域:

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

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

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

做用域嵌套,有词法做用域同样的特性,查找变量时,老是寻找最近的做用域;

一样是,词法做用域,例子2,同一份代码,若是 是动态做用域:

var testValue = 'outer';

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

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

bar();
复制代码

固然,JavaScript 除了this以外,其余,都是根据词法做用域查找!!!

为何要理解动态做用域呢?由于,这能让你更好地学习 this 引用!!!

参考和鸣谢:

  • 《你不知道的JavaScript》
  • 《JavaScript函数式编程》

首先,感谢这两本书的做者,我也算是结合本身的实例和理解,好好地总结了一下做用域;

相关文章
相关标签/搜索