JavaScript中letconstvar这几个关键字的区别是常考知识点。其中,letconst具有块级作用域、变量不可重复声明等特性,而var与之不同。今天咱们就来探讨一下,如何用var模拟实现letconst的核心效果。在这之前,先一起回顾下JavaScript的三种作用域。

一、JavaScript的三种作用域回顾

(一)全局作用域

全局作用域就像是一个“大广场”,在这个“广场”里声明的变量,所有地方都能访问到。简单来说,那些没有被包含在任何函数或者大括号里声明的变量,都处于全局作用域。比如下面这个例子:

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

在这个代码里,变量a和函数test都在最外层,属于全局作用域。内层函数testbar可以访问到外层的变量ab,最终输出结果为6

(二)函数作用域

函数作用域可以理解为每个函数都是一个独立的“小房间”,在函数内部声明的变量,就像是放在这个“小房间”里的东西,函数外面是无法访问的。看下面这个示例:

function test() { var age = 123; } test(); console.log(age); 

运行这段代码,会报错Uncaught ReferenceError: age is not defined ,这是因为在函数test外面访问其内部声明的变量age,是不被允许的。

(三)块级作用域

ES6引入letconst关键字后,才有了块级作用域的概念。可以把块级作用域想象成用大括号{}围起来的一个“小区域”,在这个“小区域”内定义的变量和常量,出了这个“小区域”就访问不到了。对比下面这段代码:

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

运行结果中,age可以正常输出123,但访问age2时会报错Uncaught ReferenceError: age2 is not defined。这表明,var声明的变量没有块级作用域,而let声明的变量有块级作用域。

二、用var模拟let的块级作用域

提到JavaScript的作用域,除了let,大家应该能想到函数作用域。利用立即执行函数(IIFE),可以模拟出let的块级作用域效果。立即执行函数会在定义后马上执行,同时创建一个新的作用域,这个作用域里的变量和外部变量相互独立。

先看看常规使用letconst的示例:

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

在这个例子里,在块级作用域外面访问x会报错is not defined

再看用var模仿的效果:

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

这里用立即执行函数把var x = 88包起来,就把x的作用域限制在了函数内部,达到了类似letconst的块级作用域效果。

三、用var模拟const声明变量后不可变

实现用var模拟const声明变量后不可变,有几种方法,下面给大家详细介绍。

(一)使用Object.defineProperty

Object.defineProperty这个API可以用来定义对象的属性特性。如果对它不太熟悉,可以去深入学习一下。下面看个例子:

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,就使得属性val不可变,即使尝试修改它,也不会生效。需要注意的是,在严格模式下,如果尝试修改不可写的属性,会抛出TypeError错误。

(二)Proxy代理拦截

Proxy代理也是一个很有用的API。下面通过代码示例来看看如何用它实现变量不可变:

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 

这段代码利用Proxyset方法,对属性修改进行拦截。当尝试修改指定属性value时,直接返回false,从而禁止了修改操作。

(三)用闭包实现

闭包也能实现类似的效果,看下面的代码:

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方法。这样,当尝试修改testConst.value时,因为没有对应的设置方法,所以修改不会生效。

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

我们都知道,用var声明的变量可以重复声明,后面声明的会覆盖前面的。但letconst在同一作用域内不允许重复声明。那怎么用var模拟这个效果呢?可以借助Map()来实现。通过封装一个类,能达到模拟的目的。下面是基本的封装代码:

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对象,这个Map用来存储变量。
  • declareVar方法用于声明变量。它会先检查变量是否已经存在于Map中,如果存在,就返回false,表示不能重复声明;如果不存在,就把变量添加到Map中,并返回true
  • get方法用于获取变量的值。它会先判断变量是否存在,如果存在,就返回对应的值;否则返回undefined
  • set方法用于设置变量的值。同样会先检查变量是否存在,存在的话就设置新值并返回true,不存在则返回false

下面看看这个类的基本使用:

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 

在这个示例中,分别进行了变量的声明、重复声明尝试、获取和修改操作。当尝试重复声明myVar时,imitateVar.declareVar('myVar', 20)会返回false,表示重复声明失败。

五、总结

通过上面的方法,我们实现了用var模拟letconst的核心效果。这道面试题主要考察大家对JavaScript基础知识的掌握程度,能否快速联想到相关知识点来解决这些小需求。varletconst还有很多其他的不同之处,但这里主要介绍了几个比较核心的知识点。如果在面试中遇到这个问题,能说出这些内容,就说明你的基本功还是不错的。要是文章中有哪里写得不对,或者你有更好的建议,欢迎指出来,咱们一起学习进步。