JavaScript基础之变量与类型

230 阅读7分钟

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内部进行了一些列处理(装箱), 使得它能够调用方法。在这个基本类型上调用方法,其实是在这个基本类型对象上调用方法。这个基本类型的对象是临时的,它只存在于方法调用那一行代码执行的瞬间,执行方法后立刻被销毁。实现机制:

  1. 创建String类型的一个实例;
  2. 在实例上调用指定的方法;
  3. 销毁这个实例;
  4. 显示装箱
  5. 通过内置对象可以对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 //报错啦

nullundefined没有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 + "]" 形式的字符串。