JavaScript-作用域与作用域链

1. 定义

作用域是静态的(相对于执行上下文来说),也就是在写代码的时候就决定好了。作用域分为全局作用域和函数作用域。ES6新增块级作用域。
作用:隔离变量。不同作用域下同名变量定义不会冲突。

var a = 1
function fn() {
	var a = 2
}

这里外层的a存在于全局作用域中,内层的a存在于函数作用域中,并不会冲突。
数量:n+1 n代表定义的函数个数,也就是你写代码的时候写了几个函数。

2. 与执行上下文的区别

  1. 创建时机不同
    作用域是在写代码的时候就有了,全局作用域之外,每个函数都会创建自己的作用域,执行上下文是在函数执行前创建
    全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
    函数执行上下文是在调用函数时, 函数体代码执行之前创建
  2. 状态不同
    作用域写完函数代码就确定了,是静态的,且不会变化
    函数执行上下文是动态的,调用函数时创建,函数执行完毕自动销毁
  3. 联系
    执行上下文从属于作用域
    全局执行上下文=》全局作用域
    函数执行上下文=》当前函数所在的作用域

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"
posted @ 2021-07-14 23:01  北诗远  阅读(71)  评论(0)    收藏  举报