Home

js面试题

# 1. get和post的区别?

get 参数通过 url 传递,post 放在 request body 中。

因参数暴露在url中所以不要传递敏感信息

url传递的参数有长度限制,一般是2kb

get 没有body体, 设置Content-type 是无用的,只能进行 url 编码

post 支持多种编码方式。 默认都是 "application/x-www-form-urlencoded" ,也是浏览器原生form表单的方式,提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val都进行了 URL 转码。 post还支持 multipart/form-data 用于文件上传,application/json 现在非常流行,用来告诉服务端消息主体是序列化后的 JSON 字符串。 application/octet-stream 二进制流数据等

get 请求会被浏览器缓存 ,像图片 css js 文件其实都是get请求,post不会被缓存

get 产生一个 TCP 数据包, post 可能会产生两个 TCP 数据包,因为有的浏览器先发送header(请求头),响应后再发送data(消息主体)。Firefox就只发送一次

# 2. 什么是闭包? 闭包的作用?

闭包就是能够读取其他函数内部变量的函数。

  • 闭包的第一个作用是在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量,避免全局污染

  • 闭包的另一个作用是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收

# 3. 对作用域的理解

# 全局作用域和函数作用域

全局作用域:最外层定义的变量、未定义直接赋值的变量、window对象的属性 函数作用域:函数内部声明的变量、内部函数可以访问外层作用域,反之不行

# 块级作用域

ES6中新增的 let const 可以声明块级作用域({ }包裹的代码片段)

let和const声明的变量不会有变量提升(暂时性死区),也不可以重复声明

在循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量限制在循环内部

const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动(尽量用来声明常量)

# 作用域链

当前作用域找不到该变量就去父级作用域查找,依次向上直到访问到window对象终止,这一层层的关系就是作用域链

# 4. 如何判断是否是整数

  • 任何整数都会被1整除,余数是0。 严谨点需要先判断是数值typeof num === 'number'
function isInteger(num) {
 return typeof num === 'number' && num % 1 === 0
}
  • 使用 Math.round、Math.ceil、Math.floor, parseInt判断,整数取整后还是自己
function isInteger(num) {
 return  Math.floor(num) === num
}
function isInteger(num) {
 return  parseInt(num,10) === num
}
  • 通过位运算符 |0 取整,取整后还是自己
function isInteger(num) {
 return (num | 0) === num
}
  • ES6提供了Number.isInteger
Number.isInteger(3) // true
Number.isInteger(3.1) // false
  • 字符串检索
`"3.1".indexOf(".") != -1 ` 
//1!=-1 true

需要先把数字转成字符串,也可以用正则test()

`RegExp(/\./).test('3')`
// false

# 5.节流&&防抖

节流 思路利用时间戳判断,每次调用判断和上一次调用的时间差确定是否需要调用。 实际是一个工厂函数

防抖 思路利用定时器延时执行函数,新调用发生时如果旧调用没有执行就清除之前的定时器

/**
 * 节流  首次有效
 * @param {number} delay
 */
export function throttle(fn, delay) {
  // 定义上次触发时间
  let last = 0
  return (...args) => {
    const now = +Date.now()
    console.log('call', now, last, delay)
    if (now > last + delay) {
      last = now
      fn.apply(this, args)
    }
  }
}

/**
 * 防抖Debounce 延迟后有效
 * @param {number} delay
 */
export function debounce(fn, delay) {
  let timer
  return (...args) => {
    // 判断定时器是否存在,清除定时器
    if (timer) {
      clearTimeout(timer)
    }
    // 重新调用setTimeout
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay)
  }
}

return 不定参数的的函数传递...args,需要在函数被调用的时候再传参(柯里化)
throttle(a,1000)('arg1','arg2')

# 6. 一行代码实现数组扁平化、去重、排序

// 输入数据
const arr =  [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]

// 输出数据  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] 

console.log('arr', [... new Set(arr.flat(Infinity))].sort((a, b) => a - b))

# 扁平化的几种方案

  1. flat函数
// depth代表展开深度默认值1
// Infinity为无限深度
const newArray = arr.flat([depth]);
  1. 循环+递归 (5行)
const flatten = input => {
    result = []
    input.forEach(v => Array.isArray(v) ? result = result.concat(flatten(v)) : result.push(v))
    return result
}
  1. 归并方法:reduce
const flatten = input => input.reduce((prev, next) => prev.concat(Array.isArray(next) ? flatten(next) : next), []);
  1. ...扩展运算符(ES6)
const flatten = input => {
    while(input.some(v => Array.isArray(v))){
        input = [].concat(...input)
    }
    return input
}
  1. toString法(只适用于数组元素全部为数字的情况下)
const flatten = ary => ary.toString().split(",").map(v => +v)

# 7.理解map

res = [
  { code: 37067101, name: '马山街道', sub: [], parent: null}, 
  { code: 37067102, name: '金山湾', sub: [], parent: null }
]
const data = res.map(({ code, name }) => ({ code, name }))

//data
// [{ code: 37067101, name: '马山街道'}, { code: 37067102, name: '金山湾' }]

实际上是如下的简写

const data = res.map(item => ({ code: item.code, name: item.name }))

箭头函数返回一个对象时不能直接去写 {} ,要给对象套上括号, 再利用 ES6 解构赋值,箭头函数前面的参数 ({code, name}) 实际上是把 item 解构成了两个变量,其中这两个变量是 item 中存在的,即为 code, name ,然后后面是返回了一个对象,就可以用前面两个解构出来的变量。