前言
个人平时喜欢逛逛掘金社区, 逛逛脉脉, 一来看看近期的技术趋势, 二来也了解一下行业内的信息, 而就在昨天, 刚好看到脉脉上一个哥们问到一个问题: "用es5如何实现es6的let和const", 这个问题引起了我的好奇心, 评论区很多人各有各的说法, 但光听只是眼睛会了, 耳朵会了, 手还不会, 就打算自己试试, 这才有了这篇文章
以及个人技术有限, 实现方案可能不是最好的, 算是自己的一个思考吧, 然后记录一下, 有更好的方法的话希望大家能不吝赐教
简介
let和const语法是es6中定义变量的操作符, 作用就像es5中的var一样, 但是又不一样, 具体的介绍以及区别本文就不探讨了, 有需要的小伙伴可以移步阮一峰老师的这本书中查看: ES6 入门教程, 书中介绍地十分详细, 而且还有其他es6相关语法的介绍
核心思路
核心思路在于实现es6的块级作用域和值不可变的特性, 这里的值不可变的特性特指const, 确切的说是同一个内存地址中的数据不可修改
块级作用域
而es5中并没有块级作用域这个概念, 只有函数作用域的概念, 比如如下的一段代码:
function foo() {
var a = 1;
}
console.log(a);
这时浏览器就会报错:Uncaught ReferenceError: a is not defined
同时我们在使用webpack来将模块化的代码做打包操作的时候, 打包之后的代码也是使用函数作用域来隔离作用域, 从而使得我们开发的各个模块代码之间互不影响, 确切的说是使用了IIFE
值不可变
而值不可变的特性主要是要区分基本类型值和我们的复合类型值, const保证的只是指针总是指向相同的地址, 而指向的数据结构是否可变就不可控了, 基本类型值中, 变量指向的地址就是数据保存的地址, 而复合类型值中则不同, 变量指向的地址保存的是指针, 指针指向实际数据的地址, const保证的是指针的指向是固定的, 比如:
const a = 1;
a = 2;
此时浏览器就会报错: Uncaught TypeError: Assignment to constant variable.
但如果是复合类型值, 比如对象, 那么结果就会有所不同:
const obj = {a: 1, b: 2};
obj.c = 3;
console.log(obj); //{a: 1, b: 2, c: 3}
obj = {};
obj.c = 3;这句是可以执行的, 执行之后obj的值从{a: 1, b: 2}变为了{a: 1, b: 2, c: 3}, 可是当我们执行obj = {};这一句的时候就报错了: Uncaught TypeError: Assignment to constant variable.
这是因为一开始, 变量obj的指针指向的是{a: 1, b: 2}, 而我们修改了指针的指向, 让它指向了一个新的值{}, 因此就报错了, 违背了const指针指向不可变的规则
了解了这些之后就可以着手用es5实现es6中的let和const了
实现let
使用IIFE可以方便的创造出一个块级作用域, 而在这个作用域之外的地方访问其中的变量都会报错, 既隔离了作用域, 也防止了变量声明提升的发生:
try{
console.log('变量声明提升a', a);
}catch(error) {
console.error('变量未定义');
}
(function(){
var a = 1;
console.log('内部a:', a);
})();
try{
console.log('外部a:', a);
}catch(error) {
console.error('变量未定义');
}
此时浏览器中的输出结果为:
变量未定义
内部a: 1
变量未定义
这就实现了我们的let
实现const
那么接下来就是实现我们的const了, const除了要创造块级作用域之外还要处理它的值不可变的特性, 这里的值还要区分基础类型值和复合类型值
基础类型值中我使用的方法是定义另一个变量来保存之前的值, if判断如果之前的值被修改了就抛出错误并且再改回来
复合类型值的赋值操作是引用的赋值, 就是变量不同, 但指针指向的是同一个内存地址中的数据, 可以理解为不同变量指向了同一个对象, 以及==或者===是比较的指向是否相同, 相同则返回true, 不同则返回false, 比如:
//两个变量中指针指向的是两个不同的内存地址中的对象, 因为每次变量的声明赋值操作都会开辟新的内存空间,
//所以两次变量声明赋值, 开辟了两个内存空间, 它们的地址是不一样的, 哪怕两个对象里的数据结构是一样的
var obj = {a: 1, b: 2};
var obj2 = {a: 1, b: 2};
console.log(obj == obj2); //false
console.log(obj === obj2); //false
但此时就是相等的了:
var obj = {a: 1, b: 2};
var obj2 = obj; //将obj的对象引用赋值给obj2, 或者说将obj2指向obj
//obj和obj2都指向同一个对象
console.log(obj == obj2); //true
console.log(obj === obj2); //true
最终实现const的代码如下:
try{
console.log('变量声明提升a', a);
}catch(error) {
console.error('变量未定义');
}
(function(){
var a = {a: 1, b: 2};
//对象
if(Object.prototype.toString.call(a) === '[object Object]') {
var b = a;
a.c = 3;
//或者
// a = {};
if(a !== b) {
console.error('变量不能再次赋值');
a = b;
}
console.log(a);
}else{
//基础类型值
var prev = a;
a = 2;
if(a !== prev) {
console.error('变量不能再次赋值');
a = prev
}
console.log(a);
}
})();
try{
console.log('外部a:', a);
}catch(error) {
console.error('变量未定义');
}
好的, 关于如何用es5实现es6中的let和const就和大家分享到这啦, 欢迎大家在评论区和我一起交流探讨, 最后, 如果你觉得这篇文章写得还不错, 别忘了给我点个赞, 如果你觉得对你有帮助, 可以点个收藏, 以备不时之需
参考文献: