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)的更多相关文章
-
js值----你所不知道的JavaScript系列(6)
1.数组 在 JavaScript 中,数组可以容纳任何类型的值,可以是字符串.数字.对象(object),甚至是其他数组(多维数组就是通过这种方式来实现的) .----<你所不知道的JavaS ...
-
闭包----你所不知道的JavaScript系列(4)
一.闭包是什么? · 闭包就是可以使得函数外部的对象能够获取函数内部的信息. · 闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. · 闭包就 ...
-
提升----你所不知道的JavaScript系列(3)
很多编程语言在执行的时候都是自上而下执行,但实际上这种想法在JavaScript中并不完全正确, 有一种特殊情况会导致这个假设是错误的.来看看下面的代码, a = 2; var a; console. ...
-
let和const----你所不知道的JavaScript系列(2)
let 众所周知,在ES6之前,声明变量的关键字就只有var.var 声明变量要么是全局的,要么是函数级的,而无法是块级的. var a=1; console.log(a); console.log( ...
-
LHS 和 RHS----你所不知道的JavaScript系列(1)
变量的赋值操作会执行两个动作, 首先编译器会在当前作用域中声明一个变量(如果之前没有声明过), 然后在运行时引擎会在作用域中查找该变量, 如果能够找到就会对它赋值.----<你所不知道的Ja ...
-
你所不知道的JavaScript数组
相信每一个 javascript 学习者,都会去了解 JS 的各种基本数据类型,数组就是数据的组合,这是一个很基本也十分简单的概念,他的内容没多少,学好它也不是件难事情.但是本文着重要介绍的并不是我们 ...
-
你所不知道的javascript数组特性
工作中,我们经常使用js的数组,但是,下面的东西你见过吗? 1,文本下标: var a=[]; a[-1]=1; 你想过数组的下标为负数的情况吗?我们对数组的下标规定从0开始.但是上面那么写也还是可以 ...
-
JavaScript中你所不知道的Object(二)--Function篇
上一篇(JavaScript中你所不知道的Object(一))说到,Object对象有大量的内部属性,而其中多数和外部属性的操作有关.最后留了个悬念,就是Boolean.Date.Number.Str ...
-
你所不知道的 CSS 阴影技巧与细节 滚动视差?CSS 不在话下 神奇的选择器 :focus-within 当角色转换为面试官之后 NPOI 教程 - 3.2 打印相关设置 前端XSS相关整理 委托入门案例
你所不知道的 CSS 阴影技巧与细节 关于 CSS 阴影,之前已经有写过一篇,box-shadow 与 filter:drop-shadow 详解及奇技淫巧,介绍了一些关于 box-shadow ...
随机推荐
-
IT自学论坛
http://yun.baidu.com/share/home?uk=4113898546&view=share#category/type=0
-
c语言 动态数组
C语言中,在声明数组时,必须明确告诉编译器数组的大小,之后编译器就会在内存中为该数组开辟固定大小的内存.有些时候,用户并不确定需要多大的内存,使用多大的数组,为了保险起见,有的用户采用定义一个大数组的 ...
-
(leetcode)Missing Number
Given an array containing n distinct numbers taken from 0, 1, 2, ..., n, find the one that is missin ...
-
Python标准库---子进程 (subprocess包)
这里的内容以Linux进程基础和Linux文本流为基础.subprocess包主要功能是执行外部的命令和程序.比如说,我需要使用wget下载文件.我在Python中调用wget程序.从这个意义上来说, ...
-
Linux批量替换文件内容
问题描述:现在需要将rack1目录下*.send文件中的"-ip="替换成“-localIp=10.0.0.1/n-ip=” 刚才那个批量文本内容替换,只能替换内存中的内容,并不会 ...
-
HDOJ(1000) A + B Problem
代码如下: #include <stdio.h> int main(void) { int a, b; ){ printf("%d\n", a+b); } ; }
-
通过JavaScript判断当前浏览器版本
从别人处拷贝,只用于自己学习之用.参考源自:http://www.cnblogs.com/leadzen/archive/2008/09/06/1285764.html JavaScript是前端开发 ...
-
Unity3D 引擎基础 C# (数据结构入门) Unity3D 界面 UI(NGUI)(动画系统,导航系统)(委托与事件,常用设计模式)
Geomagic Sculpt 2016.2 Windows Software 11个月前 (01-17) 0评论 Geomagic Sculpt 触觉式三维设计 触碰您的设计使用三维工具做三维设计. ...
-
使用python函数持续监控电脑cpu使用率、内存、c盘使用率等
方法一: # import time 导入time模块 # import psutil 导入psutil模块 # def func(): # while True: ------->持续监控得w ...
-
mysql from dual插入实现不插入重复记录
在mysql中插入一或者多条记录的时候,要求某个字段的值唯一,但是该字段没有添加唯一性索引,可用from dual解决. select * from (select '2015080109' a,2 ...