变量类型和计算
值类型 VS 引用类型
值类型
let a = 100;
let b = a;
a = 200;
console.log(b); // 100
引用类型
let a = { age: 20 };
let b = a;
b.age = 21;
console.log(a.age); // 21
值类型各论各,引用类型 b 改变后,a 也会随之改变
值类型和引用类型的区别
无论是函数环境还是全局环境,值类型都是通过在栈中存储的,栈是我们计算机中的一个内存结构,它具体什么样子的大家可以先不用理解,只知道它是存储变量的地方,这块是 js 引擎来帮我们去实现的,不用管这个地方。
在栈中我们写了一个 key value,一个抽象的表述。key 表示名字,value 表示值。
第一步 把 a 赋值成 100
第二步 把 b 赋值成 a 因为 a 等于 100 所以 b 也是等于 100
第三步 把 a 赋值成 200 这个时候把 a 改成 200 b 还是 100 所以说这种情况也比较符合主观意向 比较符合我们普通思维 b 和 a 是不一样的 他们两个相互赋值 然后 a 改变以后 b 不改变 这个没问题 然后我们再看第二个例子 稍微复杂一点
栈还是那个栈 我们又引入了一个东西叫做 堆 也就是说我们在计算机变量存储的时候 栈和堆是同时存在的 栈在上面 栈是从上往下累加 大家可以看到 a 和 b 从上往下一层层的叠加
堆是从下往上一层层的叠加,只有一个 key 如果有两个 key 它会往上叠加 那么他们两个之间会不会重合呢?可能会重合,在异常情况下可能会重合,但是我们的操作系统一般情况下分配的内存是够我们的程序使用的 所以说栈从上往下排列 堆从下往上排列 一般情况下不会重合 大家不需要考虑 那我们的引用类型就不一样了 比如说我们把 a 赋值成一个 age 等于 20 的一个对象 它会怎么办呢 它会在堆中申请一个内存地址,然后把 age 等于 20 放在堆里面 然后它的 key 会给一个内存地址 然后这个时候变量 a 指向的是一个内存地址 1 也就是说这个时候 a 里面存储的并不是对象 而是存了一个内存地址 内存地址指向这个对象 跟上面就不同了。
再往下 b 赋值成 a, a 指向是这个内存地址 1 ,b 也指向这个内存地址 1,看似 a 是等于 age:20,b 也等于 age:20。这个没有毛病,因为他们两个都是指向的这个内存地址 1 ,接下来 b 的 age 赋值成 21,b 指向内存地址 1,b.age 赋值成 21 的话 它肯定会把堆中的地方改成 21 ,所以说这个时候再去访问 a.age, 因为 a 也是指向的内存地址 1,所以它也变成了 21
所以我们从 js 引擎去解析变量,堆栈模型,我们就能很明白的看出来 值类型 或者 引用类型 它们的现象和本质,大家要理解这个本质的问题。 那我们知道了现象本质之后 我们还要知道 为什么值类型是这样的赋值形式,为什么引用类型必须赋值内存地址 为什么呢?
原因还是考虑到性能以及存储的问题,因为值类型的占用空间比较少,所以它直接可以放在栈中赋值没问题,赋值的时候对内存不会造成太大的影响。
引用类型就不一样了,虽然我们这里只写了 age:20,但是大家可以想一下一个 json 一个对象它可能非常大,如果遇到这种情况,我们直接放在栈中会导致存储地址过大 不好管理 再加上我们复制的时候 会导致复制的过程中非常慢 ,所以说 计算机所有的程序 所有的代码 所有的语言都是采用这种方式,值类型和引用类型严格区分,他们的存储机制 赋值机制 拷贝机制 也是严格的区分开来,它是基于内存空间和 cpu 的计算耗时来做区分的,它并不是故意为难你 它是不得已为之
所以我们怎么去像值类型赋值一样去把引用类型也去像这种方式来赋值呢,那就是深拷贝(现在先不说),现在我们只需要知道引用类型表示的一个现象是什么,以及它的一个本质是什么,以及它为什么会出现这种本质。js 引擎是怎么思考的 大家要明白
// 常见的值类型
let a; // undefined(此处需要注意 如果用const赋值undefined必须定义 如果是let可忽略不写)
const str = "abc";
const n = 100";
const b = true;
const s = Symbol("s")
// 常见引用类型
const obj = {x:100}
const arr = ['a','b','c']
const n = null // 特殊引用类型,指针指向为空地址
// 特殊引用类型 但不用于存储数据 所以没有拷贝 复制函数这一说
function fn() {}
typeof 能判断哪些类型
1.识别所有的值类型
let a; typeof a // undefined
const str = "abc"; typeof str // string
const n = 100; typeof n // number
const b = true; typeof b // boolean
const s = Symbol("s") typeof s // symbol
2.识别函数
typeof console.log; // function
typeof function () {}; // function
3.判断是否是引用类型(不可再细分)
typeof { x: 100 }; // object
typeof ["a", "b", "c"]; // object
typeof null; // object
关于引用类型如何区分,等讲到原型链的时候再说
手写深拷贝
注意判断值类型和引用类型
注意判断是数组还是对象
递归
/**
* 深拷贝
*/
const obj1 = {
age: 20,
name: "Jason",
address: {
city: "beijing",
},
arr: ["a", "b", "c"],
};
const obj2 = deepClone(obj1);
obj2.address.city = "shanghai";
obj2.arr[0] = "a1";
console.log(obj1.address.city);
console.log(obj1.arr[0]);
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj = {}) {
if (typeof obj !== "object" || obj == null) {
// obj 是 null 或者不是对象和数组 直接返回
return obj;
}
// 初始化返回结果
let result;
if (obj instanceof Array) {
result = [];
} else {
result = {};
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用
result[key] = deepClone(obj[key]);
}
}
// 返回结果
return result;
}
变量计算 - 类型转换
字符串拼接
const a = 100 + 10; // 100
const b = 100 + "10"; // '10010'
const c = true + "10"; // 'true10'
这个比较简单 不多说 但是这种情况大家也一定要注意
等号运算符
==
100 = "100"; // true
0 == ""; // true
0 == false; // true
false == ""; // true
null == undefined; // true
两等会尽量的去隐式转换让它们相等 可能会造成一些莫名其妙的 bug 那么这种情况我们应该怎么办呢 请看下文
===
记住一个规则:除了==null 之外,其他都一律用 === ,例如:
const obj = { x: 100 };
if (obk.a == null) {
}
// 相当于:
if (obj.a === null || obj.a === undefined) {
}
你如果是在看一下 jQuery 的源码或者你经常用 eslint 它也是这样一种规则,所以这种规则在实际开发中都是用的这种规则
if 语句和逻辑运算
这里也是经常会发生一个类型转换的问题,首先我们来了解一个概念 truly 变量和 falsely 变量
truly 变量: !!a === true 的变量
falsely 变量: !!a === false 的变量
就是说一个真的变量和假的变量 这是一个翻译过来的意思 但是我感觉翻译过来没有那么清晰直白
什么叫 truly 变量呢 truly 变量不是 true falsely 变量也不是 falsely 是我们经过两步非运算,如果他是 true 就是 true,如果它是 false 两步非运算就是 flasely 变量,我们也可以试一下
// 以下是控制台
const n = 100;
// undefined;
n;
// 100;
!n;
// false;
!!n;
// true;
n 等于 100,然后一步非运算出来一个布尔值 给他取反,两步非运算出来一个布尔值,这个布尔值属于没有取反的布尔值的本身 比如我们定义一个 n1 等于 0 两步非运算就是 false 一步非运算就会 true 。两步非运算,如果他是 true 就是 true,如果它是 false 两步非运算就是 flasely 变量 我们再说一个 如果是!!null 就是 falsely 变量 !!"" 肯定也是 falsely 变量 如果是 undefined 肯定也是 falsely 变量 如果是个对象 !!{} 就是 truly 变量
// 以下是falsely变量。除此之外都是truly变量
!!0 === false;
!!NaN === false;
!!"" === false;
!!null === false;
!!undefined === false;
!!false === false;
if 语句判断的并不是 true 和 false 而是判断的 truly 变量和 falsely 变量
// truly变量
const a = true;
if (a) {
}
const b = 100;
if (b) {
}
// false变量
const c = "";
if (c) {
}
const d = null;
if (d) {
}
let e;
if (e) {
}
逻辑判断
console.log(10 && 0); // 0
// 因为10是一个truly变量 继续往后判断 返回第二个值
// 如果我们倒过来 0 && 10 就会返回0 因为0是一个falsely变量就直接返回了 因为与运算就是这么一个规则
console.log("" || "abc"); // 'abc'
// 空字符串是falsely变量 所以说falsely也会继续判断往后反而第二个值
// 如果我们把这个也倒过来 就会直接返回'abc' 因为abc是一个truly变量 直接返回
console.log(!window.abc); // true
// 非就是直接取反