前端基础高频面试题

177 阅读6分钟

前端基础高频面试题

var和let、const的区别

  • var是es5语法,let const是es6语法,var存在变量提升
  • var和let是变量,可修改;const是常量,不可修改
  • let const有块级作用域,var没有
let i
for (i = 1; i <= 3; i++) {
  setTimeout(function () {
    console.log(i) // 4 4 4
  }, 0)
}

typeof返回哪些类型

  • undefined string number boolean symbol
  • object(注意,typeof null === 'object')
  • function

列举强制类型转换和隐式类型转换

  • 强制转换:parseInt parseFloat toString等
  • 隐式转换:if、逻辑运算、==、+拼接字符串

手写深度比较,模拟lodash isEqual

// 判断是否是对象或者数组
function isObject(obj) {
  return typeof obj === "object" && obj !== null
}

function isEqual(obj1, obj2) {
  // 值类型
  if (!isObject(obj1) || !isObject(obj2)) {
    return obj1 === obj2
  }
  // 地址相同的对象
  if (obj1 === obj2) {
    return true
  }

  // 取出key
  const obj1Key = Object.keys(obj1)
  const obj2Key = Object.keys(obj2)
  if (obj1Key.length !== obj2Key.length) {
    return false
  }

  // 以obj1为基准 和obj2递归比较
  for (const key in obj1) {
    const res = isEqual(obj1[key], obj2[key])
    if (!res) {
      return false
    }
  }

  return true
}

pop、push、shift、unshift、splice和concat、map、filter、slice

注意纯函数和非纯函数的区别

const arr = [10, 20, 30, 40]

// ------------------非纯函数-------------------------------------------------------

// 删去最后一个元素返回,修改原数组
// const popRes = arr.pop()
// console.log(popRes, arr) // 40 [10, 20, 30]

// 删去开头一个元素返回,修改原数组
// const shiftRes = arr.shift()
// console.log(shiftRes, arr)

// 向数组最后push添加 返回数组长度 修改原数组
// const pushRes = arr.push(50)
// console.log(pushRes, arr)

// 向数组开头添加一个元素 返回数组长度 修改原数组
// const unshiftRes = arr.unshift(4)
// console.log(unshiftRes, arr)

// 数组切片 start开始位置 count切割数量 后面接的是替换的值 返回值为切割出来的值
// const spliceRes = arr.splice(1, 2, 'a')
// console.log(spliceRes, arr)

// ------------------纯函数-------------------------------------------------------
// 1. 不改变原数组 2.返回一个数组

// 用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
// const arr1 = arr.concat(50)
// console.log(arr1, arr)

// 返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
// const arr2 = arr.map(num => num * 2)
// console.log(arr2, arr)

// 返回一个新数组,数组元素为原始数组过滤处理的值
// const arr3 = arr.filter(num => num > 25)
// console.log(arr3, arr)

// 返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。
// const arr4 = arr.slice(1, 4)
// console.log(arr4, arr)

split()和join()的区别

split分割 join连接

let testStr = 'air-hua-byte'
const str1 = testStr.split('-')
console.log(str1) // ['air', 'hua', 'byte']
const str2 = str1.join('-')
console.log(str2) // air-hua-byte

[10, 20, 30].map(parseInt)返回结果是什么

返回结果为:[10, NaN, NaN]

// 拆解式子 parseInt第二个参数为2-36之间的整数,表示被解析字符串的基数。
[10, 20, 30].map((num, index) => {
  return parseInt(num, index)
})

ajax请求get和post的区别

  • GET在浏览器回退时是无害的,而POST会再次提交请求
  • GET产生的URL地址可以被Bookmark(书签),而POST不可以
  • GET请求会被浏览器主动cache,而POST不会,除非手动设置
  • GET请求只能进行url编码,而POST不会,除非手动设置
  • GET请求参数会被完整保留在浏览器历史记录,而POST参数不会被保留
  • GET请求在URL传输参数是有长度限制的,而POST没有
  • 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
  • GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
  • GET参数通过URL传递,POST放在Request body中

函数call和apply的区别

第二个参数中call是参数列表(不是单一数组),而apply是函数参数所组成的数组

事件代理(委托)是什么

“事件代理”即是把原本需要绑定在子元素的响应事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。

更多内容参考事件冒泡、事件捕获和事件委托

闭包是什么,有何特性、有何影响

闭包:

  1. 闭包可以使用在它作用域外面定义的变量

  2. 闭包可以存在定义该变量的函数作用域中

  3. 产生场景:函数作为返回值,函数作为参数被传递

优点:

  • 避免全局变量的污染
  • 相当于产生一个私有成员,其他用不了这个变量

缺点:

  • 常驻内存,增加内存使用量
  • 使用不当会容易造成内存泄漏

更多内容参考:作用域和闭包

如何阻止事件冒泡和默认行为

event.stopProppagation()

event.preventDefault()

如何减少DOM操作

  • 缓存DOM查询结果(循环查询)
  • 多次DOM操作,合并一次(循环插入)

解释jsonp的原理,为何不是真正的ajax

jsonp的原理:浏览器可以动态插入一段js代码并执行

为什么不是真正的ajax:

  • ajax核心:通过XMLHttpRequest获取非本页内容

  • jsonp的核心:加载script标签调用服务器js脚本

  • 浏览器的同源策略和跨域(script和img可以越过跨域)

document load和ready的区别

load需要全部资源加载完才会执行,包括图片、视频等

ready渲染完即可执行,此时图片、视频可能还没有加载完

函数声明和函数表达式的区别

函数声明 function fn() {...}会进行预处理加载(类似var变量提升)

函数表达式 const fn = function(){...}需要先定义在使用

new Object() 和Object.create()区别

const obj1 = {
  name: 'airhua',
  score: {
    math: 95,
    english: 90
  },
  sum() {
    return this.score.math + this.score.english
  }
}

const obj2 = new Object(obj1)
console.log(obj2 === obj1)

const obj3 = Object.create(obj1)
console.log(obj1 === obj3)

1644550083766.png

从上面例子总结以下:

  • new Object()等同于{},原型为Object.prototype
  • Object.create({...})等于指定原型

this场景题

function Foo() {
  getName = function () {
    console.log(1);
  };
  return this;
}
Foo.getName = function () {
  console.log(2);
};
Foo.prototype.getName = function () {
  console.log(3);
};
var getName = function () {
  console.log(4);
};

function getName() {
  console.log(5);
}

Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3

手写字符串trim方法,保证浏览器兼容性

if (!String.prototype.trim) {
  String.prototype.trim = function () {
    // /^\s+/ :匹配以空白格开头的 /\s+$/ :匹配以空白格结尾
    return this.replace(/^\s+/, '').replace(/\s+$/, '')
  }
}

如何获取多个数字中的最大值

// 手写方法
const max = (...argumnets) => {
  let maxNum = 0
  const arr = Array.prototype.slice.call(argumnets)

  arr.forEach(num => {
    if (num > maxNum) maxNum = num
  });

  return maxNum
}

console.log(max(10, 60, 80, 4, 6, 500))
// Math.max
console.log(Math.max(10, 60, 80, 4, 6, 500))

如何捕获js程序的异常

try...catch

  • 针对代码写比较好

window.onerror(message, source, lineNum, colNum, error)

  • 全局报错信息、
  • 但是对于跨域的js和CDN引入不会见擦汗
  • 压缩js需要配合sourceMap反查出错行和列

将url参数解析成js对象

普通方法

function queryToObj() {
  const res = {}
  // 去掉 ?
  const search = location.search.substr(1)
  search.split('&').forEach(paramStr => {
    const arr = paramStr.split('=')
    const key = arr[0]
    const value = arr[1]
    res[key] = value
  })

  return res
}

使用URLSearchParams

function queryToObj() {
  const res = {}
  const pList = new URLSearchParams(location.search)
  pList.forEach((value, key) => {
    res[key] = value
  })

  return res
}

手写数组flatern

写完一个后查了一下,惊人发现这个很多实现方式,具体就没一个一个去看了,这里放一个链接,有空还可以补一补:面试官连环追问:数组拍平(扁平化) flat 方法实现

// 1
function flat(arr) {
  const isDeep = arr.some(item => item instanceof Array)
  if (!isDeep) {
    return arr
  }

  const res = Array.prototype.concat.apply([], arr)
  return flat(res)
}

// 2
function flat(arr) {
  return [].concat(...arr.map(item => {
    return Array.isArray(item) ? flat(item) : item
  }))
}

const res = flat([1, 2, [54, 85, [8, 9]]])
console.log(res)

数组去重

// 普通方式
function unique(arr) {
  let res = []
  arr.forEach(item => {
    if (res.indexOf(item) < 0) {
      res.push(item)
    }
  });

  return res
}
// ES6
function unique(arr) {
  let res = new Set(arr)
  return [...res]
}

requestAnimationFrame

属于异步宏任务,执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。下面可以来看看setTimeout实现动画和requestAnimationFrame的区别,setTimeout可能出现掉帧情况。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    #app {
      background-color: red;
      height: 100px;
    }
  </style>
</head>

<body>
  <div id="app"></div>
</body>
<script>
  const app = document.getElementById('app')
  let curWidth = 100
  const maxWidth = 640

  // function animate() {
  //   curWidth = curWidth + 3
  //   app.style.width = curWidth + 'px'
  //   if (curWidth < maxWidth) {
  //     setTimeout(animate, 16.7)
  //   }
  // }

  function animate() {
    curWidth = curWidth + 3
    app.style.width = curWidth + 'px'
    if (curWidth < maxWidth) {
      window.requestAnimationFrame(animate)
    }
  }
  animate()
</script>

</html>