闭包作用域相关面试题

125 阅读3分钟

本文所有图的黑色框框表示全局执行上下文,其他颜色框框表示私有上下文,圆角框框表示堆内存。

ARG和形参的映射机制「非严格模式」

var a = 4;

function b(x, y, a) {
	console.log(a);
	arguments[2] = 10;
	console.log(a);
}

a = b(1, 2, 3);

console.log(a); 
// 输出 3  10  undefined

看下真正的执行过程:

 
// EC(G) 进栈
//  	a --> 4  --> undefined
//  	b --> 0x001 [[scope]]: EC(G)
// 变量提升
//      function b(x, y, a) { ... }
//      var a;
var a = 4;

function b(x, y, a) {
	// EC(b) 进栈
	//      x --> 1,  
	//      y -- > 2
	//      a --> 3  --> 10
	// 		作用域链: <EC(B),EC(G)>
	// 		初始化 ARG(实参集合): { 0: 1, 1: 2, 2: 3, length: 3 } 类数组结构
	//         形参赋值: x = 1, y = 2, a = 3
	//      变量提升: ----
	console.log(a); // 3
	arguments[2] = 10;
	console.log(a); // 10
}

a = b(1, 2, 3); // 全局的 a 变为 undefined

console.log(a); // undefined

在非严格模式下,初始化 ARG 和 形参赋值后,会建立一个映射机制,一个改另一个也改,但是在严格模式下,是没有这个机制的,严格模式输出 3 3 undefined

闭包来了

var test = (function(i) {
	return function() {
		alert(i *= 2);
	}
})(2);

test(5); 

// 4

这道题画图理解即可,考验闭包的基础,输出 4.

闭包作用域

var x = 4;

function func() {
	return function(y) {
		console.log(y + (--x)); 
	}
}

var f = func(5);
f(6);
func(7)(8);
f(9);
console.log(x);

// 输出:9  10  10  1

这道题建议画图理解,或者参考前面写过的另一篇文章 (一道闭包作用域面试题)[blog.yangshuaiweb.com/2021/05/25/…]

一道关于惰性思想的题(函数重构)

var x = 5, y = 6;

function func() {
	x += y;
	func = function(y) {
		console.log(y + (--x));
	}
	console.log(x, y);
}

func(4);
func(3);
console.log(x, y);

// 输出: 11,6  13  10,6  

这道题需要注意的点是,第一次 func(4) 被调用时,执行的是大函数。

我们可以看到,并非大函数返回小函数才能形成闭包,而是函数执行形成一个全新的私有的上下文,在私有上下文中的某些东西,被上下文以外的地方引用,那么当前上下文是不能被释放的,供下级上下文调取使用,这样的保护 + 保存机制,称之为闭包,所以函数重构也能形成闭包。

一道套娃面试题(复杂)

function fun(n, o) {
	console.log(o);
	return {
		fun: function(m) {
			return fun(m, n);
		}
	}
}

var c = fun(0).fun(1);

c.fun(2);
c.fun(3);

// 0  1  1

这个题有点难度,还是老规矩,一定要画图理解,需要注意每一次返回的对象中的函数的父作用域究竟是谁。

匿名函数具名化引发的事情

var b = 10;

(function b() {
	b = 20;
	console.log(b); // func
})();

console.log(b); // 10

自执行函数本身就是自执行的匿名函数,函数表达式的函数名不能修改,且只能内部访问。

一般使用场景是,匿名递归函数自己调用自己,arguments.callee 也可以访问函数本身,但在严格模式下,就访问不到了。

但是具名化的函数名权重较低,但凡我们在函数内部重新基于 let/var/function/const等声明过同名的变量,都可以把它替换掉。

(function b() {
	let b = 20;
	console.log(b); // 20
})(); 

(function h() {
	console.log(h); // undefined
	var h = 20;
	console.log(h); // 20
})();