拯救你的面试:前端面试大全--js面试题(二)

739 阅读11分钟

HTTP 篇面试题

一、js内部函数和闭包区别

内部函数:一般来说在一个函数内部定义另外一个函数,这样的函数就是内部函数。

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

二、简述下js事件代理(事件委托)以及它有什么缺点

事件代理:一般来说就是通过事件冒泡把一个元素的响应事件的函数代理到它的父层或者更外层元素上。

缺点:

  1. 只能支持冒泡的事件,对于不冒泡的事件无法代理( focus/blur )
  2. 所有事件都代理容易出错,建议就近委托
  3. 内部元素层级过多,容易被某层阻止掉

三、怎么样判断对象的真实类型?

  1. Object.prototype.toString.call(val)
  2. instanceof
  3. constructor

四、你平时怎么实现深拷贝?

  1. 对于简单的数据来说使用 JSON.stringifyJSON.parse 来实现
  2. 对于复杂的使用第三方库 Lodash

1、 让你实现深拷贝你有什么想法?

  1. 递归赋值
  2. 值类型的判断(数组、null、函数、Symbol ),不同类型处理方式不同
  3. 循环引用的处理
  4. 注意性能问题

五、说下 JSON.stringify 和 JSON.parse

1、 JSON.stringify

定义:将一个 JavaScript 对象或值转换为 JSON 字符串。

参数:有三个参数

JSON.stringify(value[, replacer [, space]])

1. replacer

replacer 参数可以是一个函数或者一个数组。

作为函数,它有两个参数,键( key )和值( value ),它们都会被序列化。

replacer 是一个数组,数组的值代表将被序列化成 JSON 字符串的属性名。

2. space

space 参数用来控制结果字符串里面的间距。

如果是一个数字, 则在字符串化时每一级别会比上一级别缩进多这个数字值的空格;

如果是一个字符串,则每一级别会比上一级别多缩进该字符串。

2、 JSON.parse

定义:用来解析 JSON 字符串。

参数:有两个参数

JSON.parse(text[, reviver])

1. reviver

转换器, 如果传入该参数(函数),可以用来修改解析生成的原始值。

3、 JSON.stringify 转化下面对象返回什么值?

1. 转化对象

问题:
const data = {
  a: "aaa",
  b: undefined,
  c: Symbol("dd"),
  fn: function() {
    return true;
  }
};
JSON.stringify(data) // ?
答案:
'{"a":"aaa"}'
解析:

undefined、任意的函数以及 symbol 作为对象属性值时 JSON.stringify() 将跳过(忽略)对它们进行序列化

2. 转化数组

问题:
const arr = ["aaa", undefined, function aa() {
    return true
}, Symbol('dd')]
JSON.stringify(arr) // ?
答案:
'["aaa",null,null,null]'
解析:

undefined、任意的函数以及 symbol 作为数组元素值时,JSON.stringify() 会将它们序列化为 null

3. 单独转化 函数、undefinedSymbol

问题:
JSON.stringify(function a() {console.log(1)}) // ?
JSON.stringify(undefined) // ?
JSON.stringify(Symbol('aa')) // ?
答案:
undefined
undefined
undefined
解析:

undefined、任意的函数以及 symbolJSON.stringify() 作为单独的值进行序列化时都会返回 undefined

4. 当转化的对象包含 toJSON 函数时?

问题:
const obj = {
    say: "hello JSON.stringify",
    toJSON: function() {
        return "today i learn";
    }
}
JSON.stringify(obj) // ?
答案:
"today i learn"
解析:

转换值如果有 toJSON() 函数,该函数返回什么值,序列化结果就是什么值,并且忽略其他属性的值。

5. 转化 Date 日期

问题:
a = new Date()
// Tue Jan 26 2021 20:26:21 GMT+0800 (中国标准时间)
JSON.stringify(a) // ? 
答案:
"2021-01-26T12:26:21.469Z"
解析:

Date 日期调用了 toJSON() 将其转换为了 string 字符串(同 Date.toISOString() ),因此会被当做字符串处理。

a.toISOString()
"2021-01-26T12:26:21.469Z"

6. 转化 NaNInfinitynull 时?

问题:
JSON.stringify(NaN)
JSON.stringify(Infinity)
JSON.stringify(null)
答案:
"null"
"null"
"null"
解析:

NaNInfinity 格式的数值及 null 都会被当做 null

六、js声明变量的方法?

letconstvarclassimportfunction

1、letconstvar 有什么区别?

varletconst
没有块级作用域有块级作用域有块级作用域
声明全局变量在 window
(全局属性下)
全局变量不在全局属性下全局变量不在全局属性下
重定义变量不会报错会报错会报错
声明变量声明变量声明一个常量
存在变量提升不存在变量提升不存在变量提升
声明之后随时赋值声明之后随时赋值声明之后立即赋值

2、const 定义常量可不可以修改?

const 定义基础类型是不可以修改的

const 定义引用类型是可以修改引用类型里面的值

3、如果我想 const 定义引用类型也不能改变它的值该怎么做?

  1. Object.freeze
  2. 代理( proxy/Object.defineProperty )
  3. 修改对象的 configurablewritable 属性

七、问一下关于数组的问题吧

1、ES6新增数组方法

Array.from()Array.of()copyWithin()find()findIndex()fill()entries()keys()values()includes()

2、ES5新增数组方法

forEach()map()filter()some()every()indexOf()lastIndexOf()reduce()reduceRight()

3、数组的这些方法,哪些能改变原数组?

1. 改变原数组的方法

  1. copyWithin()
  2. fill()
  3. pop()
  4. push()
  5. reverse()
  6. shift()
  7. sort()
  8. splice()

4、someevery 有什么区别?

从中文含义能看出来,some 是某些,every 是每一个,它们都返回一个 Boolean

5、数组里面有10万条数据,取第一个元素和第10万个元素哪个用时长?

用时基本上一样,因为 js 里面没有数组类型,数组其实也是一个对象,keyvalue

6、数组去重你有几种方法?

  1. set() 实现
  2. 双重遍历循环实现
  3. Object 实现和单层遍历实现
  4. 利用数组内置方法加单层遍历去重( indexOf、includes )实现
  5. Map 和单层遍历实现
  6. 数组 filter 方法实现

7、for 循环和 forEach 的性能哪个更好一点?

for 循环的性能更好

  • for 循环没有任何额外的函数调用栈和上下文;
  • forEach 不是普通的 for 循环的语法糖,还有诸多参数和上下文需要在执行的时候考虑进来,这里可能拖慢性能

8、sort 排序是按照什么方式来排序的?

默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16 代码单元值序列时构建的

9、多维数组转为一维数组

  1. reduce 递归实现
  2. joinsplit 实现
  3. 递归遍历
  4. flat 方法
  5. toStringsplit 实现
  6. 广度优先遍历/深度优先遍历

10、广度优先遍历和深度优先遍历如何实现

1. 深度优先遍历

const depth = (node) => {
    let stack = []
    let nodes = []
    if (node) {
        stack.push(node)
        while (stack.length) {
        	//每次取最后一个
            let item = stack.pop()
            let children = item.children || []
            nodes.push(item)
            //判断children的长度
            for (let i = children.length - 1; i >= 0; i--) {
                stack.push(children[i])
            }
        }
    }
    return nodes
}

2. 广度优先遍历

const breadth = (node) => {
    let nodes = []
    let stack = []
    if (node) {
        stack.push(node)
        while (stack.length) {
        	//取第一个
            let item = stack.shift()
            let children = item.children || []
            nodes.push(item)
            for (let i = 0; i < children.length; i++) {
                stack.push(children[i])
            }
        }
    }
    return nodes
}

八、for infor of 有什么区别?

比较for infor of
不同点可以遍历普通对象
遍历出数组的原型对象
可以遍历出数组自身属性
遍历出来的值是 key
不可以遍历 map/set
不可以迭代 generators
IE 支持
不能遍历普通对象
不会遍历出原型对象
不会遍历自身属性
遍历出来的值是 value
可以遍历 map/set
可以迭代 generators
IE 不支持
相同点可以遍历数组
可以 break 中断遍历
可以遍历数组
可以 break 中断遍历

九、promise 相关面试问题

1、如何实现一个 sleep 函数(延迟函数)

通过 promisesetTimeout 来简单实现

/**
 * 延迟函数
 * @param {Number} time 时间
 */
function sleep (time = 1500) {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(true)
        }, time)
    })
}

2、那说下 promise 构造函数、then 方法、catch 方法、finally 方法哪个异步哪个同步?

promise 构造函数是同步执行的,thencatchfinally 方法是异步执行的。

3、如何取消一个 promise

使用 promise.race()

Promise.race(iterable)

iterable 参数里的任意一个子 promise 被成功或失败后,父 promise 马上也会用子 promise 的成功返回值或失败详情作为参数调用父 promise 绑定的相应句柄,并返回该 promise 对象。

4、多个 promise 如何获取第一个成功的 promise

1. Promise.all 改进

利用 promise.all 的特性,遍历 promise 数组,根据返回值进行判断,当成功的时候,转为 reject 返回,当失败的时候转为 resolve 继续执行。

//第一个成功的Promise
function firstProSuccess (allProMise) {
  //遍历promise数组,根据返回值进行判断,当成功的时候,转为reject返回,当失败的时候转为resolve继续执行。
  return Promise.all(allProMise.map(item => {
    return item.then(
      res => Promise.reject(res),
      err => Promise.resolve(err)
    )
  })).then(
    errors => Promise.reject(errors),
    val => Promise.resolve(val)
  )
}

2. Promise.any

Promise.any(iterable)

接收一个 Promise 对象的集合,当其中的一个 promise 成功,就返回那个成功的 promise 的值。

缺点:有兼容问题

5、多个 promise,所有的 promise 都取得返回结果(不管成功/失败都要返回值)

1. Promise.all 改进

和上面原理类似,只不过是当成功的时候不进行操作,当 reject 时进行 resolve 操作

2. Promise.allSettled()

Promise.allSettled(iterable)

返回一个在所有给定的 promise 都已经 fulfilledrejected 后的 promise

缺点:有兼容问题

6、说说 promise 的静态方法有哪些?

1. Promise.all(iterable)

接收一个 promise 数组对象(可迭代的 promise 实例对象),全部成功时,返回所有 promise 的数组集合;当其中一个失败时,返回当前失败的 promise 对象。

2. Promise.allSettled(iterable)

接收一个 promise 数组对象,全部完成时(不管成功/失败)返回新的 promise 数组集合

3. Promise.any(iterable)

接收一个 promise 数组对象,当其中任何一个成功时,返回成功的 promise

4. Promise.race(iterable)

接收一个 promise 数组对象,当其中任意一个成功/失败时,返回该 promise

5. Promise.reject(reason)

返回一个状态为失败的 Promise 对象。

6. Promise.resolve(value)

返回一个状态由给定 value 决定的 Promise 对象。

7. Promise.finally(onFinally)

在当前 promise 运行完毕后被调用,无论当前 promise 的状态是完成( fulfilled )还是失败( rejected )

8. Promise.try(f)

接收一个函数,返回一个 promise

为所有操作提供了统一的处理机制,所以如果想用 then 方法管理流程,最好都用 Promise.try 包装一下。

  • 更好的错误处理
  • 更好的互操作性
  • 易于浏览

Promise-try

7、 Promise.then 的第二个参数有了解吗?和 .catch 有什么区别?

then() 方法返回一个 Promise

它最多需要有两个参数:Promise 的成功和失败情况的回调函数。

p.then(onFulfilled[, onRejected]);

p.then(value => {
  // fulfillment
}, reason => {
  // rejection
});

第二个参数也是一个函数,是对失败情况的回调函数。

then 第二个参数catch
then 方法的参数Promise 的实例方法
then 的第一个参数抛出异常
捕获不到
then 的第一个参数抛出异常
可以捕获
是一个函数本质是 then 方法的语法糖
如果第二个参数和 catch 同时存在,
promise 内部报错,第二个参数可以捕获
此时,catch 捕获不到,
第二个参数不存在,catch 才会捕获到
不建议使用建议使用 catch 进行错误捕获

8、 Promise.resolve 有几种情况?

1. 参数是一个 Promise 实例

参数是 Promise 实例,那么 Promise.resolve 将不做任何修改、原封不动地返回这个实例。

2. 参数是一个 thenable 对象

Promise.resolve() 方法会将这个对象转为 Promise 对象,然后就立即执行 thenable 对象的 then() 方法。

3. 参数不是具有 then() 方法的对象,或根本就不是对象

如果参数是一个原始值,或者是一个不具有 then() 方法的对象,则 Promise.resolve() 方法返回一个新的 Promise 对象,状态为 resolved

4. 不带有任何参数

直接返回一个 resolved 状态的 Promise 对象。

9、如果 .then 中的参数不是函数,那会怎样?

Promise.resolve(1)
    .then(2)
    .then(console.log)
1

如果 .then 中的参数不是函数,则会在内部被替换为 (x) => x,即原样返回 promise 最终结果的函数。

10、如果 .finally 后面继续跟了个 .then,那么这个 then 里面的值是什么?

Promise.resolve('resolve')
  .finally(() => {
    console.log('this is finally')
  	return 'finally value'
  })
  .then(res => {
    console.log('finally后面的then函数, res的值为:', res)
  })
this is finally
finally后面的then函数, res的值为: resolve

1.finally 的回调函数中不接收任何参数; 2. 在 promise 结束时,无论结果是 fulfilled 或者是 rejected ,都会执行 finally 回调函数; 3. finally 返回的是一个上一次的 Promise 对象值。

11、.all.race 在传入的数组有第一个抛出异常的时候,其他异步任务还会继续执行吗?

会的,会继续执行,只是不会在 then / catch 中表现出来。

浏览器执行下面代码,可以看出当报错的时候 console 还是会继续执行的,只是在 对应的回调函数里面没有表现出来。

function sleep (n) {
    return new Promise((resolve, reject) => {
        console.log(n)
        Math.random() > 0.5 ? reject(n) : resolve(n)
    }, n % 2 === 0 ? 1000 * n : 1000)
}
Promise.all([sleep(1), sleep(2), sleep(3)])
  .then(res => console.log('all res: ', res))
  .catch(err => console.log('all err:', err))
Promise.race([sleep(1), sleep(2), sleep(3)])
  .then(res => console.log('race res: ', res))
  .catch(err => console.log('race err:', err))
参考