🐍 作用域

作用域决定执行代码时的上下文,包括可以访问哪些变量、函数、对象等。通常大家会接触到的有全局作用域、函数作用域、块级作用域、词法作用域、作用域链。

全局作用域只有一个,就是定义在最外面的那个。

函数会创建新的作用域,即函数作用域,每次执行函数,都会创建新的作用域。函数作用域会在函数执行时被绑定到函数执行时的上下文。函数作用域里的变量只能在函数体内被访问到。

if、switch、while、for 等语句会创建块级作用域。那块级作用域里的变量能在外部被访问吗?这要看是用 var 还是 let/const 了。var 是函数作用域级,而 let 和 const 是块级作用域级。

JavaScirpt 处理作用域的方式是词法作用域,也叫静态作用域,与之相对的是动态作用域,很少语言用,比如 Bash 就是动态作用域。词法作用域决定了作用域在代码被 JavaScript 引擎编译时即会决定,而不是在代码被运行时决定。

最后,作用域链是什么?执行上下文会为每个词法环境创建指向父作用域的引用,所以往里的作用域就能访问往外的作用域,而外面的不能访问里面的。

🐹 闭包

理解了作用域,闭包就很好理解了。闭包是指嵌套函数里,子函数能访问父函数的作用域,就算父函数已经完成执行。原因是子函数的执行上下文创建了指向父函数作用域的引用,所以子函数没结束,引用一直存在,父函数的作用域就一直存在。

🐢 Hoist

作用域内的变量和函数会在编译阶段被放到内存里的过程叫 Hoist。有个误解是大家认为 Hoist 是把他们提到作用域的前面,但其实并不是,var、let、const、function 都会被 hoist,只是有些不会被初始化,所以提前使用会报 ReferenceError。

var 会被初始化为 undefined,传统的函数定义 functon foo() {} 会被初始化,let 和 const 不会被初始化。所以 var 定义的可以提前使用但值是 undefined,传统函数定义可以正常提前使用,let 和 const 提前使用会报错。注意,函数也可通过 var、let、const 的方式定义,比如 let foo = () => {}

a; // undefined b; // Reference Error c; // Reference Error d; // function d() {}

var a = 1; let b = 2; const c = 3; function d() {}

🦀 this 关键字

函数被调用时,会创建执行上下文,这个上下文中存了很多信息,比如函数从哪里被调用、传入的参数等,其中 this 关键字就是用来保存函数从哪里来。

不同场景下 this 的指向是不同的,并且 this 可以被修改,这些规则要吃透需要稍微花的时间,以下是我之前整理的题,大家可以试试。(迟几天公布答案)

1、函数被正常执行时 foo(),this 指向啥? 2、箭头函数的 this 指向啥? 3、this 可通过哪些方式被修改? 4、箭头函数的 this 可被修改吗? 5、class 箭头函数定义的方法 this 指向啥? 6、bind 过的函数,还可通过 call 和 apply 修改 this 吗? 7、函数作为对象成员调用时 foo.bar(),this 指向啥? 8、函数或其父作用域为严格模式时,this 指向啥?

参考:

https://medium.com/@amsingh714