内置类型
null、undefined、string、number、boolean、symbol(ES6)、object
基本类型
- null
空值,指示变量未指向任何对象。给尚未创建对象但以后会创建具体对象的变量赋值为 null 可以避免变量值为 undefined。虽然 typeof null 会输出 object ,但不是对象类型,因为最初 JS 底层为了性能考虑使用低 3 位存储变量的类型信息,000 开头代表是对象,而 null 表示为全零,所以将它错误的判断为 object ,通常在不再使用对象时手动置为 null 解除引用,便于内存回收 - undefined
变量声明未初始化时访问变量,值为 undefined
(而变量未声明就访问是报错 not defined )
null 和 undefined 的区别和联系
- null 是一个关键字,值 null 是一个字面量;而 undefined 是全局对象的一个属性。即它是全局作用域的一个变量。undefined的初始值就是原始数据类型undefined。但在 ES5中 undefined被修改为一个不能被配置(non-configurable),不能被重写(non-writable)的属性。对 undefined 赋值不会报错但这是无效的,没有意义的
- null == undefined 而 null !== undefined
检测变量是否已经赋值必须使用 严格相等(全等) 而不是 标准相等,否则会因为标准相等进行类型转换,将值为 null 的变量误判为 undefined 变量未赋值。
var d = null;
console.log(d == undefined); // true
-
数值转换
Number() 强制转换:
因此注意使用加号运算符进行加法运算时,若数值与 null 相加会得到 NaN,而数值与 undefined 相加相对于数值与 0 相加
parseInt() 转换
-
number
JS 中的 number 是浮点类型,而计算机是二进制来表示浮点数的,无法准确表示一个浮点数,只能逼近取近似值。在0.1 + 0.2这个式子中,0.1 和 0.2 都是近似表示的,在他们相加的时候,两个近似值进行了计算,导致最后得到的值是0.30000000000000004,此时对于JS来说,其不够近似于0.3,于是就出现了0.1 + 0.2 != 0.3 这个现象。 这是其他语言也无法避免的。
number 中有一个特殊的值 NaN(not a number), NaN 与任何都不相等包括它自己 -
string
string 的值是不可变的,调用 String 的方法时,实际上后台进行了自动处理,返回的结果是一个新的字符串 -
symbol
表示独一无二的值,两变量不相等也不严格相等,但不表示不可以有两个相同的值; Symbol( ‘desc1’) 不会将字符串 ‘desc1’ 强制转换为 symbol 类型,而是每次都创建一个新的 Symbol 类型
可用来定义对象的唯一属性名
symbol 值通过 Symbol() 生成;Symbol()前不能使用 new 否则报错;作为对象属性名时用 [] 不能用 .let sy = Symbol (“ key1”)
let obj = {}
obj [sy] = “ val1 ”
obj.sy = " val2 "
console.log(sy)// Symbol( key1) 与字符串区分
console.log(obj.sy)// " val2 "
console.log(obj[sy])// “ val1 ”因为 . 运算符后面是字符串,所以取到的是obj 的字符串 sy 属性,而不是 Symbol 值 sy 属性。
symbol值作为属性名时,该属性是公有属性不是私有属性,可以在类的外部访问,但是不会出现在 for…in 、for…of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames() JSON.stringify() 返回,可通过 Object.getOwnPropertySymbols() 和 Reflect.ownKeys()获取。
typeof 能检测 symbol 类型而 instanceof 结果为 false
如果 Symbol() 的参数是一个对象,就会调用该对象的 toString 方法,将其转为字符串,然后才生成一个 Symbol 值。
Symbol.for(key)
使用给定的key搜索现有的symbol,如果找到则返回该symbol。否则将在全局作用域注册一个 symbolSymbol.keyFor(sym)
在全局 symbol 注册表中查找
引用类型
Object(Array、Date、RegExp、Function、基本包装类型(Boolean、Number、String)),Object 本质上是由一组无序的名值对组成的。JavaScript不支持任何创建自定义类型的机制,而所有值最终都将是上述数据类型之一。
基本类型和引用类型的区别
-
内存上(保存和复制)
基本类型按值存放在栈中,可直接访问,占据空间小,大小固定,由系统自动分配内存空间,自动释放。
引用类型同时在栈和堆中占据空间,在栈中存放该对象在堆中的地址(指针),该对象的值存放在堆中,因此不可直接操作对象的值;当创建对象时,计算机会在堆中存放对象的值,将堆中的地址值存在栈中,访问对象时先在栈中找到该对象在堆中的地址,再根据该地址到堆中寻找该对象的内存空间。引用类型占据空间较大,大小不固定,动态分配内存,不会自动释放;复制变量值 a = b (假设已声明初始化)
(1)若变量 b 是基本类型值,则为 a 在堆中创建一块内存空间,初始化时,访问 b ,取出它的值复制一份副本,赋值给 a 完成初始化,a 和 b 在不同的内存空间中,互相独立
(2)若变量 b 是引用类型值,该值是一个指针,存放对象在堆中的地址,同样在栈创建一个新内存空间给 a 并完成初始化,但是这个值是一个地址,访问 a 时它也可以在堆中找到与 b 一样指向的对象,即 a 和 b 指向了同一个堆空间,同一个对象此处涉及 浅拷贝 和 深拷贝
浅拷贝:
对象的属性都是基本类型值,则会创建独立的内存空间,拷贝值初始化,a 和 b 是两个结构相同的独立的变量。但若拷贝的对象的属性含有引用类型,此时只会在栈中开辟内存空间,复制该引用的地址,不会在堆中开辟内存空间,进一步访问复制引用类型的值;即两变量的引用类型属性指向同一内存,会出现修改其中一个变量的引用类型属性,另一个也随之改变的问题有哪些操作是浅拷贝:
(1) Object.assign()
(2) 对象扩展运算符
(3) 数组的 slice()和 concat()深拷贝
深拷贝是指拷贝对象引用类型属性时,会为该引用类型属性值在堆中开辟新的内存空间,复制该属性的对象值,并将该副本对象值的地址存放在栈中,而不是存放原对象在堆的地址,两个变量的引用类型属性也会各自拥有内存空间,相互独立
原生JS中没有实现深拷贝(实现思路:递归拷贝对象属性)
下面涉及函数的形参与实参的问题,虽然变量有按值和按引用访问两种方式,但要理解函数的参数只能是按值传递
将对象 { name:‘p1’, age:25 } (下文用对象1代替)存放在堆中,给变量 p1 在栈中开辟一块内存空间,并将对象堆中的首地址赋值给变量 p1 完成初始化;
随后调用函数 test() 并将 p1 作为实参传递给形参 person,注意形参 person 只是 p1 的一个副本,person 有了 对象1 的地址,在 test () 中通过点运算符,访问了地址操作对象,即将对象 1 的 age 属性值改为 26
随后创建了另一个对象{ name:‘p2’, age:30 },并将它在堆中的地址给了 person,此时person 被重新赋值了新地址,指向了新的对象,注意这个过程跟 p1 没有关系 ,因为 person 和 p1 不是同一个变量,它们都是值类型,只是初始化 person 时给了它 p1 的值,重新赋值 person 并不会改变 p1 (回想一下上面 a = b ,b(此处的 p1 )是基本类型时,创建新变量拷贝值初始化,a (此处的person) 和 b 互不影响)
最后将 person 的值(对象2的堆地址)返回给新创建的变量 p2,即栈中的 p2 指向了对象2,因此 p1 和 p2 是两个对象。
-
属性、方法的操作
基本类型值不是对象,逻辑上没有方法可以调用,但实际上我们可以对基本类型调用方法(仅限string、boolean、number)是因为后台自动进行处理(创建基本包装类型对象)
图 2 中,先将 number 类型的 1 存放到变量中,在第二行访问 numb 时处于读取模式,即从内存中将该值取出,读取模式中后台进行下列处理:
(1)创建该值的基本包装类型实例(如此处创建Number类型的实例)
(2)在实例上调用指定方法 (基本包装类型是对象类型)
(3)返回结果,销毁该实例回到图1, ‘1’.toString() 同理,后台读取 ‘1’ 自动进行上述处理,而 undefined 没有基本包装类型(逻辑上也不需要), 而 1.toString() 是因为解析时把点解析为小数点,而不是点运算符,因此浮点数中出现非数字值,语法错误
通过括号,将点排除了小数点的情况,即解析为点运算符,因此 (1).toString() 相当于 String ((1)) , 返回 1由后台自动处理操作可知,最后实例被销毁,因此当我们为基本类型添加属性时,最后会被销毁失效,因此我们不能给基本类型值添加属性;而引用类型添加属性时会被存在堆空间,一直伴随着对象的生命周期;
基本包装类型和其他引用类型的主要区别就是对象的生命周期。使用 new 操作符创建的引用类型实例,在执行流离开当前作用域前一直保存在内存中,而自动创建的基本包装类型的对象值存在于一行代码的执行瞬间,然后立即被销毁。
类型检测
typeof
原理:
js 在底层存储变量的时候,变量的机器码的低 3 位存储其类型信息。
000:对象
010:浮点数
100:字符串
110:布尔
1:整数
undefined 和 null 这两个值的信息存储是特殊的。
null:所有机器码均为0
undefined:用 −2^30 整数来表示。
因此 typeof 可以判断所有基本类型值的类型,除了 null 判断为 object;但还需注意 typeof 可以判断 函数对象,返回function,其他引用类型均不能具体判断,均返回 object
instanceof
对于引用类型,可以使用 instanceof
原理:
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
target instanceof Constructor
若 target 不是对象,是基本类型值则返回 false;
可以检测到自定义对象类型的继承关系
需要注意的是,如果表达式 obj instanceof Foo 返回 true,并不意味着该表达式会永远返回 true。
因为 Foo.prototype 属性的值有可能会改变,或者对象 obj 的原型链的也能被改变。
Object.prototype.toString.call()
因为Array、Function 等引用类型会重写 toString(), 而调用 Object 原型上的方法即可检测到该对象的类型,对于所有对象都能正确返回其类型
但对于自定义类型对象,不可检测到继承的关系
constructor
判断数组的方法
- instanceof 操作符判断
arr instanceof Array - 对象构造函数的 constructor判断
Object的每个实例都有构造函数 constructor,用于保存着用于创建当前对象的函数
arr.constructor === Array - Array 原型链上的 isPrototypeOf
Array 构造函数的原型的一个方法: isPrototypeOf() 用于测试一个对象是否存在于数组对象的原型链上。
Array.prototype.isPrototypeOf(arr) - Object.getPrototypeOf()
返回指定对象的原型,再与Array的原型比较
Object.getPrototypeOf(arr) === Array.prototype - Object.prototype.toString.call ()
Object.prototype.toString.call(arr) === ‘[object Array]’ - Array.isArray
Array.isArray(arr), es5新增的方法,ie8及以下不支持
判断空对象
-
将 json 对象转化为 json 字符串,再判断该字符串是否为"{}"
let data = {};
let b = (JSON.stringify(data) == “{}”);
alert(b); //true -
for in 循环判断对象中是否有键值对
let obj = {};
let b = () => { for(let key in obj) { return false; } return true; }
alert(b()); //true -
Object.getOwnPropertyNames()方法
此方法是使用Object对象的getOwnPropertyNames方法,获取到对象中的属性名,存到一个数组中,返回数组对象,我们可以通过判断数组的length来判断此对象是否为空
let data = {};
let arr = Object.getOwnPropertyNames(data);
alert(arr.length == 0); //true -
使用ES6的Object.keys()
与4方法类似,是ES6的新方法, 返回值也是对象中属性名组成的数组
let data = {}; let arr = Object.keys(data); alert(arr.length == 0);//true
这些方法都不可检测到 Symbol作为属性名的属性,除了 Object.getOwnPropertyNames() 外的其他方法都不可检测出不可枚举属性,除 for in 外其他方法都不能检测到对象的继承属性
ES6 中的 Reflect.ownKeys(obj)
包含对象自身的所有属性,即可检测键名是 Symbol 和不可枚举的属性,只是不包含继承属性。