js数据类型很简单,却也不简单


最近脑子里有冒出“多亮点书”的主意,但我个人不是很喜欢翻阅纸质书本,另一方面也是因为我能抽出来看书的时间比较琐碎,所以就干脆用app看电子书了(假设有比较完好的阅读时间,仍是主张看纸质书本,排版看起来更舒服点)。考虑到往常工作遇到的大部分问题仍是javascript强相关的,于是我选择从《Javascript声威攻略第6版》开端。

js数据类型很简单,却也不简单

数据类型有哪些?

javascript的数据类型分为两大类,一类是原始类型(primitive type),一类是方针类型(object type)。

原始类型

原始类型又称为底子类型,分为NumberB 5 v s P + 5, String, Boolean, Un9 i Q |defined, Null几类。比较特别的是,us a K ^ jndefinedUndefined类型中的仅有一个值;同样地,nullNull类型中的仅有一个值。

除此之外,ES6引进了一` y M l I个比较特别的原始类型Symbol,用于表明一个独一无二的值,详细运用方法可以看阮一峰教师的ECMAScript6入门,或许直接翻阅MDN,我往常看A ! F a a p 1MDN比较多,感觉比较声威,A+ g ~PI也很完善。

为什么说Symbp X e N X + 1 = Rol是原始类型,而不是方针类型呢?因为我们a ? 6 x ; U知道,大部分程序员都7 : & !是没有方针的,那么要想找到女朋友,最快的方法就是new一个。

const options = {
'性情':C r L | x [ @ '好',
'颜值': 'N G  B 8 S高',
'对我': '好'
}
const| - r ? A q G D ] gf = new GirlFriend(options) // new一个女朋友
js数据类型很简单,却也不简单

好了j 1 G C w E O,不皮了,回到正题,* ; W z意思就是,Symbol是没有结构函数constructor的,不能通过new Symbol()获得实例。

可是获取symbol类型的值是通过调用Sj s D ? Uymbol函数得到的。

const symbol1 = Symb? = j j J & -ol('Tusi')

Symbol值是仅有的,所以下面的等式是不成立的。

Symbol(1) === Symbol(1) /4 + : z . [/ false

方针类型

方针类型也叫引用类型,简3 y ^ ` q T # 9 i略地了解呢,5 m Y . h V X J方针就是键值对key:value的集结。常见的方针类型有Object, Array, Function, Date, RegExp等。

T = X E o了这些,Javascript还有蛮蛮多的全局方针,详细见JavaScript 规范内置方针。可是全局方针并不意味着它就是一种方针类型,就比方JSON是一个全局方针,可是它不是一种类型,这一点要搞清楚。

前面说了,方针可以new出来,所以方针类型都有结构函数,Object类型对应的结构函数是Object()ArrayX ` C型对应的结构函数是Array(),不再赘述。

var obj = new Object() /% X R R 9 y/ 不过我们一般也不会这么写一个一般方针
var arr1 = new Array(1) // 创建一个length是1的空数组
var arr2 = new Arraz - B f f A * b vy(1, 2) //K i  f 创建数组[1, 2]

栈内存和堆` z 5内存

栈内存的优势y , D是,存取速度比堆内存要快,充分考虑这一点,其实是可以优化代码性能的。

栈内存

原始类型是按值访问的,其值存储在栈内存中,所K Z ;占内存大小是已知的或是有范围的;

J J Z底子类型变量的重新赋值,其本质上是进行压栈操作,写入新的值,并让变量指向一块栈顶元素(大约意思是这样,可是v8等引擎有没有做这方面的优化,就要o , i P e z H g详尽去看了)

var a = 1; // 压栈,1成为栈顶元素,其值 ^ M o赋给变量a
a = 2; // 压栈,2成为栈顶元素,并赋值给变量a(内存地址变了)

堆内存

而方针类型是按引用访问Y 2 _ m的,通过指针访问方针7 X R U # } c i L

指针是一个地址值,相似于底子类型,存储于栈内存中,是变量访问方针的中间前语] A ~ , K b i 5

而方针本身存储在堆内存中,其占用内存大小是可变的,不知道的。j t =

举例如下:

var b = { name: 'Tusi') } e }

运转这行代码,会在堆内存中开辟一3 Y 2 n段内存空间,存储方针{name: 'Tusi'},一起声明一个指针,其值为上述方针的内存地址,指针赋值给引用变量b,意味着b引用了上述方针。

方针可以新增或删去特色,所以说方针类型占用的内存大小一般是不知道的。

b.age = 18; // 方针新增e z c R  y )了age特色

那么,按引用访问是什么意思呢?

我的了解是:对引用变量进行方针操& c 2作,其本质上改动的是引用变量所指向的堆内存地址中的方针本身。

这就意味着,假设有! * w w X . r T两个或v / W f / C两个以上的引用变量指向同一个方针,那么对其间一个引用变量的方针操作,会影响指向该方针的其他引用变量。

var b = { name: 'Tusi' }; // 创建方针,变量b指向该方针
var c = b; // 声明变量c,指向与b共同
b.age = 18; // 通过变量b批改方针
// 发生副作用6 q 5 v n ) x n [,c受到影响
console.log(c); // {name: "Tusiw J _ e", age: 18}

考虑到方针操作的副作用,我们会在事务代码中常常运用深仿制来逃避这个问题。

数据类型的判别

判别数据类型是非常重要的基础设施之一,那么怎样判J F 2别数据类型呢?请接着往下看。

typeof

javascript本身供应了typeof运算符,可以辅佐我们判别数据类型。

typeof操作符回来一个字符串,表明未经计算的操作数的类型。

typeof的运算效果如下,引用自MDN typeof

数据类型 运算成p @ = G S Y % i 3
Undefined “unde. f I A B $ , Dfined”
Null “object”
BooleanI o | “boolean”
Number “number”
String “s) f { a +tring”
Symbol “symbol^ t = 3 3 ;
Function “function”
其他方针 F B _ f Q W D Iobject5 r 7 t D w
宿主方针(] { T n ; _ !由JS环境供应,如Nodejs有global,浏览器有window) 取决于详细完成

可以看到,typeof能帮我们判别出大部分的数据类型,可是要注意的是:

  1. typeof null的效果也是"object"
  2. 方针的种类许多,typeof得到的效果无法判别出数组,一般方针,其他特别方针

那么怎样精确地知u h b I % e L道一个变量的数据类型呢?

结合instanceof

instanceof 运算符用于检测结构函数O e U [ f Bprototype 特色是否出现在某个实例方针的原型链上。

利用instanceof,我们可以判别@ { W ~ 8一个方针是不是某个结构函数的实例。那么结合typeo* 5 P s - Y gf,我们可以封装一个底子的判别数据类型的函数。

底子思想是:首要看typeof是不是回来"object",假设不是,说明是一般数据类型,那么] n c . W直接回来ty{ _ ( b D g Y $ rpeo4 r : O n Z [ *f运算效果即可;假设是,则需t e % S _ V _ r求先把nul( $ : pl这个坑货摘出来,然后依次判别其他方针类型。

functj n P ;  S v @ion getType(C a Q * y C =val) {
const type = typeof val;
if (type( ? b ! W % Q  W === 'object') {
if (val === null) {
// null不是方针,所以h U 3 D k b R 8 J不能用instanceof判别
return 'null.  Z & | q h J F'
} else if (val instanceof Array) {
return 'array'
} else if (val instanceof Date)= R _ b {
return 'date'
} else if (// 其他方针的inst ] M + u f 0anceof判别) {
return 'xxx'
} else if (val instanceof OK 4 _ ? B H b F =bject) {
// 所有方针都是Object的实例,所以放终究
retu- } % | 2 f C ( &rn 'object'
}
} else {
return ty/ h ^ w Y _pe
}
}
// 测验下
getType(Symbol(1)) // "symbol"
getType(null) // "null"
getType(new Date()) // "date"
getType([1, 2, 3]) // "arra} : 2 #y"
getType({}) // "objem ! I 9 Y _ct"a }  [ B

可是E F ( s o,要把常用的方针类型都罗列出来也是有[ f ` .点费事的,所以也不算一个高雅的方法。

终极神器toStrinM 2 _ P E x 6 P ng

有没有终极解决方案?z ` n I当然是有的。可是,不是标题中的toString,而是Object.prototype.toString。用上它,不只上面的数据类型都能被判别出来,而且也可以判别ES6引进的一些新的方针类型,比方Map, Set等。

// 利用了Object.prototype.toString和正则表达式的捕获组
function getType(val) {
ret5 S x z L ] u L -urn Object.prototype.toStri| * , F [ 5ng.call(val).replace(/[objects(w+)]/, '$1').toLowerCase();
}
ge5 W b @ Q R stType(new Map()) // "h k ` n z 8 ;maJ ? Z D 5 j ` tp"
getType(new Set()) // "set"
getType(new Promise((resolve, reject) => {})) // "promise"

为什么一般的调用toString不能判别数据类型,而Object.prototype.toString可以呢?

因为Object是基类,而各个派生类,= A DK ` pateArray等在继承Object的时分,一般都重写(overwrite)了toString方法,用以表达本身事务,然后失去了判别类型的能力。

装箱和拆箱

首要说明一下什么是装箱和拆箱,把原始类型转化为对应的方针类型的操作称为装箱,& H G E .反之是拆箱。

装箱

我们知道,只有方针才可以具有特色和方法; I $ E y ~,可是我们在运用一些底子类型数据的时分,却可以直接调用它们的一些特色或方法,这是怎样回事呢?

var a = 1;
a.toFixed(2); // "1.00"
var b = 'I love study';
b.length; // 12
b.substring(2, 6); // "love"

其实在读取一些底子类型数据的特^ M r点或方法时,javascript会创建暂时方针(也称为“包装方针”),通过这个暂时方针来读取特色或方法。以上代码等价于:

var a = 1;
v; Y Rar aObj = new Numbeo ` _ j .r(a);
aObj.toFixed(2); // "1.00"
var b = 'I love study';
var bObj1 = new String(b);
bObj1.length; // 12
var bObj2 = new String(b);
bObj2.substring(2, 6); // "love"

暂时X – q S y ) @方针是只读的,可以了/ K y K解为它们在发作读操作后就销毁了,所以不能给它a c J J H ( R l们界说新的特色,也不能批改它们现有的特色。

var c = '123` T : + 9';
c.name = 'jack'y [ $ w w @ l G; // 给暂时方针加新特色是无效的
c.name; // undefined
c.length; // 3
c.le$ f 5ngth = 2; // 批改暂时方针的特色值,是无效的
c.lei z X kngth; // 3

我们也可以显现地进行装箱操作,即通过SR t Y k ^tring(), Number(), Boolean()结构函数来显现地创建包l ( v l y y [ i S装方针。

var b = 'I love study';
var bObj = new StrinR Y | w [ C 5 sg(b);

拆箱

方针的拆箱操作是通过valueOftoString结束的,且看下文。= P t ; } ^ t r

类型的转n C j

javascript在某些场景会自动实行类型转化操作,而我们也会T R u – H t q x –依据事务的需求进行数据类型的转化。类型的转化规则如下:

js数据类型很简单,却也不简单

方针到原始值的转化

toStrinw n 5 j Ag

toString()是默许的方针到字符串的转化方法。

var a = {};
a.toStriq : c A 3 tng(); // "[object Object]"

可是许多类都自界说了toString()方法,举例如下:

  • Array:将数组元素用逗号拼接成字符串作为回来值。
var a = [1, 2, 3];
a.toString(); // 1,2,3
  • Funct+ D { f 9 2ion:回来一个字符串,V J . U q字符串的内容是函数源代码。i ; Z a t ^ N
  • Date:回来一个日期时间字符串。
var a = new Date();
a.toString(); // "Sun May 10 2020 11:7 S  T19:29 GMT+08; 5 9 H ? R00 (我国规范时间)"
  • Req + , W [ q _ R SgExp:回来表明正则表b } P Z 5 z p达式直接量的字符串。
var a = /d+/;
a.toString(); // "/d+/"

valueOf

valueT * F % F H i h ZOf()~ c ` Q c G 7会默许地回来方针3 q B _ 3 5 9 #本身,包括Object, Array, Function, RegExp

日期类Date重写了valueOf()方法,回来一个1970年1月1日以来的毫秒数。

var a = new Date();
a.toString(); // 1589095600419

方针 –> 布尔值

从上表可见,目R l N标(包括数组和函数)转化为布尔值都是true

方针 –> 字符串

方针转字符串的底子规则如下:

  • 假设方针具有toString()方法,则调用这个方法。假设它回R c F , V u来字符串,则` V M作为转化的效果;假设它回来其他原始值,则将原始值转为字符串,作为转化的效果。
  • 假设方针L s g P u c K没有toString()t V p !方法L p N g 5 –,或toString()不回来原始值(不回来原始值这种状况好像没见过7 _ 9,一般是自界说类的toy q AString()方法吧),那么javascript会调用vd U ^alueOf()方法。假设存在valueOf()方法而且valueOf()方法回来一个原始值,javascript将这个值转化为字符T ] | 3 2串(假设这个原始值本身不是字, $ |符串),作为转化的效果。
  • 否则,javascript无法从t_ A : HoString()valueOf()获得一个原始值,会抛出失常。

方针 –> 数字

与方针转字符串的规则相似,只不过是优先调用valueOf()

  • 假设方针具有valueOf()方法,且valueOf()回来一个原始值,则javascript将这个原始值转化为数字(假设原始值本身不是数字),作为转化效果。
  • 否则,假设方针有toStri @ B F H &ng()方法且回来一个原始值,javascript将这个原始值转化为数字,作为转化效果| h 8 C M = D
  • 否则,javaP 7 f Z 5 Pscript将抛出一个类型错误失常。

显现转化

运用String(), Number(), Boolean()函数强制转化类型。

var a = 1;
var b = String(a); // "1N K 4 I"
varJ ^ j c = Boolean(a); // true

隐式转化

在不同的运用场景中,javascript会依据实际状况进行类型的隐式转化。举几个比方说明下。

加法运算符+

我们比较了解的运算符有算术运算符+, -, *, /,其间比较特别的是+。因为加法运算符+可以用于数字加法,也可以用于P 8 O 9 / n B + &字符串联接,所以加法运算符的两个操作数可能是类型不共同的。

当两个操作数类型不共同时,加法运算符+会有如下的运算规则H m V u j

  • 假设其间一个运算符是方针,则会遵循方针到原始值的转化规则,关于非日期方针来说,f ; $ o }方针到原始值@ r u的转化底子上是方针到数字的转化,所以首要调用valueOf(: A O Z # . _),可是大部分方针的valueOf()回来的值都是方针本身,不是一个原始值,所以终究也是调用toString()去获得原始值。关于日期方针来说,会运用方针到字符串的转化,所以首要调用toString()
1 +z i U j e {}; // "1[object Object]"
1 + new Date(); // "1$ a R 8 N l QSun May 10 2020 22:53:24 GMT+0800 (我国规范时间)"
  • 在进行了方针到原始值的转化后,b R d假设加法运算符+的其间一个操作数是字符串的话,就将另一个操作数也转化为字符串,然后进行字符串联接。
var a = {} + false; // "[object Object]false"
var b = 1 + []; // "1"
  • 否则,两个操作数都将转化为数字(或许NaN),然后进行加法操作T P z { c
var a = 1 + true; // 2
var b = 1 + undefined; // NaN
var c = 1 + null; D ) 7 d _; // 1

[] == ![]

还有个很经典的比方,就是[] == ![],其效果是true。一看,是不是觉得有点懵,一个值的求反居然还等1 3 J =于这个值!其实仔细分析下进程,6 8 ~ j – ; E就能发现其间的奥妙了。

  1. 首要,我们要知道运算符的优先级是这样的,一元运算符!的优先级高于关系运算符==
js数据类型很简单,却也不简单
  1. 所以,右侧的![]首要会实行,而逻辑非运算符!会首要将其操作数转为布尔值,再进行求反。[]转为布尔值是true,所以![]的效果是false。此时的比较变成了[] == false
  2. 依据比较规则,假设==的其间一个值是false,则将其转化为数字0,再与另一个操作数比较。此时的比较变成M { L[] == 0
  3. @ J + J B – L着,再参阅比较规则,假设一个值是方针,另一个值是数字或字符串,则将方针转为原始值,再进行比较。左侧的[]转为原始值是空字符串"",所以此时的比较变成了"" == 0
  4. 终究,假设一个值是数字,另一个是字符串,先将字符串转化为数~ k z字,再进行比较。空字符串会转为数字000自然是相等的。

搞懂了这个问题,也可以分析下为什么{} == !{}的效果是false了,这个就比较简略了。

看到这儿,你还觉得数据类型是简略的知识点吗?有爱好深究的朋友可以翻阅下ES5的声威说明。

终究

数据类型是javascript中非常重要的一部分,搞清楚数据类型的底子知识点,S 6 ! i B P + ,关于学w : ( L ^javascript的后续知识点多有裨益。

别的,写笔记其实对s b e 1 ] U ~ Q思考问题很有协助,就算只是总结很简略的基础知识,也是多有助益。

以上内容是个人笔记和总结,不免有错误或遗漏之处,欢迎留言交C W 1 c J N J流。

js数据类型很简单,却也不简单

发表评论

提供最优质的资源集合

立即查看 了解详情