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'
- 浅拷贝得到的新变量,是将旧变量的第一层数据 赋值 给新变量
// 对于对象,常用的浅拷贝方式有: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 }