回望Javascript:数据类型相关

1. JavaScript有哪些数据类型,它们的区别?

Javascript有八种数据类型:Number,String,undefined,null,Boolean,Symbol,BigInt,Object。

其中Symbol和BigInt是ES6中新增的数据类型:

  • symbol代表创建后独一无二且不可变的数据类型,主要是为了解决可能出现的全局变量冲突的问题
  • BigInt是一种数字类型的数据,它可以表示任意精度格式的整数,适用BigInt可以安全的存储和操作大整数,即使这个数已经超过了Number能够表示的安全整数范围。

这些数据可以分为原始数据类型和引用数据类型:

  • 原始数据类型: Object 以外的所有类型都是不可变的(值本身无法被改变)

  • 引用数据类型:对象数组函数等

两种数据类型的区别主要是存储的位置不同:

  • 原始数据类型直接存储在栈中的简单的数据段,占据的空间小,大小固定,因为频繁被使用,因此被存入栈中
  • 引用数据类型存储在堆中,占据的空间大小不一,如果将这些数据存储在栈中则会影响程序的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当使用时会先在栈中找到栈中引用堆得地址然后通过地址获得堆中的实体

关于堆和栈的概念存在数据结构和操作系统内存中

数据结构中:

栈:采用先进后出的模式来存取数据

堆:优先队列,按照优先级来进行排序,优先级可以按照大小来规定

操作系统中:

栈:栈区的内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。操作方式也是先进后出

堆:堆中的内存一般由开发者分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。

注意:面试时可以分为五点,一一展开来描述,先说基本类型有什么,再说ES6新增的两个数据类型,其次就是基本数据类型和引用数据类型的区别,最后就是他们的存储方式

2. 数据类型的检测方式?

typeof

引用类型的变量和null会被判断为Object,其余均正常

instanceOf

可以判断对象的类型,不能用来判断基本类型数据。其原理使用的是原型链

constructor

constructor有两个作用,一个是用来判断数据的类型,二是实例对象通过constructor对象访问它的构构造函数。注意,如果我们改变了一个函数对象的原型,那他的实例对象就不能使用constructor来判断类型。

Object.prototype.toString.call()

使用Object原型上的toString方法来判断数据类型

3. 判断数组的方式有哪些?

  • Object.prototype.toString.call()
  • Array.isArray(obj)方法
  • instanceOf
  • Array.prototype.isPrototypeOf(obj):在obj的原型链上搜索Array

4. null和undefined的区别

首先的话说一下共同点,他们都是属于JavaScript的基本数据类型,而且这两个类型的值都是唯一的,就是它们本身。

接着他们最主要的区别:

  • undefined是指当前的变量没有被定义赋值,没有定义;而null是指我们手动给当前变量赋了一个null,并且他代表的往往是一个空的对象(并且往往在变量不再使用的时候将变量指控,便于浏览器回收上一个垃圾对象引用)
  • typeof的返回值类型不同,undefined是返回undefined,而null是返回一个Object

关键字:未定义,空对象

5.为什么null返回一个Object

对于这个问题各方解释不一,有人说是bug,有人说就是设计如此。

我认为:

按照null的定义其实是一个空对象的话这样解释也是合理:

从逻辑角度来看,null值表示一个空对象指针,而这正是使用typeof操作符检测null值时会返回“object”的原因。

深层此理解:

在JavaScript第一个版本中,所有的值都存储在32位的单元中,每个单元包含一个小的类型标签(1-3bits)以及我们要真实存储值的真实数据。类型标签存储在每个单位的低位中。

000: object   - 当前存储的数据指向一个对象。
  1: int      - 当前存储的数据是一个 31 位的有符号整数。
010: double   - 当前存储的数据指向一个双精度的浮点数。
100: string   - 当前存储的数据指向一个字符串。
110: boolean  - 当前存储的数据是布尔值。

如果最低位是 1,则类型标签标志位的长度只有一位;如果最低位是 0,则类型标签标志位的长度占三位。

其中有两种特殊的类型:

  • undefined的值是(-2)的30次方
  • null的值是机器码NULL指针(null指针的值全是0)

也就是null的类型标签和object的类型标签是一样的,因此会被判定位Object。

关键字:类型标签,null和Object的类型标签都是000.

我的理解:其实就是JavaScript最早期的时候使用32位单元来存储值,每个单元包含一个类型标签,这个类型标签就是判定数据类型的东西。然而null类型的类型标签和Object被设计成相等的。因此null是Object类型

6.intanceof 操作符的实现原理及实现

A instance B

首先先需要清楚instance是干什么的:如果B函数的显式原型对象在A对象的原型链上则返回true

面试官:小伙子,手写一个instanceof吧!

好的!

  function instancof(left, right) {
    let leftp = left.__proto__;//拿到left的隐式原型
    let rightp = right.prototype;//拿到right的显式原型对象的引用
    //找就完了
    while (true) {
      if (leftp == null) {
        false
      }
      if (leftp == rightp) {
        return true
      }
      leftp = leftp.__proto__
    }
  }

其实手写一个instance并不算什么难点,下面我们在来出几道题来刺激刺激你:

  console.log(Object instanceof Function)//true
  console.log(Object instanceof Object)//true
  console.log(Function instanceof Function)//true
  console.log(Function instanceof Object)//true
  function Foo(params) { }
  console.log(Object instanceof Foo);//false

是不是感到头大,其实这个结合上面的图很容易得出答案,只要我们谨记,instance的原理是判断后面的函数对象的原型对象是否在前面的示例对象的原型链上的,这样我们就可以很容易的写出原理和判断结果了。

7. 为什么0.1+0.2 ! == 0.3,如何让其相等?

关键字:精度丢失

这是浏览器在计算时的一个流程:

首先计算机在进行运算时会先将数据转为二进制的数,再去进行后续运算。然,在现在的浏览器中都是使用浮点数形式的二进制数来存储二进制数,因此还要讲将二进制数转为浮点数。最后将得到的二进制浮点数进行相加。计算结束后将浮点数转为十进制数。

注意:浮点数分为32位操作系统的单精度浮点数和64位操作系统的双精度浮点数。

对于这个问题的解答:

计算时发生了两次精度丢失。

第一次精度丢失:将二进制的0.1和0.2转为浮点数时,由于二进制的浮点数的小数位只能存储52位,导致小数点后53位的数要进行为1则进为0则舍的操作,这里会造成精度丢失。

第二次精度丢失:二进制的浮点数进行相加时,小数位相加导致导致小数位多出一位,又要让53位的数进行为1则进为0则舍的操作

可能引起的BUG:

价格300.01,优惠300元出现支付金额不足0.01元(人民币最小面值为0.01元)等

解决方案:

可以使用Math.toFixed()或者ES6中的Number.EPSION

8.如何获得安全的undefined值

undefined是一个z标识符,因此可以被拿到当作变量的值,但是这样做会影响undefined的正常判断。表达式void没有返回值,因此结果返回undefined。因此我们可以使用void 0来获得安全的undefined值。

9.typeof NaN的结果是多少

首先NaN是指“不是一个数字的值”,通常在我们进行计算时发生错误会返回这个值。

typeof NaN的值时Number

NaN时一个特殊的值,它是一个唯一不等于自身的值,也就是说NaN===NaN的结果时false

10.isNaN与Number.isNaN函数的区别

isNaN():这个函数首先会将传入的数据尝试转成数字,如果不能转,就直接返回true,以至于String、Boolean类型的数据都会返回true

Number.isNaN(): 这种方式是es6新引入的,首先会判断内部是不是数字,不会进行类型转换。因此更加有针对性,只在数字与NaN之间比较,如果是其他直接返回false

11.==操作符的强制类型转换规则

对于==来说,两边的类型不一样就会进行类型转换,具体流程如下:

  1. 如果双方数据类型相同,就直接比较内容
  2. 如果是在比较null和undefined就直接返回true
  3. 如果是Number和String的话,将String转换为Number
  4. 如果是Boolean和Number的话,将Boolean转为Number
  5. 如果一方有Object的话且一方为string、number或者symbol的话,将Object转为基本类型

12.其他值转到string规则

  • Null和Undefined直接转换
  • Boolean直接转换
  • Number类型直接转换,极大极小的数字会转成指数的形式
  • symbol类型的值直接转换,但是只允许显示强制类型转换,隐式类型转换会报错
  • 普通对象如果没有自己的toString()方法,就会调用Object的toString()方法,就会返回"[object Object]"

13.其他值转到数字值规则

  • undefined类型转为NaN
  • null类型转为0
  • Boolean类型分别为0,1
  • String类型如果包含数字会转为数字,如果是非数字会转为NaN,如果是空会转为0
  • Symbol类型的值不能转换为数字,会报错
  • 对象类型的值会先转为基本类型的值,然后根据以上规则进行转换

14.其他值转到布尔值规则

一下这些是假值:

  • null
  • undefined
  • +0、-0和NaN
  • “”

除此之外都是真值

15.||和&&操作符的返回值

||和&&首先会对第一个操作数执行条件进行判断

  • ||时第一个值是true则返回true,如果为false就返回第二个操作数
  • &&时第一个值是true就返回第二个操作数,如果为false就返回false

他们返回的都是其中一个操作数的结果,而非条件判断的结果

16.Object.is()与比较操作符“=”、“”的区别

  • “==”这里在进行相等判断时,如果两边的类型不一致,则会进行隐式类型转换后再去进行比较
  • "==="这里比较时如果两边的类型不一致时,直接返回false
  • Object.is()大体上和三等号相似,就是做了一些处理,比如NaN和NaN相等,“+0”和“-0”不想等
  console.log(Object.is(NaN,NaN))//true

17.什么是JavaScript中的包装类型

在JavaScript中,基本类型是没有属性和方法的,但是为了更加方便的操作基本类型的值,在调用基本类型的属性和方法时Javascript会在内部隐式的将基本类型的值转换为对象

比如:

let a='12';
console.log(a.length)

在我们访问一些属性时,Javascript就在后台将’12’这个字符串转化为String('abc'),然后再去访问对应的属性。

Javascript还可以通过Object(内容)显式的将基本类型转换为包装类型,然后再去通过valueOf方法将包装类型转为基本类型。因此也常常被用于显式类型转化

18. JavaScript中如何进行隐式转换

在js中,当运算符两边数据类型不统一就无法进行运算,这时js引擎就会隐式的将运算符两边的数据类型转为相同类型再去运算。

主要涉及的三种转换方式为:

  1. 将引用类型值转换为原始值:ToPrimitive()
  2. 将值转为数值,ToNumber()
  3. 将值转为字符串,ToString()

主要介绍一下ToPrimitive()函数的执行过程:
JavaScript基本每个值都会隐含这个参数,用来将值转换为基本类型的数据,所以经常被用于引用类型中。它的大概过程是这样的。

ToPrimitive(obj,type)

接受两个参数,obj是对象本身,type是我们期望的结果类型,一般有String和Number。

如果type为number时规则如下:

  • 调用obj的valueOf方法,如果返回一个原始值,则返回,否则进入下一步
  • 调用obj的toString方法,后续同上
  • 抛出TypeError异常

如果type为string时规则如下:

  • 调用obj的toString方法,如果返回一个原始值,则返回,否则进入下一步
  • 调用obj的valueOf方法,后续同上
  • 抛出TypeError异常

主要区别就在于type参数,一般我们默认的参数值时number,但是如果时Date对象的值时用的是string。

以下是基本类型的值在不同操作符下隐式转换的规则:

  1. +操作符:**(string与number)**两边至少有一个string类型的变量时,两边的变量都会被隐式转换为字符串,其他情况两边变量会被转为数字。
1 + '23' // '123'
 1 + false // 1 
 1 + Symbol() // Uncaught TypeError: Cannot convert a Symbol value to a number
 '1' + false // '1false'
 false + true // 1
  1. -、*、/、%操作符:**(number)**只能对于number类型进行运算,其他类型会转为number
1 * '23' // 23
 1 * false // 0
 1 / 'aa' // NaN
  1. ==操作符(number)

img

3 == true // false ,true转为number为1
'0' == false //true, '0'转为number为0,false转为number为0
'0' == 0 // '0'转为number为0
  1. 对于<>比较符

如果两边都是字符串,则比较字母的顺序

'ca' < 'bd' // false
'a' < 'b' // true

其他情况下转换为数字再去比较,包括对象,会被转换为基本类型

'12' < 13 // true
false > -1 // true

比如下面这行代码的输出及其内部执行:

var a = {}
a > 2 // false
a >'a' //false

首先将a转为基本类型,通过valueOf()得出结果返回{},发现不是基本类型,再去执行toString()方法,返回[object Object],那么第一行代码输出false,第二行代码分别对第一个字符串进行对比也返回false

19.+操作符什么时候用于字符串的拼接

对于+操作符而言,只要某一方的数据是字符串或者可以通过某种方式转换为字符串,就进行字符串的拼接操作,比如引用类型数据被toPrimitive()函数操作。否则进行数学运算

也有例外,比如:

{}+2 //输出2
2+{} //输出"2[object Object]"

这是因为当{}在前时浏览器自动将其认为成了一个块级作用域,因此直接进行了后面的操作。不同的浏览器结果不同。所以第二个才是我想要的答案。

20.为什么会有BigInt提案

JavaScript如果在计算时数字超出了规定的最大安全数,就会出现精度丢失的情况,因此引入了Bigint的提案。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值