面试题:你能使用var模拟实现let和const的核心效果吗?

前言

众所周知,letconstvar的最主要区别是变量提升、块级作用域和声明变量不可变,因此想要模拟letconst要先抛出三个核心问题。

1、怎样用var实现块级作用域效果?

2、怎样用var使声明的变量不可修改?

3、怎样用var使变量不可重复声明?

前置知识-先了解javascript的三种作用域

先回顾更好地理解javascript作用域才能更好地往下思考实现效果。

全局作用域

所谓全局,简单说就是所有地方都能访问到的变量。不包含在任何作用域内声明的变量都是全局作用域,即不在任何函数或者大括号里面。

直接上示例

scss 复制代码
var a = 1;
function test() {
  var b = 2;

  function testbar(c) {
    console.log(a + b + c);   // 输出6
  }
  testbar(3);
}
test();

变量a和方法test都在最外层都是全局作用域中,内层可以访问外层的变量,最终结果输出6

函数作用域

函数作用域顾名思义函数,因此在JavaScript规则中,所有定义在某个函数体内的变量都无法被函数外访问,因为这些变量的作用域仅限于这个函数内部。

直接上示例和效果

ini 复制代码
  function test() {
    var age = 123;
  }
  test();
  console.log(age);

结果会无法访问,并且报错ReferenceError: *** is not defined

块级作用域

ES6引入letconst关键字之后才出现块级作用域,因此letconst才会出现块作用域var不会。

简单说就是在{}内定义的变量和常量就算是一个块级作用域,在这个{}之外是无法访问到的。

直接看示例对比区别

ini 复制代码
function test() {
  if(true){
    var age = 123;
    let age2 = 456;
  }
  console.log("age", age);
  console.log("age2", age2);
}
test();

效果如上图所示,如果使用var不会有块级作用域能直接获取到值,但使用letconst会有块级作用域。

一、模拟let块级作用域

javascript如果提到作用域除了let你还想到了什么??

那必须是想到函数作用域啊,可以使用立即执行函数(IIFE),在 javascript 中的作用域规则决定了它会立即执行,然后创建一个新的作用域,并且内部变量与外面的变量相互独立从而达到效果。

常规使用letconst示例效果

javascript 复制代码
{
  let x = 88;
  console.log('里面x', x); // 正常输出 88
}
console.log('外面x', x);

如图所示,在块级作用域会报错is not defined

使用var模仿效果

javascript 复制代码
{
  (function() {
    var x = 88;
    console.log('里面x', x); // 输出 88
  })();
  console.log('外面x', x); // 报错
}

在上述例子中,我们用立即执行函数包住var x = 88, 这样就使作用域限制在函数内部,从而实现了类似letconst的块级作用域效果。

二、模拟const声明变量后不可变

实现这个小功能其实有几种方法我们一个个说。但声明后不可以重新赋值,这句话你第一时间能想到什么JavaScriptAPI???没错就是Object.defineProperty, 如果你对这个API不熟悉的话,建议你去学习一下,下面有我之前写的相关文章链接。

方法一:Object.defineProperty

理解vue2响应式原理核心Object.defineProperty

直接上个例子

javascript 复制代码
var testConst = {};
Object.defineProperty(testConst, 'val', {
  value: 10,
  writable: false,
  configurable: false,
});

console.log('修改前', testConst.val); // 打印:10
testConst.val = 20; // 由于writable为false所以不生效
console.log('修改后', testConst.val); // 打印:10

通过设置writable: false,使属性不可变,修改也不会生效。

注意: 严格模式下会抛出TypeError,原因是writable设置为false

方法二:Proxy代理拦截

如果你对这个API不熟悉的话,建议你去学习一下,下面有我之前写的相关文章链接。

理解vue3响应式核心proxy代理

直接上代码例子

javascript 复制代码
var testConst = new Proxy({}, {
  value: 10,
  set(target, prop, value) {
    if (prop === 'value') {
      return false; // 修改属性时,直接返回false
    }
    target[prop] = value;
    return true;
  }
});

console.log('修改前', testConst.value); // 10
testConst.value = 20; // 不生效
console.log('修改后', testConst.value); // 10

主要核心在set方法强行拦截处理,禁止对指定属性进行一切修改从而实现功能。

方法三:用闭包实现

直接上示例代码

javascript 复制代码
function createConst(value) {
  return {
    get value() {
      return value;
    }
  };
}

var testConst = createConst(10);

console.log('修改前', testConst.value); // 10
testConst.value = 20; // 不生效,因为并没有set方法
console.log('修改后', testConst.value); // 10

核心思路是只设置了一个get方法,不设置set方法,这样在修改指定属性的时候,就会不会生效。

其实还有其它方法,就先说这三个吧....

三、使用var实现不可重复声明的变量

众所周知,使用var声明的变量是可以重复声明的,并且后面会覆盖前面的变量,而同一作用域内letconst不可以重复声明。

那么如何用var实现相似效果呢???一听到不重复这个词,小脑瓜一转第一反应想到的是Map()。我们可以通过calss封装一个类进行统一调用,达到模拟的效果。

基本封装如下代码所示

kotlin 复制代码
class ImitateVar {
  constructor() {
    this.scopevar = new Map();
  }

  // 用于声明变量
  declareVar(name, value) {
    if (this.scopevar.has(name)) {
      return false;
    }
    this.scopevar.set(name, value);
    return true;
  }

  // 获取变量
  get(name) {
    if (this.scopevar.has(name)) {
      return this.scopevar.get(name);
    }
    return undefined;
  }

  // 设置变量
  set(name, value) {
    if (!this.scopevar.has(name)) {
      return false;
    }
    this.scopevar.set(name, value);
    return true;
  }
}

这里简单讲解分析一下这个ImitateVar类。

其中constructor初始化一个 Map 用来存储指定变量;

declareVar用来声明变量,并且添加判断,如果变量已经存在,则抛出异常,如果变量不存在,则添加到Map中;

get获取变量的值,并且判断变量是否存在,如果存在,则返回变量的值,否则输出错误信息;

set设置变量的值,并且判断变量是否存在,如果存在,则设置变量的值,否则输出错误信息;

下面来看看基本使用

csharp 复制代码
var imitateVar = new ImitateVar();

// 声明
imitateVar.declareVar('myVar', 10);
console.log('看看myVar', imitateVar.get('myVar')); // 10

// 尝试重复声明
if (!imitateVar.declareVar('myVar', 20)) {
  console.log('重复声明失败');
}

console.log('再次看看myVar', imitateVar.get('myVar')); // 10

// 修改变量
imitateVar.set('myVar', 25);
console.log('修改后的myVar', imitateVar.get('myVar')); // 25

上述示例分别进行了创建、重复创建、获取和修改操作,其中imitateVar.declareVar('myVar', 20)如果重复会返回false

小结

这个问题其实考的是基本功是否扎实,能不能快速想到能实现小需求的相应知识点。

varlatconst的不同之处其实还有其它知识点,但这里主要说了一下比较核心的几个知识点,毕竟如果面试官真的问到你这个问题你说出这几个点已经是基本功就好的了。

好听这文章先写到这里,如果那里写的不对或者有更好的建议欢迎指出啊。

相关推荐
负责的蛋挞1 小时前
异步HttpModule的实现方式
java·服务器·前端
丹宇码农3 小时前
把 HLS 字幕玩出花:zwPlayer 如何让 M3U8 视频支持全文搜索、翻译与码率自适应
前端·javascript·音视频·hls·视频播放器
2501_943782353 小时前
【共创季稿事节】猜数字游戏:二分法思维与交互式反馈
前端·游戏·microsoft·harmonyos·鸿蒙·鸿蒙系统
GV191rLvq4 小时前
基于Socket实现的最简单的Web服务器【ASP.NET原理分析】
服务器·前端·asp.net
吠品4 小时前
LangChain 里 tool_call_id 为空?一次 MCP 工具集成的排查记录
前端
微信开发api-视频号协议4 小时前
企业微信二次开发中的文件系统设计:媒体资源、临时文件与业务附件
前端·微信·企业微信·媒体·ipad·微信开放平台
柒和远方4 小时前
Phase 7.4 学习博客:为什么多 API 项目需要 Swagger / OpenAPI
前端·后端·架构
张龙6874 小时前
拼多多开放平台对接踩坑实录:从 CLIENT_ID 配置到 MD5 签名算法的完整填坑指南
前端
GuWenyue4 小时前
提示词彻底过时?一套上下文工程方案,3步让LLM落地生产,代码直接复用
前端·javascript·人工智能