一、基本数据类型
1-1 六种基本数据类型
number、string、boolean、null、undefined、symbol
1-2 基本数据类型概念
- 首先基本数据类型存储的都是值,是没有函数可以调用的,比如
undefined.toString(),'1'.toString()可以使用是因为 '1' 在调用 toString 的时候被强制转换成了 String 对象类型,这也称为是装箱 - 虽然 typeof null 是 object,但是 null 不是对象类型,这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来
1-3 装箱、拆箱操作
- 基本类型 number、string、boolean 对应的内置对象分别是 Number(),String(), Boolean()
- 装箱:就是把基本类型转变为对应的对象。装箱分为隐式和显示
// 隐式装箱: 每当读取一个基本类型的值时,后台会创建一个该基本类型所对应的对象
// 在这个基本类型上调用方法,其实是在这个基本类型对象上调用方法
// 这个基本类型的对象是临时的,它只存在于方法调用那一行代码执行的瞬间,执行方法后立刻被销毁
let num = 123;
num.toFixed(2); // '123.00'
// 当基本数据类型调用方法时候,后台的真正步骤为
let c = new Number(num);
c.toFixed(2);
c = null;
// 显式装箱: 通过内置对象 Boolean、Object、String 等可以对基本类型进行显示装箱。
var obj = new String('123');
- 拆箱:拆箱与装箱相反,把对象转变为基本类型的值
let numObject = new Number(1)
let strObject = new String('1')
console.log(numObject) // Number {1}
console.log(strObject) // String {'1'}
// 拆箱
numObject.valueOf() // 1;
strObject.toString() // '1';
1-4 null 和 undefined
- undefined 表示“未定义”
// undefined 出现的五种情况
// 1.真的是没定义(仅 typeof 可用),其他情况直接报错
console.log(typeof a); // "undefined"
// 2.定义了但没赋值
let a;
console.log(a); // undefined
// 3.直接赋值 undefined 或返回 undefined
let a= undefined;
console.log(a); // undefined
// 4.函数空 return 或者干脆没有 return
function blueFn1(){
return;
}
console.log(blueFn1()) // undefined
// 5.没有对应属性
const blue={age: 18, gender: 'male'};
console.log(blue.height); // undefined ,因为就没有叫 height 的属性
- null 表示“空指针”, null 不会平白无故出现,是主动使用时候出现
- null的本质是 0,undefined 的本质是特殊对象
Number(null); //0
Number(undefined); //NaN
12 + null; //12
12 + undefined; //NaN
-5 < null; //true ——null是0,-5<0
-5 < undefined; //false
补充:JavaScript 中 Number() 与 new Number() 区别
二、引用数据类型
2-1 三种引用数据类型
- object,array,function
- 也有一种说法就是除了基本数据类型,其他都是都是对象,数组是对象,方法是对象
三、基本数据类型和引用数据类型的区别
3-1 声明变量时内存分配不同
- 基本数据类型的变量和值都是保存在栈内存中
- 引用数据类型的变量是一个保存在栈内存中的指针,值是保存在堆内存中的对象
原因:引用数据类型值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。放在栈空间中的值是该对象存储在堆中的地址(指针)。(指针)的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响
3-2 访问机制不同
- 在 JavaScript 中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的按引用访问
- 而基本数据类型的值则是可以直接访问到的
3-3 复制变量不同
- 复制基本数据类型变量是将原始值的副本赋给新变量,此后两个变量是完全独立的,只是值相同而已
- 复制引用数据类型的变量,是将该变量的内存地址赋给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,他们其中任何一个做出改变都会反映到另一个变量上
3-4 参数传递不同
- 基本数据类型变量当作参数,只是把变量里的值传递给参数,之后参数和这个变量互不影响
- 引用数据类型变量当作参数,传递的值就是这个内存地址(指针),参数和传递的变量指向同一个对象
四、案例
4-1 函数参数是对象
function test(person) {
person.age = 26
person = {
name: 'yyy',
age: 30
}
return person
}
const p1 = {
name: 'yck',
age: 25
}
const p2 = test(p1)
console.log(p1) // { name:'yck', age:26 }
console.log(p2) // { name:'yyy', age:30 }
- 首先,函数传参是传递对象指针的副本
- 到函数内部修改参数的属性这步, p1 的值也被修改了
- 重新为 person 分配了一个对象,与p1没有任何关系
五、typeof vs instanceof
5-1 typeof
- typeof 对于基本数据类型类型来说,除了 null 都可以显示正确的类型
typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'
typeof null //object
- typeof 对于引用数据类型类型来说,除了函数都可以显示object
typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'
5-2 instanceof
- 如果想判断一个对象的正确类型,首选 instanceof,因为他的内部机制是通过原型链来判断的
const p1 = new Person()
p1 instanceof Person // true
var str = 'hello world'
str instanceof String // false
var str1 = new String('hello world')
str1 instanceof String // true
- 如果需要 instanceof 来判断基本数据类型,可以通过 Symbol.hasInstance
class PrimitiveString {
static [Symbol.hasInstance](x) {
return typeof x === 'string'
}
}
console.log('hello world' instanceof PrimitiveString) // true
Symbol.hasInstance 是一个能让我们自定义 instanceof 行为的东西,以上代码等同于 typeof 'hello world' === 'string',所以结果自然是 true 了。这其实也侧面反映了一个问题, instanceof 也不是百分之百可信的。
5-3 instanceof 原理
instanceof 主要的实现原理就是只要右边构造函数的 prototype 在左边实例的原型链上即可。因此,instanceof 在查找的过程中会遍历实例的原型链,直到找到右边构造函数的 prototype,如果查找失败,则会返回 false
// 手写 instanceof
function instance_of(L, R) { // L 表示instanceof左边,R 表示instanceof右边
let O = R.prototype; // 取 R 的显示原型
L = L.__proto__; // 取 L 的隐式原型
while (true) { // 循环执行,直到 O 严格等于 L
if (L === null) return false;
if (O === L) return true;
L = L.__proto__; // 获取祖类型的__proto__
}
}