前端笔记(面试题)

203 阅读8分钟

1. var和let const 的区别

  • var是ES5语法,let const 是ES6语法;var有变量提升
  • var和let 是变量,可修改;const是常量,不可修改;
  • let const 有块级作用域,var没有

2. typeof返回哪些类型

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

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

  • 强制类型转换:parseInt parseFloat toString等
  • if(truely)、逻辑运算(boolean)、==(字符转换为数字)+ 拼接字符串(转换为字符串类型)

4. 手写深度比较,模拟lodash isEqual

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

function isEqual(obj1,obj2) {
  if (!isObject(obj1) || !isObject(obj2)) {
    // 值类型 (注意,参与 equal 的一般不会是函数)
    return obj1 === obj2
  }
  //判断引用类型是否是一种类型
  const obj1type  = Object.prototype.toString.call(obj1)
  const obj2type = Object.prototype.toString.call(obj2)
  if(obj1type !== obj2type){
    return false
  }
  // obj1与obj2是否是一个内存地址
  if (obj1 === obj2) {
    return true
  }
  // key长度是否相等
  const obj1Keys = Object.keys(obj1)
  const obj2Keys = Object.keys(obj2)
  if (obj1Keys.length !== obj2Keys.length) {
    return false
  }
  // 以obj1为基准,和obj2 依次递归比较
  for (let key in obj1) {
    // 比较当前可以 的val ---递归
    const res = isEqual(obj1[key],obj2[key])
    if (!res) {
      return false
    }
  }
  return true
}
console.log(isEqual(obj1,obj2)) //true
****

5. split()和join()的区别

'1-2-3'.split('-') //[1,2,3]
[1,2,3].join('-') //'1-2-3'

6. 数组的pop push unshift shift分别做什么

功能是什么?

  • pop() 删除数组的最后一个元素并返回删除的元素。
  • shift() 删除并返回数组的第一个元素。
  • unshift() 向数组的开头添加一个或更多元素,并返回新的长度。
  • push() 向数组的末尾添加一个或更多元素,并返回新的长度。

返回值是什么?

  • pop shift 返回被删除的数
  • unshift() push() 返回增加后数组的长度

是否会对原数组造成影响?

扩展: 数组的API,哪些是纯函数?

  • concat 连接两个或更多的数组,并返回结果。
  • map 通过指定函数处理数组的每个元素,并返回处理后的数组。
  • filter() 检测数值元素,并返回符合条件所有元素的数组。
  • slice() 选取数组的一部分,并返回一个新数组。
 const arr = [10, 20, 30, 40]

 // concat
 const arr1 = arr.concat([50, 60, 70])
 // map
 const arr2 = arr.map(num => num * 10)
 // filter
 const arr3 = arr.filter(num => num > 25)
 // slice
 const arr4 = arr.slice()

非纯函数

  • push pop shift unshift
  • forEach
  • some every
  • reduce

7. 数组slice和splice的区别

功能

  • slice:选取数组的一部分,并返回一个新数组
  • splice:从数组中添加或删除元素

参数和返回值

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

// slice 纯函数
const arr1 = arr.slice() // arr1=[10, 20, 30, 40, 50]
const arr2 = arr.slice(1, 4) //arr2= [20, 30, 40]
const arr3 = arr.slice(2) // arr3 = [30, 40, 50]
const arr4 = arr.slice(-2)//arr4 = [40, 50]

// splice 非纯函数 返回截取的值
const spliceRes = arr.splice(1, 2, 'a', 'b', 'c') //spliceRes =[20, 30] arr=[10,'a', 'b', 'c', 40, 50]
const spliceRes1 = arr.splice(1, 2) //spliceRes1 = [20,30] arr=[10, 40, 50]
const spliceRes2 = arr.splice(1, 0, 'a', 'b', 'c') //spliceRes2 = [], arr = [10, "a", "b", "c", 20, 30, 40, 50]
console.log(spliceRes, arr)

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

const res = [10, 20, 30].map(parseInt)
console.log(res) //[10,NaN,NaN]

// 拆解
[10, 20, 30].map((num, index) => {
    return parseInt(string, index)
})

string

  • 要被解析的值。如果参数不是一个字符串,则将其转换为字符串(使用ToString抽象操作)。字符串开头的空白符将会被忽略。

radix

  • 从 2 到 36,表示字符串的基数。例如指定 16 表示被解析值是十六进制数。请注意,10不是默认值! 所以返回结果为[10,NaN,NaN]

9. ajax请求get和post的区别

  • get一般用于查询,post一般用于用户提交操作
  • get参数拼接在url上,post放在请求体内(数据体积更大)
  • 安全性:post易于防止CSRF

10. 函数call 和apply的区别

一个分别传值,一个数组或类数组传值

fn.call(this,p1,p2,p3)
fn.apply(this,arguments)

11. 事件代理(委托)是什么?

const p1 = document.getElementById('p1')
const body = document.body
bindEvent(p1,'click',e=>{
    e.stopPropagation()  //阻止事件冒泡
    alert('激活')
})
bindEvent(body,'click',e=>{
    alert('取消')
})

12. 闭包是什么,有什么特性?有什么负面影响

  • 作用域和自由变量
  • 回顾闭包应用场景:函数作为参数被传入,作为返回值被返回
  • 回顾:自由变量的查找,要在函数定义的地方(而非执行的地方)
  • 影响:变量会常驻内存,得不到释放。闭包不要乱用

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

  • event.stopPropagetion()
  • event.preventDefault()

14.查找、添加、删除、移动DOM节点的方法

  • 见DOM笔记

15.如何减少DOM操作

  • 缓存DOM查询结果
  • 多次DOM操作,合并到一次插入

16. 解析jsonp的原理,为何它不是真正的ajax

  • 浏览器的同源策略(服务端没有同源策略),和跨域

  • 哪些html标签能绕过跨域?

<img src=xxx>
<link href=xxxx>
<script src=xxxx>
  • jsonp的原理
  • jsonp通过js标签,ajax是通过XMLHttpRequset msedge_xGYrZas313.png

17. document load 和 ready 的区别

msedge_oDX6GTScQj.png

18. == 和 === 的区别

  • == 会尝试类型转换
  • === 严格相等

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

  • 函数声明 function fn() {...}
  • 函数表达式 const fn = function() {...}
  • 函数声明会在代码执行前预加载,而函数表达式不会

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

  • {}等同于new Object(),原型Object.prototype - Object.create(null)没有原型
  • Object.create({...})可指定原型,即:object.__proto__ 是{...}

21. 关于this的场景题

msedge_SH1B9lBeFE.png

  • 第一个this是User 执行结果是1
  • 第二个是this是window 执行结果是undefined

22. 关于作用域和自由变量的场景题 -1

msedge_FzI8HQmun1.png 结果3个3

23. 判断字符串以字母开头,后面字母数字下划线,长度6-30

  • 答案:const reg = /^[a-zA-Z]\w{5,29}$/

常用正则:

// 邮政编码
/\d{6}/

// 小写英文字母
/^[a-z]+$/

// 英文字母
/^[a-zA-Z]+$/

// 日期格式 2019.12.1
/^\d{4}-\d{1,2}-\d{1,2}$/

// 用户名
/^[a-zA-Z]\w{5, 17}$/

// 简单的 IP 地址匹配
/\d+\.\d+\.\d+\.\d+/

24. 关于作用域和自由变量的场景题 -2

msedge_IdfB40SQuV.png

  • 答案:100 10 10

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

判断是否兼容
if(String.prototype.trim == null){
  String.prototype.trim = function() {
    return this.replace(/^\s+/,'').replace(/\s+$/,'')
  }
}

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

ES5
function max() {
  const nums = Array.prototype.slice.call(arguments)//变为数组
  let max = 0
  nums.forEach(n=>{
    if( n>max ){
      max = n
    }
  })
  return max
}
ES6
function max(...args){
  const nums = args
//变为数组
  console.log(nums)
  let max = 0
  nums.forEach(n=>{
    if( n>max ){
      max = n
    }
  })
  return max
}
//API
max(10,20,30,40,50,32,65,45) //65
Math.max(10,20,30,40,50,32,65,45) //65

27. 如何用JS实现继承

  • class继承
  • prototype继承

28. 如何捕获js程序中的异常

//手动捕获
try {
    //todo尝试执行代码块
}
catch (error) {
    console.error(error) //错误代码
} finally {
    //todo 无论 try / catch 结果如何都会执行的代码块
}
// 自动捕获
window.onerror = function (message,source,lineNum,colNum,error) {
    // 第一,对跨域的js,如CDN的,不会有详细的报错信息
    // 第二,对于压缩的js,还要配合 sourceMap 反查到未压缩代码的行、列
}

29. 什么是JSON

  • json是一种数据格式,本质是一段字符串。
  • json格式和JS对象结构一致,对JS语言更友好
  • window.JSON是一个全局对象:JSON.stringify JavaScript 对象转换为 JSON 字符串 JSON.parse JSON 字符串转换为 JavaScript 对象
{
    "name":"张三""info":{
        "single":true,
        "age": 30,
        "city":"北京"
    }"like":["篮球","音乐"]
}

30. 获取当前页面url参数

  • 传统方式:查找location.search
  • 新API,URLSearchParams
 // 传统方式
function query(name) {
  const search = location.search.substr(1) // 类似 array.slice(1)
  // search: 'a=10&b=20&c=30'
  const res = search.split('&')
  // res :["a=10", "b=20", "c=30"]
  if (res === null) {
      return null
  }
  for(let i = 0;i < res.length;i++){
   let pair = res[i].split('=')
   if(pair[0]==name){
     return pair[1]
   }
  }
}
 query('a')

// URLSearchParams
function query(name) {
    const search = location.search
    const p = new URLSearchParams(search)
    return p.get(name)
}
console.log( query('b') )

31. 将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 val = arr[1]
        res[key] = val
    })
   return res
}
// URLSearchParams
function queryToObj(name) {
    const res = {}
    const pList = new URLSearchParams(search)
    pList.forEach((val,key)=>{
        res[key] = val
    })
    return res
}

32. 手写数组flatern,考虑多层级

var arr = [1,2,[3,4],5]
function flat(arr){
    const isDeep = arr.some(item =>item instanceof Array)
    if(!isDeep){
        return arr // 已经是 flatern [1,2,3,4]
    }
    const res = Array.prototype.concat.apply([],arr)
    return flat(res) //递归
}
const res = flat(arr)
console.log(res)

33. 数组去重

  • 传统方式,遍历元素挨个比较、去重
  • 使用set
//传统方式
function unique(arr){
    const res = []
    arr.forEach(item =>{
        if(res.indexOf(item)<0){
            res.push(item)
        }
    })
    return res
}
//set (无序,不能重复)
function unique(arr) {
    const set = new Set(arr)
    return [...set]
}
const res = unique([30,10,20,30,40,10])
console.log(res)

34. 手写深拷贝

/**
 * 深拷贝
 * @param {Object} obj 要拷贝的对象
 */
function deepClone(obj = {}) {
    if (typeof obj !== 'object' || obj == null) {
        // obj 是 null ,或者不是对象和数组,直接返回
        return obj
    }

    // 初始化返回结果
    let result
    if (obj instanceof Array) {
        result = []
    } else {
        result = {}
    }

    for (let key in obj) {
        // 保证 key 不是原型的属性
        if (obj.hasOwnProperty(key)) {
            // 递归调用!!!
            result[key] = deepClone(obj[key])
        }
    }

    // 返回结果
    return result
}
//object.assign不是深拷贝,只拷贝第一层级,如

const obj = {a:10,b:{c:20,d:30},e:40}
const obj1 =  object.assign({},obj,{f:40}) 
console.log(obj1) //{a:10,b:{c:20,d:30},e:40,f:40}
//如果修改obj.b.c
obj.b.c = 50
//则
obj1.b.c = 50

35. 介绍一下RAF requestAnimationFrame

  • 想要动画流畅,更新频率要60帧/s,即16.67ms更新一次视图
  • setTimeout要手动控制频率,而RAF浏览器会自动控制
  • 后台标签或隐藏iframe中,RAF会暂停,而setTimeout依然执行
// 3s 把宽度从 100px 变为 640px ,即增加 540px
// 60帧/s ,3s 180 帧 ,每次变化 3px

const $div1 = $('#div1')
let curWidth = 100
const maxWidth = 640

 // setTimeout
 function animate() {
     curWidth = curWidth + 3
     $div1.css('width', curWidth)
     if (curWidth < maxWidth) {
         setTimeout(animate, 16.7) // 自己控制时间
     }
 }
 animate()

// RAF
function animate() {
    curWidth = curWidth + 3
    $div1.css('width', curWidth)
    if (curWidth < maxWidth) {
        window.requestAnimationFrame(animate) // 时间不用自己控制
    }
}
animate()

36. 前端性能如何优化?一般从几个方面考虑

  • 笔记 运行环境
  • 原则:多使用内存、缓存、减少计算、减少网络请求
  • 方向:加载页面更快,页面渲染,页面操作流畅度