JavaScript 常见面试题
一、浅拷贝 VS 深拷贝,手写一个深克隆
- 其实深拷贝和浅拷贝都是针对的引用类型,JS中的变量类型分为值类型(基本类型)和引用类型;对值类型进行复制操作会对值进行一份拷贝,而对引用类型赋值,则会进行地址的拷贝,最终两个变量指向同一份数据,此时如果对其中一个进行修改,就会影响到另外一个。
- 根据拷贝的层级不同可以分为浅拷贝和深拷贝,浅拷贝就是只进行一层拷贝,深拷贝就是无限层级拷贝。
- 浅拷贝:实现方式很简单,实现原理就是遍历对象属性。
- slice()
- concat()
- Object.assign(target, …sources)
- ES6 扩展运算符 {…obj} 和 […arr]
- 循坏push
- 深拷贝:实现原理可以就是浅拷贝 + 递归。
- JSON.parse(JSON.stringify(obj));
- 手写实现深拷贝
答案链接
二、call、apply、bind的区别,手写一个bind函数
四、promise的使用方法?常用的API有哪些?
- 概念:Promise是异步编程的一种解决方案,主要用来解决回调地狱的问题,可以有效的减少回调嵌套。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。Promise有三个状态:Pending(等待)、Resolved(完成)、Rejected(失败)。
- 特点:
- 对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 一旦状态改变,就不会再编,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。
- 缺点:
- 无法取消Promise,一旦新建它就会立即执行,无法中途取消。和一般的对象不一样,无需调用。
- 如果不设置回调函数,Promise内部抛出的错误,不会反应到外部。
- 当处于Pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
- API:
-
new Promise 创建实例
-
PromiseObj.then(resolveFn,rejectFn)
- resolveFn:就是Promise对象成功的回调处理函数,指向 resolve
- rejectFn:就是Promise对象失败的回调处理函数
- 除了第一个 then ,以后的所有的 then 都是上一个 then 中的返回结果
- then 接收到结果一共有三种情况:
- 上一个 then 没有返回值,则就是 undefined
- 上一个 then 有普通返回值:数组、数字、字符串、对象。。。
- 上一个 then 又返回了一个新的 Promise 对象,如果是上一个 then 里面返回了一个 Promise 对象,则这个 then 的回调处理函数就指向了上一个 then 中返回的 Promise 对象中的 resolve。
-
Promise.all()
- all 方法需要接收一个数组作为参数数组中存储的多个 Promise 对象实例
-
PromiseObj.catch() 错误回调
-
PromiseObj.resolve() 将一个值,数字,字符串…转换为Promise对象
-
PromiseObj.reject() 错误回调
-
=> .then(null, err=> {
// err
})
=> new Promise((resolve,reject)=>{
reject('出错了')
})
五、ES6的解构赋值
- 概念:就是快速从对象或者数组中取出成员的一个语法方式
- 数组赋值:批量声明变量,批量赋值变量
- 应对批量的参数传递,我们可以使用解构赋值
- 数据解构变量声明时可以用剩余运算符表示变量被赋值的范围: …
- 复杂解构 : [ ]
- 对象赋值:
- 声明变量的时候 : 大括号里面只写变量名:
- 可以解构出一个API里面的某个方法
- 对象的扩展运算符
- … 剩余运算符:[ …a ] 在数组中,把其他内容放在一个变量之中,往变量里面放;
- … 扩展运算符:{ …obj } 是向对象上添加的扩展;
六、map 和 forEach的区别
- map()方法: 创建一个新的数组,其中每一个元素由调用数组中的每一个元素执行提供的函数得来。
- foreEach()方法: 针对每一个元素执行提供的函数。
- 区别:
- 返回结果:
- forEach()方法不会返回执行结果,而是undefined。也就是说,forEach()会修改原来的数组。
- map()方法会得到一个新的数组并返回。
- 执行速度:
forEach()的执行速度 < map()的执行速度 - 使用场景:
- forEach适合于你并不打算改变数据的时候,而只是想用数据做一些事情 – 比如存入数据库或则打印出来。
- map()适用于你要改变数据值的时候。不仅仅在于它更快,而且返回一个新的数组。这样的优点在于你可以使用复合API(composition)(map(), filter(), reduce()等组合使用)。
- 返回结果:
- 总结:
- forEach()可以做到的东西,map()也同样可以。反过来也是如此。
- map()会分配内存空间存储新数组并返回,forEach()不会返回数据。
- forEach()允许callback更改原始数组的元素。map()返回新的数组。
七、如何实现组合继承
八、手写代码实现去除首位空格
- jquery库提供了
$.trim()
方法 - 手写实现 .trim()
function trimStr(str){
return str.replace(/(^\s*)|(\s*$)/g,"");
}
九、手写数组去重并返回重复项
十、浏览器端事件循环(event loop)
- 事件循环(event-loop):主线程从"任务队列"中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。此机制具体如下:主线程会不断从任务队列中按顺序取任务执行,每执行完一个任务都会检查microtask队列是否为空(执行完一个任务的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有microtask。然后再进入下一个循环去任务队列中取下一个任务执行。
- 什么需要:因为JavaScript是单线程的。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。为了协调事件(event),用户交互(user interaction),脚本(script),渲染(rendering),网络(networking)等,用户代理(user agent)必须使用事件循环(event loops)。
十一、JS的基本数据类型,判断方法有哪些?typeof 判断哪个类型会出现问题?
十二、原型链
十三、手写一个柯里化函数
十四、简述ES6的新特性
- 不一样的变量声明:const和let
- 模板字符串
- 箭头函数(Arrow Functions)
- 函数的参数默认值
- Spread / Rest 操作符
- 二进制和八进制字面量
- 对象和数组解构
- 对象超类
- for…of 和 for…in
- ES6中的类 Class