[你不知道的JavaScript(中)] 原生函数

97 阅读6分钟

先来吐个槽,加班多了,下班就没啥学习的动力了,回来洗完澡之后只想躺床玩手机,而且因为没私人时间导致报复性的熬夜,又影响到第二天的工作,然后第二天的工作又不能高效完成,然后又加班.......进入了死循环

那么回到正题,啥是JS的原生函数呢?那就是前面说过的String()、Number()之类的,比较常用的如下

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Function
  • RegExp
  • Date
  • Error
  • Symbol
  • BigInt

这些原生函数是可以当做构造函数来用的,但是这个构造出来的对象可能跟我们想象的不完全一致。

const a = new String('abc')
console.log(typeof a) // object 而不是 string
console.log(a instanceof String) // true
console.log(Object.prototype.toString.call(a)) // [object String]

内部属性 [[ Class ]]

所有typeof返回object的对象都包含一个内部属性[[Class]],可以看做一个内部的分类,我们无法直接访问这个属性,但是我们可以通过Object.prototype.toString.call来得到这个值。

Object.prototype.toString.call([]) // [object Array]
Object.prototype.toString.call(new Date()) // [object Date]

然后来看看基本类型,比如null和undefined

Object.prototype.toString.call(null) // [object Null]
Object.prototype.toString.call(undefined) // [object Undefined]

虽然没有Null和Undefined这两个构造函数,但是依然识别出来了类型。

Object.prototype.toString.call(1) // [object Number]
Object.prototype.toString.call('str') // [object String]
Object.prototype.toString.call(true) // [object Boolean]

对于其他的基础类型会被各自的封装类型对象自动包装,也就是接下来将要说的。

封装对象包装

这个其实我们经常会用到这个属性,或者说Javascript会自动帮我们做到这一点。比如基本类型是没有.length.toString这些属性和方法的,但是我们却能使用他们。

const a = 'abc'
a.length // 3

因为JavaScript引擎已经对一些常用的属性方法做了性能优化了,所以我们定义变量值的时候不要使用包装对象构造,而是直接定义,让JavaScript引擎自己识别我们什么时候需要包装对象。

// yes
let num = 666
let str = 'str'
let bool = true

// no!!!
let num = new Number(666)
let str = new String('str')
let bool = new Boolean(true)

需要注意的一点是我们用包装函数构造出来的并不是真正的基础类型,而是一个对象。

const a = new Boolean(false)

if (!a) {
    console.log('demaxiyawansui~~~~~~~~~~~~') // 这里不会执行,所以德玛西亚不会万岁
}

拆包

对于以上我们创建的封装对象,如果我们想要获取到他的基本类型值,可以通过valueOf函数。

const a = new String('str')

console.log(a.valueOf()) // str

在一些场景下也会出现自动拆包的情况

const a = new String('str')
const b = a + ''

typeof a //object
typeof b // string

原生函数作为构造函数

一般来说,对于数组、函数、正则等类型我们一般习惯于用字面量的形式声明,但是我们也可以使用构造函数的方式来声明,尽管我们不建议这么干。

Array

const a = new Array(1,2,3) // [1,2,3]
const b = [1,2,3] // [1,2,3]

以上两种创建数组的方式都一样的,构造函数的方式也不要求添加new关键字,不带的时候会自动补上。

用构造函数创建对象时,有个弊端就是当参数为一个数字的时候,这个数字则作为数组的长度,而不是数组项。参数为单个非数字或者多个参数时,又是作为数组的项,这很容易让人记混。并且,JS的数组没有预设长度这一回事,它只是把数组的length设置为指定的值而已,里面还是空数组。并且在不同的浏览器,他们显示不一致,在谷歌浏览器中,这么构建出来的空数组会显示为[undefined × 3],这会让人很迷惑,因为这个和[undefined, undefined, undefined]又不是一致的。

const a = Array(3)
const b = [undefined, undefined, undefined]

a.map((item, index) => index) // [undefined × 3]
b.map((item, index) => index) // [0, 1, 2]

因为forEachmap等数组遍历方法都会跳过空元素(和null、undefined不一样),所以遍历出来结果是不一致的。

所以以上种种,都不建议创建和使用空单元数组。

Object、Function、RegExp

如非必要,不要用这三个构造函数

const obj = new Object()
obj.a = '666'
obj.c = true
const obj1 = {
    a: 666,
    b: false
}

const func = new Function('a', 'return a * 2')
const func1 = a => a * 2

const reg = new RegExp(/hello/g)
const reg1 = /hello/g

Object定义对象你无法一次性定义多种属性,只能一个一个的设定。

基本上除了特别特别特别特殊的场景,没人会使用Function去定义一个对象。

但是RegExp跟其他两兄弟又不完全一样,我们依然建议用字面量去定义正则,因为JavaScript引擎会预编译和缓存,但是我们可以用这个构造函数去动态定义正则表达式,举个简单的例子

const are = 'you ok?'

const a = new RegExp(`are ${are}`, 'ig')

Date和Error

相对于其他构造函数,这两者我们会使用的更多,因为我们没有其他的办法去创建时间对象和错误对象了。

相信大家对Date方法不陌生了,也不展开讲了。

说说Error对象,创建错误对象可以获得当前运行栈的上下文,包括调用栈信息和代码行号,便于我们调试。通常会和throw关键字一起使用

throw new Error('wdnmd')

错误对象至少包含一个message属性,也有一些其他的属性,比如type等。除了访问stack外,最好的方法是调用toString方法格式化便于阅读。

除开Error对象以外,还有一些特定类型的错误对象,比如TypeErrorReferenceError等。

原生原型

原生构造函数都有自己的.prototype属性,这些对象包含了对应的子类型的所有行为特征,所以对应的子类型可以通过原型链去访问这些方法属性,比如String下的toUpperCaseNumber下的toFixedArraymap等等。

以下当做额外的知识了解了解得了,我们在项目中千万千万别搞这些骚操作

  1. 并不是所有构造函数的原型都是纯对象!!!
typeof Function.prototype // function
Function.prototype() // 空函数

RegExp.prototype.toString() // /(?:)/ 空正则表达式
  1. 他们能被修改!!!
Array.isArray(Array.prototype) //true
Array.prototype.push(1,2,3)
Array.prototype // [1,2,3]

完!!! 希望以后所有版本都顺顺利利,别在加班了 - -!。加班多了完全没有继续学习的热情了,而且很蛋疼的一点就是最近心脏附近有点异动,说疼吧也算不上,就是忽然肌肉紧缩了一下的感觉,这是往前二十多年都没有过的感觉,程序员猝死的新闻看多了真的有点怕怕的,周末还是去做个心电图吧。哭,赚的钱都拿去医院了。