一、js内部函数和闭包区别
内部函数:一般来说在一个函数内部定义另外一个函数,这样的函数就是内部函数。
闭包:能够读取其他函数内部变量的函数。
二、简述下js事件代理(事件委托)以及它有什么缺点
事件代理:一般来说就是通过事件冒泡把一个元素的响应事件的函数代理到它的父层或者更外层元素上。
缺点:
- 只能支持冒泡的事件,对于不冒泡的事件无法代理(
focus/blur) - 所有事件都代理容易出错,建议就近委托
- 内部元素层级过多,容易被某层阻止掉
三、怎么样判断对象的真实类型?
Object.prototype.toString.call(val)instanceofconstructor
四、你平时怎么实现深拷贝?
- 对于简单的数据来说使用
JSON.stringify和JSON.parse来实现 - 对于复杂的使用第三方库
Lodash
1、 让你实现深拷贝你有什么想法?
- 递归赋值
- 值类型的判断(数组、
null、函数、Symbol),不同类型处理方式不同 - 循环引用的处理
- 注意性能问题
- 更多可以查看 深拷贝的终极探索
五、说下 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. 单独转化 函数、undefined、Symbol 时
问题:
JSON.stringify(function a() {console.log(1)}) // ?
JSON.stringify(undefined) // ?
JSON.stringify(Symbol('aa')) // ?
答案:
undefined
undefined
undefined
解析:
undefined、任意的函数以及 symbol 被 JSON.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. 转化 NaN、Infinity 和 null 时?
问题:
JSON.stringify(NaN)
JSON.stringify(Infinity)
JSON.stringify(null)
答案:
"null"
"null"
"null"
解析:
NaN 和 Infinity 格式的数值及 null 都会被当做 null。
- 更多可以查看 你需要了解的JSON.stringify()
六、js声明变量的方法?
有 let、const、var、class、import、function
1、let、const、var 有什么区别?
var | let | const |
|---|---|---|
| 没有块级作用域 | 有块级作用域 | 有块级作用域 |
声明全局变量在 window 下(全局属性下) | 全局变量不在全局属性下 | 全局变量不在全局属性下 |
| 重定义变量不会报错 | 会报错 | 会报错 |
| 声明变量 | 声明变量 | 声明一个常量 |
| 存在变量提升 | 不存在变量提升 | 不存在变量提升 |
| 声明之后随时赋值 | 声明之后随时赋值 | 声明之后立即赋值 |
2、const 定义常量可不可以修改?
const 定义基础类型是不可以修改的
const 定义引用类型是可以修改引用类型里面的值
3、如果我想 const 定义引用类型也不能改变它的值该怎么做?
Object.freeze- 代理(
proxy/Object.defineProperty) - 修改对象的
configurable、writable属性
七、问一下关于数组的问题吧
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. 改变原数组的方法
copyWithin()fill()pop()push()reverse()shift()sort()splice()
4、some 和 every 有什么区别?
从中文含义能看出来,some 是某些,every 是每一个,它们都返回一个 Boolean 值
5、数组里面有10万条数据,取第一个元素和第10万个元素哪个用时长?
用时基本上一样,因为 js 里面没有数组类型,数组其实也是一个对象,key 和 value。
6、数组去重你有几种方法?
set()实现- 双重遍历循环实现
- 空
Object实现和单层遍历实现 - 利用数组内置方法加单层遍历去重(
indexOf、includes)实现 Map和单层遍历实现- 数组
filter方法实现
7、for 循环和 forEach 的性能哪个更好一点?
for 循环的性能更好
for循环没有任何额外的函数调用栈和上下文;forEach不是普通的for循环的语法糖,还有诸多参数和上下文需要在执行的时候考虑进来,这里可能拖慢性能
8、sort 排序是按照什么方式来排序的?
默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16 代码单元值序列时构建的
9、多维数组转为一维数组
reduce递归实现join和split实现- 递归遍历
flat方法toString和split实现- 广度优先遍历/深度优先遍历
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 in 和 for of 有什么区别?
| 比较 | for in | for of |
|---|---|---|
| 不同点 | 可以遍历普通对象 遍历出数组的原型对象 可以遍历出数组自身属性 遍历出来的值是 key 不可以遍历 map/set 不可以迭代 generators IE 支持 | 不能遍历普通对象 不会遍历出原型对象 不会遍历自身属性 遍历出来的值是 value 可以遍历 map/set 可以迭代 generators IE 不支持 |
| 相同点 | 可以遍历数组 可以 break 中断遍历 | 可以遍历数组 可以 break 中断遍历 |
九、promise 相关面试问题
1、如何实现一个 sleep 函数(延迟函数)
通过 promise 和 setTimeout 来简单实现
/**
* 延迟函数
* @param {Number} time 时间
*/
function sleep (time = 1500) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(true)
}, time)
})
}
2、那说下 promise 构造函数、then 方法、catch 方法、finally 方法哪个异步哪个同步?
promise 构造函数是同步执行的,then 、catch 和 finally 方法是异步执行的。
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 都已经 fulfilled 或 rejected 后的 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 包装一下。
- 更好的错误处理
- 更好的互操作性
- 易于浏览
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))