一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 4 天,点击查看活动详情。
理解不同类型的作用域 【1】
1. 词法作用域
之前我们有说到,在大部分的编译语言执行前会有一个“编译”的过程,里面的第一步叫做词法化,词法化阶段会对源代码中的字符串进行检查。而词法作用域就是定义在此法阶段的作用域,换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里决定的。,所以在词法分析器处理代码时会保持作用域不变(但并不是完全,在使用with和evel也会发生改变,之后再说到有关的欺骗词法)
function foo(a) {
var b = a * 2;
function bar(c) {
console.log(a,b,c); // 如何找到这三个变量
}
bar(b * 3);
}
foo(2);
// 输出 2,4,12
首先在这个代码例子中有3个作用域
- 全局作用域 标志符 foo函数
- foo的作用域 标志符 a bar函数 b
- bar的作用域 标志符 c
console.log(a,b,c);
首先c所以bar的作用域里面,就直接拿就可以,b不属于bar的作用域,而是上一层foo函数的作用域,a也属于foo函数作用域里面。因此可以看到,查找作用域,是由内到外,首先找到最近的作用域,如果没找到,就继续往外查找。
而某个标志符的作用域是由它声明时候确定的(除了使用with,evel,但这两个通常不会被使用)
2. 函数作用域
2.1. 了解函数作用域
首先来看下一段代码
function foo(a) {
var b = 2;
...
function bar () {
...
}
...
var c = 3;
}
在这段代码中,foo函数的作用域保护了标志符a,b,c和bar。无聊标志符声明出现在作用域中的何处,这个标志符所代表的变量或者函数都将属于所处的作用域。
bar函数拥有自己的作用域,全局作用域也拥有自己的作用域,而且它只有一个标志符foo
由于标志符a,b,c,foo和bar 在foo那边都是可以被访问的,因此无法从外部对它们进行访问。这些标志符是不能从全局作用域中进行访问。
比如:
bar() // 报错
console.log(a,b,c); // 报错
但是,这些标志符a,b,c,foo和bar,在foo的内部都是可以被访问到的,同样在bar内部也可以被访问到
我的理解其实是,作用域查找只能由内到外,不能由外到内。
所以再来看什么是函数作用域呢:属于这个函数的全部变量都可以在整个函数范围内被使用或者复用(在嵌套函数里面也可以)
2.2. 函数作用域封装变量和函数
2.2.1 私有化,避免被更改
在代码块的周围创建作用域,也就是说这段代码中的任何声明都将绑定在这个新创建的包装函数的作用域中,而不是先前所在的作用域。
可以把变量和函数包裹在一个函数的作用域中,然后用这个作用域来封装它们(隐藏)
为什么要进行封装呢?
- 封装一个api或者方法,会让代码更加简洁
- 如果所有的代码都放在全局作用域,在局部作用域我们当然可以访问得到,但是有些变量我们可能并不希望它被其他地方使用,这样的我们需要将它
特殊化。举例:
function foo1(a) {
b = a + foo2(a * 2);
console.log(b * 3);
}
function foo2(a) {
return a - 1;
}
var b;
foo1(2);
在函数foo1中,b和foo2是内部具体的内容,本应该不放到外部,如果放外部可能会被其他地方用到。
当我们将b和foo2封装到foo1函数里面去,那么外面的就不能用到它们啦,保证了一个私有性。
function foo1(a) {
function foo2(a) {
return a - 1;
}
var b;
b = a + foo2(a * 2);
console.log(b * 3);
}
foo1(2);
2.2.2 规避冲突
将变量和函数进行作用域封装还有一个好处是,可以避免同名标志符之间的冲突,两个标志符也许会出现相同的命名,但是用途不一样,会造成同名覆盖。
function foo () {
function bar (a) {
i = 3; // 每次执行bar,i都被设为3,永远满足小于10这个条件
console.log(a + 1);
}
for (var i = 0;i < 10;i++) {
bar (i * 2); // 循环了。。
}
}
foo();
改成下面这样就可以
function bar (a) {
var i = 3; // 每次执行bar,i都被设为3,永远满足小于10这个条件
console.log(a + i);
}
函数作用域
在变量或者函数外部加作用域,外部就无法访问到作用域里面的标志符。
var a = 2;
function foo () {
var a = 3;
console.log(a);
}
foo();
console.log(a); // 2
但这个方法也会有一些问题,首先因为我们需要创建一个作用域是个具名函数foo,这个foo的名称本身是在全局作用域中,其次,必须要显示的通过foo()调用,才能运行当中的代码,如果不需要函数名,并且可以自动运行,会更好。
采用立即执行函数:
var a = 2;
(function foo () {
var a = 3;
console.log(a);
})()
console.log(a); // 2
包装函数的声明以(function开始 而不是以function...,函数会被当作函数表达式而不是一个标准的函数声明来处理。
函数声明和函数表达式直接的区别:
-
第一段代码,函数声明:foo被绑定在所处的作用域中,可以通过foo()来调用执行
-
第二段代码,函数表达式:foo被绑定在函数表达式自身的函数中而不是所在的作用域中
(function foo() {..})作为函数表达式意味着foo只能在...所代表的位置中被访问,外部作用域则是访问不到的。
foo变量名被隐藏在自身的作用域中,意味着不会非必要的污染外部作用域。
还有一个块级作用域,明天再梳理下