求职无忧之 JavaScript 每日 3 道 面试题
请自行思考整理相应的面试题,使用精炼的语言作答填写答案,每天早上 8:20-9:20 认真背记对应的面试题
01、什么是深浅拷贝?如何实现深拷贝?
浅拷贝:
只能克隆最外层数据,内层数据还会共享同一个数据地址
1.可以通过遍历,将旧数组赋值给新数组 var brr = {} for(var key in arr){ brr[key] = arr[key] } 2.可以利用展开运算:var brr= [...arr] 3.利用object.assign(新数组,旧数组) 4.利用数组方法slice,将旧数组从头到尾截取出来赋值给新数组 5.利用数组方法concat,将旧数组与空气合并,赋值给新数组
深拷贝:
外层数据和内层数据完全不共享地址。
1、利用json数据转换:
var 新数据 = JSON.parse(JSON.stringify(原数据))
2、递归
function deepClone(date){ // 判断date是否是一个对象,如果是,则生成一个对象newDate if(Object.prototype.toString.call(date)==='[Object Object]'){ var newDate = {} }else if(Object.prototype.toString.call(date)==='[Object Array]'){ // 判断date是否是一个数组,如果是则生成一个数组newDate var newDate = [] }else{ // 如果date是普通类型,则直接返回date return date } // 当date满足上面两个引用类型之一,则遍历date for(var key in date){ // 如果date中遍历的值是对象或者数组, //则将刚才生成的对象或者数组的值放到函数开头递归一次, //直到不满足为止,即直到为普通类型为止 if(Object.prototype.toString.call(date[key])=== [Object Object] || Object.prototype.toString(date[key])===[Object Array]){ newDate[key] = deepClone(date[key]) }else{ // 当为普通类型时,将date中的值赋值给上面生成的 //newDate对象或者数组中 newDate[key] = date[key] } } // 返回上面生成的newDate对象或者数组 return newDate }
02、谈谈数组排序都有哪些方式?
数组排序有4种方法:
1. 冒泡排序 ------ 将数组中每相邻的两个元素进行大小比较,排列顺序 2. 选择排序 ------ 每一轮都找到最大或最小值,排在前面, 在剩下的数字中找到最大或最小值排在第二位 3. 快速排序 ------ 第一步:选择第一个数字分离出来为基数 第二步:然后将序列中大于基数的放在基数右边, 小于基数的放在基数的左边 第三步:然后对基数的左边和右边两个序列重复第二步和第三步
03、谈谈你对原型和原型链的理解?
原型就是一个为对象实例定义了一些公共属性和公共方法的对象模板 对象之间的继承关系通过构造函数的prototype指向父类对象, 直到指向Object对象为止的指向链条。 原型链是原型对象创建过程的历史记录
04、谈谈你对闭包的理解?闭包有什么优缺点?
闭包是:函数嵌套函数,函数内部可以引用外部的参数和变量, 参数和变量不会被垃圾回收机制回收 闭包的缺点有:闭包的缺点就是常驻内存,会增大内存使用量, 使用不当很容易造成内存泄露 。 使用闭包的好处: 1. 可以让一个变量长期驻扎在内存中 2. 避免全局变量的污染 3. 私有成员的存在
05、谈谈改变 this 指向都有哪些方法?
改变this的3种方法:
call方法和apply方法的作用: 都是调用函数和改变this指向 不同的是,call方法调用函数,给函数传入实参时,是从call的第二个参数开始传递 ,而apply是将所有的实参组成一个数组作为第二个参数传递。 bind方法: 作用: 复制函数,并改变新函数中的this指向 简单的说,就是复制了一个函数,但是它们的this指向和内存地址完全不一样, 而且bind还能指定新函数的this指向的是谁。
06、谈谈你对继承的理解?js 中都有哪些继承方式?
继承其实就是一个类,继承一个类中的某些属性,是类与类之间的关系;就好比子承父业,儿子继承了父亲的东西。 如果B类继承了A类,那么B类中就会有A类属性。
js的继承方式有:
js的继承方式有: - 原型继承 通过修改对象的原型,将子对象的原型变为父对象的原型。 缺点: 1.来自原型对象的所有属性被所有实例共享 2.创建子类实例时,无法向父类构造函数传参 - 借用函数继承 在子构造函数中通过call调用父构造函数并改变其中的this为子构造函数的this 缺点: 1.只能继承父类在构造函数里声明的属性和方法,不能继承父级原型链上的属性和方法 2.影响性能 - 组合继承 原型继承+借用函数继承 缺点: 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了),影响性能 - ES6--class继承 跟组合继承的效果是一样的。 如果子类中有constructor方法的话,必须在constructor中的第一行代码调用super()方法,否则会报错。 (比较完美的继承)
07、谈谈你都了解哪些常见的继承模式?
js的继承方式有:
- 原型继承 通过修改对象的原型,将子对象的原型变为父对象的原型。 缺点: 1.来自原型对象的所有属性被所有实例共享 2.创建子类实例时,无法向父类构造函数传参 - 借用函数继承 在子构造函数中通过call调用父构造函数并改变其中的this为子构造函数的this 缺点: 1.只能继承父类在构造函数里声明的属性和方法,不能继承父级原型链上的属性和方法 2.影响性能 - 组合继承 原型继承+借用函数继承 缺点: 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了),影响性能 - ES6--class继承 跟组合继承的效果是一样的。 如果子类中有constructor方法的话,必须在constructor中的第一行代码调用super()方法,否则会报错。 (比较完美的继承)
08、谈谈什么是垃圾回收机制?都有哪些垃圾回收的方法?
垃圾回收机制
- 垃圾回收可以有效的防止内存泄漏,有效的使用空闲的内存 (内存泄漏是指该内存空使用完毕后未回收,内存泄漏过多会导致内存溢出, 导致应用程序所占内存超出系统限 制,最终被系统杀掉。) - 垃圾回收是动态储存管理技术,会自动释放'‘垃圾'’(不再被程序引用的对象), 按照特定的垃圾收集算法来实 现资源自动回收的功能
垃圾回收的方法
1. 标记清除法 2. 标记整理法(主流比较常用的) 3. 引用计数清除法
拓展:通过垃圾回收可以达到性能优化:
1.避免使用全局变量(全局使用自调用函数) 2.减少判断层级 3.减少数据的读取次数 4.减少循环中的计算 5.事件绑定优化 6.闭包要销毁
09、谈谈你在 js 中都做过哪些性能优化?
js性能优化
1. 合并样式修改 ① 将标签.style.css键 = 值改成标签.style.cssText = 'css键:值;css键:值;' ② 样式通过操作标签的style属性完成 ③ 将多个样式写在类名中,进行类名操作 2. 节点操作 ① 利用文档碎片 ② 将标签隐藏 => 操作 => 显示 ③ 将标签克隆 => 操作 => 替换原标签 3. 尽量减少定时器中对节点的操作 动画时,将定时中获取标签的大小、位置 这些操作都放在定时器外面
10、js 都有哪些数据类型?检测数据类型的方式都有哪些?
基础类型:Number,String,Boolean,undefined,null
引用类型:object,array,function
检测数据类型的方法:
- 可以通typeof检测数据类型方法,但是检测数组和正则的时候返回的都是object, 所以typeof不能判断一个值是否是数组 - instanceof/constructor是检测一个实例是否属于某一个类使用的, 可以用来检测数组和正则(instanceof/constructor是检测原型的。) - Object.prototype.toString.call(value)是在Object原型上的tostring方法, 并且让方法中的this变为value(value就是我们要检测数据类型的值)。 检测的类型比较多,也比较精准。 null和undefined的区别 - null代表定义了并赋值了,只是值为null - undefined代表定义了未赋值
11、obj.toString 方法和 Object.prototype.toString.call(obj) 有什么区别?
`obj.tostring`是`object`的方法,无论是`string`或者是`array`都会重写`tostring`这个方法的 ,而 `object.prototype.tostring() `是调用 `object `对象原型的`tostring`方法。
12、什么是前端 CSRF 、XSS 攻击?如何防范?
XSS攻击:
本质:跨域脚本攻击 原理:跨域问题解决 不需要你做任何的登陆认证,它会通过合法的操作(比如url输入、在评论框中输入) 向你的页面注入脚本 防范: 1、编码;对于用户进入进行编码 2、过滤:移出用户输入和事件相关的属性。 3、校正:使用DOM Parse转换,校正不配对的DOM标签
CSRF攻击:
本质:SRF跨站请求伪造 原理: 1、登陆受信任的网站A,并在本地生成Cookie 2、在不登出A的情况下,访问危险网站B 防范: 1、token验证 2、隐藏令牌:把token隐藏在http请求的head中 3、referer验证:验证页面来源
13、网络请求 Get 和 Post 都有什么区别?
get和post的区别:
1.GET在浏览器回退不会再次请求,POST会再次提交请求 2.GET请求会被浏览器主动缓存,POST不会,要手动设置 3.GET请求参数会被完整保留在浏览器历史记录里,POST中的参数不会 4.GET请求在URL中传送的参数是有长度限制的,而POST没有限制 5.GET参数通过URL传递,POST放在Request body中 6.GET参数暴露在地址栏不安全,POST放在报文内部更安全 7.GET一般用于查询信息,POST一般用于提交某种信息进行某些修改操作 8.GET产生一个TCP数据包;POST产生两个TCP数据包
14、http 和 https 都有哪些区别?
http和https的区别:
1.HTTP 的URL 以http:// 开头,而HTTPS 的URL 以https:// 开头 2.HTTP 是不安全的,而 HTTPS 是安全的 3.HTTP 标准端口是80 ,而 HTTPS 的标准端口是443 4.在OSI 网络模型中,HTTP工作于应用层,而HTTPS 的安全传输机制工作在传输层 5.HTTP 无法加密,而HTTPS 对传输的数据进行加密 6.HTTP无需证书,而HTTPS 需要CA机构wosign的颁发的SSL证书
15、浏览器缓存有哪些 ? 什么时候用强制缓存 ? 什么时候用协商缓存 ?
浏览器缓存:
DNS缓存、CDN缓存。浏览器缓存、页面本地缓存等等
强缓存:
在浏览器第一个请求资源时,服务器端的响应头会附上Expires这个响应字段, 当浏览器在下一次请求这个资源时会根据上次的expires字段是否使用强缓存
协商缓存:
当客户端第二次向服务器请求相同的资源时, 先向服务器发送请求"询问"该请求的文件缓存在ben'd与服 务器相比是否更改,如果更改, 则更新文件,如果没有就从内存/硬盘中读取。协商缓存由 last-modified 和 etag两个字段决定
16、常用的数组 API 有哪些?有哪些操作会改变原始数组?
常用的数组API:
1、开头添加: unshift 2、结尾添加: push 3、开头删除: shift 4、结尾删除: pop 5、数组增删改: splice 6、数组拼接: concat 7、数组排列: sort 8、数组反转: reverse 9、数组指定连接符成字符串: join 10、截取数组: slice 11、查找某个元素第一次出现: indexOf 12、查找某个元素最后一次出现: lastindexOf 13、遍历并返回处理后的数组: map 14、遍历并返回满足条件的数组: fliter 15、数组求和: reduce 16、判断数组是否至少有一个满足条件的元素: some 17、判断数组中是否所有元素都满足条件: every 18、查找数组中的第一个满足指定条件的值: find 19、查找数组中第一个满足指点条件的元素对应的下标: findindex
会改变原数组的API:
1.shift:将第一个元素删除并且返回删除元素,空即为undefined 2.unshift:向数组开头添加元素,并返回新的长度 3.pop:删除最后一个并返回删除的元素 4.push:向数组末尾添加元素,并返回新的长度 5.reverse:颠倒数组顺序 6.sort:对数组排序 7.splice:splice(start,length,item)删,增,替换数组元素,返回被删除数组,无删除则不返回
17、for in 和 for of 有什么区别?如何遍历对象原型链上的属性或者方法?
for in 和 for of 的区别:
for in 遍历的是数组的键,而for of遍历的是数组元素值 for in 总是得到对象的key或数组、字符串的下标
使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问
18、谈谈你对 this 关键字的理解?
1. 一般情况下this是指向window的,如果是严格模式他就指向undefined 2. 我们可以通过call apply bind来修改this的指向 3. new出来的实例对象this是指向这个实例的 4. 在隐士的状态下是指向他调用者
19、什么是函数柯里化?
所谓的柯里化函数 指的是 把接受多个参数的函数变换成接受一个单一参数的函数 并且返回接受余下的 参数而且返回结果的新函数
// 普通的add函数 function add(x, y) { return x + y } // Currying后 function curryingAdd(x) { return function (y) { return x + y } } add(1, 2) // 3 curryingAdd(1)(2) // 3
20、在 js 中 0.1+0.2 等于 0.3 吗?如何解决这个问题?
js中的计算是以二进制方式计算的,0.1,0.2这类浮点数转二进制是无穷的, 转的时候会丢失精度,两个精度不高的数进行计算,得到的结果自然精度也不高 1.将浮点数乘以10的n次,变成整数,计算得到结果后,再将结果除以10的n次。 2.使用toFix方法将强制保留小数点后多少位。
21、常用的字符串操作 API 有哪些?
字符串方法:
两转两查三截取,分割替换去空白,大小转换三包含
1.charCodeAt---根据指定下标获取对应的字符的阿斯克码 2.String.formCharCode---根据指定的阿斯克码得到对应的字符 3.indexOf---查找字符串或字符在大字符串第一次出现的位置 4.lastindexOf---查找字符串或字符在大字符串最后一次出现的位置 5.slice---截取字符串 6.split---将字符串使用指定的分隔符分割为数组 7.replace---使用新字符串将字符串某一部分替换掉 8.trim---去除字符串两端的空白 9.toUpperCase---将字符串中的字母转成大写 10.toLowerCase---将字符串中的字母转成小写 11.substr---截取字符串 12.substring---截取字符串 13.startsWith - 判断字符串是否以某个字符或小字符串开头 14.endsWith - 判断字符串是否以某个字符或小字符串结尾 15.includes - 判断字符串中是否包含某个字符
22、构造函数中 new 关键字都干了什么事情?
1.new 构造函数在内存中创建一个空对象 2.this指向创建的空对象 3.执行构造函数内部的代码 给空对象添加属性和方法 4.返回这个新对象
23、什么是重排(回流)和重绘?如何减少重排和重绘?
什么是重排和重绘:
1.浏览器将获取的HTML文档解析成DOM树。 2.解析css生成CSSOM(CSS Object Model)。 3.将DOM和CSSOM合并为渲染树。 4.布局。 5.将渲染树的各个节点绘制到屏幕上,上色。 6.在浏览器显示。 回流:布局发生了变化,这个时候就需要倒回去重新渲染 重绘:背景色、颜色、字体改变 。重新上色
如何减少重排和重绘:
1.合并样式修改,如使用style的cssText 2.批量操作DOM,如隐藏ul后,给ul添加节点,添加完成后再将ul显示,文档碎片 3.避免多次触发布局,如防抖和节流 4.修改批量设置样式函数
24、谈谈你在工作中常用的 git 命令?如何处理 git 冲突?
常用的git命令:
git status 查看当前状态 git log 查看提交日志 git merge dev 合并dev分支至当前分支 git add . 添加当前目录全部文件至暂存区 git commit -m '测试' 提交,提交信息为测试 git push origin master 推送至远端分支(master为需要推送分支,按实际需要选择) git pull origin master 合并远端分支至本地 (git pull 等于 git fetch + git merge) git pull --rebase origin master rebase方式合并远端分支至本地 git branch 查看当前分支 git branch dev 创建dev分支 (dev可选) git branch -d dev 删除dev分支 git branch -r 查看远程分支
如何处理git冲突:
情景一:在当前分支上,直接修改冲突代码--->add--->commit。 情景二:在本地当前分支上,修改冲突代码--->add--->commit--->push
25、什么是防抖和节流?手写对应代码?
防抖:
防抖是指当一个事件触发的时候, 为防止频繁触发事件, 设置定时器,以达到一种 频繁触发期间不处理, 只有当最后 一次连续触发结束以后才处理
function debounce(cb,wait){ let timer return function(){ clearTimeout(timer) timer = setTimeout(()=>cb(),wait) } }
节流:
节流是指当一个事件触发的时候,为防止事件的连续频繁触发,设置定时器,达到一种一段事件内只触发一次的效果,在 当前事件内不会再次触发,当前事件结束以后,再次触发才有效.
function thro(cb,wait){ let timeOut return function(){ if(timeOut) return timeOut = setTimeout(function(){ cb() clearTimeout(timeOut) timeOut = null } ,wait)} }
26、谈谈什么是事件队列?什么是微任务?什么是宏任务?
js引擎是单线程的,所有的任务都在主线程上执行,形成一个执行栈。 所有都在这个线程上排队,这条队就叫事件队列。 异步任务,⼜可以细分为宏任务和微任务。 微任务排队排在宏任务前面。
27、谈谈你对 Promise 的理解?
在promise构造函数中,提供了两个参数,分别表示执行成功和失败的回调函数, 执行成功调用resolve,失败调用reject即可,具体resolve和reject的执行,分别在then和catch中。 这样可以将回调函数变成链式结构,从而解决了回调地狱的问题。
28、谈谈你对 async await 的理解?
es7提供了async/await来编写异步代码,是回调地狱的终极解决方案。 他可以将异步代码写的和同步代码一样。
29、什么是内存泄漏?如何有效避免内存泄漏?
内存泄漏是:
所谓内存泄漏即该内存空间使用完毕之后未回收
如何避免内存泄漏:
1、规范动态内存的使用,尽量避免不当用法; 2、及时检测到程序中有内存泄漏,并准确的定位到内存泄漏的具体位置(内存泄漏检测组件);
30、什么是回调地狱?如何解决回调地狱的问题?
什么是回调地狱:
在回调函数里面嵌套回调函数,这样的就叫回调地狱, 例如ajax发多个请求的时候,下一次请求要用要上一次请求的结果,这种嵌套的回调函数就是回调地狱。
如何解决回调地狱:
`promise`优化回调地狱 原理就是axios.get() 它相当于let p=axios.get()
也是一个`promise`对象,`promise`对象都有.then方法。
async 和 await 优化回调地狱的原理是async修饰后的函数
它的返回值就是await axios.get() 所以这里可以省略.then
用一个变量直接接收await axios.get()的返回值。再在函数外面接收这个函数的返回值。