JS - JS常见内置类

243 阅读29分钟

包装类

JavaScript的原始类型并非对象类型,所以从理论上来说,它们是没有办法获取属性或者调用方法的

但是在开发中,我们经常会通过基本数据类型去调用对应的属性和方法

let num = 1.888888888
let str = 'Hello World'

console.log(str.length)
console.log(str.split(' '))
console.log(num.toFixed(2))

因此JavaScript为了可以使其可以获取属性和调用方法,对其封装了对应的包装类型

默认情况,当我们调用一个原始类型的属性或者方法时,会进行如下操作:

  • 根据原始值,创建一个原始类型对应的包装类型对象

  • 调用对应的属性或者方法,返回一个新的值

  • 方法和属性使用完毕后,销毁创建的包装类对象

常见的包装类型有:String、Number、Boolean、Symbol、BigInt类型

  1. null、undefined没有任何的方法和属性,因为null和undefined没有对应的包装类

  2. 虽然规范上要求调用基本数据类型的属性和方法的时候,需要先转换为对应的包装类

    但是有些时候,JS引擎内部可以直接拿到对象的属性或操作对象的方法,而不需要转换为对应的包装类

    因此JS引擎出于优化的角度,其可以跳过创建包装类的过程在内部直接完成属性的获取或者方法的调用

我们也可以自己来创建一个包装类的对象:

const str = new String('Hello World')
const num = new Number(2)
// 在主动创建包装类的时候
// 如果加上new关键字,会返回对应的包装类对象
// 如果不加上对应的new关键字,就直接返回对应的基本类型值,也就是参数值
const str1 = new String('Hello') // => String {'Hello'}
const str2 = String('Hello') // => Hello

Number

Number类型有一个对应的数字包装类型 Number

属性

// 这种直接定义在构造函数上的方法和属性被称之为类方法和类属性(也可以被称之为静态方法和静态属性)

// 最大的正Number值
console.log(Number.MAX_VALUE)
// 最小的正Number值
console.log(Number.MIN_VALUE)

// 最大的不会失精的整数 --- 正数
console.log(Number.MAX_SAFE_INTEGER)
// 最小的不会失精的整数 --- 负数
console.log(Number.MIN_SAFE_INTEGER)

方法

实例方法

const num = 123

// toString可以将数字转换为对应的字符串
console.log(num.toString()) // => 123

// toString方法也可以传递一个base参数,表示转换为base进制表示的数字所对应的字符串
// 不传默认值为10,即按照十进制进行转换
// base的取值范围为 [2, 36]

// 注意: 因为转换后的结果为字符串,所以没有使用0b之类的进制前缀开头
// 但是如果是以数字形式表示的话,需要在开头加上对应的进制前缀
console.log(num.toString(2)) // => 1111011
// 因为数字后边加上点 会被JS引擎解析为浮点数
// 所以如果直接使用整数类型的数字字面量去调用toString方法的时候
// 需要使用.. 或 使用括号将数字字面量进行包裹
// 以便于告诉 JS引擎之前的数字是整数 不是浮点数
console.log(123..toString())
console.log((123).toString())
// toFixed(digits),格式化一个数字,保留digits位的小数
// digits的范围是0到20(包含)之间,默认值为0
const num = 3.14159

// toFixed方法默认是保留成整数
console.log(num.toFixed()) // => 3

// toFixed方法会四舍五入
console.log(num.toFixed(3)) // => 3.142

// toFixed转换后的结果为字符串类型
console.log(typeof num.toFixed(2)) // => string

静态方法

const num = '3.525'

// parseInt --- 将字符串转换为整数形式表示的Number --- 转换后结果为Number类型 --- 不会四舍五入
console.log(Number.parseInt(num)) // => 3

// parseFloat --- 将字符串转换为浮点数形式表示的Number --- 转换后结果为Number类型 --- 不会四舍五入
console.log(Number.parseFloat(num)) // => 3.525

// parseInt方法 和 parseFloat方法 不仅存在于Number对象上,同时存在于window对象上
console.log(parseInt === Number.parseInt) // => true
console.log(parseFloat === Number.parseFloat) // => true
// 以下转换方式以parseInt为例,parseFloat同理
console.log(parseInt(23)) // => 如果参数是Number类型,不进行转换,直接返回
console.log(parseInt(false)) // => 如果参数不是Number类型或String类型值,直接返回NaN

// 如果参数类型为数字开头字符串,会先从头开始尽可能的截取成合法的数字后再进行换行
// 这是parseInt和parseFloat于Number方法最大的区别
console.log(parseInt('123a234')) // => 123
console.log(parseInt('123.')) // => 123
console.log(parseFloat('123.223a24213')) // => 123.223
console.log(parseFloat('123.')) // => 123

// 如果参数不是以数字开头的字符串,直接返回NaN
console.log(parseInt('a23')) // => NaN

Math

除了Number类可以对数字进行处理之外,JavaScript还提供了一个Math对象

Math是一个内置对象(不是一个构造函数),它拥有一些数学常数属性和数学函数方法

常用属性说明示例结果
PI圆周率,约等于 3.14159Math.PI3.141582653589796
常用方法说明
Math.floor向下舍入取整
Math.ceil向上舍入取整
Math.round四舍五入取
返回值类型是Number
Math.random生成0~1的随机数
返回值范围[0, 1)之间的整数或小数
Math.pow(x, y)返回x的y次幂
// 生成 [5, 50) 之间的随机整数
// Math.floor(Math.random * (y - x)) + x ---> 生成[x, y)之间的随机整数
Math.floor(Math.random() * 45) + 5

Boolean

const bool = true

// Boolean类的实例一般只会使用toString,将其转换为对应的字符串类型值
console.log(bool.toString()) // => true

String

在开发中,我们经常需要对字符串进行各种各样的操作,String类提供给了我们对应的属性和方法

常见属性说明
length获取字符串的长度
虽然JS没有字符类型,但是字符串在内存中进行解析的时候,会作为字符数组进行解析

获取字符串对应索引位置的字符

const str = 'Hello World'

console.log(str[4]) // => o
console.log(str.charAt(4))  // => o

// 区别是索引的方式没有找到会返回undefined,而charAt没有找到会返回空字符串
console.log(str[20]) // => undefined
console.log(str.charAt(20)) // => ''(空字符串)

字符串的遍历

const str = 'Hello World'

// 使用普通for循环对象字符串进行遍历
for (let i = 0; i < str.length; i++) {
  console.log(str[i])
}

// 字符串内部重写了Symbol.iterable接口,所以字符串是一个可迭代对象,可以使用for-of遍历
for (const char of str) {
  console.log(char)
}

// for-in遍历
for (const index in str) {
  // 因为for-in遍历在遍历的时候,在遍历自身属性和方法的时候,还会遍历原型上的属性和方法
  // 所以需要使用hasOwnProperty方法来判断某一个属性key是不是被遍历对象自身上的属性和方法后再进行打印
  if (str.hasOwnProperty(index)) {
    console.log(str[index])
  }
}

三种for循环遍历的区别

const user = { name: 'Klaus', running() {} }
const proto = Object.getPrototypeOf(user)
proto.age = 24
proto.studying = () => {}

// Object.keys --- 只遍历自身属性和方法, 不遍历原型上的属性和方法
// console.log(Object.keys(user)) // => ['name', 'running']

// for-in 遍历即会遍历自身的属性和方法,也会遍历原型上的属性和方法
for (const key in user) {
  console.log(key)
  /*
    =>
      name
      running
      age
      studying
  */
}

// ------------------------------------------

const arr = ['Alex', 'Steven', 'Klaus']

// 数组也重写了Symbol.iterable属性,所以数组也可以使用for-of遍历
// 遍历出来的属性有哪些,取决于Symbol.iterable接口的实现
// 在数组中,就是每一个数组元素值
for (const value of arr) {
  console.log(value)
  /*
    =>
     Alex
     Steven
     Klaus
  */
}

JavaScript中的字符串具有不可变性,也就是说字符串在定义后是不可以修改的

所以下面的操作是没有任何意义的

const str = 'Hello World'

// 这种修改方式 在JS中是不被允许的
str[4] = 'A'

console.log(str) // => Hello World

所以在JS中很多改变字符串的操作都是生成了一个新的字符串,而不会修改原本的那个字符串

const str = 'Hello World'

// 将所有的字符转成小写
console.log(str.toLowerCase()) // => hello world

// 将所有的字符转成大写
console.log(str.toUpperCase()) // => HELLO WORLD

// 返回的都是新字符串,并不会修改原本的字符串
console.log(str) // => Hello World

在开发中我们经常会在一个字符串中查找或者获取另外一个字符串,为此JS提供了对应的方法

const str = 'Hello World'

// indexOf(str, startIndex) --- 多个符合条件的值 只会找到第一个
// 从startIndex开始,查找str是否存在
// startIndex的默认值为0
// 如果存在,返回str的首字母对应的索引值
// 如果不存在, 返回-1
console.log(str.indexOf('World')) // => 6

// 设置了startIndex,查找到对应字符串
// 返回的是对应查找字符串在原字符串中对应的索引值
console.log(str.indexOf('World', 6)) // => 6
console.log(str.indexOf('World', 7)) // => -1

// lastIndexOf(str, startIndex) --- 多个符合条件的值 之后找到最后一个
// lastIndexOf的使用方式和indexOf是完全一致的
// 唯一的区别是,lastIndexOf的索引值是从后往前的
// 也就是意味着lastIndexOf的索引值是从后往前计算的
console.log(str.lastIndexOf('World')) // => 6
console.log(str.lastIndexOf('World', 6)) // => 6
console.log(str.lastIndexOf('World', 5)) // => -1

但比较indexOf和lastIndexOf的语义化并不是十分明显,所以在ES6中提供了更为与优化的查找方式

const str = 'Hello World'

// includes(str, index) 是字符串和数组上的方法,用于进行字符串的查找
// 返回值为boolean类型值
console.log(str.includes('World')) // => true
console.log(str.includes('World', 7)) // => false
const str = 'Hello World'

// 字符串以参数对应的字符串开头
console.log(str.startsWith('Hello')) // => true

// 字符串以参数对应的字符串结尾
console.log(str.endsWith('World')) // => false

// startsWith(str, index) --- 第二个参数为从index索引开始进行判断
console.log(str.startsWith('World', 6)) // => true

// endsWith(str, length) --- 第二个参数为 在length为长度的字符串内进行匹配
console.log(str.endsWith('Hello', 5)) // => true
const str = 'Hello World'

// 查找到对应的字符串,并且使用新的字符串进行替代
// 参数一 既可以是字符串,也可以是RegExp
// 参数二 既可以是字符串,也可以是一个函数
console.log(str.replace('World', 'JavaScript')) // => Hello JavaScript

console.log(str.replace('World', () => 'HTML'))
const str = 'Hello World, Hello css'

console.log(str.replace(/Hello/g, 'Bye')) // => Bye World, Bye css

// replace中正则的g操作符 可以使用 replaceAll方法替换 他们之间是等价的
console.log(str.replaceAll('Hello', 'Bye')) // => Bye World, Bye css
const str = 'Hello World'

// slice(start, end) => 截取[start, index)对应的子串
// 因为字符串的不可变性,slice会返回一个新的字符串,而不会去修改原本的字符串,所以slice方法是一个纯函数
// 如果不写end表示 从start开始直接截取后面全部字符串
// end支持负数,-1表示最后一个字符,-2表示倒数第二个字符,。。。依次类推
console.log(str.slice(3, 7)) // => lo W
console.log(str.slice(3)) // => lo World
console.log(str.slice(3, -3)) // => lo Wo
const str = 'Hello World'

// substr 是标准附录中的方法,浏览器可以支持 也可以不进行支持
// 虽然绝大多数浏览器都支持这个方法,但是并不推荐使用
// substr(start, length)
console.log(str.substr(3, 6)) // => lo Wor
console.log(str.substr(3, 22)) // => lo World
const str = 'Hello World'

// substring(start, end) => 截取索引为[start, end)的子串
console.log(str.substring(3, 7)) // => lo W

// substring第二个参数不传,表示截取到字符串的末尾
console.log(str.substring(3)) // => lo World

// substring第二个参数不支持负值
// substring(start, end) 如果end为负值 为自动进行容错处理
// 转换为 substring(0, start)
console.log(str.substring(3, -1)) // => Hel
const user1 = 'Klaus'
const user2 = 'Steven'
const user3 = 'Alex'

// 字符串拼接方式1 +
console.log(user1 + ' ' + user2 + ' ' + user3) // => Klaus Steven Alex

// 字符串拼接方式2 concat方法
// concat方法会将拼接后的字符串作为函数的返回值进行返回
// 所以concat方法可以进行链式调用
console.log(user1.concat(' ' + user2).concat(' ' + user3)) // => Klaus Steven Alex

// concat的参数是可变参数,也就意味着其可以接收任意多个实参值
console.log(user1.concat(' ' + user2, ' ' + user3)) // => Klaus Steven Alex
const str = '    Hello World    '

// 去除首尾空格
console.log(str.trim()) // => Hello World
const str = 'Hello World'

// 如果不传入分隔符,那么就不进行分割,直接将原字符串放入一个新数组中
console.log(str.split()) // => ['Hello World']

// 指定对应的分隔符
console.log(str.split(' ')) // => [ 'Hello', 'World' ]

// 第二个参数 用来返回切片的数量
console.log(str.split(' ', 1)) // => [ 'Hello' ]

const strs = str.split(' ')

// 以参数作为拼接符 对数组进行拼接操作
console.log(strs.join('*')) // => Hello*World

// 默认的拼接符为 逗号
console.log(strs.join()) // => Hello,World
const str = '1'

// 对于padStart和padEnd方法,如果原本的长度大于其需要的长度(也就是第一个参数所设置的参数),那么对应方法会静默失效

// padStart() 方法用另一个字符串填充当前字符串(如果需要的话,会重复多次),以便产生的字符串达到给定的长度。从当前字符串的左侧开始填充
// 参数1 -- 字符串所需要达到的个数
// 参数2 -- 使用什么进行填充, 对应的参数会被转换为字符串后在进行填充
console.log(str.padStart(2, 0)) // => 01
console.log(typeof str.padStart(2, 0)) // => string

// padEnd()  方法会用一个字符串填充当前字符串(如果需要的话则重复填充),返回填充后达到指定长度的字符串。从当前字符串的末尾(右侧)开始填充
// padEnd方法的参数和padStart方法的参数完全一致
console.log(str.padEnd(2, 0)) // => 10
console.log(typeof str.padEnd(2, 0)) // => string

数组

对象允许存储键值集合,但是在某些情况下使用键值对来访问并不方便

这个时候我们需要一种有序的集合,里面的元素是按照某一个顺序来排列的

这个数据类型需要是有序的集合,以便于我们可以通过索引来获取到其中每一个元素

这个结构就是数组(Array)

image.png

// 字面量形式创建数组
const arr1 = [] // => []

// 构造函数方式创建数组
// 所以数组本身也是一种特殊的对象
// 那也就意味着 Array extends Object

// 在使用Array构造函数的时候 new可以省略 但是并不被推荐
// new Array() === Array()
const arr2 = new Array() // => []

const arr3 = new Array(3) // => [empty * 3] --- 生成一个长度为3的初始化数组

const arr4 = new Array(3, 4, 5) // => [3, 4, 5] --- 生成数组并赋初始化值
const users = ['Klaus', 'Alex', 'Steven']

// 数组元素从 0 开始编号(索引index)
console.log(users[0]) // Klaus

// 数组存在属性length 记录着数组对应的长度
console.log(users[users.length - 1]) // Steven

// 如果数组取值越界, 那么直接返回undefined
console.log(users[-1]) // => undefined
console.log(users[22]) // => undefined
let users = ['Klaus', 'Alex', 'Steven']

// 访问数组元素
console.log(users[1]) // => Alex
console.log(users[-1]) // => undefined
console.log(users[22]) // => undefined

// console.log(users.at(1)) // => Alex
console.log(users.at(-1)) // => Steven --- at方法支持负值
console.log(users.at(33)) // => undefined --- 如果 i >= 0,则与 arr[i] 完全相同

users = ['Klaus', 'Alex', 'Steven']

// 修改数组元素
users[1] = 'Jhon'
console.log(users) // => [ 'Klaus', 'Jhon', 'Steven' ]

users = ['Klaus', 'Alex', 'Steven']

// 新增数组元素(不推荐 推荐使用方法新增数组元素)
users[users.length] = 'Jhon'
console.log(users) // => [ 'Klaus', 'Alex', 'Steven', 'Jhon' ]

users[15] = 'Peter'
console.log(users) // => [ 'Klaus', 'Alex', 'Steven', 'Jhon', empty * 11, 'Peter' ]

users = ['Klaus', 'Alex', 'Steven']

// 删除数组元素(不推荐,推荐使用方法删除数组元素)
delete users[1]
console.log(users) // => [ 'Klaus', empty * 1, 'Steven' ]
let users = ['Klaus', 'Alex', 'Steven']

// 在数组末尾添加元素
// 添加元素 可以一次添加一个 也可以一次添加多个
// push方法的返回值为修改后数组的个数
// push方法会修改原数组
users.push('Jhon')
users.push('Alice', 'Peter')

console.log(users)

users = ['Klaus', 'Alex', 'Steven']

// 在数组末尾移除元素
// 一次只能移除一个
// pop方法的返回值为移除的那个元素
// pop方法会修改原数组
users.pop()

console.log(users)
let users = ['Klaus', 'Alex', 'Steven']

// 在数组头部添加元素
// 添加元素 可以一次添加一个 也可以一次添加多个
// unshift方法的返回值为修改后数组的个数
// unshift方法会修改原数组
users.unshift('Jhon')
users.unshift('Alice', 'Peter')

console.log(users)

users = ['Klaus', 'Alex', 'Steven']

// 在数组头部移除元素
// 一次只能移除一个
// shift方法的返回值为移除的那个元素
// shift方法会修改原数组
users.shift()

console.log(users)

shiftunshift是在数组头部进行元素的增删,所以其在添加或移除元素后,需要进行数组的整体移动

poppush方法是直接在数组尾部进行元素的增删,其不需要进行数组的整体移动

所以poppush的性能和执行速度相比shiftunshift更优,也更快

image.png

如果我们希望在中间某个位置添加,删除或者替换元素,我们可以使用splice方法

arr.splice 方法可以说是处理数组的利器,它可以做所有事情:添加,删除和替换元素

let users = ['Klaus', 'Alex', 'Steven']

// splice方法不是纯函数,会修改原数组
// 参数1 - 从start位置开始,处理数组中的元素
// 参数2 - deleteCount:要删除元素的个数,如果为0或者负数表示不删除
// 参数3 - item1, item2, ...:在添加元素时,需要添加的元素
// 返回值 - 被删除元素组成的数组,如果没有删除元素,就返回空数组

// 删除元素
users.splice(1, 1) // => [ 'Klaus', 'Steven' ]

users = ['Klaus', 'Alex', 'Steven']

// 新增元素
users.splice(1, 0, 'Jhon') // => [ 'Klaus', 'Jhon', 'Alex', 'Steven' ]

users = ['Klaus', 'Alex', 'Steven']

// 替换元素
users.splice(1, 1, 'Peter') // => [ 'Klaus', 'Peter', 'Steven' ]

length属性用于获取或设置数组的长度

当我们修改数组的时候,length 属性会自动更新

也就是说数组的length属性是可写的

  • 如果我们手动增加一个大于默认length的数值,那么数组会自动扩容,即增加数组的长度
  • 如果我们设置一个比原本length小的值,数组就会被截断
const arr = ['Klaus', 'Alex', 'Steven']

console.log(arr.length) // => 3

arr.length = 5
console.log(arr) // => ['Klaus', 'Alex', 'Steven', empty * 2]

arr.length = 2
console.log(arr) // => ['Klaus', 'Alex']

// 清空数组最简单的方法就是:arr.length = 0
arr.length = 0
console.log(arr) // => []

数组支持所有的for循环遍历方式

const arr = ['Klaus', 'Alex', 'Steven']

// 普通for循环遍历
for (let i = 0; i < arr.length; i++) {
  // 这里的i的类型是number
  console.log(i, arr[i])
}

// for-in遍历
for (const i in arr) {
  // for-in 对应的变量(即这里的i) 类型是string
  console.log(i, arr[i])
}

// for-of遍历
for (const item of arr) {
  // item表示数组中每一项的元素值
  // item的类型取决于遍历出来的元素的类型
  console.log(item)
}

// forEach方法可以用于遍历数组
// forEach方法的第二个参数用于修正callback内部的this指向
arr.forEach(function (item, index, arr) {
  // 如果需要使用修正的this值,那么callback不可以是箭头函数
  console.log(item, index, arr, this)
}, { name: 'Klaus' })
const arr = ['Klaus', 'Alex', 'Steven']

// 数组slice方法的使用 和 字符串的slice方法的使用方式完全一致
// slice方法是纯函数,不会修改原数组
// slice方法的返回值是截取后的新数组
console.log(arr.slice(1)) // => [ 'Alex', 'Steven' ]

// concat方法用于数组的拼接
const arr1 = ['Klaus', 'Steven']
const arr2 = ['Alex', 'Jhon']
const arr3 = ['Peter']

// push方法添加新数组
// 1. push方法不是纯函数
// 2. push方法会将参数作为作为一个整体进行插入
//    所以如果参数的类型是数组类型的时候,其值会作为一个整体进行插入,默认并不会自动展开
// arr1.push(arr2)
// console.log(arr1) // => [ 'Klaus', 'Steven', [ 'Alex', 'Jhon' ] ]

// 数组的concat是纯函数,参数是任意类型的可变参数
// 如果参数类型是数组,其在数组拼接的时候,会自动将数组进行展开后,再进行拼接
const newArr = arr1.concat(arr2, arr3, 'May')
console.log(arr1) // => [ 'Klaus', 'Steven' ]
console.log(newArr) // => [ 'Klaus', 'Steven', 'Alex', 'Jhon', 'Peter','May' ]

// join方法 将数组安装参数拼接为一个字符串,默认的拼接符为逗号
console.log(arr.join('-')) // => Klaus-Alex-Steven
const arr = ['Klaus', 'Alex', 'Klaus', 'Steven']

// indexOf lastIndexOf includes 方法的使用和字符串中对应方法的使用方式是完全一致的
// indexOf lastIndexOf includes 方法只适合于基本数据类型的查找,如果数组中的元素类型是复杂数据类型
// indexOf lastIndexOf includes将会无能为力
console.log(arr.indexOf('Klaus')) // => 0
console.log(arr.lastIndexOf('Klaus')) // => 2
console.log(arr.includes('Klaus')) // => true
const books = [
  {
    name: '《算法导论》',
    price: 85.00
  },
  {
    name: '《UNIX编程艺术》',
    price: 59.00
  },
  {
    name: '《编程珠玑》',
    price: 39.00
  }
]

// find 和 findIndex 是ES6中提供的在数组中进行元素项查找的高阶函数
// find 和 findIndex 的参数是一个回调函数
// 数组中有几个元素,对应的回调函数就会被调用几次,并将每一个元素,索引,数组本身作为参数传递给回调函数
// 也就是说find 和 findIndex方法中的回调函数的参数和forEach中的参数是一致的
// 如果回调函数返回true, 那么find 和 findIndex 就会终止查找 并将对应结果返回

// find 找到符合条件元素 --- 找到返回对应元素,找不到返回undefined
console.log(books.find(book => book.price === 59))

// findIndex 找符合条件的元素的索引值 -- 找到返回对应的索引值,找不到返回-1
console.log(books.findIndex(book => book.price === 59))
const books = [
  {
    name: '《算法导论》',
    price: 85.00
  },
  {
    name: '《UNIX编程艺术》',
    price: 59.00
  },
  {
    name: '《编程珠玑》',
    price: 39.00
  }
]

// sort方法也是一个高阶函数,用于对数组进行排序,并返回一个排序后的新数组
// sort方法不是一个纯函数,会改变原数组
// 如果callback返回的结果大于0,那么就交换,如果callback返回结果小于等于0,就不交换
console.log(books.sort((preBook, book) => preBook.price - book.price))

// reverse() 方法将数组中元素的位置颠倒,并返回该数组
// reverse方法是一个非纯函数,会改变原数组
console.log(books.reverse())
const arr = new Array(3)

// fill 方法将数组中的每一个元素项全部填充为对应的参数值
// fill方法是非纯函数
arr.fill(3)
console.log(arr) // => [ 3, 3, 3 ]
// 数组的绝对多数 高阶函数的参数都是 item,index,arr
// 绝大多数数组的高阶函数,除了第一个callback外,都存在第二个参数thisArg,用于修正callback内部的this指向

let nums = [23, 34, 454, 3423, 123]

// map() 方法创建一个新数组 --- map方法是纯函数
// 这个新数组由原数组中的每个元素都调用一次提供的函数后的返回值组成,也就是说map方法是用来进行数组映射的
console.log(nums.map(function(item, index, arr) {
  // console.log(item, index, arr, this)
  return item * item
}, { name: 'Klaus' }))

// filter() 方法创建一个新数组 - filter方法是一个纯函数
// 新数组中只包含每个元素调用函数返回为true的元素
console.log(nums.filter(function(item, index, arr) {
  // console.log(item, index, arr, this)
  return item % 2 === 0
}, { name: 'Klaus' }))

// reduce的第一个参数为callback, 第二个参数为initalValue
// 1. 如果设置了initalValue,那么第一次执行的preV的值即为initalValue,item为nums[0]
//                      第二次执行的时候,preV为上一次callback的返回值,item为nums[1]
//                      依次类推,如果全部遍历完毕,那么就直接将callback的返回值作为reduce方法的返回值进行返回

// 2. 如果没有设置initalValue,那么第一个执行的preV的值为nums[0], item的值为nums[1]
//                       第二次执行的preV的值为上一次callback的返回值,item的值为nums[2]
//                       依次类推,如果全部遍历完毕,那么就直接将callback的返回值作为reduce方法的返回值进行返回

// 所以reduce方法一般可以用来做数组的累加操作 --- reduce是一个纯函数
console.log(nums.reduce(function(preV, item, index, arr) {
  // console.log(preV, item, index, arr)
  return preV + item
}, 0))
const nums = [10, 30, 45, 223]

// every方法 数组中的每一项都符合条件的时候 才会返回true
console.log(nums.every(num => num % 2 === 0)) // => false

// some方法 数组中只要有一个元素符合对应的条件 就会返回true
console.log(nums.some(num => num % 2 === 0)) // => true

综合练习

计算一个数组中所有偶数的平方的和

const nums = [23, 34, 454, 3423, 123]

// ps: 逻辑非运算符的优先级高于取余之类的数学运算符
console.log(nums.filter(item => !(item % 2)).
            map(item => item ** 2)
            .reduce((preV, item) => preV + item))

Date

国际标准时间是英国伦敦的皇家格林威治( Greenwich )天文台的标准时间(刚好在本初子午线经过的地方),这个时间也称之为GMT(Greenwich Mean Time)

  • 其他时区根据标准时间来确定自己的时间,往东的时区(GMT + hh:mm),往西的时区(GMT - hh:mm)
  • 例如: 中国位于东8区,假设GMT的零时区时间为00:00 那么此时中国的时间为08:00

但是,公转有一定的误差,因此会造成GMT时间有一定的误差,于是就提出了根据原子钟计算的标准时间UTC(Coordinated Universal Time)

在浏览器中,GMT时间表示,是按照UTC格式时间再加上对应的时区值进行计算得到

在JavaScript中我们使用Date来表示和处理时间

// 没有参数 生成当前时间
const date1 = new Date()

//  直接输出日期实例会自动调用date的toString方法,输出对应的标准日期格式字符串
console.log(date1)

/*
  > 在创建时间对象的时候,new关键字可以省略,表现形式和加上new是基本一致的 --- 不推荐
  > 之所以JS的大部分内置类在创建的时候,既可以使用new关键字,也可以不使用new关键字
  > 是因为ES5中,类就是构造函数,而其本身本质上就是一个普通函数
  > 所以其既可以通过new来进行调用,也可以作为普通函数进行调用
  > 所以JS为了避免在调用的时候,调用者直接将内置类当做普通函数进行调用
  > 所以也对其进行了实现,但是不使用new关键字来调用构造函数是不规范的
  > 所以这种调用方式并不被推荐
*/
const date2 = Date()
console.log(date2)

// 根据具体时间生成对应的日期对象
// 参数为日期格式字符串 除了年份外 其余都可以省略
// 如果月份和日期省略 默认就是 01 即为1月1日
// 如果时分秒省略 默认就是0:0:0
const date3 = new Date('2022-02-13')
console.log(date3) // => Sun Feb 13 2022 08:00:00 GMT+0800 (中国标准时间)

// 传入多个参数,分别对应 年,月,日,时,分,秒,毫秒
// 至少传入2个参数,也就是年和月,其余参数可以省略
// 不可以单单传入年,否则年份会被当做时间戳进行解析
// 日期不传 默认值为01, 其余不传默认值为00
const date4 = new Date(2022, 02, 13, 08, 22, 45, 222) 

// => Sun Mar 13 2022 08:22:45 GMT+0800 (中国标准时间)
console.log(date4)

// 传入一个时间戳
const date5 = new Date(1652693601880)
console.log(date5) // => Mon May 16 2022 17:33:21 GMT+0800 (中国标准时间)

时间戳

计算机中的时间戳分为两种

种类说明
时间戳它是一个整数值,表示自1970年1月1日00:00:00 UTC以来的毫秒数
时间戳的长度为13位
JavaScript采用的就是这种毫秒为单位的时间戳
Unix 时间戳它是一个整数值,表示自1970年1月1日00:00:00 UTC以来的秒数
时间戳的长度为10位
Go语言采用的就是Unix时间戳

dateString表示形式

日期的表示方式有两种: RFC 2822 标准 或者 ISO 8601 标准

标准描述
RFC 28221. RFC 2822主要用于电子邮件中的日期和时间的表示
2. RFC 2822使用英文月份缩写和星期缩写,日期顺序为日-月-年,时间使用24小时制
时区表示形式是一个四位数的偏移量,表示与UTC的时差,或者使用GMT表示零时区
ISO 8601 标准1. ISO 8601则是一种通用的日期和时间表示标准,广泛应用于数据交换、通信协议和软件开发等领域
2. ISO 8601使用年-月-日的顺序表示日期,时间使用24小时制,并使用冒号分隔小时、分钟和秒
3. 时区表示形式可以是一个带正负号的偏移量,或者使用字母"Z"表示零时区(即UTC)
标准示例说明
RFC 2822Thu, 07 Oct 2023 12:00:00 GMT
Thu, 07 Oct 2023 09:30:00 -0400
ISO 8601 标准2022-05-16T09:24:22.113Z
2022-05-03T17:30:08+08:00
1. 在浏览器和Node.js中,ISO 8601是默认的日期输出格式
2. 在浏览器中,输出格式会自动进行时区转换,
在Node.js中,输出格式不会自动进行时区转换

PS:

其它的一些时间表示格式 ,为用户自定义的时间表示格式,并不是国际标准的时间表示格式

其中最为常见的是Sat Oct 07 2023 17:20:59 GMT+0800 (中国标准时间) 、2022-02-12 12:03:25.333、 2022/02/12 12:03:25.333 等

  1. Sat Oct 07 2023 17:20:59 GMT+0800 (中国标准时间) 是 中国特有日期格式
  2. 2022-02-12 12:03:25.333 或 2022/02/12 12:03:25.333
    • 此类格式时间都是为了方便阅读而产生的非标准格式
    • 在这类格式中,根本没有时区的概念
    • 日期所对应的时区一律认为是系统所在的时区

ISO转换标准

标识说明
YYYY年份,0000 ~ 9999
YYYY表示四位年份
YY表示两位年份
MM月份,01 ~ 12
月份使用MM表示,以区分 分钟
DD日,01 ~ 31
T分隔日期和时间,没有特殊含义,可以省略
HH小时,00 ~ 23
HH表示24小时制
hh表示12小时制
mm分钟,00 ~ 59
分钟使用mm表示,以区分 月份
ss秒,00 ~ 59
.sss毫秒
Z时区
Z表示零时区
其他时区可以用±HH:mm表示,例如+08:00表示东八区

日期格式之间的相互转换

编程语言格式

const date = new Date()

// Wed Aug 07 2024 20:33:17 GMT+0800 (中国标准时间)
console.log(date.toString())

// Wed Aug 07 2024
console.log(date.toDateString())

// 20:33:17 GMT+0800
console.log(date.toTimeString())

ISO格式

const date = new Date()

// 2024-08-07T12:33:17.768Z
console.log(date.toISOString())

// 2024-08-07T12:33:17.768Z
console.log(date.toJSON())

RFC格式

const date = new Date()

// Wed, 07 Aug 2024 12:40:45 GMT
console.log(date.toUTCString())

本地格式

const date = new Date()

// 2024/8/7 20:33:17
console.log(date.toLocaleString())

// 2024/8/7
console.log(date.toLocaleDateString())

// 20:33:17
console.log(date.toLocaleTimeString())

但是ISO和RFC格式表示的时间,并不是我们日常生活中,常用的时间,所以多数情况下,我们需要获取到对应的时间值后进行自定义转换

获取和设置时间信息

我们可以从Date对象中获取各种详细的信息,同样我们也可以设置Date中各种各样的信息

我们在对日期进行设置的时候,如果设置的值超出了对应数值的范围,JS会进行自动校准

获取方法说明设置方法
getFullYear()获取年份(4 位数)setFullYear()
getMonth()获取月份,从 0 到 11
注意: 月份是从0开始计算的
setMonth()
getDate()获取当月的具体日期,从 1 到 具体月份的最大值setDate()
getHours()获取小时setHours()
getMinutes()获取分钟setMinutes()
getSeconds()获取秒钟setSeconds()
getMilliseconds()获取毫秒setMilliseconds()
getDay()获取一周中的第几天,从 0(星期日)到 6(星期六)
注意: 1. 在浏览器中 一周开始的日期为周日
2. 周几的计算也是从0开始计算的
getTime获取时间戳setTime
const date = new Date()

console.log(date) // => 2022-05-16T10:59:12.232Z

// date中的getYear方法已经被弃用
console.log(date.getFullYear()) // => 2022
console.log(date.getMonth() + 1) // => 5
console.log(date.getDate()) // => 16
console.log(date.getHours()) // => 18
console.log(date.getMinutes()) // => 59
console.log(date.getSeconds()) // => 12
console.log(date.getMilliseconds()) // => 232
console.log(date.getDay() + 1) // => 2
const date = new Date()

date.setDate(32)

// 因为5月份最大的日期值为31天,所以32越界了
// JS自动修正 将5月32日 自动修正为了 6月1日
console.log(date) // => 2022-06-01T11:01:18.820Z
// 自定义日期格式
const date = new Date()

console.log(
  `${date.getFullYear()}-${(date.getMonth() + '1').padStart(2, '0')}-${(date.getDate() + '').padStart(2, '0')} ${(date.getHours() + '').padStart(2, '0')}:${(date.getMinutes() + '').padStart(2, '0')}:${(date.getMinutes() + '').padStart(2, '0')}`
)

获取时间戳

// 当前时间对应的时间戳
const now = Date.now()

// 根据某一个具体的时间,转换为对应的时间戳
const date = new Date('2022-02-13')

// 使用实例方法getTime或valueOf
// getTime 和 valueOf 方法的功能是一样的
console.log(date.valueOf()) // => 1644710400000
console.log(date.getTime()) // => 1644710400000

// date对象有重写toString方法和valueOf方法
// 也就是说如果date对象转字符串的时候,会转换成RFC标准或ISO标准的日期格式字符串
// 如果date对象转换为数字类型的时候,会转换为对应的时间戳
// 此时如果在date对象前边加上正号或负号,date对象就会被转换为数值类型
// 也就是我们所需要的时间戳
console.log(+date) // => 1644710400000
// Date.parse(str) 方法可以从一个字符串中读取日期,并且输出对应的时间戳
// Date.parse(str) === new Date(dateString).getTime()
// Date.parse方法的参数必须是符合 RFC2822 或 ISO 8601 日期格式的字符串
// 其他格式的日期字符串作为参数的时候,该方法可能可以正常解析,但是不保证结果一定正确
// 如果输入的格式不能被解析,那么会返回NaN
console.log(Date.parse('Mon May 16 2022 19:32:45 GMT+0800 (中国标准时间)')) // => 1652700765000

通过时间戳计算时分秒

如果我们有一个三位数,我们希望分别取出其百位,十位,个位上的值,我们可以使用如下方式

const num = 234

console.log(Math.floor(num / 100)) // => 2 - 百位
console.log(Math.floor(num / 10) % 10) // => 3 - 十位
console.log(num % 10) // => 4 - 个位

如果我们有一个时间戳,想要获取其对应的时,分,秒,也可以使用类似的方式

const now = new Date()

console.log(now) // => 2022-05-21T04:57:40.413Z

// 计算当前时间的时分秒,需要先计算出当前时间和单日零点之间的时间戳
// 日期是一个对象,所以需要重新新建一个新的日期对象
const yesterday = new Date()

// 将新的日期对象的时间设置为当日零点
// 对于setHours 除了可以设置小时,也可以设置分钟... 一直可以设置到毫秒
// 对于setMinutes, setSeconds 之类的函数都是类似的
yesterday.setHours(0, 0, 0)

// 计算时间戳的差值,并将毫秒制转换为秒制
const intervalTime = Math.floor((now - yesterday) / 1000)

// 根据时间戳的差值,计算时分秒
console.log(Math.floor(intervalTime / (60 * 60))) // => 12 - 时
console.log(Math.floor(intervalTime / 60) % 60) // => 57 - 分
console.log(intervalTime % 60) // => 39 - 秒