在ES6中,我们都知道letconst声明的变量是块级作用域,只在{}内有效,现在普遍而言,项目中默认使用let和const,避免使用var,最基本的定义就不再进行阐述,网上文章多得满天飞。

重点是,ES6推出了let和const,什么时候用let,什么时候用const?

先说说两者的区别,简单点说人话,就是let声明变量,值可变,const声明一个只读的常量,一旦声明,常量的值就不能改变。再详细点说,const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。这个后面再详细讲。

所以,依据最小原则,一般情况下,默认使用const,只有在循环、值需要改变的场景下,使用let,有的人从头到尾全部使用let也无所谓,但其实很多值无需改变,使用const,更加符合语义化。

下面用几个具体的例子解释这句话 const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动

1、const定义基本数据类型(简单数据类型)

const name = 'JiaJia';

name = 'Tom';

console.log(name);

先使用const定义name的值为'JiaJia',第二行又改变name的值为'Tom',打印name,看看是什么:

意思是name是常量,不可变;

由此可见,使用const定义js中的基本数据类型:字符串类型(Sting),数字类型(Number),布尔类型(Boolean),undefined(变量声明未初始化),null(空对象或理解为空指针),定义后无法变动;

2、const定义引用数据类型(复合类型)

const person = {
    name: "JiaJia"
};

person.name = 'Tom';

console.log(person);

person是一个对象,其中name的值为'JiaJia',接下来修改对象中的name值为'Tom',打印person这个对象,看看具体是什么:

输出的名字为'Tom',值被改变了;

那么由此可见,使用const定义js中的引用数据类型:对象(Object)、数组(Array)、函数(Function),经过修改,值还是会改变;


const不是我们简单认为的只要定义一个值,它就不会变, const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。

那下面具体讲一讲,为什么const定义基本数据类型,值不能被改变,而定义引用数据类型,值可以被改变,其实关键在于栈内存堆内存

在JS中,栈内存用于存储基本型的变量值,堆内存用于存储引用型的值。因为JS这门语言和其他语言有一个不同之处:不允许直接访问内存的位置,也就是说不能直接操作对象的内存空间,操作的是对象的引用而已,那么引用可以理解为是一个指针,是一个具体的堆内存的地址。

先来定义几个值,后面画图进行详细说明:

//字符串
const _str = 'JiaJia';

//数字
const _num = 99;

//布尔值
const _boolean = true;

//null
const _null = null;

//undefined
const _undefined = undefined;

//数组
const _arr = [1, 2, 3, 4, 5];

//对象
const _obj = {name: "Tom"};

上面代码在声明变量的时候,在电脑内存中就发生了以下事情:(纯手画,不抄袭)

可以看到,像String、Number等基本类型,声明后,变量都是存储在栈内存当中,存储的就是我们定义的值本身。

所以当我们使用了const定义基本类型,随后进行二次修改的话,栈内存当中存储的值是不可被改变的;

当我们使用了const定义引用数据类型时,实际在栈内存中存储的是一个指针,说简单点可以认为存储的是一个地址,地址的名字类似0x0012ff7c,而这个地址,又指向了堆内存中的一个区域,区域中才存放着我们当时声明的具体的数组或对象,如[1, 2, 3, 4, 5],{name: "Tom"}等;

那么就算我们使用了const声明了一个数组或对象,实际上const保证的是栈内存中0x0012ff7c的指针(地址)不被改变,而堆内存中真正的值,是可以被改变的,所以在之前的例子中,数组和对象的值可以被修改;

我们再来看一个例子:

const _obj_1 = {name: "JiaJia"};

const _obj_2 = _obj_1;

_obj_1.name = 'Tom';

console.log(_obj_2);

先声明一个名为 _obj_1 的对象,再声明一个 名为 _obj_2 的对象,接下来改变 _obj_1 对象的值,打印 _obj_2,最终会输出什么?

答案:

先定义了 _obj_2,后面才改变 _obj_1的值,为什么 _obj_2 也受到了影响?

在JS中访问变量时,是把变量名作为索引来寻找值的,无论是基本型还是引用型。也就是说,访问变量的过程就是:通过变量名找到栈内存中存储的具体的值,如果是基本型,直接就返回值,如果是引用型,返回一个指向堆内存的地址。

所以给 _obj_2 赋值,其实给的是栈内存中,_obj_1的指针(地址),即0x0012ff8b,那么_obj_1和_obj_2在栈内存中的地址是一样的,那么最终指向堆内存中的区域也是一样的,相当于两者共用了堆内存中地址为0x0012ff8b的区域

那么无论是改变_obj_1的值还是_obj_2的值,另一个肯定会跟着变,因为大家共用了值。


讲了这么多,肯定有人要掀桌子,既然const不能真正意义上将值变为只读,那要你有何用!

经过查资料,ES6 的编辑 Allen Wirfs-Brock 曾说过,如果能重来一次,他希望把 let 设计成像现在的 const 一样,而新增一个比如 let var 代替现在的 let。但因为 const 这个保留字在 JS 刚发明的时候就从 Java 抄过来了,let/const 这对儿关键字在制定 ES5 的时候就计划在 ES6 里用了,所以 ES6 里也就没再改了。(你现在后悔想重来一次?当初为什么不把握机会呢?这就是渣男)

其实目前这种状况,如果能确定一个值不再被更改,那么果断使用const,如果该值有可能被修改,就使用let,毕竟语义化的命名,能更好地让别人理解编写者的意图。


扩展内容:JS中如何比较两个数组的内容是否相同?用 == 或 === 是否可行?

const a = [1,2,3,4,5];
const b = a.slice();
console.log(`数组a: ${a}`);
console.log(`数组b: ${b}`);
console.log(`==比较a和b: ${a == b}`);
console.log(`===比较a和b: ${a == b}`);
console.log(`==比较[]和[]: ${[] == []}`);
console.log(`===比较[]和[]: ${[] === []}`);

slice() 方法返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变;

看看执行结果:

其实这里比较的是栈内存中的地址,slice方法进行浅拷贝,b是新生成的数组,实际上比较的是栈内存中的两个地址,如0x0012ff7c0x0012ff8b,所以肯定是不相同的,那么数组该如何比较值是否相同呢?

/**
 * @description: 数组比较
 * @param {Array} arrA 数组A
 * @param {Array} arrB 数组B
 * @return {Boolean} true:两个数组的内容相同  false:两个数组的内容不同 
 */
const equar = (arrA, arrB) => {
    // 判断数组的长度
    if (arrA.length !== arrB.length) {
        return false;
    } else {
        // 循环遍历数组的值进行比较
        for (let i = 0; i < arrA.length; i++) {
            if (arrA[i] !== arrB[i]) {
                return false;
            }
        }
        return true;
    }
}

console.log(equar([1,2,3] ,[1,2,3]));
console.log(equar([1,2,3] ,[1,'2',3]));

看看输出的结果:

[1,2,3]和[1,'2',3]肯定不相同,输出false;

殊途同归,最终还是要看计算机原理,才能彻底明白出现某些问题的原因。