JavaScript-作用域与作用域链
1. 定义
作用域是静态的(相对于执行上下文来说),也就是在写代码的时候就决定好了。作用域分为全局作用域和函数作用域。ES6新增块级作用域。
作用:隔离变量。不同作用域下同名变量定义不会冲突。
var a = 1
function fn() {
var a = 2
}
这里外层的a存在于全局作用域中,内层的a存在于函数作用域中,并不会冲突。
数量:n+1 n代表定义的函数个数,也就是你写代码的时候写了几个函数。
2. 与执行上下文的区别
- 创建时机不同
作用域是在写代码的时候就有了,全局作用域之外,每个函数都会创建自己的作用域,执行上下文是在函数执行前创建
全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
函数执行上下文是在调用函数时, 函数体代码执行之前创建 - 状态不同
作用域写完函数代码就确定了,是静态的,且不会变化
函数执行上下文是动态的,调用函数时创建,函数执行完毕自动销毁 - 联系
执行上下文从属于作用域
全局执行上下文=》全局作用域
函数执行上下文=》当前函数所在的作用域
3. 作用域链
从全局作用域开始,层层嵌套函数定义就形成作用域链。它的方向是从下到上(从内到外的)
作用:查找变量时就按着作用域链来查找。如果作用域链找完了也没找到变量,那么抛出异常
var a = 1
function fn(){
console.log(b) // b is not defined
}
var obj = { name:"yanjie" }
console.log(obj.age) // age is undefined
这里想说明什么问题?对于变量来说,查找是沿着作用域链来的,找不到会报错;对于对象来说,查找是按照隐式原型链来的,找不到只会输出undefined,并不会报错。
4. 面试题
var num = 10
function fn1() {
console.log(num)
}
fn1() // 输出10
function fn2(f) {
var num = 20
f()
}
fn2(fn1) //输出10 为什么? 这里相当于把fn1传入fn2内部进行执行,相当于执行fn1的函数体console.log(num),在fn1的函数体内部没有num,于是按着作用域链往上找,找到的是全局作用域下的num=10。为什么不是fn2下的num=20呢?因为fn2所在的作用域和fn1所在的作用域是同级别的,压根儿他俩就不再一条作用域链上。
var obj = {
fn2: function () {
// console.log(fn2)
console.log(this.fn2)
}
}
obj.fn2()
// 第一个会报错 fn2 is not define 为什么?在执行log打印fn2的时候,当前函数作用域内的函数执行上下文中没有fn2这个变量,于是往外层找,外层作用域是全局,那全局也没有叫fn2的变量啊,故报错。
// 第二个会打印当前函数对象,为什么?因为通过obj.fn2执行的函数,那么fn2的函数执行上下文中的this即为当前obj对象,当执行log打印this.fn2时,是在当前对象上去找fn2,所以找得到。
say()
var name = "yanjie"
function say() {
console.log(name)
}
say()
// 这段代码在变量提升中有说明过,这里再次使用。第一个say打印undefined,第一个打印yanjie
原理:
var name
function say() {...}
say()
name = "yanjie"
say()
//这里可能会这样想?当我第一个执行say的时候,函数作用域对应的函数执行上下文中没有name,那么沿着作用域链往外找,找到了全局作用域的var name = "yanjie" ,那么应该打印"yanjie"啊?
//正确思路:诚然,当执行第一个say时,往外层作用域找name,但是我们注意到,作用域查找规则是 从 下 到 上!我们看伪代码,第一个say执行,在第三行,从下往上找到的 name是没有赋值的undefined状态,故输出undefined。第二个say从下往上找找到的是name="yanjie",故输出"yanjie"
浙公网安备 33010602011771号