本文的上篇:五分钟带你秒杀JavaScript预编译
一同服用,可治面试前的小焦虑,欢迎前来学习指正。
前言
一墙之隔,那里坐着一位 正在经受HR拷打的满头大汗的求职者,你不禁心头一紧:五分钟后,就轮到你了。糟糕!好像记不太清js的类型相关知识了?莫急莫慌,笔者带你五分钟回忆起js的类型。
js的类型
一言以蔽之,JavaScript的类型有两种:
- 基本类型(原始类型)
- 引用类型(复杂类型、对象类型)
让我们分别好好聊聊。
js的基本类型
基本类型有 7 种,我先为你罗列出来
stringnumberbooleanundefinednullbigintsymbol
string类型
失去的记忆逐渐找回来了?我们来逐个分析巩固印象,先来第一段代码,看看字符串的定义与拼接:
let str = 'hello world'
let str2 =str + 'js'
console.log(str2)
是的,这里定义了两个变量,他们的类型都是字符串string
输出 hello worldjs,这表明字符串之间进行 + 运算,会直接进行拼接。
let str2 = `${str}js`
这样写也可以,区别是输出为 hello world js,会在原有拼接的基础上加入一个空格占位符 " "。
渐入佳境了,我们来看看字符串的输出
let str ='hello world'
console.log(str.at(3))
变量从0开始数,要输出第三位,显然输出的是 l。
如果我需要不止输出一位呢?可以这样做:
console.log(str.slice(0,7))
输出的是hello w。
console.log(str.slice(0,7))是指从第0位索引到第6位,这是一个左闭右开的区间。
现在,我们来回忆一下字符串转换为数字该怎么做
let n =1
let m = '2'
console.log(Number(n+m))
Number(n+m),这句代码意思是把 n+m转换为数字,这会先把两个变量转换为字符串。因此会输出的是 12,如果我改变一下形式呢:
console.log(n+Number(m))
这里就该输出3了。面试官随口一问,这两段代码区别在哪?你看,这里是用n去加上转换为数字的m,因此就是普通的加减法。
也就是说,Number()可以把字符串转换为数字。这是数字的字符串转换为数字,字符串里可不止有数字。面试官邪魅一笑,如果我将不是数字的符号传入Number()中会怎样?
let m = 'm'
console.log(Number(m))
对,就是你想的这样。这时应当输出NaN,无法表达的数字也是一种数字。如果不是无法表达的,而是大到超出js的极限安全值的数字呢?
console.log(123123123123123123+1)
输出为 123123123123123120。这很明显不对嘛!但看尾数,3+1也是等于4才对呀!这个问题在后续bitint 类型将得到解决。
js的极限安全值:JavaScript和其他语言系统一样,设计了一个极大值,这个值是 9007199254740992,也就是2的53次方。
number类型
关于number类型,你快速回忆起:
1. js里的所有数都是 number类型,没有什么浮点型
2. 上文我们学到字符串转换为数字类型。数字类型同样也可以转换为字符串类型
可以使用这个函数
let num = 1
console.log(num.toString())
这样一来,num就变成了字符串的 “1”
boolean类型
布尔类型只有 true 或 false
if(true){
console.log('lele')
}
当判断为 true 的时候,可以直接输出,反之为 false 时,则不会输出。
你马上联想到,如果放个数字进去呢?会报错吗?
if(1){
console.log('lele')
}
会正常输出 'lele'。当你传了一个数字进去,V8引擎会自动将其转换为 boolean类型,仅仅 0 和 NaN 会被判定为 false
undefined类型
undefined既是一个值也是一个类型
let u =undefined +''
会输出字符串 undefined
null类型
null类型是一个空指针,代表一个不指向任何值的指针。
bigint类型
bigint类型来表示超过安全值的数字
let b = 123123123123n
let c = 9007199254740992n
输出的就是本身,后边带了个n。再也不必担心安全值设定的范围不够用了。(虽然一般情况下也用不上)
symbol类型
看这样一段代码,他们会相等吗?
let s = Symbol('hello')
let p = Symbol('hello')
console.log( s== p)
看上去完全一致呀!可是输出判断结果为 false!
这是因为Symbol表示独一无二的值,完全相等也不会判断为相同的值。
面试官问你,特地设置这样的类型有什么意义?
意义在于,当你要为了一串非常长的代码里增加一些功能时,你要怎么确定你定义的变量和写这段代码的程序员定义的变量不会冲突?
于是你巧妙地运用Symbol规避了变量名冲突的风险,你写的这段代码都用独一无二的类型声明,面试官听完满意的笑了。
拓展:== 和 === 的区别:==在判断时,会自动转换为数字进行比较,因此数字 1 和字符串 ‘1’也会判定为true。为了避免有些情况出问题,js设计了 ===,表示完全一致的时候才判断为true。
引用类型
引用类型有以下四种:
- Array
- Object
- Function
- Data
跟着我,一步步回忆一下。
Array
先来回忆一下数组的语法,增删改查还记得吗?面试官给你这样一个简单的数组:
var arr = ['a','b','1','2']
面试官说,你给我加一个数字3进去好了。从数组头部存一个数字,这还不简单?
arr.push(3)
这样就在数组末尾加了一个 3 。面试官说,那你再从数组头部删除一个数字。
arr.pop()
数组又变回去了。面试官毫无波澜,又让你在数组头部加一个 0 进去
arr.unshift(0)
好的好的,我加进去了。面试官又笑笑,让你把**数组头部的数字删了*。
arr.shift()
这里你捏了一把汗,差点分不清shift和unshift的区别。但这种程度就像吃饭喝水一般简单。面试官也不是吃素的,他发难道:
1.删除下标为1的元素
2.插入一个0,到下标为2的位置
你游刃有余,三下五除二解决了:
arr.splice(1)
arr.splice(2,0,0)
你解决了简单的增删改查,面试官终于发出了真正的问题:
数组存值效率为什么不高? 那么从头加/删一个元素呢?从尾加/删一个呢?从中间删一个呢?时间复杂度O(n)会怎么变化?
- 数组在内存中是连续存储的
- 直接在数组两端操作,不涉及元素移动的情况下,时间复杂度为O(1)
- 设计头部、中间位置,需要移动元素,时间复杂度为O(n)
Object
Object 对象,内容在上一篇文章有具体介绍,可以移步学习一下。这里我给出一个例子:
var a = 1
var b = 'hello'
var obj = {
name : '猪猪侠',
age : 18,
like :{
one :'eat',
two :{
sports:['basketball','running']
}
}
}
obj.girlFriend = 'lele'
console.log(obj)
Function
var fn = function(){}
函数也是引用类型
Date
在浏览器中,使用Date()函数,会输出当前的日期。这是一个专门的格式,并非字符串
js的调用栈
面试官突然问你,基本类型和引用类型都是直接存在调用栈里的吗?,你脑海中自然而然浮现出一个结论:原始类型存栈,引用类型存堆。但具体怎样来着?我们先来对V8的执行过程进行补充:
V8的执行过程
1.创建一个 调用栈 (来存放预编译的过程)
2.在调用栈中,存入 xx 执行上下文(GO、AO),GO/AO中分别由词法环境和变量环境。
3.执行代码,将 原始值 存入 栈中,如果遇到引用类型的值,则将值存入堆空间并生成一个引用地址(指针),将引用地址存入栈中。
在这里我们讨论一个问题:预编译为什么需要用调用栈来存放过程?
这是因为,引用类型直接入栈,可能会出现占用过高,运存无法支持;或占用过低的情况。因此我们需要调用栈来存放引用类型。可以分为这么几点:
- 执行效率:栈的读取速度远快于堆,适合存储频繁访问的小数据
- 空间限制:栈空间较小(通常几MB),无法存储大型对象
- 共享引用:多个变量可以引用同一个堆对象,节省内存
回想起 对象 那一段的代码你的脑海中浮现出这样一幅图,你一眼就想起调用栈、GO/AO、变量环境、词法环境的关系。
总结
核心要点
- js基本类型,
string、number、boolean、undefined、null、bigint、symbol - js引用类型,
Array、Object、Function、Date - 内存模型:栈存原始值、堆存引用值
- V8机制:调用栈->执行上下文->变量环境/词法环境
- 别忘了 === 和 == 的区别;数组操作的时间复杂度要点
写在最后
五分钟已经过去,轮到你进去大显身手了,这一次,至少js的基本类型你熟记于心。
如果本文对你有帮助,欢迎点赞、收藏、关注,一起学习、一起讨论。你的支持是我创作的动力!🌹🌹