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

时间:2022-04-22 09:24:29

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 不在话下 神奇的选择器 &colon;focus-within 当角色转换为面试官之后 NPOI 教程 - 3&period;2 打印相关设置 前端XSS相关整理 委托入门案例

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

随机推荐

  1. IT自学论坛

    http://yun.baidu.com/share/home?uk=4113898546&view=share#category/type=0

  2. c语言 动态数组

    C语言中,在声明数组时,必须明确告诉编译器数组的大小,之后编译器就会在内存中为该数组开辟固定大小的内存.有些时候,用户并不确定需要多大的内存,使用多大的数组,为了保险起见,有的用户采用定义一个大数组的 ...

  3. &lpar;leetcode&rpar;Missing Number

    Given an array containing n distinct numbers taken from 0, 1, 2, ..., n, find the one that is missin ...

  4. Python标准库---子进程 &lpar;subprocess包&rpar;

    这里的内容以Linux进程基础和Linux文本流为基础.subprocess包主要功能是执行外部的命令和程序.比如说,我需要使用wget下载文件.我在Python中调用wget程序.从这个意义上来说, ...

  5. Linux批量替换文件内容

    问题描述:现在需要将rack1目录下*.send文件中的"-ip="替换成“-localIp=10.0.0.1/n-ip=” 刚才那个批量文本内容替换,只能替换内存中的内容,并不会 ...

  6. HDOJ&lpar;1000&rpar; A &plus; B Problem

    代码如下: #include <stdio.h> int main(void) { int a, b; ){ printf("%d\n", a+b); } ; }

  7. 通过JavaScript判断当前浏览器版本

    从别人处拷贝,只用于自己学习之用.参考源自:http://www.cnblogs.com/leadzen/archive/2008/09/06/1285764.html JavaScript是前端开发 ...

  8. Unity3D 引擎基础 C&num; (数据结构入门) Unity3D 界面 UI(NGUI)(动画系统,导航系统)(委托与事件,常用设计模式)

    Geomagic Sculpt 2016.2 Windows Software 11个月前 (01-17) 0评论 Geomagic Sculpt 触觉式三维设计 触碰您的设计使用三维工具做三维设计. ...

  9. 使用python函数持续监控电脑cpu使用率、内存、c盘使用率等

    方法一: # import time 导入time模块 # import psutil 导入psutil模块 # def func(): # while True: ------->持续监控得w ...

  10. mysql from dual插入实现不插入重复记录

    在mysql中插入一或者多条记录的时候,要求某个字段的值唯一,但是该字段没有添加唯一性索引,可用from dual解决. select * from (select '2015080109' a,2 ...