Lexical & Dynamic Scoped
lexical scoped: 指的是编译器, 解析器, 处理器执行之前,进行了一此预编译, 这就是词法作用域。词法作用域不是在运行时决定的, 是在编译的时候决定的。
Dynamic Scoped: 像bash是一种解释型语言, 他的作用域是动态作用域。因此它没有词法作用域, 不是编译型语言。
Lexical Scoped
- 词法作用域,其实映射的概念就是预编译,js在运行之前进行了预编译,把作用域, 和作用域内的变量以及函数信息都保存起来, 供引擎运行时使用。
- 词法作用域定下来就确定了, 一次编译,运行时就可以对此进行优化。
- 作用域之间是层级关系。
Dynamic Scoped
- 动态作用域指的是, 黑箱, 我只有打开它(运行的时候)才知道是怎么回事。
- 动态作用域下, 你对一个变量引用的结果取决于你的上下文环境。
- 所以, 动态作用域是在运行时候确定的, 而词法作用域是在编写代码时确定的。但是js有一种机制, 提供了类似的动态作用域的效果。
function scoped
- 函数作用域给了我们隔离外部作用域的机会(提供了命名空间), 我们应该尽量的保持函数内操作的变量私有。
- 并且持续遵循最小变量原则。函数内的变量函数内自己去创建和消化(object记得要深拷贝)。
IIFE Pattern(立即执行函数表达式模式)
- 使用函数表达式,创建一个作用域, 立刻调用它, 产生了作用域后, 执行函数体内容, 作用域消失。只运行一次,就消失了, 不会留下副作用。比如变量污染。
- IIFE是表达式, 而不是函数定义式。
- 如果你需要一个函数表达式,那么你完全可以使用IIFE去做这件事。比如try-catch语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14let a = (() => {
try {
someFn() // return value.
}catch() {
return 'defaultValue'
}
}())
let value = "some";
((value)=> {
console.log(value);
value = "big";
})(value);
console.log(value); // some
Block Scoping
块作用域的特点: 只有在使用let/const定义时的作用域有效, 出了定义的作用域就无效.
块作用域的产生:
- 使用IIFE
1
2
3
4
5
6let value = "some";
((value)=> {
console.log(value);
value = "big";
})(value);
console.log(value); // some - 在花括号内使用let/const定义
1
let a = 2; {let a = 1; console.log(a)};console.log(a)
let vs var (个人认为: const > let > var)
有作用域好过没有作用域。不用考虑边界情况.
当然,除非你想利用hosting(提升), 那么可以使用var.
一个有趣的冷知识:
1 | var a = 1; |
explicit let block
1 | (sum) => { |
const
你应该优先使用const, 之后再是let.
当时,const也存在一定的问题, 使用const, 不可以二次赋值。但是如果const定义的不是基础类型(string/boolea/number), 而是数组的话, 那么const的行为就有些怪异了。历史因素导致的, 当我们使用object类型(数组)的时候, 变量内存的是一个地址,而,这个地址中存的是对象属性中的地址。而我们修改属性的地址时, 实际上, 变量的地址是没有改变的。来看个例子:
1 | const arr = [1, 2, 3] |
从语义上理解, const是常量,意味着我不会再给他重新赋值。
遵循两个使用原则: 1. 原语类型 2. 只赋值一次, 不会再次赋值
Hoisting
在es的规范中,没有提及提升,我们所说的提升其实是一个比喻。就像我们说的词法作用域,都是一种比喻,一种理解这个概念的介质, 来看个例子:
1 | a; // undefined |
实际上,js引擎把以上的代码转换成了这样: 把变量定义,函数定义自动的放在函数执行的顶部。
1 | var a; |
提升的特性: 变量定义提升,函数定义提升, 函数表达式不提升。
1 | a(); // a |
关于函数提升这一点,我们能理解到, 函数提升, 其实本质上就是做了C语言中,提前把要用的函数暴露出来,这一点他们是一致的。
同时,我们也可以用预编译来解释这件事, 预编译只对LSH做处理,也就是只处理收集了var b这个信息,而没有获取到RSH的信息,所以在使用b()的时候,没能获取到函数体本身.获取到函数体本身要到执行阶段才能获取到.
Hoisting explames
1 | var a = 'a'; |
其实得出这个结果一点也不怪异, 想想之前说的, 预编译阶段,在预编译阶段,function b 中就存有自己的变量a, 那么在执行阶段,console.log语句拿a变量其实是从,b函数的作用域中拿a。
let doesn’t hoist? No.
1 | { |
这有个概念叫临时死区, 在用let定义之前的区域都为死区,不可以在该区域使用a变量, 否则会报错。
大胡子说, 其实var和let/const都进行了提升,只是提升的行为不一样, var提升了变量定义,并且初始化了这个变量, 赋予初始值undefined.但是let/const也进行了提升, 只是没有进行初始, 这里没有进行初始化等价于不可以去使用它(是不是很像临时死区)