自我整理,若是有回答的不详细或还不错的面试题可评论告诉我
Js基础
0.1+0.2 !== 0.3 ?
因为计算机在计算时是使用二进制计算,0.2转换成二进制为1.10011001...无限循环,从而出现精度问题,很多其他的语言亦是如此
['1','2','3'].map(parseInt)
结果:[1, NaN, NaN]
map函数回调的第一个参数是值,第二个是索引,parseInt的第一个参数是值,第二个是进制,
| item | index | 进制 |
|---|---|---|
| 1 | 0 | 传0默认10进制 |
| 2 | 1 | 没有1进制,直接NaN |
| 3 | 2 | 2进制不会出现3,NaN |
AJAX、Fetch、Axios 区别
Ajax即Asynchronous Javascript And XML(异步JavaScript和XML),是一个技术统称,最早用XMLHttpRequest实现
- Fetch是js的原生网络请求API,是为了替代XMLHttpRequest而出现的,使用Promise封装而成
- Axios是第三方请求库
- Fetch和Axios都是Ajax技术
防抖和节流
防抖:限制执行次数,密集触发只执行最后一次
用于搜索框触发事件等
function debounce(fn,delay=200){
let timer = 0
return function (){
if(timer) clearTimeout(timer)
timer = setTimeout(()=>{
fn.apply(this,arguments)
timer = 0
},delay)
}
}
节流:限制执行频率,有节奏的执行
用于滚动或拖拽等
function throttle(fn){
let time = null
return ()=>{
if(time) return
setTimeout(()=>{
fn.apply(this,arguments)
},100)
}
}
什么时候不能使用箭头函数
箭头函数无法使用call更改this指向!
- 对象中的属性使用箭头函数,this不是对象本身
- 原型对象上的函数使用箭头函数,this不是原型对象本身
- 构造函数也不能使用箭头函数
总之,箭头函数的this指向是其父级作用域,注意即可
for...in和for...of的区别 | for...await...of
for...in: 用于枚举数据,数组、对象、字符串
for...of: 用于迭代数据,数组、迭代器、Map、Set
for...await...of: 类似Promise.All()方法
HtmlCollection和NodeList的区别
都是不是数组,而是"类数组"
HtmlCollection只包含标签
NodeList会包含标签、文本、注释
严格模式是什么
JavaScript被设计为新手开发者更容易上手,所以有时候本来错误语法,被认为也是可以正常被解析的;但是这种方式可能给带来留下来安全隐患; 在严格模式下,这种失误就会被当做错误,以便可以快速的发现和修正
严格模式通过在脚本或函数的头部添加 use strict; 表达式来声明。
- 全局变量必须声明
- 禁止使用with
- eval的作用域隔离
- 禁止this直接指向window
- 禁止函数参数同名
HTTP的options请求是什么
当发起跨域请求时,浏览器默认会先发送一个options预检请求,来检查请求的服务器是否支持该请求
垃圾回收机制
- 引用计数:当一个变量被持有的引用为0时则释放内存,但无法解决循环引用的问题,存在内存泄漏
- 标记清除:当一个变量无法从window作用域中找到引用时则释放其内存
内存泄漏
什么情况下会发生内存泄漏
- 使用不当的闭包
- 遗忘的定时器
- 意外的全局变量
闭包是内存泄漏吗
是也不是,因为确实无法释放闭包内的引用,但这是调用者可控的,也可称为js特性的一种
怎么检测是否存在内存泄漏
开发者工具 Performance
Map和WeakMap的区别
- Map的key可以是任意类型,WeakMap只能是引用类型
- 垃圾回收机制不会考虑WeakMap持有的引用
EventLoop
- 微任务:promise,dom渲染前执行
- 宏任务:settimeout/setinterval,dom渲染后执行
for和forEach哪个快
for快,forEach需要创建一个函数来调用,会开辟额外的作用域,有额外的开销,但是比for看起来简洁
requestAnimationFrame和requestIdleCallback
- requestAnimationFrame:可以自动匹配设备帧率来展示动画,高性能
- requestIdleCallback:渲染空闲时调用,优先级低
script标签上的defer和async的区别
默认是浏览器解析到script标签会暂停解析剩余html,获取script资源并执行过后在继续解析
defer:解析和获取script资源并行执行,html解析完成后再运行js
async:解析和获取script资源并行执行,获取完毕后运行js再继续解析
repaint和reflow的区别
repaint:重绘,仅元素自身的外观改变时 reflow:重排,元素的定位布局改变时,性能损耗高
避免多次重排
- 集中修改样式,如切换class
- 设置display: none脱离文档流,修改样式后再恢复
- BFC
多标签通讯
localstorage通讯
- 必须同域
- storage监听事件
SharedWorker
网页和iframe通讯
postMessage Api
常用设计模式
| 标题 | 描述 |
|---|---|
| 工厂模式 | 提供一个函数来创建对象,省去new,简化创建对象,根据传入参数来对应返回需要的对象 |
| 单例模式 | 适用于单一对象,只生成一个对象实例,避免频繁创建和销毁实例,减少内存占用 |
| 发布订阅模式 | 发布者,调度中心,订阅者三个角色,发布者和订阅者互不感知,完全由调度中心来处理 |
| 观察者模式 | 观察者和被观察者是一对多的关系,其中一个对象的状态发生改变,所有依赖它的对象都会收到通知 |
| 装饰器模式 | 给类、参数、方法封装新的功能 |
实现Array.flat
这里是深度flat,只需一层的话去掉递归即可
function flattenDeep(arr: any[]): any[] {
let res: any[] = []
arr.forEach(item => {
if (Array.isArray(item)) {
const flatItem = flattenDeep(item) // 递归
res = res.concat(flatItem)
} else {
res = res.concat(item)
}
})
return res
}
实现获取变量类型的函数
function getType(x) {
const originType = Object.prototype.toString.call(x) // '[object String]'
const type = originType.slice(8, -1) // 'String'
return type.toLowerCase()
}
实现new函数
function customNew<T>(constructor: Function, ...args: any[]): T {
// 1. 创建一个空对象,继承 constructor 的原型
const obj = Object.create(constructor.prototype)
// 2. 将 obj 作为 this ,执行 constructor ,传入参数
constructor.apply(obj, args)
// 3. 返回 obj
return obj
}
LazyMan
class LazyMan {
private name: string
private tasks: Function[] = [] // 任务列表
constructor(name: string) {
this.name = name
setTimeout(() => {
this.next()
})
}
private next() {
if(this.tasks.length){
this.tasks.shift()!()
}
}
eat(food: string) {
const task = () => {
console.info(`${this.name} eat ${food}`)
this.next() // 立刻执行下一个任务
}
this.tasks.push(task)
return this // 链式调用
}
sleep(seconds: number) {
const task = () => {
console.info(`${this.name} 开始睡觉`)
setTimeout(() => {
console.info(`${this.name} 已经睡完了 ${seconds}s,开始执行下一个任务`)
this.next() // xx 秒之后再执行下一个任务
}, seconds * 1000)
}
this.tasks.push(task)
return this // 链式调用
}
}
函数转换成柯里化函数
function curry(fn: Function) {
const fnArgsLength = fn.length // 传入函数的参数长度
let args: any[] = []
// ts 中,独立的函数,this 需要声明类型
function calc(this: any, ...newArgs: any[]) {
// 积累参数
args = [ ...args, ...newArgs ]
if (args.length < fnArgsLength) {
// 参数不够,返回函数
return calc
} else {
// 参数够了,返回执行结果
return fn.apply(this, args)
}
}
return calc
}
实现instanceof
原理:实例的原型对象和目标的原型对象是否一致
function myInstanceof(instance: any, origin: any): boolean {
if (instance == null) return false // null undefined
const type = typeof instance
if (type !== 'object' && type !== 'function') {
// 值类型
return false
}
let tempInstance = instance // 为了防止修改 instance
while (tempInstance) {
if (tempInstance.__proto__ === origin.prototype) {
return true // 配上了
}
// 未匹配
tempInstance = tempInstance.__proto__ // 顺着原型链,往上找
}
return false
}
实现bind、call、apply
Function.prototype.myBind = function (ctx:any,...args: any) {
return (...args2:any)=> {
this.apply(ctx,args.concat(args2))
}
}
Function.prototype.myCall = function (ctx:any,...args: any) {
let key = Symbol()
Object.defineProperty(ctx,key,{
enumerable: false,
value: this
})
ctx[key](...args)
}
Function.prototype.myApply = function (ctx:any,args: any[] = []) {
let key = Symbol()
Object.defineProperty(ctx,key,{
enumerable: false,
value: this
})
ctx[key](...args)
}
实现EventBus
class EventBus {
private readonly event: {
[key in string]: Array<{isOnce: boolean, fn:Function}>
}
constructor() {
this.event = {}
}
once(key:string,fn:Function){
this.on(key,fn,true)
}
on(key:string,fn:Function,isOnce=false){
if (!this.event[key]) {
this.event[key] = []
}
this.event[key].push({isOnce,fn})
}
emit(key:string){
if (!this.event[key]) return
this.event[key] = this.event[key].filter(item=>{
let {fn,isOnce} = item
fn()
return !isOnce
})
}
off(key:string,fn ?:Function){
if(!this.event[key]) return
if(fn){
this.event[key] = this.event[key].filter(item=>item.fn!==fn)
}else{
this.event[key] = []
}
}
}
实现LRU缓存
LRU:Least Recently Used 仅保留最近使用的数据
map实现
class LRUCache {
private length: number
private data: Map<any, any> = new Map()
constructor(length: number) {
if (length < 1) throw new Error('invalid length')
this.length = length
}
set(key: any, value: any) {
const data = this.data
// 若已存在 删除重新添加 刷新到最前
if (data.has(key)) {
data.delete(key)
}
data.set(key, value)
if (data.size > this.length) {
// 如果超出了容量,则删除 Map 最老的元素
const delKey = data.keys().next().value
data.delete(delKey)
}
}
get(key: any): any {
const data = this.data
if (!data.has(key)) return null
const value = data.get(key)
data.delete(key)
data.set(key, value)
return value
}
}
链表实现
class LRUCache {
private length: number
private data: { [key: string]: IListNode } = {}
private dataLength: number = 0
private listHead: IListNode | null = null
private listTail: IListNode | null = null
getData(){
console.log(this.data);
}
constructor(length: number) {
if (length < 1) throw new Error('invalid length')
this.length = length
}
private moveToTail(curNode: IListNode) {
if (this.listTail === curNode) return
// ---- 1. 让 prevNode nextNode 断绝与 curNode 的关系 ----
const prevNode = curNode.prev
const nextNode = curNode.next
if (prevNode) {
prevNode.next = nextNode
}
if (nextNode) {
nextNode.prev = prevNode
if (this.listHead === curNode) this.listHead = nextNode
}
// ---- 2. 让 curNode 断绝与 prevNode nextNode 的关系 ----
delete curNode.prev
delete curNode.next
// ---- 3. 在 list 末尾重新建立 curNode 的新关系 ----
if (this.listTail) {
this.listTail.next = curNode
curNode.prev = this.listTail
}
this.listTail = curNode
}
private tryClean() {
while (this.dataLength > this.length) {
const head = this.listHead
if (head == null) throw new Error('head is null')
const headNext = head.next
if (headNext == null) throw new Error('headNext is null')
// 1. 断绝 head 和 next 的关系
delete headNext.prev
delete head.next
// 2. 重新赋值 listHead
this.listHead = headNext
// 3. 清理 data ,重新计数
delete this.data[head.key]
this.dataLength = this.dataLength - 1
}
}
get(key: string): any {
const data = this.data
const curNode = data[key]
if (curNode == null) return null
if (this.listTail === curNode) {
// 本身在末尾(最新鲜的位置),直接返回 value
return curNode.value
}
// curNode 移动到末尾
this.moveToTail(curNode)
return curNode.value
}
set(key: string, value: any) {
const curNode = this.data[key]
if (curNode == null) {
// 新增数据
const newNode: IListNode = { key, value }
// 移动到末尾
this.moveToTail(newNode)
this.data[key] = newNode
this.dataLength++
if (this.dataLength === 1) this.listHead = newNode
} else {
// 修改现有数据
curNode.value = value
// 移动到末尾
this.moveToTail(curNode)
}
// 尝试清理长度
this.tryClean()
}
}
实现深拷贝
function cloneDeep(obj: any, map = new WeakMap()): any {
if (typeof obj !== 'object' || obj == null ) return obj
// 避免循环引用
const objFromMap = map.get(obj)
if (objFromMap) return objFromMap
let target: any = {}
map.set(obj, target)
// Map
if (obj instanceof Map) {
target = new Map()
obj.forEach((v, k) => {
target.set(cloneDeep(v, map), cloneDeep(k, map))
})
}
// Set
if (obj instanceof Set) {
target = new Set()
obj.forEach(v => {
target.add(cloneDeep(v, map))
})
}
// Array
if (obj instanceof Array) {
target = obj.map(item => cloneDeep(item, map))
}
// Object
for (const key in obj) {
target[key] = cloneDeep(obj[key], map)
}
return target
}
Css基础
px、%、em、rem、vw/vh、vmax/vmin 的区别
- px: 像素
- %: 百分比
- em: 当前元素的fontsize
- rem: root的fontsize
- vw/vh: 屏幕宽/高
- vmax/vmin: 屏幕宽高对比哪个大/小取哪个
offset、client、scroll
- offsetHeight|offsetWidth: border + padding + content
- clientHeight|clientWidth: padding + content
- scrollHeight|scrollWidth: padding + 实际内容尺寸
Vue相关
watch和computed的区别
- computed函数不能有异步;watch可以
- computed必须有返回
- computed有缓存,watch每次监听对象发生改变时都会调用回调
- computed适用于多个属性影响一个属性,watch则是一个属性影响多个属性
组件通信方式
父子
- props
- $emit
- $attrs, 未声明的属性
- provide/inject
- refs
兄弟
- 全局事件总线
- vuex/pinia
虚拟DOM和真实DOM哪个快
- 虚拟DOM没有操作原生DOM快
- 虚拟DOM的优势在于节点进行改动的时候会进行diff算法,尽量减少开销
- 真正的价值是数据驱动视图,大大节省了开发的效率
生命周期
- beforeCreate:会在实例初始化完成、props 解析之后、
data()和computed等选项处理之前立即调用 - created:初始化vue实例完成
- beforeMount:生成vdom
- mounted:组件被挂载之后调用
- beforeUpdate:data发生变化,dom更新之前
- updated:data发生变化,dom更新后(不要再update里修改data,死循环
- beforeUnmount:组件销毁前调用,还保留vue实例所有功能
- unmounted:组件实例被卸载之后调用,响应式作用已停止
- activated:keepalive组件被激活
- deactivated:keepalive组件被隐藏
Vue2/3 diff算法的区别
Vue2/3 响应式的区别
常用优化
- v-if和v-show
- v-for必须有key
- computed缓存
- 频繁切换用keep-alive
- 耗时加载的组件使用异步加载
- 路由懒加载
- nuxt.js ssr渲染
Node.js相关
扩展
URL到页面展示的完整过程
- DNS查询获取服务器地址,建立TCP连接
- 发起HTTP请求
- 获取HTML并解析,获取js css等静态资源
- 生成dom树并渲染页面
TCP的三次握手和四次挥手
cookie和token的区别
- cookie不能跨域携带,token无限制
- cookie需要配合session在服务端查询用户信息,token是无状态的
- token可防止csrf攻击
Session和JWT的区别
- session是空间换时间,token是cpu计算时间换取存储空间
SSO单点登录
相同顶级域名 多个子域名通过设置domain为顶级域名的cookie可实现信息交换
不同顶级域名 CAS方案
HTTP、TCP、UDP
HTTP
- 处于应用层
- 无状态的短连接
- 是传输数据的内容的规范
TCP
- 处于传输层
- 有稳定的连接和断开,传输稳定
- 有状态的长连接
- 是数据传输和连接方式的规范
UDP
- 处于传输层
- 无连接无断开,传输效率高,丢包率也高
HTTP1.0、1.1、2.0的区别
HTTP1.0
- 默认短链接,需要手动开启长连接 HTTP1.1
- 默认长连接
- 支持断点续传
- 支持只发送header信息
HTTP2.0
- header可压缩
- 多路复用,做到同一个连接并发处理多个请求
- 服务端主动推送
WebSocket和HTTP
WebSocket请求需要先发送一个HTTP请求建立连接,再把这个请求升级为WS请求,双端都可主动推送消息
WebSocket和长轮询
长轮询是模拟服务端发送消息的一个常见手段,由客户端主动发送一个请求,服务端将这个请求挂起等待,当需要发送消息的时候在返回,客户端发现该请求超时需要继续发送一个请求
常见的攻击手段
XSS CSRF DDos SQL注入
基础算法
队列
栈实现
一个栈用来入列顺序,出列时 出栈到第二个栈颠倒顺序,在将第一个出栈,在全部回到第一个栈
class MyQueue {
private stack1: number[] = []
private stack2: number[] = []
add(n: number) {
this.stack1.push(n)
}
delete(): number | null {
// 将 stack1 所有元素移动到 stack2 中
while(this.stack1.length) {
const n = this.stack1.pop()
if (n != null) {
this.stack2.push(n)
}
}
// stack2 pop
let res = this.stack2.pop()
// 将 stack2 所有元素“还给”stack1
while(this.stack2.length) {
const n = this.stack2.pop()
if (n != null) {
this.stack1.push(n)
}
}
return res || null
}
get length(): number {
return this.stack1.length
}
}
数组实现
class MyQueue {
data: number[]
add(n: number) {
this.data.push(n)
}
delete(): number | null {
if(this.data.length){
return this.data.shift()
}
return null
}
get length(): number {
return this.data.length
}
}
链表实现
性能最好
interface IListNode {
value: number
next: IListNode | null
}
class MyQueue {
private head: IListNode | null = null
private tail: IListNode | null = null
private len = 0
add(n: number) {
const newNode: IListNode = {
value: n,
next: null,
}
// 处理 head
if (this.head == null) {
this.head = newNode
}
// 处理 tail
const tailNode = this.tail
if (tailNode) {
tailNode.next = newNode
}
this.tail = newNode
// 记录长度
this.len++
}
delete(): number | null {
if (this.head == null || this.len <= 0) return null
// 取值
const value = this.head.value
// 处理 head
this.head = this.head.next
// 记录长度
this.len--
return value
}
get length(): number {
return this.len
}
}
反转单向链表
function reverseLinkList(listNode: ILinkListNode): ILinkListNode {
// 定义三个指针
let prevNode: ILinkListNode | undefined = undefined
let curNode: ILinkListNode | undefined = undefined
let nextNode: ILinkListNode | undefined = listNode
// 以 nextNode 为主,遍历链表
while(nextNode) {
// 第一个元素,删掉 next ,防止循环引用
if (curNode && !prevNode) {
delete curNode.next
}
// 反转指针
if (curNode && prevNode) {
// 把当前节点的下个节点变成上个节点
curNode.next = prevNode
}
// 整体向后移动指针
prevNode = curNode
curNode = nextNode
// 获取原顺序的下个节点,若无则结束循环
nextNode = nextNode?.next
}
// 循环结束,curNode是原最后一个节点,此时变成第一个
curNode!.next = prevNode
return curNode!
}
二分查找法
O(logn)
必须有序
循环
function binarySearch1(arr: number[], target: number): number {
if (arr.length === 0) return -1
let startIndex = 0 // 开始位置
let endIndex = arr.length - 1 // 结束位置
while (startIndex <= endIndex) {
const midIndex = Math.floor((startIndex + endIndex) / 2)
const midValue = arr[midIndex]
if (target < midValue) {
// 目标值较小,则继续在左侧查找
endIndex = midIndex - 1
} else if (target > midValue) {
// 目标值较大,则继续在右侧查找
startIndex = midIndex + 1
} else {
// 相等,返回
return midIndex
}
}
return -1
}
递归
function binarySearch2(arr: number[], target: number, startIndex?: number, endIndex?: number): number {
if (arr.length === 0) return -1
// 开始和结束的范围
if (startIndex == null) startIndex = 0
if (endIndex == null) endIndex = arr.length - 1
if (startIndex > endIndex) return -1
// 中间位置
const midIndex = Math.floor((startIndex + endIndex) / 2)
const midValue = arr[midIndex]
if (target < midValue) {
// 目标值较小,则继续在左侧查找
return binarySearch2(arr, target, startIndex, midIndex - 1)
} else if (target > midValue) {
// 目标值较大,则继续在右侧查找
return binarySearch2(arr, target, midIndex + 1, endIndex)
} else {
// 相等,返回
return midIndex
}
}
深度优先和广度优先遍历dom树
// 递归 深度
depthFirstTraverse1(root: Node) {
console.log(root);
const childNodes = root.childNodes; // .childNodes 和 .children 不一样
if (childNodes.length) {
childNodes.forEach(child => {
depthFirstTraverse1(child); // 递归
});
}
}
// 循环 深度
function depthFirstTraverse2(root: Node) {
const stack: Node[] = [];
// 根节点压栈
stack.push(root);
while (stack.length > 0) {
const curNode = stack.pop(); // 出栈
if (curNode == null) break;
console.log(curNode);
// 子节点压栈
const childNodes = curNode.childNodes;
if (childNodes.length > 0) {
// reverse 反顺序压栈
Array.from(childNodes).reverse().forEach(child => stack.push(child));
}
}
}
// 广度优先
function breadthFirstTraverse(root: Node) {
const queue: Node[] = []; // 模拟单项队列,链表更合适
// 根节点入队列
queue.unshift(root);
while (queue.length > 0) {
const curNode = queue.pop();
if (curNode == null) break;
console.log(curNode);
// 子节点入队
const childNodes = curNode.childNodes;
if (childNodes.length) {
childNodes.forEach(child => queue.unshift(child));
}
}
}
斐波那契数列 | 青蛙跳台阶
循环
function fibonacci2(n: number): number {
if (n <= 0) return 0
if (n === 1) return 1
let n1 = 1 // 记录 n-1 的结果
let n2 = 0 // 记录 n-2 的结果
let res = 0
for (let i = 2; i <= n; i++) {
res = n1 + n2
// 记录中间结果
n2 = n1
n1 = res
}
return res
}
递归
function fibonacci1(n: number): number {
if (n <= 0) return 0
if (n === 1) return 1
return fibonacci1(n - 1) + fibonacci1(n - 2)
}
将数组中所有的0移动到末尾
不使用额外空间
循环
function moveZero1(arr: number[]): void {
const length = arr.length
if (length === 0) return
let zeroLength = 0
// O(n^2)
for (let i = 0; i < length - zeroLength; i++) {
if (arr[i] === 0) {
arr.push(0)
arr.splice(i, 1) // 本身就有 O(n)
i-- // 数组截取了一个元素,i 要递减,否则连续 0 就会有错误
zeroLength++ // 累加 0 的长度
}
}
}
双指针
function moveZero2(arr: number[]): void {
const length = arr.length
if (length === 0) return
let j = -1 // 指向第一个 0
for (let i = 0; i < length; i++) {
if (arr[i] === 0) {
// 第一个 0
if (j < 0) {
j = i
}
}
if (arr[i] !== 0 && j >= 0) {
// 交换
[arr[j],arr[i]] = [arr[i],arr[j]]
j++
}
}
}
字符串中连续相同最多的字串
也可嵌套循环实现
function findContinuousChar2(str: string): IRes {
const res: IRes = {
char: '',
length: 0
}
const length = str.length
if (length === 0) return res
let tempLength = 0 // 临时记录当前连续字符的长度
let j = 0
// O(n)
for (let i = 0 ; i < length; i++) {
if (str[i] === str[j]) {
tempLength++
}
if (str[i] !== str[j] || i === length - 1) {
// 不相等,或者 i 到了字符串的末尾
if (tempLength > res.length) {
res.char = str[j]
res.length = tempLength
}
tempLength = 0 // reset
if (i < length - 1) {
j = i // 让 j “追上” i
i-- // 细节
}
}
}
return res
}
快速排序
function quickSort(arr: number[]): number[] {
const length = arr.length
if (length === 0) return arr
const midIndex = Math.floor(length / 2)
const midValue = arr[midIndex]
const left: number[] = []
const right: number[] = []
for (let i = 0; i < length; i++) {
if (i !== midIndex) {
const n = arr[i]
if (n < midValue) {
// 小于 midValue ,则放在 left
left.push(n)
} else {
// 大于 midValue ,则放在 right
right.push(n)
}
}
}
// return [...quickSort2(left), midValue, ...quickSort2(right)]
return quickSort2(left).concat(
[midValue],
quickSort2(right)
)
}
对称数 | 回数
转数组反转
数组操作很慢
function findPalindromeNumbers1(max: number): number[] {
const res: number[] = []
if (max <= 0) return res
for (let i = 1; i <= max; i++) {
// 转换为字符串,转换为数组,再反转,比较
const s = Array.from(String(i))
if (s === s.reverse()) {
res.push(i)
}
}
return res
}
前后双指针
转字符串相对数组更快
function findPalindromeNumbers2(max: number): number[] {
const res: number[] = []
if (max <= 0) return res
for (let i = 1; i <= max; i++) {
const s = i.toString()
const length = s.length
// 字符串头尾比较
let flag = true
let startIndex = 0 // 字符串开始
let endIndex = length - 1 // 字符串结束
while (startIndex < endIndex) {
if (s[startIndex] !== s[endIndex]) {
flag = false
break
} else {
// 继续比较
startIndex++
endIndex--
}
}
if (flag) res.push(i)
}
return res
}
数字反转
直接操作数字,不做转换最快
function findPalindromeNumbers3(max: number): number[] {
const res: number[] = []
if (max <= 0) return res
for (let i = 1; i <= max; i++) {
let n = i
let rev = 0 // 存储翻转数
// 数字反转公式
while (n > 0) {
rev = rev * 10 + n % 10
n = Math.floor(n / 10)
}
if (i === rev) res.push(i)
}
return res
}
数字千分位分割
循环拼接
function format2(n: number): string {
n = Math.floor(n) // 只考虑整数
let res = ''
const s = n.toString()
const length = s.length
for (let i = length - 1; i >= 0; i--) {
const j = length - i
if (j % 3 === 0) {
if (i === 0) {
// 第一位不加
res = s[i] + res
} else {
res = ',' + s[i] + res
}
} else {
res = s[i] + res
}
}
return res
}
正则
吊炸天的写法,但正则开启有性能消耗,性能测试慢一倍
function format3(n:number): string{
let reg = /(?=\B(\d{3})+$)/g
return (String(n)).replace(reg,",")
}