js类型----你所不知道的JavaScript系列(5)
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 ...
随机推荐
- [20171128]rman Input or output Memory Buffers.txt
[20171128]rman Input or output Memory Buffers.txt --//做一个简单测试rman 的Input or output Memory Buffers. 1 ...
- bash的内置字符串处理工具
bash内置字符串处理工具: 1.字符串切片: ${var:offset:number} 取字符串的子串: ${var: -length} 取字符串的最右侧的length个字符.注意:冒号右侧必须有 ...
- 路由交换04-----STP
路由交换-----STP STP协议(生成树协议)逻辑上断开环路,防止二层网络的广播风暴的产生. 为什么需要STP协议? 假设有这样一个拓扑: 现在PC1ping网关192.168.1.3,而这个网关 ...
- 报数的golang实现
报数序列是一个整数序列,按照其中的整数的顺序进行报数,得到下一个数.其前五项如下: . . . . . 1 被读作 "one 1" ("一个一") , 即 11 ...
- leetcode 7. Reverse Integer [java]
public int reverse(int x) { long res = 0; while (x != 0){ res = res* 10 + x % 10; x /= 10; } if(res ...
- python3的C3算法
一.基本概念 1. mro序列 MRO是一个有序列表L,在类被创建时就计算出来. 通用计算公式为: mro(Child(Base1,Base2)) = [ Child ] + merge( mro(B ...
- 怎么配置 Oracle 侦听器来使用SQL操作ST_Geometry
关于这个内容,其实从ArcSDE9.2推出ST_Geometry就让用户感到很有吸引力,而且特别是在ArcSDE9.3之后,用户使用SQL操作ST_geometry越来越多,但是在配置Oracle监听 ...
- CF 633 E. Binary Table
题目链接 题目大意:给定一个棋盘,棋盘上有0或1,你可以将一整行取反或者一整列取反,要使得最后剩的1最少.\((1\le n\le 20,1\le m\le 100000)\). 一个容易想到的思路就 ...
- Android中如何使用xmlns
http://blog.csdn.net/lihenair/article/details/41009711 工作中时常需要自定义控件,除了按键,draw以外,还需要对控件属性进行一些初始化的操作,比 ...
- Java关于远程调试程序教程
本节尝试一下Java远程调试的东西,记录一遍简单入门的东西.也就算是使用记录吧! 写一个简单程序打成jar丢到远程服务器运行,模拟远程Server在运行.就拿Java调用shell脚本提交作业程序为例 ...