js类型----你所不知道的JavaScript系列(5)

ECMAScirpt 变量有两种不一样的数据类型:基本类型,引用类型。也有其余的叫法,好比原始类型和对象类型等。面试

一、内置类型

JavaScript 有七种内置类型:
 • 空值(null
 • 未定义(undefined
 • 布尔值(boolean
 • 数字(number
 • 字符串(string
 • 对象(object
 • 符号(symbolES6 中新增)
数组

除对象以外,其余统称为“基本类型”。 对象称为“引用类型”。函数

咱们能够用 typeof 运算符来查看值的类型,它返回的是类型的字符串值。有意思的是,这七种类型和它们的字符串值并不一一对应: spa

typeof undefined === "undefined"; // true
typeof true === "boolean"; // true
typeof 42 === "number"; // true
typeof "42" === "string"; // true
typeof { life: 42 } === "object"; // true // ES6中新加入的类型
typeof Symbol() === "symbol"; // true

以上六种类型均有同名的字符串值与之对应。 你可能注意到 null 类型不在此列。它比较特殊typeof 对它的处理有问题:
prototype

typeof null === "object"; // true

按照类型的定义,这里正确的返回结果应该是 "null"。但这个 bug 由来已久,在 JavaScript 中已经存在了将近二十年,也许永远也不会修复,由于这牵涉到太多的 Web 系统,“修复”它会产生更多的bug,令许多系统没法正常工做。 scala

因此在平常开发中,为了更好的检测null类型,咱们须要使用复合条件来检测 null 值的类型: 指针

var a = null; (!a && typeof a === "object"); // true

null 是基本类型中惟一的一个“假值”类型, typeof对它的返回值为 "object"code

这里还有一张状况,看下面:对象

typeof function a(){ /* .. */ } === "function"; // true

或许这里你会说js的内置类型没有function类型呀,为何这里typeof会返回"function"。在介绍里面,function类型其实是 object 的一个“子类型”。具体来讲,函数是“可调用对象”,它有一个内部属性 [[Call]],该属性使其能够被调用。 blog

函数不只是对象,还能够拥有属性。例如:

function a(b,c) {  /* .. */ } a.length; // 2 函数对象的 length 属性是其声明的参数的个数
 a.name; // "a" 函数对象的 name 属性返回函数名

再来看看数组。 JavaScript 支持数组 ,数组也是对象,它也是 object 的一个“子类型”,数组的元素按数字顺序来进行索引(而非普通像对象那样经过字符串键值),其 length 属性是元素的个数。

typeof [1,2,3] === "object"; // true

咱们来比较一下下面的代码:

typeof   null;   // "object"
typeof   [];   // "object"
typeof   {};   // "object"

返回的都是"object",那这样咱们要怎么知道区分对象,数组和null呢?

其实方法有不少,这里罗列几种给你们。

 

一、instanceof

instanceof 是用来判断 A 是否为 B 的实例对,表达式为:A instanceof B,若是A是B的实例,则返回true,不然返回false。 在这里须要特别注意的是:instanceof检测的是原型。其内部原理大体能够这样理解:

instanceof (A,B) = { var L = A.__proto__; var R = B.prototype; if(L === R) { //A的内部属性__proto__指向B的原型对象
        return true; } return false; }

虽然上述代码很不严谨也有一些问题,可是从上述过程能够看出,当 A 的 __proto__ 指向 B 的 prototype 时,就认为A就是B的实例,咱们再来看几个例子:

[] instanceof Array;  // true
{} instanceof Object;  // true
new Date() instanceof Date;  // true
 
function Person(){}; new Person() instanceof Person; [] instanceof Object;  // true

细心的你可能发现了 [] instanceof Array 和 [] instanceof Object 居然都返回 true。为何?回想一下前面我讲过的“instanceof检测的是原型”,在刚才的例子里面就涉及到了原型和原型链的概念。若是对原型和原型链不了解的同窗,能够先去看看相关知识,这个知识点是十分重要的,不论是之后工做仍是面试,都是很是重要的!那么。。。如今继续赚回来看咱们的例子。从原型链上来讲 [].__proto__ 指向 Array.prototype, 而 Array.prototype.__proto__ 又指向了Object.prototype,Object.prototype.__proto__ 指向了null,标志着原型链的结束。因此按照 instanceof 的判断规则,[] 就是Object的实例。固然,相似的new Date()、new Person() 也会造成这样一条原型链,例如:new Date() instanceof Object 也会返回 true。所以,instanceof 只能用来判断两个对象是否属于原型链的关系, 而不能获取对象的具体类型。

 

二、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(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({}) ; //"[object Object]"

基本上全部对象的类型均可以经过这个方法获取到。也能够简写成 toString.call ,例如 toString.call({ }),toString.call([ ])

 

 

二、值和类型

 JavaScript 中的变量是没有类型的, 只有值才有。变量能够随时持有任何类型的值。   ----《你所不知道的JavaScript(中)》P6

JavaScript 不作“类型强制”,也就是说,JavaScript 拥有动态类型,这意味着相同的变量可用做不一样的类型。语言引擎不要求变量老是持有与其初始值同类型的值。一个变量能够如今被赋值为字符串类型值,随后又被赋值为数字类型值。

42 的类型为 number,而且没法更改。而 "42" 的类型为 string。数字 42 能够经过强制类型转换(coercion)为字符串 "42" 。

在对变量执行 typeof 操做时,获得的结果并非该变量的类型,而是该变量持有的值的类型,由于 JavaScript 中的变量没有类型

var a = 42; typeof a; // "number"

a = true; typeof a; // "boolean"

 

 

三、值和引用

开头曾说过,ECMAScirpt 变量有两种不一样的数据类型:基本类型,引用类型。为何有这两种区分呢?咱们先来看下面的例子:

var a = 2; var b = a; b++; a; // 2
b; // 3

var c = [1,2,3]; var d = c; d.push( 4 ); c; // [1,2,3,4] d; // [1,2,3,4]

在JavaScript中,简单值(即标量基本类型值, scalar primitive老是经过值复制的方式来赋值 / 传递,包括nullundefined、字符串、数字、布尔和 ES6 中的 symbol。 复合值(compound value)——对象(包括数组和封装对象,参见第 3 章)和函数,则
经过引用复制的方式来赋值 / 传递。
上例中 2 是一个标量基本类型值,因此变量 a 持有该值的一个复本, b 持有它的另外一个复
本。 b 更改时, a 的值保持不变。c d 则分别指向同一个复合值 [1,2,3] 的两个不一样引用。请注意, c d 仅仅是指向值[1,2,3],并不是持有。因此它们更改的是同一个值(如调用 .push(4))。随后它们都指向更改后的新值 [1,2,3,4]。 

 

因为引用指向的是值自己而非变量,因此一个引用没法更改另外一个引用的指向。 

var a = [1,2,3]; var b = a; a; // [1,2,3]
b; // [1,2,3] // 而后
b = [4,5,6]; a; // [1,2,3]
b; // [4,5,6]

代码前两句使得a、b都是对复合值 [1,2,3] 的两个不一样引用,因此代码第三第四句输出的结果就都是值[1,2,3]。代码第五句b=[4,5,6] 的执行使得变量b从新指向了另外一个复合值,而不会修改的原先的复合值,也不会不影响 a 指向值 [1,2,3],因此a输出的值依旧是复合值[1,2,3]。 

有了上面的例子以后,再来看看下面一段代码,

function foo(x) { x.push( 4 ); x; // [1,2,3,4] // 而后
   x = [4,5,6]; x.push( 7 ); x; // [4,5,6,7]
} var a = [1,2,3]; foo( a ); a; // 是[1,2,3,4],不是[4,5,6,7]

咱们向函数传递 a 的时候,实际是将引用 a 的一个复本赋值给 x,而 a 仍然指向 [1,2,3]在函数中咱们能够经过引用 x 来更改数组的值(push(4) 以后变为 [1,2,3,4])。但 x =[4,5,6] 并不影响 a 的指向,因此 a 仍然指向 [1,2,3,4]。 

咱们不能经过引用 x 来更改引用 a 的指向,只能更改 a x 共同指向的值 。若是要将 a 的值变为 [4,5,6,7],必须更改 x 指向的数组,而不是为 x 赋值一个新的数组。

function foo(x) { x.push( 4 ); x; // [1,2,3,4] // 而后
   x.length = 0; // 清空数组
   x.push( 4, 5, 6, 7 ); x; // [4,5,6,7]
} var a = [1,2,3]; foo( a ); a; // 是[4,5,6,7],不是[1,2,3,4]

x.length = 0 x.push(4,5,6,7) 并无建立一个新的数组,而是更改了当前的数组。因而 a 指向的值变成了 [4,5,6,7]。 

关于引用类型的比较和基本类型的比较,咱们来看下面的代码:

//基础类型的比较
var a = 1; var b = 1; a===b;   // true


//引用类型比较
var c = [1]; var d = c; var e = [1]; c===d;   // true
c===e;   // false

基础类型的比较是值的比较,只要值相等,就返回 true,而引用类型的比较是两个引用的比较,上面例子中c、d都是对复合值 [1] 的应用,他们都指向同一个值,因此返回 true ,虽然 e 所指的值也是 [1],可是这个 [1] 和 c 所指的 [1] 不必定,c和e是两个不一样的引用,就像C++里面所说的指针,虽然两个指针所指的内存区域存放的值同样,可是这两个指针所指的地址不同,在机器看来这就是两个彻底不一样的变量,因此在比较的时候就返回 false 。

 

总之请谨记:咱们没法自行决定使用值复制仍是引用复制,一切由值的类型来决定。

相关文章
相关标签/搜索