函数作用域
# 1. 函数作用域
下面代码输出什么?
function foo() {
var a = 'bar'
console.log(a)
}
foo()
2
3
4
5
查看答案
// bar
变量a在函数foo作用域里面,所以可以访问到
# 2. 函数作用域2
下面代码输出什么?
var a = 'bar'
function foo() {
console.log(a)
}
foo()
2
3
4
5
查看答案
// bar
foo在自身函数作用域中没有找到a变量,会继续扩大查找范围,在全局作用域中找到了a
# 3. 函数作用域3
下面代码输出什么?
function bar() {
var a = 'bar'
}
function foo() {
console.log(a)
}
foo()
2
3
4
5
6
7
查看答案
// Uncaught ReferenceError: a is not defined
foo和bar是在两个独立的作用域上,foo内部无法访问bar内部的变量,a在foo的作用域以及其原型链上直到全局也没有找到对应的变量,所以会报错。
# 4. 函数作用域4
var b = 10;
(function b(){
b = 20
console.log(b)
})();
2
3
4
5
查看解析
ƒ b(){
b = 20
console.log(b)
}
2
3
4
- 因为function b是自调用函数,在其作用域中不会提升声明
- 在自调用作用域中,b = 20 没有加var声明,表示将全局作用域b的10改成了20
- 自调用作用域中打印的b,从最近的作用域中获取变量,先是函数b,然后才是全局的b
# 5. 函数作用域5
将下面的代码简单改造使之分别打印 10 和 20。
var b = 10;
(function b(){
b = 20
console.log(b)
})();
2
3
4
5
查看解析
打印20的:
// 解法一:在自调用函数中不要有b
var b = 10
(function (){
b = 20
console.log(b) // 20
})()
// 解法二:在自调用函数中声明变量b
var b = 10;
(function b(){
var b = 20
console.log(b) // 20
})();
2
3
4
5
6
7
8
9
10
11
12
13
打印10的:
var b = 10
(function (){
console.log(b) // 10
b = 20
})()
2
3
4
5
# 6. 函数作用域6
下面代码输出什么?
var a = 10
(function () {
console.log(a)
a = 5
console.log(window.a)
var a = 20
console.log(a)
})()
2
3
4
5
6
7
8
9
查看答案
undefined
10
20
- 因为IIFE内部声明了a变量,那么首先变量提升但是未赋值,所以第一个输出undefined
- 第二个因为当前作用域中已经有了a,修改当前作用域的值外界的值不变,所以window.a是10
- 之后IIFE内部的a又赋值为20,所以当前作用域的a为20
# 7. 函数作用域7
下面代码输出什么?
var a = 10
(function () {
console.log(a)
a = 5
console.log(window.a)
console.log(a)
})()
2
3
4
5
6
7
8
查看答案
10
5
5
因为IIFE内部没有声明a变量,会去全局找a,所以输出的值都是全局的a
# 8. 块级作用域
下面代码输出什么?
function foo() {
console.log(a)
var a = 1
var b
console.log(b)
b = 2
console.log(c)
console.log(c)
let c = 3
}
foo()
2
3
4
5
6
7
8
9
10
11
查看答案
// undefined
// undefined
// 2
// Uncaught ReferenceError: a is not defined
- var声明的变量可以进行变量提升,函数内部声明的会提升到函数顶部,没有赋值的a为undefined,b同理。
- let声明的变量在声明之前都为TDZ,暂时性死区,不可调用,会报错。
# 9. 块级作用域2
下面代码输出什么?
function foo(arg1) {
let arg1
}
foo('arg1')
2
3
4
5
查看答案
// Uncaught SyntaxError: Identifier 'arg1' has already been declared
函数参数的作用域是整个函数体,在函数体内再声明同名变量就会报错。
# 10. 函数参数TDZ
下面代码输出什么?
function foo(arg1 = arg2, arg2) {
console.log(`${arg1} ${arg2}`)
}
foo('arg1','arg2')
foo(undefined,'arg2')
2
3
4
5
查看答案
// arg1 arg2
// Uncaught ReferenceError: Cannot access 'arg2' before initialization
- 当第一个参数传递的时候,是不走arg1 = arg2的,传什么是什么。
- 当第一个参数传递undefine的的时候,会走arg1 = arg2,这个时候因为后面的arg2还没有赋值,之前就有了TDZ
# 11. 函数参数
下面代码输出什么?
let foo = 1
const bar = value => {
value = 2
console.log(value)
}
bar(foo)
console.log(foo)
2
3
4
5
6
7
查看答案
// 2
// 1
在bar函数中,如果是基本数据类型,会复制一份参数值,而不会影响原参数的实际值。
- 第一个值,在函数内部value变成了2
- 第二个值,在函数外部foo值没有改变,还是1
# 12. 函数参数2
下面代码输出什么?
let foo = {bar: 1}
const bar = obj => {
obj.bar = 2
console.log(obj.bar)
}
bar(foo)
console.log(foo)
2
3
4
5
6
7
查看答案
// 2
// {bar: 2}
在bar函数中,如果是引用数据类型,则会将参数的地址指向原来的对象地址,如果修改某个属性值,则会一起修改。
- 第一个值,在bar函数内部,obj.bar值改为了2
- 第二个值,因为对象的属性值改变内部和外部一起改,所以外部的foo中的bar也变成了2
# 13. 函数参数3
下面代码输出什么?
let foo = {bar: 1}
const bar = obj => {
obj = 2
console.log(obj)
}
bar(foo)
console.log(foo)
2
3
4
5
6
7
查看答案
// 2
// {bar: 1}
在bar函数中,如果是引用数据类型,函数内部直接修改了参数的引用地址,那么任何操作都不会影响原参数的实际值。
- 第一个值,在bar函数内部,obj.bar值改为了2
- 第二个值,因为内部修改
# 11. 声明提升
下面代码输出什么?
function bar() {
console.log('bar1')
}
var bar = function() {
console.log('bar2')
}
bar()
2
3
4
5
6
7
8
查看答案
// bar2
上述代码,函数声明会在变量声明之前,就算bar2的函数在bar1的上面,依旧会输出bar2,因为编译之后的顺序是下面代码。
function bar() {
console.log('bar1')
}
var bar
bar = function() {
console.log('bar2')
}
bar()
2
3
4
5
6
7
8
9
10
可以函数bar函数是下面的,所以调用输出bar2
# 12. 声明提升2
下面代码输出什么?
foo(10)
function foo(num) {
console.log(foo)
foo = num
console.log(foo)
var foo
}
console.log(foo)
foo = 1
console.log(foo)
2
3
4
5
6
7
8
9
10
查看答案
// undefined
// 10
// ƒ foo(num) {}
// 1
上述代码,因为编译时的顺序是下面代码。
function foo(num) {
var foo
console.log(foo)
foo = num
console.log(foo)
}
foo(10)
console.log(foo)
foo = 1
console.log(foo)
2
3
4
5
6
7
8
9
10
首先函数声明提升,调用的时候,函数foo内部的变量提升到函数顶部。
- 第一个是函数内部foo已经声明但是没有赋值,所以是undefined
- 第二个是讲参数10赋值给了foo,所以foo值为10
- 第三个是在函数外部,打印foo,foo内部的变量只在函数作用域内,全局是函数foo
- 第四个将变量foo赋值了1,那么值改变为1
# 13. 闭包
下面代码输出什么?
const foo = (function() {
var v = 0
return () => {
return v++
}
})()
for(let i = 0; i < 10; i++) {
foo()
}
console.log(foo())
2
3
4
5
6
7
8
9
10
11
12
查看答案
// 10
foo是一个立即执行函数,其内部形成了一个闭包,v变量不会因为函数执行完毕就垃圾回收,在循环遍历的时候,foo()就是调用内部返回的函数,调用了10次v就增加了10次,最后输出的时候输出v,然后v自己变成了11。
# 14. 闭包2
下面代码输出什么?
const foo = () => {
var arr = []
var i
for(i = 0; i < 10; i++) {
arr[i] = function() {
console.log(i)
}
}
return arr[0]
}
foo()()
2
3
4
5
6
7
8
9
10
11
12
查看答案
// 10
本题中i是一个自由变量,循环之后,将数组中都存满了函数,此时i循环之后值为10,所以在执行foo()()的时候,执行函数返回的i变量也是10
# 15. 闭包3
改良上面的代码,下面代码输出什么?
const foo = () => {
var arr = []
for(var i = 0; i < 10; i++) {
arr[i] = (function(i) {
return function() {
console.log(i)
}
})(i)
}
return arr[0]
}
foo()()
2
3
4
5
6
7
8
9
10
11
12
13
查看答案
// 0
本题在循环的时候使用了闭包,i以函数参数的形式传递给了内层,所以i的变量保存在了内存中没有被释放,所以是0
# 16. 闭包4
下面代码输出什么?
var fn = null
const foo = () => {
var a = 2
function innerFoo() {
console.log(a)
}
fn = innerFoo
}
const bar = () => {
fn()
}
foo()
bar()
2
3
4
5
6
7
8
9
10
11
12
13
查看答案
// 2
本题,虽然在foo执行完毕之后应该要内存回收,但是因为fn是一个全局变量,全局变量在这里有引用,所以a变量的值没有被释放。在执行bar的时候,会打印出来a为2
# 17. 闭包5
下面代码输出什么?
var fn = null
const foo = () => {
var a = 2
function innerFoo() {
console.log(c)
console.log(a)
}
fn = innerFoo
}
const bar = () => {
var c = 100
fn()
}
foo()
bar()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
查看答案
// Uncaught ReferenceError: c is not defined
本题在bar执行fn时,fn已经被复制为innerFoo了,此时的c的作用域链是innerFoo -> foo -> 全局,而bar中的变量c不在其作用域链上,所以会报错。