热门标签 | 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重难点实例精讲》


推荐阅读
  • 八段代码完全控制Promise
    1.Promise的马上实行性varpnewPromise(function(resolve,reject){console.log(createapromise);resolve ... [详细]
  • 理解浏览器历史记录(2)hashchange、pushState
    阅读目录1.hashchange2.pushState本文也是一篇基础文章。继上文之后,本打算去研究pushState,偶然在一些信息中发现了锚点变 ... [详细]
  • H5技术实现经典游戏《贪吃蛇》
    本文将分享一个使用HTML5技术实现的经典小游戏——《贪吃蛇》。通过H5技术,我们将探讨如何构建这款游戏的两种主要玩法:积分闯关和无尽模式。 ... [详细]
  • 本文探讨了如何通过优化 DOM 操作来提升 JavaScript 的性能,包括使用 `createElement` 函数、动画元素、理解重绘事件及处理鼠标滚动事件等关键主题。 ... [详细]
  • 1.绑定htmlcss1.1对象语法:  传给v-bind:class一个对象,以动态地切换class   ... [详细]
  • 本文详细介绍了如何利用 Bootstrap Table 实现数据展示与操作,包括数据加载、表格配置及前后端交互等关键步骤。 ... [详细]
  • 本文介绍了如何正确配置Ajax POST请求,以确保前端发送的数据能够被后端正确解析。重点在于前端JSON对象的键名需要与后端实体类的字段名严格匹配。 ... [详细]
  • 在将 Android Studio 从 3.0 升级到 3.1 版本后,遇到项目无法正常编译的问题,具体错误信息为:org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:processDemoProductDebugResources'。 ... [详细]
  • 本文深入探讨了Go语言中的接口型函数,通过实例分析其灵活性和强大功能,帮助开发者更好地理解和运用这一特性。 ... [详细]
  • 入门指南:使用FastRPC技术连接Qualcomm Hexagon DSP
    本文旨在为初学者提供关于如何使用FastRPC技术连接Qualcomm Hexagon DSP的基础知识。FastRPC技术允许开发者在本地客户端实现远程调用,从而简化Hexagon DSP的开发和调试过程。 ... [详细]
  • 解决JavaScript中法语字符排序问题
    在开发一个使用JavaScript、HTML和CSS的Web应用时,遇到从SQLite数据库中提取的法语词汇排序不正确的问题,特别是带重音符号的字母未按预期排序。 ... [详细]
  • Web动态服务器Python基本实现
    Web动态服务器Python基本实现 ... [详细]
  • 深入理解:AJAX学习指南
    本文详细探讨了AJAX的基本概念、工作原理及其在现代Web开发中的应用,旨在为初学者提供全面的学习资料。 ... [详细]
  • 本文介绍如何使用JavaScript中的for循环来创建一个九九乘法表,适合初学者学习循环结构的应用。 ... [详细]
  • 在Qt框架中,信号与槽机制是一种独特的组件间通信方式。本文探讨了这一机制相较于传统的C风格回调函数所具有的优势,并分析了其潜在的不足之处。 ... [详细]
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社区 版权所有