用es5如何实现let const

745 阅读5分钟

前言

个人平时喜欢逛逛掘金社区, 逛逛脉脉, 一来看看近期的技术趋势, 二来也了解一下行业内的信息, 而就在昨天, 刚好看到脉脉上一个哥们问到一个问题: "用es5如何实现es6的let和const", 这个问题引起了我的好奇心, 评论区很多人各有各的说法, 但光听只是眼睛会了, 耳朵会了, 手还不会, 就打算自己试试, 这才有了这篇文章

以及个人技术有限, 实现方案可能不是最好的, 算是自己的一个思考吧, 然后记录一下, 有更好的方法的话希望大家能不吝赐教

简介

letconst语法是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中的letconst

实现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中的letconst就和大家分享到这啦, 欢迎大家在评论区和我一起交流探讨, 最后, 如果你觉得这篇文章写得还不错, 别忘了给我点个赞, 如果你觉得对你有帮助, 可以点个收藏, 以备不时之需

参考文献:

  1. ES6 入门教程
  2. IIFE(立即调用函数表达式)