7种变量类型
js的类型分为基本类型和引用类型。
- 基本类型:number、string、boolean、null、undefined、symbol。直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
- 引用类型:Object、Array、RegExp、Date、Function。引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
内置对象
Object是JavaScript中所有对象的父对象,处于原型链的最顶端
- 数据封装类对象:Object、Array、Boolean、Number 和 String
- 其他对象:Function、Math、Date、RegExp、Error。
- 特殊的基本包装类型(String、Number、Boolean)
- arguments: 只存在于函数内部的一个类数组对象
装箱和拆箱
装箱
把基本数据类型转化为对应的引用数据类型的操作叫装箱,装箱分为隐式装箱和显示装箱.
隐式装箱
在《javascript高级程序设计》中有这样一句话: 每当读取一个基本类型的时候,后台就会创建一个对应的基本包装类型对象,从而让我们能够调用一些方法来操作这些数据。(隐式装箱)
let a = 'sun' //String
let b = a.indexof('s') // 0 // 返回下标
// 上面代码在后台实际的步骤为:
let a = new String('sun')
let b = a.indexof('s')
a = null
在上面的代码中,a是基本类型,它不是对象,不应该具有方法,js内部进行了一些列处理(装箱), 使得它能够调用方法。在这个基本类型上调用方法,其实是在这个基本类型对象上调用方法。这个基本类型的对象是临时的,它只存在于方法调用那一行代码执行的瞬间,执行方法后立刻被销毁。实现机制:
- 创建String类型的一个实例;
- 在实例上调用指定的方法;
- 销毁这个实例;
- 显示装箱
- 通过内置对象可以对Boolean、Object、String等可以对基本类型显示装箱
显示装箱
通过内置对象可以对Boolean、Object、String等可以对基本类型显示装箱
let a = new String('sun ')
拆箱
拆箱和装箱相反,就是把引用类型转化为基本类型的数据,通常通过引用类型的valueof()和toString()方法实现
let age = new Number(24) //new一个object
//拆箱操作
age.valueOf() //number型
ES6的 Symbol
一般对象的属性名都是字符串,有可能造成属性名的冲突,比如你使用了一个他人提供的对象,但又想为这个对象添加新的方法或属性,就有可能产生属性名的冲突,所以es6就引入了Symbol。它是JavaScript的第七种数据类型。Symbol值是通过Symbol函数生成的
let s1 = Symbol();
typeof s1 //"symbol"类型
let s2 = Symbol();
s2 === s2 //false
上面代码中,s1和s2两个都是Symbol值,但是在全等符号(===)下,s1和s2并不相等,因为Symbol生成的是独一无二的值。
作为属性名的Symbol
由于每一个 Symbol 值都是不相等的,这意味着 Symbol 值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。
let mySymbol = Symbol(); //属性名
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
a[mySymbol] // "Hello!"
Symbol还提供了很多内置方法,在官网 es6.ruanyifeng.com/#docs/symbo… 上有比较详细的介绍,这里我只介绍一种我平时用得比较多的isConcatSpreadable。
对象的Symbol.isConcatSpreadable属性等于一个布尔值,表示该对象用于Array.prototype.concat()时,是否可以展开。
let arr1 = ['c', 'd'];
['a', 'b'].concat(arr1, 'e') // ['a', 'b', 'c', 'd', 'e']
arr1[Symbol.isConcatSpreadable] // undefined
let arr2 = ['c', 'd'];
arr2[Symbol.isConcatSpreadable] = false; //arr2不可展开
['a', 'b'].concat(arr2, 'e') // ['a', 'b', ['c','d'], 'e']
上面代码说明,数组的默认行为是可以展开,Symbol.isConcatSpreadable默认等于undefined。该属性等于true时,也有展开的效果。类似数组的对象正好相反,默认不展开。它的Symbol.isConcatSpreadable属性设为true,才可以展开。
let obj = {length: 2, 0: 'c', 1: 'd'};
['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e'] 对象是默认不展开
obj[Symbol.isConcatSpreadable] = true;
['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']
通过Symbol的内置方法,看出了它不仅仅是有独一无二的属性名这么简单,还可以有很多的方法供我们在开发中的使用。
null与undefined的区别
null
null表示没有对象,即没有值。它是对象原型链的终点。
let a = null
typeOf null //object
tyoeOf a //object
undefined
undefined表示缺少值,即本应有值,却没有定义。
typeOf undefined //undefined
console.log(a) // a属性名不存在,返回undefined
function test1 () {
let b = 666
}
function test2 () {
return str
}
test1() //undefined 没有return 返回值
test2() // undefined 没有定义str
两者在es6的绝对相等(===)中,两者皆是false,所以相等。
null === undefined //true
同时,转为number时,null是0,而undefined是NaN.
判断数据类型的方法与优缺点
1. typeOf
//基本类型
typeOf 111 // "number"
typeOf '111' // "string"
typeOf false // "boolean"
typeOf null // "object"
typeOf undefined // "undefined"
typeof Symbol('111') // "symbol"
//引用类型
typeof function test() {} // "function"
typeof {a:111} // "object"
typeof [] // "object"
对于基本数据类型,null返回object,然而null有自己的数据类型Null。在引用类型中,除了function返回"function"之外,其他均返回object,像数组、正则、日期这些也有属于自己的具体类型,而typeOf对于这些类型的处理,只返回处于其原型链的最顶端的Object类型,是没有错,但是不是我们想要的类型,害。
2. instanceof
instanceof 检测的是原型,用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回true,否则返回false。
let a = [1,2,3]
a instanceof Array // true
let b = '123'
b instanceof String // false 因为String是基本类型,只能用于判断对象
let c = new String('123')
c instanceof String // true new生成的实例属于string包装对象所以可检测。
3. constructor
constructor是原型prototype的一个属性,当函数被定义时候,JavaScript引擎会为函数添加原型prototype,并且这个prototype中constructor属性指向函数引用, 因此重写prototype会丢失原来的constructor。
'123'.constructor===String // true
false.constructor===Boolean //true
null.constructor //报错啦
null和undefined没有constructor,所以无法用此方法判断。还有,如果自定义对象,开发者重写prototype之后,原有的constructor会丢失,因此,为了规范开发,在重写对象原型时一般都需要重新给 constructor 赋值,以保证对象实例的类型不被篡改。
4. object.prototype.toString.call()
Object.prototype.toString.call('') ; // "[object String]"
Object.prototype.toString.call(1) ; // "[object Number]"
Object.prototype.toString.call(true) ; // "[object Boolean]"
Object.prototype.toString.call(Symbol()); // "[object Symbol]"
Object.prototype.toString.call(undefined) ; // "[object Undefined]"
Object.prototype.toString.call(null) ; // "[object Null]"
Object.prototype.toString.call(new Function()) ; // "[object Function]"
Object.prototype.toString.call(new Date()) ; // "[object Date]"
Object.prototype.toString.call([]) ; // "[object Array]"
Object.prototype.toString.call(new RegExp()) ; // "[object RegExp]"
Object.prototype.toString.call(new Error()) ; // "[object Error]"
Object.prototype.toString.call(document) ; // "[object HTMLDocument]"
Object.prototype.toString.call(window) ; // "[object global]" window 是全局对象 global 的引用
对于 Object.prototype.toString() 方法,如果对象的 toString() 方法未被重写,就会返回一个形如 "[object XXX]" 的字符串。
({}).toString(); // => "[object Object]"
Math.toString(); // => "[object Math]"
但是因为大多数对象的toString()方法都是重写了的,这时,需要用 call() 或 Reflect.apply() 等方法来调用。它的原理就是对于null或者undefined会直接返回结果,而其他基本数据类型如number,需要上文中提到的转为对象的方法即装箱,转为对象后,取得该对象的 [Symbol.toStringTag] 属性值(可能会遍历原型链)作为 tag,如无该属性,或该属性值不为字符串类型,则依下表取得 tag, 然后返回 "[object " + tag + "]" 形式的字符串。