变量类型
typeof 和 instanceof 的区别?
typeof 对于string
,boolean
,number
,undefined
,function
,symbol
等类型可正确判断
对于null
,array
,object
判断结果均为 object
特殊的对于 null
,null
不是一个对象,尽管 typeof null
输出的是 object
,这是一个历史遗留问题,JS
最初为了性能使用低位存储变量的 类型信息 ,000
开头代表是对象,null
表示为全零,所以将它错误的判断为 object
instanceof 代码形式为*object instanceof constructor
(***object 是否是 constructor 的实例****),该操作符用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上(可用于判断引用类型)
何时使用 === 何时使用 ==
除了 == null 之外 其它地方一律用===
const obj = { a: 2 }
if (obj.b == null) {
// 相当于 if(obj.b===null||obj.b===undefined)
console.log('b')
}
原始值和引用值的区别
内存的分配不同
- 原始值存储在栈中
- 引用值存储在堆中,栈中存储的变量,是指向堆中的引用地址
访问机制不同
- 原始值是按值访问
- 引用值按引用访问,JavaScript 不允许直接访问保存在堆内存中的对象,在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象的值
复制变量时不同
- 原始值:a=b;是将 b 中保存的原始值的副本赋值给新变量 a,a 和 b 完全独立,互不影响
- 引用值:a=b;将 b 保存的对象内存的引用地址赋值给了新变量 a;a 和 b 指向了同一个堆内存地址,其中一个值发生了改变,另一个也会改变
比较变量时不同
-
原始值:==比较值是否相等(先进行类型转换再确定操作数是否相等—引自 js 高级程序设计(第四版) P71),===不仅比较值是否相等,还会比较数据类型是否相同
-
引用数据类型:不管是 == 还是 === ,都是比较内存地址是否相同,即比较是否都指向同一个对象
参数传递的不同
函数传参都是按值传递(栈中的存储的内容):原始值,拷贝的是值;引用值,拷贝的是引用地址
手写深拷贝
仅仅是解决了深复制的关键问题,还需要针对不同的数据类型进行完善,
lodash
的深拷贝针对不同的数据类型进行了处理
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
// 判断为数组
result = []
} else {
result = {}
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 保证key不是原型的属性 for...in的问题
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
function deepCopy(target) {
let copyed_objs = [] //此数组解决了循环引用和相同引用的问题,它存放已经递归到的目标对象
function _deepCopy(target) {
if (typeof target !== 'object' || !target) {
return target
}
for (let i = 0; i < copyed_objs.length; i++) {
if (copyed_objs[i].target === target) copyed_objs[i].copyTarget
}
let obj = {}
if (Array.isArray(target)) obj = [] //处理target是数组的情况
copyed_objs.push({ target: target, copyTarget: obj })
Object.keys(target).forEach(key => {
obj[key] = _deepCopy(target[key])
})
return obj
}
return _deepCopy(target)
}
JSON.sringify 和 JSON.parse 方法拷贝的缺陷
这是 JS
实现深拷贝最简单的方法了,原理就是先将对象转换为字符串,再通过 JSON.parse
重新建立一个对象。 但是这种方法的局限也很多:
- 不能复制
function
、正则、Symbol
- 循环引用(当对象 1 中的某个属性指向对象 2,对象 2 中的某个属性指向对象 1 就会出现循环引用)报错
- 相同的引用会被重复拷贝
let obj = { asd: 'asd' }
let obj2 = { name: 'aaaaa' }
obj.ttt1 = obj2
obj.ttt2 = obj2
let cp = JSON.parse(JSON.stringify(obj))
obj.ttt1.name = 'change'
cp.ttt1.name = 'change'
console.log(obj, cp)
对于上面的代码,原对象改变 ttt1.name
也会改变 ttt2.name
,因为他们指向相同的对象。但是,复制的对象中,ttt1
和 ttt2
分别指向了两个对象。拷贝的对象没有保持和原对象一样的结构。因此,JSON
实现深拷贝不能处理指向相同引用的情况,相同的引用会被重复拷贝