今天看到一个有意思的面试题:设计一个方案,用var来模拟let,这里总结一下
我们比较容易想到的方案,最简单的方案,即用闭包+函数自执行(IIFE)方案
(function() {
var x = 1;
{
var y = 2;
console.log(x); // Output: 1
console.log(y); // Output: 2
}
console.log(x); // Output: 1
console.log(y); // Output: ReferenceError: y is not defined
})();
假如用栈的形式,函数模拟的话,有以下思考:
首先,我们知道,var 和 let 都用于声明变量,但是它们的作用域和绑定方式是不同的
var声明的变量具有函数级作用域和变量提升的特点let声明的变量具有块级作用域和不会变量提升的特点 因此要模拟let的行为,需要实现以下功能
- 声明变量的时候,将其绑定到当前块级作用域中,如果变量已经存在当前作用域中,则抛出异常
- 变量在块级作用域之外是不可见的,即在块级作用域外不能访问该变量
- 如果在块级作用域内重复声明变量,则将其覆盖当前作用域内的变量
- 在变量声明前访问变量会抛出一个错误
以下是一个用 var 模拟 let 的示例实现
function letVarFn(){
const scopeStack = []; // 使用一个 `scopeStack` 数组来存储每个作用域内的变量
let letVar = function(name, value){
if(scopeStack.length === 0){
scopeStack.push({})
}
const scope = scopeStack[scopeStack.length - 1]
if(scopeStack.hasOwnProperty(name)){
throw new Error(`Identifier '${name}' has already been declared`);
}
scope[name] = value;
}
letVar.startScope = function(){
scopeStack.push({})
}
letVar.endtScope = function(){
if(scopeStack.length <= 1){
throw new Error('No scope to end');
}
scopeStack.pop()
}
letVar.get = function(name){
for(let i =scopeStack.length -1 ; i>=0; i-- ){
const curScope = scopeStack[i]
if (curScope.hasOwnProperty(name)) { return curScope[name]; }
}
throw new Error(`Identifier '${name}' is not defined`);
}
return letVar
}
const letVar = letVarFn();
letVar('x', 1);
letVar.startScope();
letVar('x', 2); letVar('y', 3);
console.log(letVar.get('x')); // output: 2
console.log(letVar.get('y')); // output: 3
letVar.endScope(); // 栈顶出栈, 即y被pop出去了,不在当前的作用域栈中了
console.log(letVar.get('x')); // output: 1
console.log(letVar.get('y')); // throws "Identifier 'y' is not defined"
- 在实现中,我们使用一个
scopeStack数组来存储每个作用域内的变量。 - 在声明变量时,我们会检查当前作用域中是否已经存在该变量,如果存在则抛出一个错误;否则,将变量添加到当前作用域中。
- 在创建块级作用域时,我们使用
letVar.startScope()方法将一个新的空对象添加到scopeStack中。 - 在结束块级作用域时,我们使用
letVar.endScope()方法将最后一个作用域从scopeStack中弹出
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天 点击查看详情