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学习笔记

posted @ 2020-01-10 11:04  半虹  阅读(146)  评论(0编辑  收藏  举报