Skip to content

常见基础面试题

数据类型

原始类型:number,string,boolean,null,undefined,bigint,symbol

引用类型:Object

  • 标准普通对象:Object
  • 标准准特殊对象:Array,Date,Math,Error,RegExp
  • 非标准特殊对象(包装类型):Number,String,Boolean
  • 可执行对象:Function

Symbol应用

  1. 对象唯一值属性
  2. 唯一标识统一管理(pinia 中的 storeId)
  3. iterator底层实现机制

类型检测方式

  1. typeof:所有数据类型,在计算机底层都是按照‘64 位二进制’进行存储的, typeof是按照二进制值进行检测类型的
    详细转换机制
  2. 若二进制前三位是‘0’,并且实现了 call 方法,则返回 function,没有实现则返回 object
  3. null是 64 个 ‘0’,typeof null ---> 'object'(局限性)
  4. 检测未被声明的变量,返回 undefined :::
js
const isObj = option => option !== null && (typeof option === 'object' || typeof option === 'function')

数据类型转换

其他类型转为数字类型

规则:

  1. 宇符串转换为数宇:空宇符串变为0,如果出现任何非有效数宇宇符,结果都是NaN

  2. 把布尔转换为数宇true->1 ; false->0

  3. null->0 ; undefined->NaN

  4. Symbol无法转换为数宇,会报错:Uncaught TypeError

  5. BigInt转为数字,会去除“n”(超过安全数宇的,会按照科学计数法处理)

  6. 把对象转换为数字

  • 先调用对線的 Symbol.toPrimitive 这个方法,如果不存在这个方法
  • 再调用对泉的 valueof 获取原始值,如果获取的值不是原始值,比如数组
  • 再调用对線的 toString 把其变为字符串
  • 最后再把宇符串基于Number方法转换为数字
js
Number.parseInt(val, radix)
Number.parseFloat(val)
Number.parseInt(null)
Number.parseInt(undefined) // -> NaN
/*  规则:val 必须是字符串,不是则自动转为字符串;
      然后再 从左往右 找符合 radix(进制)有效数字,一个没找到则返回 NaN,
      遇到一个非有效数字字符,则停止查找,并 parseInt(之前符合的字符串)  parseInt('12px') -> 12
*/

2023-03-04-22-31-38

其他类型转为字符串

隐式转换:String(val)

js
let a = '10'
const n = a++ // 10(数字)  ++a / a++ 一定是数字运算
// +出现在左边,转换为数字    +'10'->10

2023-03-04-22-57-17

其他类型转为布尔类型

除了falsy值: 0、-0、NaN、null、undefined、空串,其余都是 true

相等与全等区别

相等(==):两边数据类型不同,会先进行类型转换,再进行比较

  1. 对象与字符串:对象转字符串 Symbol.toPrimitive -> valueOf -> toString,再比较
  2. null == undefined --> true ;null/undefined 与其他任何值都不相等
  3. 对象与对象,比较的是内存地址是否相同
  4. NaN 与其他,NaN 不与任何值相等,NaN == NaN --> false
  5. 除了以上情况,只要两边类型不一致,剩下的都是转为数字,再进行比较

    全等(===):两个操作数类型相同,值也需相同才返回 true

可用==的情况:使用时相当于 obj.a === null || obj.a === undefined

  1. 判断对象的属性是否存在 obj.a == null
  2. 判断函数的参数是否存在 arg == null
面试题
js
//题1:
[] == false  // -> 0 == 0   -> false
![] == false  // -> false == false   -> true

//题2:
var a = ?;
if(a == 1 && a == 2 && a == 3){
  console.log('OK')
}
//解法一:利用==会进行类型转换,对象转为数字会经历:
//Symbol.toPrimitive -> valueOf -> toString
var a = {
  i: 0,
  [Symbol.toPrimitive](){
    return ++this.i
  }
}
//解法二:重写 toString
var a = [1, 2, 3]
a.toString = a.shift

//解法三: 数据劫持
var i = 0
Object.defineProperty(window, 'a', {
  get(){
    return ++i
  }
})

怎么解决精度问题

根本原因:所有数据类型在计算机底层都是以64位二进制存储的,可能出现无限循环,超过 64 位部分被丢弃

  1. 将数字转为整数【扩大系数法】
  2. 第三方库:Math.js, decimal.js, big.js

var/let/const 区别

varletconst
变量提升支持不支持不支持
块级作用域没有
暂存性死区没有存在存在
能否修改变量的值不能修改
变量的值不能修改允许不允许不允许

函数声明会覆盖其他的同名的变量声明。如果有多个函数声明,则是由最后的一个函数声明覆盖之前所有的声明。

const

  1. 声明必须初始化

  2. 只读常量,变量的值不能修改

    • 简单类型 :值就保存在变量指向的那个内存地址,等同于常量。

    • 引用类型:变量指向的内存地址,保存的只是一个指向实际数据的指针;

      const 只能保证指针固定,不能控制指向的数据结构。

暂存性死区

暂存性死区

暂存性死区:使用 let / const 定义该变量之前的区域,

不能在声明前访问,作用域内被声明,不受外部影响

基于 let/const 变量声明,在词法解析阶段就已经明确了未来上下文中必定会有相关变量声明;

如果在声明前使用,则报错 Uncaught ReferenceError

this指向

  1. 全局作用域中或者普通函数中指向全局对象window
  2. 立即执行函数必定指向window
  3. 构造函数中指向对象实例
  4. 事件绑定指向事件源对象
  5. 方法中谁调用就指向谁
  6. 定时器回调为普通函数指向window,箭头函数指向声明时所在外部作用域
  7. forEach等回调函数默认指向window,指定第二个参数存在则指向第二个参数
  8. 严格模式指向window的变为undefined

防抖与节流

防抖:频繁触发某个事件时,在设定的时间内再次触发,会重新清除上一次定时器,重新开启定时器开始计时。

节流:频繁触发某个事件时,保证设定的时间内只会执行一次,只有等执行完才会打开节流阀执行下一个事件。

防抖

js
// 实现原理:每次触发事件时,取消之前的定时器,重新计时
function debounce(func, delay = 500) {
  let timer = null
  return function (...args) {
    if (timer)
      clearTimeout(timer) // 清除上一次
    timer = setTimeout(() => {
      func.apply(this, args)
    }, delay)
  }
}
防抖拓展写法
js
function debounce(func, delay = 500, immediate = false) {
  // 参数判断处理
  if (typeof func !== 'function')
    throw new TypeError('func is not a funciton')
  // debounce(func,true)
  if (typeof delay === 'boolean') {
    immediate = delay
    delay = undefined
  }
  delay = +delay
  Number.isNaN(delay) ? 500 : delay // 若不是数字则默认 500
  if (typeof immediate !== 'boolean')
    immediate = false

  let timer = null
  return function (...args) {
    // 第一次自执行完,timer 已经有值,
    const now = !timer && immediate
    if (timer)
      clearTimeout(timer) // 清除上一次
    timer = setTimeout(() => {
      // 结束边界触发
      if (!immediate)
        func.apply(this, args)
      // 清除最后一个定时器
      timer = clearTimeout(timer)
    }, delay)
    // 若为立即执行,则第一次,开启边界触发
    if (now)
      func.apply(this, args)
  }
}

节流

js
// 实现原理:每次触发事件时,判断当前是否存在等待执行的延时函数
// 方法一:定时器存在则什么都不做
function throttle1(func, delay) {
  let timer = null
  return function (...args) {
    if (timer)
      return
    timer = setTimeout(() => {
      func.apply(this, ...args)
      timer = null
    }, delay)
  }
}
// 方法二:
function throttle2(func, delay) {
  let flag = true // 节流阀:开启状态
  return function (...args) {
    if (!flag)
      return
    flag = false // 已经在处理:关闭
    setTimeout(() => {
      func.apply(this, args)
      flag = true // 处理完:重新打开
    }, delay)
  }
}
// 实现三:当前时间-上次执行时间 > 设定时间,才执行
function throttle3(func, delay) {
  let start = 0
  return function (...args) {
    const now = Data.now()
    if (now - start > delay) {
      func.apply(this, args)
      start = now
    }
  }
}

应用

防抖:输入框搜索、编辑框实时保存,(手机息屏策略,王者荣耀回城操作) 节流:滚动到底部加载更多、图标跟随鼠标(地铁发车时间,王者荣耀技能冷却)

前端本地存储方式

区别

存储大小存储时间同源策略在同一浏览器的相同域名、不同端口号下
localStorage5M键值对以字符串形式存储,数据长期保留,不主动删除一直存在受限制不可以共享
sessionStorage5M会话结束自动清除(浏览器窗口关闭时)协议隔离跳转的页面可以共享
cookie4kb设置到期时间,过期清除受限制可以共享

用法

localStorage

js
localStorage.setItem('key', 'value') // 添加数据
const val = localStorage.getItem('key') // 读取数据
localStorage.removeItem('key') // 移除单个
localStorage.clear() // 移除所有

sessionStorage

js
sessionStorage.setItem('key', 'value') // 保存数据到 sessionStorage
const data = sessionStorage.getItem('key')// 获取数据
sessionStorage.removeItem('key') // 删除
sessionStorage.clear() // 删除所有

cookie

js
const cookies = document.cookie // 同源下获取所有cookie
document.cookie = 'key=value' // 添加
document.cookie = 'key=; expires=xxx' // 设定到期时间自动清除

IndexedDB

Generator

一是,function关键字与函数名之间有一个星号;

二是,函数体内部使用yield表达式,定义不同的内部状态。

js
function* helloWorldGenerator() {
  const a = yield 'hello'
  console.log(a) // 123
  const b = yield 'world'
  console.log(b) // undefined
  return 'ending'
}

const hw = helloWorldGenerator()
hw.next()
hw.next(123) // a = 123

nex()传参会赋值给上一次yield的的返回值

异步流程同步化

js
function* test() {
  const res1 = yield new Promise((resolve) => {
    setTimeout(() => {
      resolve('第一秒执行')
    }, 1000)
  })
  console.log(res1) // 第一秒执行

  const res2 = yield new Promise((resolve) => {
    setTimeout(() => {
      resolve('第二秒执行')
    }, 1000)
  })
}

function generatorRunner(fn) {
  const generator = fn()
  const step = generator.next()
  // 定义递归函数
  function loop(stepArg, generator) {
    // 获取本次 yield 右侧的结果
    const value = stepArg.value
    if (value instanceof Promise) {
      // 如果是 Promise 对象就在 then 函数的回调中获取本次程序结果
      // 并且等待回调执行的时候进入下一次递归
      value.then((promiseValue) => {
        if (!stepArg.done) {
          loop(generator.next(promiseValue), generator)
        }
      })
    }
  }
  loop(step, generator)
}

requestAnimationFrame

60HZ/1000ms = 16.67ms(显示屏每毫秒刷新频率)

起因:setTimeout/setInterval是异步 API,设置的时间间隔没办法保证。

解决:为了设置更精确动画时间间隔、达到平滑动画效果。用法类似 setTimeout API, 本质采用的是系统时间间隔,而不是 JS 执行时间间隔。

希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。 该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

用法2023-03-02-15-53-32

浅拷贝和深拷贝

解释

浅拷贝:只拷贝一层,属性为对象时只复制值,不复制对象本身,指向同一对象 深拷贝:完全拷贝一份新的,新对象更改不会影响到旧对象

浅拷贝

js
const newObj = Object.assign({}, obj) // 只拷贝对象自身的可枚举的属性
const newObj1 = [...obj]

// 数组
Array.prototype.slice()
Array.prototype.concat()

const arr = [
  {
    name: 'zhan',
    age: 23
  },
  123,
  456
]
const newArr1 = arr.slice()
const newArr2 = arr.concat()
// 改变内部的引用类型两者都会有影响
js
// 方式一:
// 会忽略undefined,function,RegExp,Date
const newObj = JSON.parse(JSON.stringify(obj))

// 方式二:
function deepClone(obj) {
  if (obj === null || typeof obj !== 'object')
    return obj
  if (obj.constructor === Date)
    return new Date(obj)
  if (obj.constructor === RegExp)
    return new RegExp(obj)
  const newObj = Array.isArray(obj) ? [] : {}
  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key))
      newObj[key] = deepClone(obj[key])
  }
  return newObj
}

事件捕获和冒泡机制

事件传播过程:事件捕获 ---> 目标阶段---> 事件冒泡

事件捕获:从根元素(html)向内传播,直到目标元素,途中会触发绑定事件捕获元素的回调。element.addEventListener(event, function, true)

事件冒泡:从目标元素向外传播,直到根元素,途中会触发绑定事件冒泡元素的回调。onclickaddEventListener默认绑定冒泡阶段。

阻止冒泡:event.stopPropagation()

阻止默认行为:event.preventDefault(),例如:a标签跳转,表单按钮数据提交至服务器。

阻止同一节点其他后绑定事件执行:event.stopImmediatePropagation()

Promise链式调用

是指在一个Promise对象上多次调用then方法,每个then方法都可以接收上一个Promise对象的返回值,

并返回一个新的Promise对象,从而形成一个Promise链 ,从而实现多个异步操作的顺序执行和数据传递。

如果then方法中抛出了异常,那么后续的then方法会被跳过,直接执行catch方法。

如果then方法中返回了一个Promise对象,那么后续的then方法会等待该Promise对象的状态发生改变后再执行。

可以避免回调地狱,使得代码更加清晰易读。