你真的会作用域面试题吗?

308 阅读5分钟

前言:上文我们讲了js的执行上下文-作用域链,如果还有童鞋对执行上下文还没搞懂的话,建议看上篇文章 这有传送门:JavaScript深入之执行上下文
话不多说,我们直接看题目:

面试题一:

var n = 100
function foo() {
  n = 200
}
foo()
console.log(n)

首先,简单描述一下(伪代码描述),解析全局代码的时候GO对象里有

这里写伪代码:
var globle object={
n:undefined,
foo:0xa00
}

开始执行第一行代码 var n=100;将GO对象的n变量赋值给100

伪代码:
var globle object={
n:100,
foo:0xa00
}

AO对象包括函数的形参、定义的变量、以及函数的声明

后面调用foo(),会先解析函数,先创建FEC执行上下文,其中包括VO对象,当FEC进入调用栈时,VO被激活成了AO会创建FEC,在FEC进入ECS时,VO对象被激活成AO对象,
很明显foo函数体这些都没有,foo的AO对象是空的

伪代码:
var foo AO={}

最终找到父级的VO也就是GO对象里面的n变量,修改赋值为200

伪代码:
var globle object={
n:200,
foo:0xa00
}

执行完毕,foo的FEC被弹出栈
执行最后一行代码 console.log(n),找到GO里面的n,最终打印为200
你做对了吗?

面试题二:

function foo() {
  console.log(n)
  var n = 200
  console.log(n)
}
var n = 100
foo()

首先还是老样子,解析全局代码,创建GO对象

这里写伪代码:
var globle object={
foo:0xa00,
n:undefined
}

开始执行代码,先执行var n = 100,GO对象的里面的n变量被赋值为100

伪代码:
var globle object={
foo:0xa00,
n:100
}

调用foo()函数,会先解析函数,先创建FEC执行上下文,其中包括VO对象,当FEC进入调用栈时,VO被激活成了AO,AO伪代码:

伪代码:
var foo AO={
n:undefined
}

访问一个变量的时候,会沿着作用域链一层一层往上找,最后没有找到则会报错

开始执行foo函数的第一行代码 console.log(n),根据作用域链的查找规则,会先在自己的AO对象中找,foo的AO对象n的值为undefined,所以打印为undefined
开始执行foo函数的第二行代码: var n = 200,将自己AO对象的n的值赋值为200
开始执行foo函数的第二行代码: console.log(n),此时,foo的AO对象里面的n变量已经被赋值为200了,所以这行打印200,执行完毕,foo的FEC被弹出栈
你做对了吗?

面试题三:

var a = 100
function foo() {
  console.log(a)
  return
  var a = 200
}
foo()

还是老样子,解析全局代码,创建GO对象

这里写伪代码:
var globle object={
a:undefined,
foo:0xa00
}

开始执行全局代码,第一行 var a = 100,将GO对象中的a变量赋值给100

伪代码:
var globle object={
a:100,
foo:0xa00
}

继续执行全局代码,调用foo()函数,会先解析函数,先创建FEC执行上下文,其中包括VO对象,当FEC进入调用栈时,VO被激活成了AO

伪代码:
var foo AO={
a:undefined
}

foo函数的AO对象为啥有n变量呢,这里是不是有同学有疑问呢?其实我们不要被return给干扰,当代码解析的时候,他是不会执行里面的东西,所以有return也没关系的(return是在代码执行的时候会执行return),不影响AO对象里面的内容(VO在解析阶段生成),FEC进入调用栈会先解析函数里面的代码
执行foo函数第一行代码:console.log(a),找到foo的AO对象,a为undefined,打印undefined
执行foo函数第一行代码:return,foo函数执行结束
你做对了吗?

面试题四:

function foo() {
 var m = 100
}
foo()
console.log(m)

老规矩,解析全局代码,创建GO对象,在这里的GO对象中,我们自己定义的变量是空的(go对象有一些是默认的变量,Array,date等等,这里都没有写出来)

伪代码:
var globle object={
}

接着调用foo()函数,会先解析函数,先创建FEC执行上下文,其中包括VO对象,当FEC进入调用栈时,VO被激活成了AO

伪代码:
var foo AO={
m:undefined
}

执行foo函数,执行第一行代码var m = 100
执行foo函数完毕,foo的FEC从执行栈中弹出
开始执行最后一行代码console.log(m),此时根据作用域链的查找规则,会先在GO里面找,由于是全局代码没有父级作用域,GO里面没找到,最终代码报错ReferenceError: m is not defined 你做对了吗?

面试题四-2:

我们把上个面试题改一下,把foo函数里面的var声明去掉

function foo() {
  m = 100
}
foo()
console.log(m)

其实严格来讲,这样写会报错,但是由于js边边角角的东西,确实可以这样写,当我们没有去声明或定义一个变量而是直接给他赋值时,会默认把这个变量提到全局。
所有很明显咯,m被提到了全局,所以最后一行打印为100

面试题五:

function foo() {
  var a = b = 10
}

foo()
console.log(b)

其实在面对 var a = b = 10这种写法时,js引擎会转化为 var a=10; b=10 经过前面4道详细详解,那么咱们这道就简单讲一哈。

function foo() {
  var a = b = 10
  // => 转成下面的两行代码
  // var a = 10
  // b = 10
}
foo()
console.log(b)

先执行全局代码foo(),此时foo的AO对象中a的值为10,由于b没有被声明或者定义,所有b会被添加到全局对象,所以最终打印为10

面试题五-2:

function foo() {
  var a = b = 10
}

foo()
console.log(a)
console.log(b)

如何第五道面试题改改呢,再打印一个a变量呢,其实结果很显然啦,全局对象并无a变量,根据作用域的查找规则,其实代码会报错啦ReferenceError: a is not defined

好啦,码字不易,经过前几道的作用域细讲,小伙伴们有没有彻底搞懂作用域面试呢?