热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入理解JavaScript(一):从数据类型说起

深入理解JavaScript(一):从数据类型说起-在JavaScript中,数据类型可以分为基本数据类型和引用数据类型:基本数据类型:Undefined、Null、Boole

在Javascript中,数据类型可以分为基本数据类型和引用数据类型

  • 基本数据类型:Undefined、Null、Boolean、Number、String、Symbol
  • 引用数据类型:Object、Function、Array、Date等

一、基本数据类型

1、Undefined和Null的异同点:

?相同点:

  • 它们都只有一个字面量值,分别为undefined和null;
  • 转换为Boolean类型的值时,都是false;
  • 如果一个对象是Undefined或Null,访问属性时会出现引用错误:
    let a
    let b = null
    console.log(a.name)
    // Uncaught TypeError: Cannot read property 'name' of undefined
    console.log(b.name)
    // Uncaught TypeError: Cannot read property 'name' of null

    ?️不同点:

  • typeof返回的类型不同:
    let a
    let b = null
    typeof a // 'undefined'
    typeof b // 'object'
  • 通过call调用Object.prototype.toString函数时返回结果不同:
    let a
    let b = null
    Object.prototype.toString.call(a)
    // '[object Undefined]'
    Object.prototype.toString.call(b)
    // '[object Null]'
  • 转换为字符串类型时,null会转换为字符串'null',而undefined会转换为字符串'undefined';
  • 转换为数值类型时,undefined会转换为NaN,无法参与计算;null会转换为0,可以参与计算。

PS:不要将一个变量显式设为undefined!如果需要定义某个变量来保存将来要使用的对象,应该将其初始化为null,这样不仅能将null作为空对象指针的惯例,还有助于区分null和undefined。

2、“幻假值”都有哪些

所谓“幻假值”,指的是非布尔值但经过Boolean()转换之后为false的值,在js中,属于“幻假值”的有:

  • 空字符串
  • 0和NaN
  • null (注意空对象{}并不是幻假值哦)
  • undefined

3、关于Number类型需要知道的几点

1、藏在map()函数与parseInt()函数中的隐形坑

设想这样一个场景:存在一个数组,数组中的每个元素都是Number类型的字符串['1','2', '3', '4'],如果我们想要将数组中的元素全部转换为整数,我们该怎么做呢?我们可能会想到在Array的map()函数中调用parseInt()函数:

let arr = ['1','2', '3', '4']
let result = arr.map(parseInt)
console.log(result)
// [1, NaN, NaN, NaN]

并不是我们期望的结果[1, 2, 3, 4]啊,这是为什么呢?

这就是一个藏在map()函数与parseInt()函数中的隐形坑!上面的代码其实和下面的代码等效:

let result = arr.map(function(value, index) {
    return parseInt(value, index)
})

parseInt()函数接收的第二个参数实际为数组的索引值,但它本身是将第二个参数作为转换Number类型的进制基数,所以实际处理的过程是这样的:

parseInt('1', 0) // 1
// 任何整数以0为基数取整时,都会返回本身
parseInt('2', 1) // NaN
parseInt('3', 2) // NaN
parseInt('4', 3) // NaN
// parseInt()函数对应的基数只能为2~36
// 且数值不能比进制基数大,所以类型转换失败

所以我们可以改动一下以达到我们的目的:

let result = arr.map(function(value) {
    return parseInt(value, 10)
})
2、isNaN()和Number.isNaN()有啥区别

判断NaN时,ES5提供了isNaN()函数,ES6为Number类型增加了静态函数isNaN(),既然isNaN()能提供判断NaN的功能,ES6为何要新增一个呢?它俩有啥区别呢?

我们来看看isNaN的作用,它用来确定一个变量是不是NaN。如果传递的参数是Number类型数据,可以很容易判断是不是NaN。如果传递的参数是非Number类型,它返回的结果往往会让人费解。比如:

isNaN({}) // true

这里把空对象判断为NaN的原因是isNaN会进行数据的类型转换,它在处理的时候会去判断传入的变量值能否转换为数字,如果能转换成数字则会返回“false”,如果无法转换则会返回“true”。

既然在全局环境中有isNaN函数,为什么在ES6中会专门针对Number类型增加一个isNaN函数呢?这是因为之前的isNaN函数本身存在误导性,而ES6中的Number.isNaN()函数会在真正意义上去判断变量是否为NaN,不会做数据类型转换。只有在传入的值为NaN时,才会返回“true”,传入其他任何类型的值时会返回“false”。

如果在非ES6环境中想用ES6中的Number.isNaN()函数,有以下兼容性处理方案:

if(!Number.isNaN){
  Number.isNaN = function(n) {
    // 只有在变量值为NaN时才会返回false
    return n!==n
  }
}
3、0.1+0.2还能不等于0.3?

我们知道,一个浮点型数在计算机中的表示总共长度是64位,其中最高位为符号位,接下来的11位为指数位,最后的52位为小数位,即有效数字。

因为浮点型数使用64位存储时,最多只能存储52位的小数位,对于一些存在无限循环的小数位浮点数,会截取前52位,从而丢失精度,所以会出现0.1+0.2===0.3为false的结果,那这个结果具体是怎么得到的呢?

首先将各个浮点数的小数位按照“乘2取整,顺序排列”的方法转换成二进制表示。具体做法是用2乘以十进制小数,得到积,将积的整数部分取出;然后再用2乘以余下的小数部分,又得到一个积;再将积的整数部分取出,如此推进,直到积中的小数部分为零为止。然后把取出的整数部分按顺序排列起来,先取的整数作为二进制小数的高位有效位,后取的整数作为低位有效位,得到最终结果。

举个?,0.1的二进制表示过程为:

0.1*2=0.2 // 取整数部分0
0.2*2=0.4 // 取整数部分0
0.4*2=0.8 // 取整数部分0
0.8*2=1.6 // 取整数部分1
0.6*2=1.2 // 取整数部分1
0.2*2=0.4 // 取整数部分0
0.4*2=0.8 // 取整数部分0
0.8*2=1.6 // 取整数部分1
0.6*2=1.2 // 取整数部分1
...... // 无限循环

因此0.1转换成二进制表示为0.0 0011 0011 0011 0011 0011 0011……(无限循环)。

同理对0.2进行二进制的转换,计算过程与0.1类似,直接从0.2开始,相比于0.1,少了第一位的0,其余位数完全相同,结果为0.0011 0011 0011 0011 0011 0011……(无限循环)。

将0.1与0.2相加,然后转换成52位精度的浮点型表示,得到的结果为0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 11001100,转换成十进制值为0.30000000000000004。

我们该如何解决这种精度丢失的问题呢?一种可行的思路是将浮点数先乘以一定的数值(比如浮点数的小数位后的长度)转换为整数,通过整数进行运算,然后将结果除以相同的数值转换成浮点数后返回。具体实现可以参考《Javascript重难点实例精讲》。

4、String类型常见算法

1、字符串逆序输出
思路1:借助数组的reverse()
function reverseStr(str) {
  return str.split('').reverse().join('')
}
思路2:利用字符串本身的charAt()
function reverseStr(str) {
  let res = ''
  let len = str.length
  for(let i=len-1;i > -1;i--){
    res += str.charAt(i)
  }
  return res
}
思路3:利用栈的先进后出
// 代码略,自己写着玩玩
2、统计字符串中出现次数最多的字符及出现的次数
3、去除字符串中重复的字符
4、判断一个字符串是否为回文字符串

leetcode都有,去leetcode慢慢刷~

5、使用typeof运算符时需要考虑的问题

1、typeof运算符区分对待Object类型和Function类型

《Javascript高级程序设计》一书中讲到,从技术角度讲,函数在ES中是对象,不是一种数据类型。然而,函数也确实有一些特殊的属性,因此通过typeof运算符来区分函数和其他对象是有必要的。

2、typeof运算符对null的处理
typeof null // 'object'

这是一个在Javascript设计之初就存在的问题。在Javascript中,每种数据类型都会使用3bit表示:

  • 000表示Object类型的数据
  • 001表示Int类型的数据
  • 010表示Double类型的数据
  • 100表示String类型的数据
  • 110表示Boolean类型的数据
    由于null代表的是空指针,大多数平台中值为0x00,因此null的类型标签就成了0,所以使用typeof运算符时会判断为object类型,返回“object”,虽然在后来的提案中有提出修复方案,但是因为影响面太大,所以并没有被采纳,从而导致这个问题一直存在。

二、JS中的引用数据类型

1、Object类型及其实例和静态函数

1、new操作符都干了点什么
function Cat(name, age) {
  this.name = name
  this.age = age
}
let cat = new Cat()

从表面上看,new的主要作用是创建一个Cat对象的实例,并将这个实例值赋予cat变量,cat变量就会包含Cat对象的属性和函数。

其实new操作符做了4件事情:

  • 首先创建一个空对象,这个对象将会作为执行 new 构造函数() 之后,返回的对象实例:
    let cat = {}
  • 将上面创建的空对象的原型(proto)指向构造函数的 prototype 属性:
    cat.__proto__ = Cat.prototype
  • 将这个空对象赋值给构造函数内部的this,并执行构造函数逻辑:
    Cat.call(cat)
  • 根据构造函数执行逻辑,返回第一步创建的对象或者构造函数的显式返回值:
    return cat
    2、Object类型的静态函数
    1、Object.create()

    该函数的主要作用是创建并返回一个指定原型和指定属性的对象:

    let obj = Object.create(prototype, property)
    // prototype会作为obj的原型,property用于制定obj的属性
    // 举个?
    let obj = Object.create(null, {name: 'Hah'})

    如果我们要自己实现一个Object.create(),核心部分在于:

    Object.create = function(proto, property){
    function F(){}
    // 中间部分省略
    F.prototype = proto
    return new F()
    }

    2、Array类型

    1、判断一个变量arr是不是数组
    Array.isArray(arr)
    Object.prototype.toString.call(arr) //'[object Array]'
    2、reduce函数

    reduce()函数最主要的作用是做累加处理,即接收一个函数作为累加器,将数组中的每一个元素从左到右依次执行累加器,返回最终的处理结果,基本用法:

    array.reduce(callback[,initialValue])

    initialValue用作callback的第一个参数值,如果没有设置,则会使用数组的第一个元素值。

callback会接收4个参数(accumulator、currentValue、currentIndex、array)。
· accumulator表示上一次调用累加器的返回值,或设置的initialValue值。如果设置了initialValue,则accumulator=initialValue;否则accumulator=数组的第一个元素值。
· currentValue表示数组正在处理的值。
· currentIndex表示当前正在处理值的索引。如果设置了initialValue,则currentIndex从0开始,否则从1开始。
· array表示数组本身。
用法举例?:

求数组每个元素相加的和
let arr = [1,2,3,4,5]
arr.reduce(function(accumulator,currentValue){
  return accumulator + currentValue
})
// 15
统计数组中每个元素出现的次数
let arr = [1,2,2,3,4,4,4,4,5]
arr.reduce(function(accumulator,currentValue){
  accumulator[currentValue] ? accumulator[currentValue]++ : accumulator[currentValue] = 1
  return accumulator
},{})
// {1: 1, 2: 2, 3: 1, 4: 4, 5: 1}

3、Date类型

1、比较日期大小

在实际开发中,经常会碰到需要判断开始时间是否在结束之间之前的需求,怎么实现呢?

大致思想:以斜线(/)作为分隔符的时间类型字符串,可以直接转换为Date类型对象并直接进行比较。

function compareDate(start, end){
  // 将传入的带有“-”分隔符的时间字符串通过正则表达式匹配替换为“/”
  // 转换原因主要是为了兼容各个浏览器
  let date1 = start.replace('/-/g', '\/')
  let date2 = end.replace('/-/g', '\/')
  return new Date(date1) 
2、计算当前日期前后N天的日期

获取前后N天的日期的主要思想是对date值的设置,在Date对象的实例函数中提供setDate函数,用于设置日期值。

function getDate(count){
  // count参数表示前后N天的具体值 eg: 30为一个月后,-90为三个月前
  let date = new Date() //当然这里可以传参获取制定日期的前后N天
  date.setDate(date.getDate() + count)
  let y = date.getFullYear()
  let m = date.getMonth() + 1
  let d = date.getDate()
  return y + '-' + m + '-' + 'd'
}

注:参考《Javascript重难点实例精讲》


推荐阅读
  • 模板引擎StringTemplate的使用方法和特点
    本文介绍了模板引擎StringTemplate的使用方法和特点,包括强制Model和View的分离、Lazy-Evaluation、Recursive enable等。同时,还介绍了StringTemplate语法中的属性和普通字符的使用方法,并提供了向模板填充属性的示例代码。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
  • 本文介绍了获取关联数组键的列表的方法,即使用Object.keys()函数。同时还提到了该方法在不同浏览器的支持情况,并附上了一个代码片段供读者参考。 ... [详细]
author-avatar
心在想念-小凡
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有