ECMAScirpt 变量有两种不同的数据类型:基本类型,引用类型。也有其他的叫法,比如原始类型和对象类型等。

1、内置类型

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

除对象之外,其他统称为“基本类型”。 对象称为“引用类型”。

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

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 对它的处理有问题:

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

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

所以在日常开发中,为了更好的检测null类型,我们需要使用复合条件来检测 null 值的类型:

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

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

这里还有一张情况,看下面:

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

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

函数不仅是对象,还可以拥有属性。例如:

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呢?

其实方法有很多,这里罗列几种给大家。

1、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 只能用来判断两个对象是否属于原型链的关系, 而不能获取对象的具体类型。

2、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([ ])

2、值和类型

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"

3、值和引用

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

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

在JavaScript中,简单值(即标量基本类型值, scalar primitive) 总是通过值复制的方式来赋值 / 传递,包括null、 undefined、字符串、数字、布尔和 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 = [,,];
var b = a;
a; // [1,2,3]
b; // [1,2,3]
// 然后
b = [,,];
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 。

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

js类型----你所不知道的JavaScript系列(5)的更多相关文章

  1. js值----你所不知道的JavaScript系列(6)

    1.数组 在 JavaScript 中,数组可以容纳任何类型的值,可以是字符串.数字.对象(object),甚至是其他数组(多维数组就是通过这种方式来实现的) .----<你所不知道的JavaS ...

  2. 闭包----你所不知道的JavaScript系列(4)

    一.闭包是什么? · 闭包就是可以使得函数外部的对象能够获取函数内部的信息. · 闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. · 闭包就 ...

  3. 提升----你所不知道的JavaScript系列(3)

    很多编程语言在执行的时候都是自上而下执行,但实际上这种想法在JavaScript中并不完全正确, 有一种特殊情况会导致这个假设是错误的.来看看下面的代码, a = 2; var a; console. ...

  4. let和const----你所不知道的JavaScript系列(2)

    let 众所周知,在ES6之前,声明变量的关键字就只有var.var 声明变量要么是全局的,要么是函数级的,而无法是块级的. var a=1; console.log(a); console.log( ...

  5. LHS 和 RHS----你所不知道的JavaScript系列(1)

      变量的赋值操作会执行两个动作, 首先编译器会在当前作用域中声明一个变量(如果之前没有声明过), 然后在运行时引擎会在作用域中查找该变量, 如果能够找到就会对它赋值.----<你所不知道的Ja ...

  6. 你所不知道的JavaScript数组

    相信每一个 javascript 学习者,都会去了解 JS 的各种基本数据类型,数组就是数据的组合,这是一个很基本也十分简单的概念,他的内容没多少,学好它也不是件难事情.但是本文着重要介绍的并不是我们 ...

  7. 你所不知道的javascript数组特性

    工作中,我们经常使用js的数组,但是,下面的东西你见过吗? 1,文本下标: var a=[]; a[-1]=1; 你想过数组的下标为负数的情况吗?我们对数组的下标规定从0开始.但是上面那么写也还是可以 ...

  8. JavaScript中你所不知道的Object(二)--Function篇

    上一篇(JavaScript中你所不知道的Object(一))说到,Object对象有大量的内部属性,而其中多数和外部属性的操作有关.最后留了个悬念,就是Boolean.Date.Number.Str ...

  9. 你所不知道的 CSS 阴影技巧与细节 滚动视差?CSS 不在话下 神奇的选择器 :focus-within 当角色转换为面试官之后 NPOI 教程 - 3.2 打印相关设置 前端XSS相关整理 委托入门案例

    你所不知道的 CSS 阴影技巧与细节   关于 CSS 阴影,之前已经有写过一篇,box-shadow 与 filter:drop-shadow 详解及奇技淫巧,介绍了一些关于 box-shadow  ...

随机推荐

  1. 微信小程序-下拉事件(onPullDownRefresh)不触发

    1.app.json 没有配置 "window": { /* 其他配置信息 */ "enablePullDownRefresh":true } 2.scroll ...

  2. Json Schema的使用

    直接上案例: 在Web Api通讯中,客户端发送json数据,服务端反序列化json(json与某个类形成对应关系),在某些情况下,需要校验其上传的json是否合法. 服务端是使用Json.net(n ...

  3. vue中对axios进行封装

    在刚结束的项目中对axios进行了实践(好不容易碰上一个不是jsonp的项目), 以下为在项目中对axios的封装,仅封装了post方法,因为项目中只用到了post,如有需要请自行进行修改 src/c ...

  4. Request获取客户端IP

    获取客户端的IP地址的方法是:request.getRemoteAddr() ,这种方法在大部分情况下都是有效的.但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实IP地址了. ...

  5. cc1plus.exe: error: unrecognized command line option "-fno-keep-inline-dllexport "

    在Windows环境下的控制台上,通过qmake指令编译Qt程序时,出现 cc1plus.exe: error: unrecognized command line option "-fno ...

  6. 【Excel】SUMIF函数的兼容性

    兼容性非常强的两个函数 SUMIF() 说兼容性,当然得说SUMIF了. 来,我们先举个例子. 现有一个表格,算起来只有"科目划分"."发生额"两列内容,但是折 ...

  7. Sql Server 只有MDF文件恢复数据库的方法以及2008清除日志文件

    首先建立同名的空数据库,然后停止数据库服务运行,将MDF文件覆盖后启动服务,并修改和执行下面的语句. alter database JinMa_NYGL set EMERGENCY alter dat ...

  8. January 29th, 2018 Week 05th Monday

    Losing all hope was freedom. 彻底绝望就是真正的自由. Losing all the hopes, and we are free to challenge everyth ...

  9. Django REST framework之版本,解释器,序列化

    1 版本 2 解释器 3.序列化 1 版本 通过?后面传版本号有两种方法: 方法一 from django.shortcuts import render from rest_framework.vi ...

  10. Kmeans基本思想

    https://blog.csdn.net/zjc_game_coder/article/details/78595833 Kmeans算法的基本思想:看如下图: 解决小样本 .非线性及高维模式识别问 ...