- 作用域定义和作用
作用域定义:JavaScript中存储变量和查找变量的一套规则。
JavaScript中一般遵循词法作用域。
变量查找遵循从内到外查找:
引擎从当前的执行作用域开始查找变量,如果找不到就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是找不到,查找过程都会停止。
- 词法作用域
定义在词法阶段的作用域。是在写代码的时将变量和块作用域写在哪里来来决定的。
- 作用域的产生
JavaScript中没有块状作用域的概念。那么什么是块状作用域呢 简单说就是{..}括起来的区域就是一个最小单元的块。
我们可以简单 回顾一下c#或者Java语言中变量的作用域和申明周期 一般情况下是这样的
C#或者java中变量的作用域:
作用域从变量定义的位置开始,到该变量所在的那对大括号结束;
生命周期:
变量从定义的位置开始就在内存中活了;
变量到达它所在的作用域的时候就在内存中消失了;
举一个最简单的例子 for循环
C#中 for中定义的i 变量在 循环外访问不到
JavaScript 中在for循环外 可以正常访问
那么JavaScript 在什么时候创建作用域呢
- 全局作用域
这个无需做过多的解释,相信大家都理解
2. 函数
javaScript中每创建一个函数都会为其自身创建一个作用域气泡。属于这个函数的所有变量可以在整个函数内部使用及复用
JavaScript 的作用域是基于函数的吧,变量的作用域为:
使用关键字var声明一个变量,那么这个变量就属于当前函数的作用域,如果声明是发生在任何函数外的顶层声明,那么这个变量则属于全局作用域。
没有使用var 关键字声明的变量会默认添加到全局作用域中,要尽量避免这样写。
- 块状作用域
上面说了js中没有块状作用域,但是凡事都有例外,js中有几种情况可以提供块状作用域的效果
1: Try/catch
Catch 分句会创建一个块状作用域,其中声明的变量仅在catch内部有效
<script type="text/javascript">
try{
undefine();
}catch(err)
{
console.log(err); //可以访问
var a=10;
console.log(a);
}
console.log("a"+a); //可以访问
console.log(err); //不可访问
</script>
以上还是要补充说明一下: 这个块状作用域是指 catch()后的这个括号的什么的变量。 在catch(){}花括号里面申明的变量不属于块状作用域。
2: Let
Let 可以将变量的作用域绑定在所在的 {…}块状作用域中
Var foo=true;
If(foo){
Let bar=foo*2;
Console.log(bar); //正常访问
}
Console.log(bar); // ReferenceError
l 使用 let 进行的声明不会再块状作用域中进行提升。
{
Console.log(bar);// ReferenceError
Let bar=2;
}
3:Const
Const 也会创建块状作用域,但是它的值是固定的,之后任何试图修改的操作都会引起错误。
4: 立即执行函数
<script type="text/javascript"> { (function(){ var j=5; //立即执行函数定义的变量属于作用快 console.log(j); })(); console.log(j); //这里访问不到 } </script>
- 作用域提升
定义: 无论var出现在一个作用域中的哪个位置,这个声明都属于整个作用域,在其中到处都可以访问的,这一行为被比喻为提升。
特点:函数声明和变量声明会提升。而赋值或其他运行逻辑会留在原地。当函数声明和变量声明同时出现时,函数会首先被提升,然后才是变量。
函数表达式不会被提升
作用域提升会被提升到所在块状作用域的的顶部,即所在{…}的顶部
<script type="text/javascript">
foo(); //访问不到
// var a=true;
{
foo(); //可以访问得到
function foo(){
console.log("a");
}
}
</script>
- 作用域闭包
前提背景:函数一般在执行后,整个函数内部作用域就会被销毁。
定义: 当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域以外执行,闭包可以使函数继续访问定义时的词法作用域。
产生的场景: 函数作为返回值赋值给一个变量。(回调函数)
示例一:
<script type="text/javascript"> function foo() { var a = 2; function bar() { console.log(a); } return bar; } var baz = foo(); baz(); // foo()作用域并美誉在执行后被销毁,a还可以被正常访问,这就是闭包的效果 </script>
示例二:
<script type="text/javascript"> function foo() { var a=2; function baz() { console.log(a); } bar(baz); } function bar(fn){ fn(); //这就是闭包的效果 } foo(); </script>
无论通过何种手段将内部函数传递到所在词法作用域以外,它都会持有对原始定义作用域的引用,无论在何处执行整个函数都会使用闭包。
常见闭包场景
场景一:这个示例比较简单
<script type="text/javascript"> function wait(message){ setTimeout(function timer(){ console.log(message); },1000); } wait("Hello,closure!"); // timer();函数被添加到任务队列中。 函数保留了词法作用域对wait();函数作用域的引用,所有可以访问到message变量 </script>
场景二:在循环中 很多同学都会有点迷惑
<script type="text/javascript"> for(var i=1;i<=5;i++) { setTimeout(function timer(){ console.log(i); },i*1000); } </script>
上面的代码会输出什么,正常情况下我我们对这段代码的期待是分别输出数组1~5,每秒一个,每次一个。但是实际上,这段代码在运行时会以每秒一次的频率输出5次6;
为什么会这样呢? timer()函数会在for循环结束后执行,fou循环结束的条件是i<=5;即i=6; timer();保留了for循环的作用域引用,此时i=6; 所有会输出5个6;
如果要实现我们的预期效果就需要为每一次的循环创建作用域闭包(块状作用域)
方式一:let
<script type="text/javascript"> for(var i=1;i<=5;i++) { let j=i; //使用let 创建块状作用域 setTimeout(function(){ console.log(j); },i*1000); } 或者 for(let a=5;a>=1;a--) // //使用let 创建块状作用域 { setTimeout(function(){ console.log(a); },a*1000); } </script>
方式二:立即执行函数
for(var i=1;i<=5;i++) { (function(){ var j=i; setTimeout(function(){ console.log(j); },j*1000); })(); } </script>
- 立即执行函数
产生和意义:
在任意代码判断外部包装一个函数,可以达到封装的的目的,函数内部的变量和函数定义不会被外部作用域访问。但是必须声明一个具名函数,这个函数的名称也会污染所在的作用域。而且必须要显示的调用这个函数才能运行其中的代码。
如果函数不需要指定函数名称,并且能够自动执行。这时候立即执行函数就诞生了
语法:把函数包含在()中可以形成还是表达式,通过在未尾加上() 可以立即执行这个函数。(function fn(){})();
-
函数表达式
定义:函数表达式区别于函数声明,也是一种定义函数的方式,形似与变量赋值,这个值就是函数体,例
var a = function(){}; // 函数表达式之匿名函数
var a = function fn(){}; // 函数表达式之具名函数
(function(){})(); // 匿名函数之立即执行函数
特点:
1 . 区别于函数声明,和普通变量一样使用前必须声明,不声明在非严格模式下被认为是全局的变量,在严格模式下报错