-
值和类型
- 八种数据类型
- 原始值和引用值
- 访问对象的方式
- 相等与全等运算符
- typeof 和 instanceof
- 深拷贝与浅拷贝
值和类型
八种数据类型
undefined
、null
、boolean
、number
、string
、symbol
、bigint
、object
原始值和引用值
原始值:按值访问。值存储在栈中。变量赋值传递时会创建该值的副本,两个变量(复制与被复制)完全独立。
常见原始值类型:undefined
、null
、boolean
、number
、string
、symbol
、bigint
。
let num1 = 5
let num1 = num2
num2 = 4 // num1此时不发生改变
引用值:按引用地址访问。对象存储在堆中,其引用地址会存储在栈中指向对象。变量赋值传递时会复制其引用地址,两个变量(复制与被复制)会指向同一个对象,对象数据的修改会同时反映到两个变量上。
常见引用类型(object
):Object
、Function
、Array
、Date
、RegExp
等。
let obj1 = { a: 1 }
let obj2 = obj1
obj2.a = 4 // 此时发生改变 obj1.a = 1 => obj1.a = 4
访问对象的方式
-
.
点访问:不支持变量名称和特殊字符。对象不存在的属性,通过点访问赋值后会创建该属性。 -
[]
中括号访问:支持变量名称和特殊字符。对象不存在的属性,通过中括号访问赋值后也会创建该属性。
let a = {}
let key = 'sex'
a.name = '小明' // 对象.属性名
a['age'] = 22 // 对象['属性名']
a[key] = '男' // 对象[变量名]
console.log(a) // { name: '小明', age: 22 }
相等与全等运算符
==
相等运算符:检查其两个操作数是否相等,它会尝试强制类型转换并且比较不同类型的操作数。
===
全等运算符:检查它的两个操作数是否相等, 全等运算符总是认为不同类型的操作数是不同的。
注意:比较对象时,需要考虑其引用地址。也可用toString()
方法转换后比较。
({name: '小红'}).toString() === ({name: '小米'}).toString() => true
相等和不相等操作符转换规则:
- 如果有一个操作数是布尔值,则在比较相等性之前先将其转换为数值——
false
转换为0
,而true
转换为1
; - 如果一个操作数是字符串,另一个操作数是数值,在比较相等性之前先将字符串转换为数值;
- 如果一个操作数是对象,另一个操作数不是,则调用对象的
valueOf()
方法(返回对象原始值),用得到的基本类型值按照前面的规则进行比较;
相等和不相等操作符比较规则:
-
null
和undefined
是相等的。 - 要比较相等性之前,不能将
null
和undefined
转换成其他任何值。 - 如果有一个操作数是
NaN
,则相等操作符返回false
,而不相等操作符返回true
。重要提示:即使两个操作数都是NaN
,相等操作符也返回false
;因为按照规则,NaN
不等于NaN
。 - 如果两个操作数都是对象,则比较它们是不是同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回
true
;否则,返回false
。
typeof 和 instanceof
typeof 操作符
可推断类型(返回值:对应类型字符串)如下:
undefined
、boolean
、stirng
、number
、object
、function
、bigint
注意:null
值表示一个空对象指针,使用typeof
操作符检测null
值时会返回"object"
instanceof 操作符
可推断类型(返回值:布尔值)如下:
Object
、Array
、Date
、Function
、RegExp
、基本包装类型(Boolean
、Number
和String
)、单体内置对象(Global
和Math
)
手写 typeof 和 instanceof 方法
前置知识:原型链、toString
方法
对象toString
方法应用:
- 检测对象类型
let obj = new Number('5')
Object.prototype.toString.call(obj) // '[object Number]'
- 实现进制转换
let num = 5
num.toString(2) // '101'
手写typeof
方法
function myTypeof(value) {
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase()
}
手写instanceof
方法:
function myInstanceof(left, right) {
while (1) {
if (left === null) {
return false
}
if (left === right.prototype) {
return true
}
left = left.__proto__
}
}
说明:变量left
为实例对象,变量right
为构造函数。
深拷贝与浅拷贝
浅拷贝
基本概念:对象的浅拷贝是其属性与拷贝源对象的属性共享相同引用(指向相同的底层值)的副本。
实现方式:
Object.assign()
Object.create()
- 扩展运算符:
{ ...obj }
Array.concat()
Array.slice()
Array.from()
- lodash API :
_.clone()
let obj1 = { a: 2, b: { s: 4 } }
let obj2 = { ...obj1 }
obj2.a = 3 // obj1.a 不发生改变
obj2.b.s = 5 // obj1.b.s 此时发生改变,值更改为5
注意:以上方式仅对对象第一层作深拷贝,无法对多层级对象实现深拷贝
深拷贝
基本概念:对象的深拷贝是指其属性与其拷贝的源对象的属性不共享相同的引用(指向相同的底层值)的副本。
实现方式:
- 递归实现(简版)
function cloneDeep(obj) {
let res = Array.isArray(obj[key]) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && obj[key] === 'object') {
res[key] = cloneDeep(obj[key])
} else {
res[key] = obj[key]
}
}
}
return res
}
-
JSON.parse(JSON.stringify(obj))
-
lodash API :
_.cloneDeep()
let obj1 = { a: 2, b: { s: 4 } }
let obj2 = JSON.parse(JSON.stringify(obj1))
obj2.a = 3 // obj1.a 不发生改变
obj2.b.s = 5 // obj1.b.s 不发生改变
注意:深拷贝会使源和副本互相独立,即对多层级对象能够实现深拷贝
参考
MDN-深拷贝
JavaScript高级程序设计(第4版)