如何用 es5 写 const 和 let

前言

无意逛某论坛,看到某位博主当面试官的经历,说是准备一道简单的面试题,可是面试者几乎没有一个答出来的。我看了下。便是如何用 es5 的方式实现一个 const。我也懵逼了,我去面试肯定也是答不出来的。仔细一想不禁觉得这个题目很妙,于是整理一下如何用 es5 实现一个 const 和 let

实现 let

let 大家应该用的非常熟悉了,定义一个仅作用于该代码块的变量。首先想到的是去 babel 上面在线转换一下。可是得到的结果竟然是 var。这很不科学,于是我们自己倒腾一下。在 es6 出现以前我们一般使用无限接近闭包的形式或者立即执行函数的形式来定义不会被污染的变量。我们这也可以做类似的操作。

1
(function(){var a = 1;console.log(a)})();console.log(a)

效果不错,这大概也是使用 es6 的方便之处吧。

实现 const

进入这次的主题,const 该怎么实现呢?const 声明一个只读的常量。一旦声明,常量的值就不能改变。有什么方法是可以限制一个值不能发生改变的呢?是的,需要用到 Object.defineProperty。其中有一个属性是这样的。writable:当前对象元素的值是否可修改。于是一切都好说的.于 ES5 环境没有 block 的概念,所以是无法百分百实现 const,只能是挂载到某个对象下,要么是全局的 window,要么就是自定义一个 object 来当容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var __const = function __const(data, value) {
window.data = value // 把要定义的data挂载到window下,并赋值value
Object.defineProperty(window, data, { // 利用Object.defineProperty的能力劫持当前对象,并修改其属性描述符
enumerable: false,
configurable: false,
get: function () {
return value
},
set: function (data) {
if (data !== value) { // 当要对当前属性进行赋值时,则抛出错误!
throw new TypeError('Assignment to constant variable.')
} else {
return value
}
}
})
}
__const('a', 10)
console.log(a)
delete a
console.log(a)
for (let item in window) { // 因为const定义的属性在global下也是不存在的,所以用到了enumerable: false来模拟这一功能
if (item === 'a') { // 因为不可枚举,所以不执行
console.log(window[item])
}
}
a = 20 // 报错

代码有点略长,但是还算通透。那。。还有没有更简单一点的实现方法呢?至少不要这么长的代码demo了。答案是肯定的,只不过没有上面例子那么透彻。我们这次将用到es5的Object.freeze();

1
2
3
var f = Object.freeze({'name':'admin'});
f.name = 'hello'; // 严格模式下是会报错的
f.name; // 打印出admin ,值没有被改变

想要的效果还是出来了,可以大概的定义一个常量了。那么一个新的问题来了,const 真的定义的一定是一个常量嘛?一定是不可变的嘛?我们看看代码的结果

1
2
3
const a ={};
a.name='admin';
a.name // admin;

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

带来的新特性

let,const问世之前,js是没有块级作用域这种特性的,只有函数作用域,导致了很多稀奇古怪的bug。这里就要理解两个的区别,函数作用域好理解,就是函数{}包裹的区域,其实这里使用var,let,const都一样,外部是不能调用函数内部的变量的。但是块级就有区别了,最明显的就是if(){}。在函数内使用条件判断,花括号内使用let,const,那么条件外是访问不到条件内的变量的,代码就会更清晰一点。
,

总结

生活中真的很少看到这种清新的题目,发生在身边却从来没有注意到。或许这就是别人为什么是大牛,而我还在这里写着业务代码的原因吧。善于思考与发挥,然后总结于身,这就是我们现在都缺失的吧。。


*作者简介: 张栓,人和未来大数据前端工程师,专注于 html/css/js 的学习与开发。