背景知识
大型js基础系列文章开更,自己深入理解js的同时并书写记录方便他人,予人玫瑰手有余香!
什么是数据类型,js中的数据类型
什么是数据类型?很多人会心一笑,说数据类型就是数据的类型啊,还有什么其他更深层层次的解释吗?
一开始我也着么认为,抱着肯定的态度打开浏览器确认一下自己的观点。于是乎下面的一段话出现在我的眼前。
数据类型在数据结构中的定义是一个值的集合以及定义在这个值集上的一组操作。
计算机是一种二进制计算工具,只存在0和1,每一位数据可存储的数据有限,只有高电位和低电位两种情况。
拿java8大数据类型中的两种最简单的数据类型int型和bool型来说。
int型最大整数:2147483647,最小整数:-2147483648。数据占位4字节。字节即B(byte)是信息的最小单位。
bool型true和false。占位1字节。
这就是数据类型存在的意义,因为不同数据信息占位不同,为了不浪费系统空间,把不同数据类型的数据尽量压缩在最小的信息单元,来节省运行程序所带来的消耗。
说着么多各位看官觉得我扯远了,可是很多时候人就是这样,只会看见事物表面的东西,而深层次的本源忘的一干二净。其实上面的一段话对于计算机毕业的学生来说并不陌生,只是我们在日常工作中忽视了代码的本源而已,只有剥茧抽丝你才能成长!
js中的数据类型一共有6种,分别是Undefine,Null,Boolean,Number,String以及Object。
数据类型的定义不说,自行查找或者理解的直接往下看。
简单数据类型和复杂数据类型关系(也有叫原始数据类型和引用数据类型的,这里按照javascript高级程序设计书中的称呼)
先看定义(引自w3school)
简单数据类型:存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。
复杂数据类型:存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存处。
简单数据类型一共有五种,分别为:Undefine,Null,Boolean,Number,String而复杂数据类型只有一种即为Object一张w3school的图帮你理解两者之间的区别。
代码方式理解如下:
1 | let stringA = 'abc' |
因为是简单数据类型,所以在改变A的时候B的值并没有改变,因为简单数据类型是在栈内存中的赋值操作。
1 | let objA = {string:'abc'} |
我们定义了一个objA, objA赋值了一个对象{string:’abc’}的地址,姑且假设{string:’abc’}的地址为0x0012ffxx,这时候我们又定义了一个objB,同时把0x0012ffxx的地址赋值给objB,所以我们第一步输出为{string: “abc”} {string: “abc”},再之后我们把0x0012ffxx地址上的原始值给改了,所以objA和objB同时发生了改变。接着往下看。
1 | objA = {string:'efg'} |
这时候我们又干了一件事,我们把objA的赋值地址给改了,而objB则没有发生改变,所以objA和objB不同了,objB还是0x0012ffxx之前的地址,而objA指向了堆内存中的另一个地址。这个就是有时候我们需要做对象深拷贝的原因。
图片理解(自己画的质量不是特别高,理解万岁)
复杂数据类型概述(也叫引用数据类型)
复杂数据类型也就是我们说的Object对象,用w3school的话来说:
引用类型通常叫做类(class),也就是说,遇到引用值,所处理的就是对象。
其实着么说并不完全正确,因为es5中并没有明确的给出’类’的概念,而我们现在说的类只是前辈通过对其他语言类的理解和对js的摸索模拟出的一个类。即使es6中加入了class和extends也是通过现有的继承和语法糖来实现的。 原型链和继承我们会单独拿出几篇文章来写,这里说一下复杂数据类型的一些概念详解。
个人觉得js中除5种简单类型以外的类型都是Object类型,即对象。这6种对象通过typeof区分也会获得到6种结果,有意思的是这些结果并不是一一对应的,Undefine,Boolean,Number,String以及Object分别会拿到自己向对应的类型,而null则拿到了一个Object类型,可能是js底层代码认为null描述的是一个没有地址的对象类型而下的定义。
而对于function类型来说,它并不是严格意义上的一种数据类型,它只能称之为函数对象,因为函数是一种特殊的对象,为了和其他对象有所区分,所以对函数对象有了一个特殊的描述。
1 | Function instanceof Object //true |
对你没有看错,这两个货用instanceof运算符算出来竟然是互相继承的关系,即你中有我我中有你!
先简单说说instanceof
instanceof 可以在继承关系中用来判断一个实例是否属于它的父类型或者祖类型(即多级继承)
1 | function Aoo(){} |
我们定义了两个函数,分别为Aoo和Foo,而Foo通过原型继承了Aoo,而foo是通过Foo new创建而来,而所有实例都是通过Object生成的,所以都为true。
这时候我们祭出一张的图来理解Function和Object的关系
通过图片我们得到以下几个结论:
1.所有实例都或直接,或间接的继承自Object.prototype,而Object.prototype的_proto_指向null。js中除了简单数据类型一切都来自Object.prototype
2.Function.prototype继承自Object.prototype但Function创造了Object
3.Object instanceof Function为true,说明Object.prototype上有一段可以生产对象的指令,名字叫函数。
4.Function instanceof Object为true,说明Function也是对象。
总之Function和Object可以用鸡生蛋和蛋生鸡的方式理解。
既然已经追溯到根源,我们在浏览器中输入如下代码,看看Object.prototype是个什么东西。
1 | console.log(Object.prototype); |
于是乎我们在浏览器中看到了如下几个方法:
因为篇幅的关系本文仅说明Object下的7种基本属性和方法,而get,set以及已经废弃或者非标准的方法会在原型链文章中详细说明。
constructor
返回创建实例对象的 Object 构造函数的引用。(引自MDN)
构造函数是什么,很多写前端er在写js的初期,对js中的对象和类理解不深刻。所以构造函数,函数重载的概念并不是很清楚,又或着还给了大学老师。我们先温习一下java中对构造函数的定义。java中的构造函数是一种特殊的方法,主要用来初始化对象。有些类中可能存在多个构造函数,根据入参的不同生成不同的对象。
先拿一段java代码理解一下什么是构造函数以及重载
1 | class Car{ |
随便找一个java在线编译工具来编译执行一下代码,输入:
1 | new Car(); |
控制台会分别输出:
1 | Car is producing!!! |
js因为历史原因,把类,构造函数等概念弱化了,所以通过js来理解类,构造函数等概念并不容易所以我们祭出一段java来理解。
首先我们定义了一个Car类,可以理解成js中我们定义了一个Car对象,这个Car对象有一个私有变量color即车的颜色,又有一个私有方法product和getColor。
在我们new Car()的时候,java会自动寻找同名构造函数即Car(),这时候我们就先调用product()方法,生产了一辆车。
当我们new Car(blue)的时候,我们也是会去寻找同名函数Car(),然后找到可以传入String类型的重载构造函数,然后调用Car(String color),然后this()即是执行Car()构造函数,然后给私有变量color赋值为blue,然后再执行getColor方法进行喷漆操作。
所以这时候我们有了两辆车,一辆没有颜色,一辆是蓝色的。
基础概念有了,我们回归到js,js的constructor是什么呢,是我们创建Object构造函数的引用。
大白话说,比如我们在浏览器中执行:
1 | new Array() |
我们会拿到:
1 | [] |
而拿到的三个数组的constructor则是js中Array()的方法。也就是说,[].proto.constructor指向了Array()方法。
我们传入了null,Number以及1,2,3,4,5,Array通过构造函数重载分别生成了3个数组,而这3个数组的constructor又指向了生成自己的母体的地址。而这个Array是如何下蛋的请各位看官去看看js的源码。
我们理解了constructor是什么,那么这时候我们去用js去写一个我们用java写的实例。
1 | function Car(color) { |
执行结果如下
1 | Car is producing!!! |
是不是跟java已经很相似了。因为js在诞生的时候就是仿照java来编写的,为了易用性作者故意弱化了js类的概念。但是js发展至今日又有使用类的需求,所以我们会模拟一些类的写法,具体如何模拟会在下一篇原型链说明中表述。
剩下几个已经没有与constructor一样难以理解的方法,所以转来一些定义和示例方便大家阅读。以下概念及示例均转自MDN
hasOwnProperty
所有继承了 Object 的对象都会继承到 hasOwnProperty 方法。这个方法可以用来检测一个对象是否含有特定的自身属性;并且忽略那些从原型链上继承到的属性。
示例
1 | o = new Object(); |
该方法检测对象是否含有私有的特定方法。
isPrototypeOf
isPrototypeOf() 方法允许你检查一个对象是否存在于另一个对象的原型链上。
示例
1 | function Foo() {} |
propertyIsEnumerable
每个对象都有一个propertyIsEnumerable方法。此方法可以确定对象中指定的属性是否可以被for…in循环枚举,但是通过原型链继承的属性除外。如果对象没有指定的属性,则此方法返回false。
示例
1 | var o = {}; |
toLocaleString
返回调用toString()的结果。会有些内置对象去复写该方法如Array,Number,Date。
toString
每个对象都有一个toString()方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 “[object type]”,其中type是对象的类型。
valueOf
JavaScript调用valueOf方法将对象转换为原始值。默认情况下,valueOf方法由Object后面的每个对象继承。 每个内置的核心对象都会覆盖此方法以返回适当的值。如果对象没有原始值,则valueOf将返回对象本身。
因为数据类型遇到的坑(实时更新)
Number最大值问题
记得一个金融公司内部账户项目,账户id是18位的,接口反给前端是数字类型。
查看账户详情是得需要账户id来查询的,于是乎查询死活查不到怎么看代码都没有问题。
但是传出去的查询请求都是错误的id,最后才发现超过最大值,js给你自动转化了。
==和===的问题
也是在工作中遇到的,刚做前端的时候不以为然,觉得没什么区别,后来发现天差地别。
js中存在相等运算符和严格运算符。
严格运算符会判断,是否同一类型,同一原始值和同一复合类型值。
而相等运算符都会把所有原始类型的值变为数值类型进行比较。(不要小看这个,他会把0==undefined==false变为true)。
如果这样还无法说服你放弃使用==的话请看下面的代码。
1 | var x = 1; |
从野生程序员到家养程序员的进化从放弃使用==开始!