JavaScript学习笔记(八) 数据类型
1、数据类型
(1)六种数据类型
JavaScript 存在六种数据类型,分别是 Number,String,Boolean,Null,Undefined 和 Object
除了 Object 是引用类型之外,其余都是原始类型(又称基本类型),其中 Null 和 Undefined 是比较特别的两个
(2)内存模型
当一个方法执行时,会建立一个内存栈,这个方法中定义的变量都会放入栈中,方法调用完成,则栈随即销毁
栈中存放的是原始类型的值以及引用类型的引用变量,引用变量的值指向堆内存中的对象地址
当对象被创建时,引用变量储存在栈中,对象本身储存在堆中,也就是说对象不会随着方法调用结束而被销毁
实际上对象的销毁由垃圾回收机制决定,只有当对象没有被任何引用变量引用时,才会被销毁
(3)按值传递
在 JavaScript 中所有的函数参数都是按值传递的,但是由于内存模型的原因,会出现一些有趣的行为
-
当我们在函数中修改基本变量时,不会修改传入的值
-
当我们在函数中修改引用变量时,将会修改传入的值(根据内存模型的解释,想一想是为什么)
let number = 123 // 基本类型
let object = { // 引用类型
name: 'Steve',
age: 18
}
function changeNumber(num) {
num = 456
}
function changeObject(obj) {
obj.age = 20
}
changeNumber(number)
changeObject(object)
console.log(number)
console.log(object.age)
/*
* 执行结果:
* 123
* 20
**/
2、类型检测
(1)typeof
用于检测一个变量的类型,需要注意的是,对于 null 和 array 类型的变量,typeof 返回 object
另外,typeof 对于几乎所有类型的对象都会返回 object,也就是说无法使用 typeof 准确判断一个对象的类型
let a = 0
let b = 'hello'
let c = true
let d = null
let e = undefined
let f = {}
let g = []
let h = function(){}
console.log('a: ' + typeof a)
console.log('b: ' + typeof b)
console.log('c: ' + typeof c)
console.log('d: ' + typeof d)
console.log('e: ' + typeof e)
console.log('f: ' + typeof f)
console.log('g: ' + typeof g)
console.log('h: ' + typeof h)
/*
* 执行结果:
* a: number
* b: string
* c: boolean
* d: object
* e: undefined
* f: object
* g: object
* h: function
**/
(2)Object.prototype.toString.call()
用于检测一个变量的类型,可以解决 typeof 不能检测 null 和 array 的问题
let a = 0
let b = 'hello'
let c = true
let d = null
let e = undefined
let f = {}
let g = []
let h = function(){}
function typeOf(value) {
return Object.prototype.toString.call(value).slice(8, -1)
}
console.log('a: ' + typeOf(a))
console.log('b: ' + typeOf(b))
console.log('c: ' + typeOf(c))
console.log('d: ' + typeOf(d))
console.log('e: ' + typeOf(e))
console.log('f: ' + typeOf(f))
console.log('g: ' + typeOf(g))
console.log('h: ' + typeOf(h))
/*
* 执行结果:
* a: Number
* b: String
* c: Boolean
* d: Null
* e: Undefined
* f: Object
* g: Array
* h: Function
**/
(3)instanceof
用于判断某个实例是否属于某种类型,可以解决 typeof 不能准确判断对象类型的问题
var Message = function(descrition) { // 构造函数
this.detail = descrition
}
let message = new Message('Hello')
let object = new Object()
console.log(message instanceof Message)
console.log(message instanceof Object)
console.log(object instanceof Message)
console.log(object instanceof Object)
console.log(Message instanceof Object)
/*
* 执行结果:
* true
* true
* false
* true
* true
**/
instanceof 的原理是判断右边对象的原型对象是否存在于左边对象的原型链上
明白了原理后,我们也能自己实现一个 instanceof
function myInstanceOf(left, right) {
let lValue = left.__proto__
let rValue = right.prototype
while (true) {
if (lValue === null) return false
if (lValue === rValue) return true
lValue = lValue.__proto__
}
}
3、类型转换
(1)强制类型转换
① String -> Number:parseInt(string[, radix])
& parseFloat(string)
parseInt
用于将字符串解析为整数,可以指定基数;parseFloat
用于将字符串解析为浮点数
两者的原理大致相同,都是从第一个非空格字符开始解析,直至字符串末尾或者遇到一个无效的数字字符
若解析成功,则返回数字;若第一个非空格字符不是数字或符号,则返回 NaN
let a = parseInt(' 123abc')
let b = parseFloat(' 123.456abc')
console.log(a)
console.log(b)
/*
* 执行结果:
* 123
* 123.456
**/
② Any -> Number:Number()
转换规则如下:
值 | 转换规则 |
---|---|
Number | 直接输出 |
String | 若字符串为空,则转换成 0 若字符串中包含有效的整数格式,则转换成十进制数 若字符串中包含有效的浮点格式,则转换成浮点数值 若字符串中包含有效的十六进制格式,则转换成十六进制数 其余情况,转换成 NaN |
Boolean | 若为 true ,转换成 1 若为 false,转换成 0 |
Null | 转换成 0 |
Undefined | 转换成 NaN |
Object | 调用 Object 的 valueOf() 方法,按照上述规则转换若为 NaN,调用 Object 的 toString() 方法,按照上述规则转换 |
let a = Number(123)
let b = Number('456')
let c = Number(true)
let d = Number(false)
let e = Number(null)
let f = Number(undefined)
let g = Number(new Date())
let h = Number(new Array())
console.log(a)
console.log(b)
console.log(c)
console.log(d)
console.log(e)
console.log(f)
console.log(g)
console.log(h)
/*
* 执行结果:
* 123
* 456
* 1
* 0
* 0
* NaN
* 1578556849729
* 0
**/
③ Any -> String:String()
转换规则如下:
值 | 转换规则 |
---|---|
Number | 转换成数字值 |
String | 直接输出 |
Boolean | 若为 true ,转换成 true 若为 false,转换成 false |
Null | 转换成 null |
Undefined | 转换成 undefined |
Object | 按特定的规则转换 |
let a = String(123)
let b = String('asdf')
let c = String(true)
let d = String(false)
let e = String(null)
let f = String(undefined)
let g = String({})
let h = String([123, 'asdf', true, false, null, undefined])
console.log(a)
console.log(b)
console.log(c)
console.log(d)
console.log(e)
console.log(f)
console.log(g)
console.log(h)
/*
* 执行结果:
* 123
* asdf
* true
* false
* null
* undefined
* [object Object]
* 123,asdf,true,false,,
**/
(2)自动类型转换
① 条件判断
在进行条件判断时,会将变量的值按照规则自动转换成布尔值,转换的规则如下:
值 | 转换规则 |
---|---|
Number | 0、NaN 转换成 false 其余情况转换成 true |
String | 空字符串转换成 false 其余情况转换成 true |
Null | 转换成 false |
Undefined | 转换成 false |
Object | 转换成 true |
let a = 123
let b = ''
let c = null
let d = undefined
let e = {}
a ? console.log('true') : console.log('false')
b ? console.log('true') : console.log('false')
c ? console.log('true') : console.log('false')
d ? console.log('true') : console.log('false')
e ? console.log('true') : console.log('false')
/*
* 执行结果:
* true
* false
* false
* false
* true
**/
② 数值运算
-
对于
+
操作符,转换规则如下:若操作数都不是字符串或对象,则通过
Number()
函数转换成 Number 类型若操作数中存在字符串或对象,则通过
String()
函数转换成 String 类型
let a = 1 + true
let b = 1 + null
let c = 1 + undefined
let d = '1' + true
let e = '1' + null
let f = '1' + undefined
let g = '1' + 1
let h = {} + true
let i = [] + false
console.log(a)
console.log(b)
console.log(c)
console.log(d)
console.log(e)
console.log(f)
console.log(g)
console.log(h)
console.log(i)
/*
* 执行结果:
* 2
* 1
* NaN
* 1true
* 1null
* 1undefined
* 11
* [object Object]true
* false
**/
- 对于
-
、*
、/
、%
操作符,都先通过Number()
函数转换成数值再进行运算
基于数值运算的隐式转换规则,我们可以得到数字与字符串相互转换的方便捷径
// number -> string
let number = 123
let string = number + ''
console.log(typeof string)
console.log(string)
/*
* 执行结果:
* string
* 123
**/
// string -> number
let string = '123'
let number = string - 0
console.log(typeof number)
console.log(number)
/*
* 执行结果:
* number
* 123
**/
③ 相等运算
在使用 ==
运算符比较两个变量是否相等时,如果变量的类型不同,则会先进行隐式转换后再比较,规则如下:
顺序 | 变量 1 | 变量 2 | 操作 |
---|---|---|---|
1 | NaN | * | 返回 false |
2 | Null | Undefined | 返回 true |
3 | Null | Null | 返回 true |
4 | Undefined | Undefined | 返回 true |
5 | Null | 除 Null、Undefined 外 | 返回 false |
6 | Undefined | 除 Null、Undefined 外 | 返回 false |
7 | Boolean | * | 将 Boolean 转换成 Number 后比较 |
8 | Object | Object | 当两个对象指向同一内存地址时,才会相等 |
9 | Object | Number、String | 将 Object 转换成原始值 (Number/String) 后比较 首先使用 valueOf() 方法若对象能转换成原始值,则返回结果 否则使用 toString() 方法若对象能转换成原始值,则返回结果 否则抛出 TypeError 异常 |
10 | Number | Number | 直接比较 |
11 | String | String | 直接比较 |
12 | String | Number | 将 String 转换成 Number 后比较 |
练习几道题目:
false == 0 // true
false == '0' // true
false == [] // true
false == {} // false
123 == [123] // true
2 == { valueOf(){ return 2 }} // true
1 == { valueOf: function(){ return [] }, toString: function(){ return 1 }} // true
由于使用 ==
进行判断的时候会进行隐式转换,所以在项目中一般使用 ===
4、赋值、浅拷贝和深拷贝
对于基本类型,赋值、浅拷贝和深拷贝都是将旧变量的值 复制 给新变量,新旧变量之间互不影响
let number = 159
let string = 'hello'
let number_copy = number
let string_copy = string
number_copy = 258
string_copy = 'hi'
console.log('number: ' + number) // 原变量的值不会改变
console.log('string: ' + string) // 原变量的值不会改变
console.log('number_copy: ' + number_copy)
console.log('string_copy: ' + string_copy)
/*
* 执行结果:
* number: 159
* string: hello
* number_copy: 258
* string_copy: hi
**/
对于引用类型:
- 赋值后新旧变量指向同一个内存地址,此时改变新变量会影响旧变量的值
let object = { first_name: 'Steve', last_name: 'Jobs' }
let array = [1, 3, 5]
let object_copy = object
let array_copy = array
object_copy['first_name'] = 'Steven'
array_copy[0] = 2
console.log('object["first_name"]: ' + object['first_name']) // 原变量的值发生改变
console.log('array[0]: ' + array[0]) // 原变量的值发生改变
console.log('object_copy["first_name"]: ' + object_copy['first_name'])
console.log('array_copy[0]: ' + array_copy[0])
/*
* 执行结果:
* object["first_name"]: Steven
* array[0]: 2
* object_copy["first_name"]: Steven
* array_copy[0]: 2
**/
- 浅拷贝得到的新变量,是将旧变量的第一层数据 赋值 给新变量
// 对于对象,常用的浅拷贝方式有:Object.assign
// 对于数组,常用的浅拷贝方式有:Array.from、slice、concat
let object = {
name: {
first_name: 'Steve',
last_name: 'Jobs'
},
age: 18
}
let array = [[1, 3], 5, 7]
let object_shallow_copy = Object.assign({}, object)
let array_shallow_copy = Array.from(array)
object_shallow_copy['name']['first_name'] = 'Steven'
object_shallow_copy['age'] = 20
array_shallow_copy[0][0] = 2
array_shallow_copy[2] = 11
console.log('object["name"]["first_name"]: ' + object['name']['first_name'])
console.log('object["age"]: ' + object['age'])
console.log('array[0][0]: ' + array[0][0])
console.log('array[2]: ' + array[2])
console.log('object_shallow_copy["name"]["first_name"]: ' + object_shallow_copy['name']['first_name'])
console.log('object_shallow_copy["age"]: ' + object_shallow_copy['age'])
console.log('array_shallow_copy[0][0]: ' + array_shallow_copy[0][0])
console.log('array_shallow_copy[2]: ' + array_shallow_copy[2])
/*
* 执行结果:
* object["name"]["first_name"]: Steven
* object["age"]: 18
* array[0][0]: 2
* array[2]: 7
* object_shallow_copy["name"]["first_name"]: Steven
* object_shallow_copy["age"]: 20
* array_shallow_copy[0][0]: 2
* array_shallow_copy[2]: 11
**/
自己来实现一个浅拷贝函数:
function shallowCopy(object) {
if (typeof object !== 'object') return
let newObject = object instanceof Array ? [] : {}
for (let key in object) {
if (object.hasOwnProperty(key)) {
newObject[key] = object[key]
}
}
return newObject
}
- 深拷贝得到的新变量,是将旧变量的所有数据(无论嵌套多深)都 复制 给新变量,新旧变量之间互不影响
// 最简单的方法就是 JSON.parse(JSON.stringify()),但是这种方法有一定的缺陷
// 如果属性中有 undefined、NaN、Infinity、function 类型,那么会转换为 null
// 如果属性中有 RegExp、Error 对象,那么会转换为空对象
let object = {
name: {
first_name: 'Steve',
last_name: 'Jobs'
},
age: 18
}
let array = [[1, 3], 5, 7]
let object_deep_copy = JSON.parse(JSON.stringify(object))
let array_deep_copy = JSON.parse(JSON.stringify(array))
object_deep_copy['name']['first_name'] = 'Steven'
object_deep_copy['age'] = 20
array_deep_copy[0][0] = 2
array_deep_copy[2] = 11
console.log('object["name"]["first_name"]: ' + object['name']['first_name'])
console.log('object["age"]: ' + object['age'])
console.log('array[0][0]: ' + array[0][0])
console.log('array[2]: ' + array[2])
console.log('object_deep_copy["name"]["first_name"]: ' + object_deep_copy['name']['first_name'])
console.log('object_deep_copy["age"]: ' + object_deep_copy['age'])
console.log('array_deep_copy[0][0]: ' + array_deep_copy[0][0])
console.log('array_deep_copy[2]: ' + array_deep_copy[2])
/*
* 执行结果:
* object["name"]["first_name"]: Steve
* object["age"]: 18
* array[0][0]: 1
* array[2]: 7
* object_deep_copy["name"]["first_name"]: Steven
* object_deep_copy["age"]: 20
* array_deep_copy[0][0]: 2
* array_deep_copy[2]: 11
**/
自己来实现一个深拷贝函数:
注意,这种简单的实现还是会有一定的缺陷
function deepCopy(object) {
if (typeof object !== 'object') return
let newObject = object instanceof Array ? [] : {}
for (let key in object) {
if (object.hasOwnProperty(key)) {
let item = object[key]
newObject[key] = typeof item === 'object' ? deepCopy(item) : item
}
}
return newObject
}
【 阅读更多 JavaScript 系列文章,请看 JavaScript学习笔记 】