章
目
录
JavaScript中let
、const
和var
这几个关键字的区别是常考知识点。其中,let
和const
具有块级作用域、变量不可重复声明等特性,而var
与之不同。今天咱们就来探讨一下,如何用var
模拟实现let
和const
的核心效果。在这之前,先一起回顾下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
可以访问到外层的变量a
和b
,最终输出结果为6
。
(二)函数作用域
函数作用域可以理解为每个函数都是一个独立的“小房间”,在函数内部声明的变量,就像是放在这个“小房间”里的东西,函数外面是无法访问的。看下面这个示例:
function test() {
var age = 123;
}
test();
console.log(age);
运行这段代码,会报错Uncaught ReferenceError: age is not defined
,这是因为在函数test
外面访问其内部声明的变量age
,是不被允许的。
(三)块级作用域
ES6
引入let
和const
关键字后,才有了块级作用域的概念。可以把块级作用域想象成用大括号{}
围起来的一个“小区域”,在这个“小区域”内定义的变量和常量,出了这个“小区域”就访问不到了。对比下面这段代码:
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
的块级作用域效果。立即执行函数会在定义后马上执行,同时创建一个新的作用域,这个作用域里的变量和外部变量相互独立。
先看看常规使用let
或const
的示例:
{
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
的作用域限制在了函数内部,达到了类似let
或const
的块级作用域效果。
三、用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
这段代码利用Proxy
的set
方法,对属性修改进行拦截。当尝试修改指定属性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
声明的变量可以重复声明,后面声明的会覆盖前面的。但let
和const
在同一作用域内不允许重复声明。那怎么用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
模拟let
和const
的核心效果。这道面试题主要考察大家对JavaScript基础知识的掌握程度,能否快速联想到相关知识点来解决这些小需求。var
、let
和const
还有很多其他的不同之处,但这里主要介绍了几个比较核心的知识点。如果在面试中遇到这个问题,能说出这些内容,就说明你的基本功还是不错的。要是文章中有哪里写得不对,或者你有更好的建议,欢迎指出来,咱们一起学习进步。