用var来模拟let的实现

136 阅读2分钟

今天看到一个有意思的面试题:设计一个方案,用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
})();

假如用栈的形式,函数模拟的话,有以下思考:

首先,我们知道,varlet 都用于声明变量,但是它们的作用域和绑定方式是不同的

  • var 声明的变量具有函数级作用域和变量提升的特点
  • let 声明的变量具有块级作用域和不会变量提升的特点 因此要模拟let的行为,需要实现以下功能
  1. 声明变量的时候,将其绑定到当前块级作用域中,如果变量已经存在当前作用域中,则抛出异常
  2. 变量在块级作用域之外是不可见的,即在块级作用域外不能访问该变量
  3. 如果在块级作用域内重复声明变量,则将其覆盖当前作用域内的变量
  4. 在变量声明前访问变量会抛出一个错误

以下是一个用 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"

image.png

  • 在实现中,我们使用一个 scopeStack 数组来存储每个作用域内的变量。
  • 在声明变量时,我们会检查当前作用域中是否已经存在该变量,如果存在则抛出一个错误;否则,将变量添加到当前作用域中。
  • 在创建块级作用域时,我们使用 letVar.startScope() 方法将一个新的空对象添加到 scopeStack 中。
  • 在结束块级作用域时,我们使用 letVar.endScope() 方法将最后一个作用域从 scopeStack 中弹出

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天 点击查看详情