此篇文章为本人学习过程中总结的相关笔记,若有错误或不恰当之处,欢迎指正。
数据类型
JavaScript是一门弱类型的脚本语言,拥有动态类型。在程序运行过程中,类型会被自动确定,同时还可能伴随着类型隐式转换,所以在JavaScript中可以使用一个变量保存不同类型的数据。
JavaScript中的数据类型
ECMAScript定义了7中数据类型:
- 原始类型(值类型):
- Number
- String
- Boolean
- Null
- Undefined
- Symbol
- 引用类型:
- Object
原始类型的值称为原始值,要注意原始值都是不可改变的,我们对原始类型的变量重新赋值时,是将变量指向了新的字符串的地址,而不是改变旧的字符串的值。
引用类型包括Object类及其所有的子类。JavaScript里的任何非原始类型都是对象(如Array、Function等),而且被存放在变量内。
JavaScript中的类型判断
在JavaScript中,判断类型一般用到的是 typeof 和 instanceof
typeof
使用typeof判断类型时,我们会得到7种结果
typeof 1; // number
typeof new Number(1); // object
typeof new String(''); // object
typeof ''; // string
typeof true; // boolean
typeof Symbol('symbol'); // symbol
typeof undefined; // undefined(Undefined类型只有一个特殊的值undefined)
typeof null; // object(Null只有一个值null,但typeof null返回的是object, null可以看做空引用)
typeof {}; // object(Object的子类也是返回object)
typeof []; // object
typeof new Set(); // object
typeof new Date(); // object
typeof function(){}; // function(Function类型的返回function而不是object)
复制代码
instanceof
instanceof运算符通过原型链判断一个对象是否为一个构造函数的实例。从一个实例对象开始向上查找原型链,直到Object.prototype,如果找到返回true,否则返回false。
当实例使用instanceof运算符时,会调用原型链上构造函数的Symbol.hasInstance方法。如obj instanceof Obj;会调用Obj[Symbol.hasIntance](obj);
class Klass {}
let obj = new Klass();
obj instanceof Klass; // true
Klass[Symbol.hasInstance](obj); // true
复制代码
再借用阮一峰老师的一个栗子:传送门
class Klass {
static [Symbol.hasInstance](obj) {
console.log(Number(obj));
return Number(obj) % 2 === 0;
}
}
1 instanceof Klass; // 1 false
2 instanceof Klass; // 2 true
new Klass() instanceof Klass; // NaN false
复制代码
上面的例子中,因为Klass类的Symbol.hasInstance方法被重写,所以产生了上面的结果。
下面是原始数据类型使用instance运算符的结果。
new Number(1) instanceof Number; // true
new Number(NaN) instanceof Number; // true
1 instanceof Number; // false(数字字面量无原型链)
NaN instanceof Number; // false
NaN === NaN; // false
new String('') instanceof String; // true
'' instanceof String; // false(字符串字面量无原型链)
true instanceof Boolean; // false
new Boolean(true) instanceof Boolean; // true
null instanceof Object; // false(null没有__proto__,无法进行原型链查找,Null也不是一个构造函数)
undefined instanceof Object; // false(同上)
Symbol(1) instanceof Symbol; // false(Symbol无法实例化)
复制代码
下面再来看看引用类型的结果
class Klass {}
class Klass {}
let obj = new Klass();
obj instanceof Klass; // true
obj.__proto__ = {}; // 通过修改实例__proto__的引用,使原型链断开
obj instanceof Klass; // false
obj instanceof Object; // true
new Array() instanceof Object; // true
new Function() instanceof Object; // true
复制代码
并非万物皆对象
到这里,再回头说一下上面说到的JavaScript的引用类型皆是对象。初学时,很多人都说,JavaScript中万物皆是对象,但实际上不是的,应该是JavaScript中非原始类型皆是对象。下面来看一下对象与非对象的区别。
instanceof返回false
这里已经说明原始类型不是对象了
1 instanceof Object; // false
'' instanceof Object; // false
复制代码
基本类型不存在属性
let number = 1;
number.prop = 'prop';
number.prop; // undefined
复制代码
引用方式不同
将基本类型赋值给变量时,每次都是在内存中开辟一块空间,然后把内存地址保存在变量中,不会出现多个变量引用同一内存地址。
而引用类型进行赋值时,是将该对象的内存地址复制给新的变量,多个变量引用的是同一内存地址。
let number1 = 1024;
let number2 = number1;
++number1; // 1025
number2; // 1024
复制代码
那么问题来了,以下几个问题该怎么解释呢?
-
typeof null; // object 复制代码
null解释为空对象引用,这是个历史遗留问题,详情不在这赘述了
-
let number = 1024; number.toString(); // 1024 复制代码
既然上面代码中number不是对象,为什么number调用Object.prototype上的方法成功了呢?这就需要了解一下基本包装类型(包装对象)了。那么包装对象和对象的区别是什么呢?
最大的区别就是生命周期不同。一个对象被声明后直到被回收,一直都是一个对象。但是包装对象的生命周期是执行方法的过程。当保存原始类型的变量调用相应的方法时,解析引擎会封装一个包装对象,然后执行方法,执行完毕后销毁这个对象。大致流程如下
let number = 1024; let string = number.toString(); // 步骤如下 // { // let number = new Number(1024); // let string = number.toString(); // number = null; // } 复制代码
-
let number = 1024; number instanceof Number; // false number.__proto__ === Number.prototype; // true 复制代码
包装对象也解释了上述代码中,基本类型可以访问.__proto__并指向Number.prototype,instance却返回false的原因。