胡哥面试视频手录
- 胡哥面试手录
- 1. 如何提高工作效率?
- 2. 面试的时候怎么自我介绍比较好?
- 3. 面试官问:“你还有什么想问的吗?”,这个时候该怎么回答?
- 4. 面试的简历,应该从这五大方面去写:
- 5. 你的近期和长期的职业规划是怎样的?
- 6. 前端工作中该如何与同事相处、沟通?
- 7. 面试官问,入职之后将如何开展工作,应该怎么回答?
- 8. 面试官:对于加班你怎么看?
- 9. 请描述一下,你以前公司的开发流程是怎么样的?
- 10. Web 前端岗位工资的基本构成和发放。
- 11. 公司面临一些问题,我们该何去何从?
- 12. 前端找工作,被公司工资压价怎么办?
- 13. async 和 await 的区别
- 14. 打包之后 dist 目录过大,该如何解决
- 15. 接口安全该如何回答
- 16. keep-alive
- 17. vuex 如何回答?
- 18. 防抖节流
- 19. 时间复杂度和空间复杂度(非胡哥)
- 20. webpack的工作原理(打包原理)
- 21. 数组扁平化算法
- 22. setTimeout时间为0_,程序发生了什么?
- 23. setTimeout时间误差的解决办法
- 24. js面向对象的相关总结
- 25. 高阶函数
- 26. 浏览器引擎Chrome(v8)
- 27. 浏览器是如何渲染页面的
- 28. js程序的执行顺序是怎样的?
- 29. vue-loader的作用
- 30. node可以通过哪些方法可以防止程序奔溃
- 31. http 和 https的区别
- 32. XSS攻击是什么?如何避免?
- 33. csrf 攻击(非胡哥)
- 34. css hack
- 35. src和href的区别
- 36. link和@import的区别?(非胡哥)
- 37. 胡哥快排
- 38. 边界塌陷(外边距重合)
- 未完待续。。。
胡哥面试手录
1. 如何提高工作效率?
我是做前端工作的,我会把工作分为三个部分。
比如说:项目前、项目中期和项目后期。
- 在项目前期,我们作为前端一定要和 UI 和产品经理进行深度的沟通,争取把每一个需求都弄明白。这样做的好处是避免了咱们后边分工。分工其实是最大的成本的浪费。
- 项目中期,在项目开始的时候,不仅仅是个人编写的现有的代码,我们需要把以前的工具包、插件、组件等等跟工作相关的一系列常用的东西都收集好,这样在工作中效率就会提高很多。在工作中,先做重要的工作,再做次要的工作,先实现功能,再进行优化,可以避免期限到了功能还没有实现的局面。
- 在项目后期,最重要的是项目的维护,以及项目 bug 的修改。在这个过程当中,如果咱们前期和中期弄得不错,沟通得好、解决得好,那么其实到了项目后期,bug 的出现会明显的降低。即使产生了,解决速度也会变快。后期一定要和后台人员进行充分的沟通,把项目争取用最短的时间部署到咱们的服务器上。
这样的话,我们在前、中、后期三个节点上面的效率就会得到最大的提升。
2. 面试的时候怎么自我介绍比较好?
注:你做自我介绍的时候,兴趣爱好、学历啥的简历上都有,不用拿来介绍。
我叫xxx,我掌握的技能有哪些?如:H5、C3、js、element-plus、node、vue等等。
在工作中我的打包工具用的是什么?如:webpack、vite。
我的版本控制工具(分布式)用的是什么?如:git
说完这些之后,最好加一些,在以前的工作当中,我们前端经常遇到的是什么问题?比如说接口安全的问题、常见的一些攻击的问题、适配的问题等等。
3. 面试官问:“你还有什么想问的吗?”,这个时候该怎么回答?
在这个时候,对于成熟的前端人员来说的话,想问的相对来说多一些:
- 咱们公司除了工资以外,还有没有什么补助?
- 公司节假日是怎么安排的?
- 考勤是怎么计算的?
- 五险一金是怎么来计算的?
- 咱们公司大概有多少技术部的同事在一起工作?
4. 面试的简历,应该从这五大方面去写:
-
个人信息:以简洁明了为主。姓名、学历、毕业院校、电话、邮箱,不要超过6项。
-
专业技能:以充实为主。把会的技能多写上一些。
-
过往的经历:过往的公司和时间要搭配,不要有空窗。
-
项目介绍:一到两年(包装2-3个项目)、三年(3-4个项目)。你工作3年就只有这3个项目吗?回答:我放到简历上的项目,是我认为比较好的,其实其他的一些小的项目,有一些雷同的地方,我没有往上写,所以从中挑选了基本较好的项目。
注意点:
- 一定要有前缀,比如说:京东商城项目。
- 针对人群,客户群是什么?
- 开发项目的周期?(四个月?五个月?)
- 项目中用到的技术栈有哪些?(vue?三个项目可以都用vue,不是三个项目一定要选择三个不同的技术栈)
- 写完以上四点内容之后,底下要写150字-200字的项目总述。
- 最后,再把你项目中的各个模块,或者你做了哪些模块,你的岗位职责是什么什么样的,你做的模块实现了什么功能?等等这些东西你要写进去。
-
自我评价:本人比较善于沟通,团队协作能力比较强,能够完成领导交待前端相关的工作任务。
5. 你的近期和长期的职业规划是怎样的?
注意:聊的时候,一定不要跟人家谈年限,最近两年要干什么。。。
回答:我作为一个前端来说,我近期的职业规划是把咱们公司的项目做好,另外呢,学无止境,把我的技术得以提升。多学一些组件、插件,多学一到两款框架。
对于中长期的规划来说的话,我打算呢将来我再研究一门后台语言,比如说像Java、PHP、Python这些我研究一门,争取研究一门之后,可以更加方便咱们前端跟后端去沟通,为咱们公司更好的去服务。另外呢,我也相信,咱们公司有合理的晋升机制和制度。所以呢,如果将来公司有这样的机会,这种机会放在我跟前,我也是会毛遂自荐,服务好公司。
这就是我近期、中期和长期的一个职业规划。
6. 前端工作中该如何与同事相处、沟通?
- 如果工作中遇到问题了,需要和 UI、后台人员去沟通,那么注意了,如果人间帮助你解决了问题了,那你无论如何下班要请人家吃顿饭,而且马上就得兑现,俗话说:“报恩不隔夜”。
- 如果是在平时,UI 那小伙子跟我关系不错,后台某个小帅哥跟你关系也不错,平时怎么和人家沟通呢?尽量组织或者参与一些这样同事之间的社交活动,以加深同事之间的友谊。
7. 面试官问,入职之后将如何开展工作,应该怎么回答?
-
如果公司录用我了,我将会带着我以前的技术和经验,把这些东西带到咱们的项目中,让咱们的项目更好,更人性化。
-
我会快速的掌握咱们现在所写的项目。包括它的路由是怎么架构的,接口的请求是怎么来处理的,还有它里面的一些难点重点的技术,我以前有没有处理过。
这些都是我们前端人员在刚入职的时候要做的事情。以避免后边在开展工作中遇到这些问题,再去处理就比较难处理了。 -
要考虑我是不是需要后台或者其他的技术人员去配合,这个也要有一个正确的评估。为啥要评估这个呢?如果问题到了跟前再去跟后台沟通,这样的话,很可能会打乱后台的工作计划。
进入公司以后,我打算基于以上三个方面开展工作。
8. 面试官:对于加班你怎么看?
注意点:
- 咱们互联网 IT 公司加班是一种常态,包括有些公司执行的996或者大小周。
- 咱们国家在 IT 行业,一般来说所有人加班基本上都是免费的。可能在一些少数的一些大公司中加班是有加班费的。
回答:
我可以接受加班,不管是自己的原因还是其他的一些什么原因,都是可以免费接受的。
9. 请描述一下,你以前公司的开发流程是怎么样的?
在我们公司以前技术部分这么几个角色:
产品经理、UI、前端人员、后台(后台是一个统称:Java、PHP、Android、iOS都是在写后台的业务逻辑),以上这些角色去进行项目开发。
前端在项目中的地位和工作流程:
产品经理: 当一个项目进来,最先跟这个项目客户去接触的一般是产品经理,产品经理的作用是:和客户进行聊天,聊天的过程中,他会做一个产品需求文档(说白了,就是客户想要什么?),在这里面有一个重要的职责,有时候客户很盲目,他不知道自己的需求到底是什么(比如说他想做一个卖菜的网站,他可能不知道什么是APP,但是他们会使用),这个时候产品经理要做的就是正确引导客户需求。然后,产品经理需要把这些需求跟UI、前端等技术人员去讲明白。
UI: 出了需求文档之后,产品经理会把文档给到UI、前端、后台各一份。UI 把需求文档做成原型图(黑白的,像黑白照片)和设计图,UI在整个工期里面大概要占到三分之一。当设计稿出来后,同时交给前端和后台,因为基本上前端和后端干活是并行的。
前端: 开始根据页面去制作各种各样的效果。或者是DIA(diagram 图表)
或者是小程序。在这个过程中后台也在干活。
后台: 后台就要开发它对应的数据管理部分。除此之外,后台还需要去做 API 接口的东西,这个接口是为前端去开发的。
等到前端的页面布局完了,后台的接口也写完了,剩下的就是前端去渲染数据,调用接口里面相对应的数据就可以了。这些完了之后,就是一个测试阶段。
测试阶段: 测试阶段是 UI、前端、后台都需要参与的,如果有专业的测试,他们的工作量就稍微会少一点。都参与可以保证出现问题可以及时得到处理。
部署: 测试完成之后,再把这个项目部署到客户的服务器上去。
基本上整个工作流程就是这样子的。
10. Web 前端岗位工资的基本构成和发放。
如:10k为例,我上家公司是这样发放的:
-
基本工资:5000
-
岗位工资:如前端 3000
-
绩效工资:2000左右(总工资的15%-20% )
绩效工资是一个考核标准,干得好才有,你负责的部分出了问题会扣一部分或者扣除全部。
-
补贴补助:1000以内(话补、餐补、车补等等)。
-
社保公积金:一般情况下公积金+社保占 10%左右。
-
扣税:5000以上需要交税
真正到手的大概是九千左右的一个范围
11. 公司面临一些问题,我们该何去何从?
-
如果这个时候我走了,我会得到什么,我会失去什么?
我得到了:我得到的肯定是一份新工作,需要吃饭穿衣嘛这个很正常。
我失去了:我会失去老板和领导对我的信任,俗话说:“患难见真情”,如果我跟他们共渡难关,在工作中共同努力克服了困难,挺过这一关,那我在这个公司的价值会大大提升。而且我的口碑也会好很多。
-
每一个员工应该各司其职,尽职尽责把自己的工作做好,做好之后,才能够让公司踏踏实实,放心去解决问题。当然在自己的事情完成得很好的情况下,在力所能及的范围之内去帮助其他的同事。如果一个团队、一个公司的人都有这样的想法,我觉得老板和领导一定会想尽办法去解决公司的困难。
12. 前端找工作,被公司工资压价怎么办?
注:我说15000,HR压榨成9000
做两手准备:
这个时候跟 HR 去较劲,很大概率是会谈崩的。
- 回复HR:行,咱们公司既然觉得我是值这个价,那肯定是没有问题的,我也相信我进入公司之后,我会用技术去把咱们的项目做得更好,这个时候我相信公司也会给我涨工资的。
- 如果入职了,继续观望市场,如果有更好的机会摆在面前,立马跳槽。
13. async 和 await 的区别
1. 区别
Async:
async 用来定义函数的,异步函数,打印函数名会得到一个promise对象。可以使用函数名称来调用.then方法,如:函数名称.then
async 内部实现,有返回值。其实是内部调用了 promise.resolve()
,然后把它转换成一个promise对象。如果出错的话,调用的是promise.reject()
。它用catch捕获了一下
Await:
await 理论上来说后面可以跟任意的表达式。但是一般是跟promise的表达式
async function add() {
const res = await 123
console.log(res) // 123
return res
}
console.log(add()) // 成功的promise,值是123
await 等待后边的promise对象执行完毕,拿到了promise.resolve()
的返回值之后,在往后继续去执行后边的代码,所以await有一个等待的意思。
await关键字只能写在async的函数里。
await 后边的表达式可能是失败的,await 只能拿到成功的结果,如果失败会向浏览器控制台抛出错误。所以建议把await的代码放到try…catch语句中捕获错误会比较好。
async function test() {
const res = await Promise.reject(123)
return res
}
console.log(test()) // Uncaught (in promise) 123
async function test() {
try {
const res = await Promise.reject(123)
} catch (error) {
console.log('error', error) // 2. error 123
}
}
console.log(test())
console.log(111) // 1. 111
2. 优点
- async和await属于ES2016(ES7)的语法。
- async和await是异步执行的,但是写代码的时候是同步的
- async和await方式更容易让开发人员维护
3. 补充
promise 和 async await 区别:
- promise 属于es6的语法,promise是链式调用的
- promise中包含了catch,而async和await是在函数内自己定义catch
- promise 提供方法要多一些。如:all, race, any, finally(原型方法), then(原型方法), catch(原型方法)
14. 打包之后 dist 目录过大,该如何解决
命令:npm run build
需要在vue.config.js
中配置 entry, output
处理方案:
-
打包后,会在dist下生成.map文件,那么这个文件其实是不必要的
解决:在vue.config.js中配置文件中:
productionSourceMap: false
。这样就不会生成.map文件了。 -
使用组件和路由懒加载
组件和路由懒加载是按需加载的,这样会降低一些文件的大小。
-
对于文件和图片,最好压缩一下。可以使用这个组件(此组件需要安装):compression-webpack-plugin
-
导入组件:compression-webpack-plugin
-
配置:
- 最小化代码配置 minisize(true)
- 分割代码 splitChunks
- 超过限定值的文件进行压缩 threshold: 文件大小(字节为单位)
-
15. 接口安全该如何回答
安全问题是一个很宽泛的问题,如:xss和csrf攻击等等。
这里主要谈论的是接口的安全问题。
接口安全:防止传输过程中,数据被抓包。我们会在数据传输的时候(前台传给后台,后台传给前台)进行加密和解密。
常用的加密算法:
aes rsa SHA1 SHA256 md5 … 有很多的算法
胡哥用过的有 aes rsa
aes加密工具:crypto.js 对称加密
aes加密工具的使用:
-
安装:npm i crypto-js –save -dev
-
在utils工具文件夹下创建crypto.js文件
-
crypto.js中写入
import cryptojs from 'crypto-js' export default { encrypto(str) { // 加密 // 生成秘钥 // 秘钥是前端和后端都知道的,但是网络上是不知道的,所以他们截取去了也没有用。比如说我们前端生成秘钥是11111,那么后端也要用11111去解密 let key = crytojs.enc.utf8.parse('11111') // 生成偏移量 --- 一般都是传进来的字符串 // 生成偏移量作用:如果字符串内容长度不够,会自动补齐 let srcs = cryptojs.enc.utf8.parse(str) // 加密 --- 参数:偏移量,秘钥,配置加密的参数 let encrypted = cryptojs.AES.encrypt(str,key,{ mode: cryptojs.mode.ECB, // 加密模式 // ECB 表示等量加密,就是说长度跟你的str相同 padding: crytojs.pad.pkcs7 // 加密、解密的标准 }) return encrypted.toString() }, decrypto(str) { // 解密 let key = crytojs.enc.utf8.parse('11111') // 解密不需要偏移量 // 加密 --- 参数:偏移量,秘钥,配置加密的参数 let decrypted = cryptojs.AES.decrypt(str,key,{ mode: cryptojs.mode.ECB, // 加密模式 // ECB 表示等量加密,就是说长度跟你的str相同 padding: crytojs.pad.pkcs7 // 加密、解密的标准 }) return decrypted.toString() } }
注意:
- 前端不论加密还是解密都是
padding: crytojs.pad.pkcs7
,而后端是padding: crytojs.pad.pkcs5
- 秘钥长度是8的整数倍(8 的倍数)
- 秘钥前后端统一
- 详细使用步骤看这个文档,步骤不多
- 前端不论加密还是解密都是
rsa加密工具:jsencrypt 算法
jsencrypt 算法是一种非对称加密算法,它得生成一对秘钥,即公钥和私钥。前后端一样的,加密的时候使用公钥,解密的时候使用私钥。
长度可以能是1024也可能是2048。已知破解的密码长度为:768位。
rsa加密工具的使用:
-
安装:npm i jsencrypt
-
在utils工具文件夹下创建jsencrypt.js文件
-
jsencrypt.js文件中写入:
这里是生成公钥 - 加密
function enjsencrypt(str) { var jsObj = new jsencrypt() var pubkey = "" jsObj.setPublicKey(pubkey) return jsObj.encrypt(str) }
这里是生成私钥 - 解密
function denjsencrypt(str) { var jsObj = new jsencrypt() var prikey = "" jsObj.setPublicKey(prikey) return jsObj.dencrypt(str) }
对称加密与非对称加密的区别是什么
- 对称加密中加密和解密使用的秘钥是同一个;非对称加密中采用两个密钥,一般使用公钥进行加密,私钥进行解密。
- 对称加密解密的速度比较快,非对称加密和解密花费的时间长、速度相对较慢。
- 对称加密的安全性相对较低,非对称加密的安全性较高。
16. keep-alive
keep-alive 组件缓存,刷新的时候保持状态。
keep-alive 缓存组件,避免组件内的数据重复渲染,直接可以在页面中调用。
注意看描述:我有一个导航栏,一级导航栏新闻、房产两个。二级导航栏,新闻下有体育、财经。我给整个一级导航栏新闻做了组件缓存。如:<keep-alive includes="News"></keep-alive>
。
问题:我先点击新闻,然后点击体育,我再点击房产。当我点击新闻的时候,我希望它能回到上一次的状态体育。但是它不会。直接停留到新闻页面了。
需求:点击新闻–》点击体育–》点击社会–》回到新闻下的体育页面
解决:
当点击新闻的时候,有两个方法会触发:activated 和 deactivated
在新闻组件中写:
let url;
beforeRouteLeave() {
url = this.$route.path
}
activated() {
this.$router.push(url)
}
17. vuex 如何回答?
vuex存放数据的状态
需要安装:npm i vuex
state 公共数据 组件中this.$store.state.变量名
mutations 同步方法 更新state中的数据
actions 异步方法,组件通过this.$store.dispatch
(‘方法名’, 参数)
getters 读取state中的数据
commit 状态提交,对mutation进行提交
vuex页面刷新,数据会重置。这里涉及到持久化的问题
解决:
-
把vuex中的数据放到localStorage中
-
插件vuex-persistedstate 这个插件需要安装,然后在vuex中添加配置。
plugins: [createPersistedState({ storage: window.sessionStorage })]
persistedstate 本质是使用了localStorage
18. 防抖节流
返回顶部:到达一定距离才会出现。
监听浏览器的滚动时间,返回当前滚动条与顶部的距离。
<div>里面的内容足够多,让其有滚动条出现</div>
<script>
function showTop() {
// 兼容获取滚动条位置
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop
console.log('滚动条位置', scrollTop)
}
window.onscroll = showTop
<script>
问题:滚动条滚动时,方法触发太频繁
-
防抖
第一次触发事件,不要立即执行,给出一个事件间隔,比如200ms
<= 200ms 大于等于200毫秒,没有触发,那就执行函数
<= 200ms 大于等于200毫秒,再次触发滚动,当前的计时取消,重新计时
思路:如果短时间内,大量执行事件,那就执行一次
实现:setTimeout + 闭包
<div>里面的内容足够多,让其有滚动条出现</div> <script> function showTop() { // 兼容获取滚动条位置 var scrollTop = document.body.scrollTop || document.documentElement.scrollTop console.log('滚动条位置', scrollTop) } function debounce(fnName, delay) { // 实现间隔调用showTop let timer = null return function() { // 1000毫秒以内,这个timer存在了,那么就清除timer if(timer) { clearTimeout(timer) } // 1000毫秒以内,这个timer不存在。setTimeout 返回的是一个ID号,从1开始 timer = setTimeout(fnName, delay) // setTimeout(showTop, 1000) } } window.onscroll = debounce(showTop, 1000) </script>
-
节流
个人感觉这里用节流会更好(沛华的代码)
原理:在一定时间内只执行一次,降低触发频率
实现:闭包
<div>里面的内容足够多,让其有滚动条出现</div> <script> function showTop() { // 兼容获取滚动条位置 var scrollTop = document.body.scrollTop || document.documentElement.scrollTop console.log('滚动条位置', scrollTop) } function throttle(fnName, delay) { // 实现间隔调用showTop let lastTime = 0 return function() { // 获取当前时间 let nowTime = Date.now() // 如果当前时间 - 上一次的时间 < 传进来的时间 // 如:100 - 0 < 1000 吗?如果小于1000,那就直接return了 if(nowTime - lastTime < time) { return } // 大于传进来的时间就会执行下面的代码 fnName.call(this) lastTime = nowTime } } window.onscroll = throttle(showTop, 1000) <script>
19. 时间复杂度和空间复杂度(非胡哥)
定义:
- 时间复杂度:指一个算法从开始执行到结束所消耗的时间。
- 空间复杂度:指对一个算法在运行过程中临时占用存储空间大小的一个量度。
20. webpack的工作原理(打包原理)
-
从入口文件开始出发,根据入口文件的依赖关系找到所有的模块
-
调用各种loader进行处理,如果遇到处理不了的调用babel进行语法转换。(babel-loader core preset)
-
如果一些功能性的,如压缩等这个时候就要调用plugins中相应的插件来进行处理
-
编辑阶段 —》根据出口的路径,打包多个chunk,如果把css和html等都摘出去了,那么就会打包多个chunk,最后统一生成一个bundle
-
loader plugin babel 的区别
- loader用于处理各种类型文件的
- plugin是用于loader处理不了的,一些功能性的插件
- babel 是一个 JavaScript 编译器。将es6+的语法转换成浏览器所认识的js代码。
babel
是一个独立的工具,可以不在webpack中使用。
-
工程化:
狭义上的理解: 将开发阶段的代码发布到生产环境,包含:构建,分支管理,自动化测试,部署
广义上理解: 前端工程化应该包含从编码开始到发布,运行和维护阶段
webpack的核心概念(天禹+熊键)
-
什么是 webpack(熊键)
- Webpack 是一个模块打包器(bundler)。
- 在 Webpack 看来, 前端的所有资源文件(js/json/css/img/less/…)都会作为模块处理。
- 它将根据模块的依赖关系进行静态分析,生成对应的静态资源。
-
五大“护法”(熊键)
- Entry:入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。
- Output: output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。
- Loader: loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只能解析 JavaScript)。
- Plugins:插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量等。
- Mode:模式,有生产模式 production 和开发模式 development。
-
理解 Loader
- Webpack 本身只能加载 JS/JSON 模块,如果要加载其他类型的文件(模块),就需要使用对应的 loader 进行转换/加载。
- Loader 本身也是运行在 node.js 环境中的 JavaScript 模块。
- 它本身是一个函数,接受源文件作为参数,返回转换的结果。
- loader 一般以 xxx-loader 的方式命名,xxx 代表了这个 loader 要做的转换功能,比如 json-loader。
loader常处理的文件类型(天禹)
- 【file-loader】:提取源代码图片资源,到指定位置,可修改文件名等操作。
- 【url-loader】:与file-loader功能几乎一致,优势是可以对图片进行动态转换base64编码(控制limit属性值可以控制阈值)。
备注:上述两个loader中url-loader应用比file-loader广泛。 - 【eslint-loader】:对项目中的js语法进行检查。
- 【babel-loader】:将es6语法转换为es5语法
备注1:直接使用只能处理简单的语法,Promise等无法处理。
备注2:借助polyfill完成高级es6语法的转换,缺点:所有都转换,无法按需转换,生成的js体积大。
备注3:使用core-js配合polyfill完成按需转换。
常用的 loader 有(熊键):
- eslint-loader:审查代码是否存在语法错误。
- babel-loader:将 es6 以上的语法编译成 es5 及以下的语法。
- style-loader:是将 css-loader 打包好的 css 代码以
<style>
标签的形式插入到 html 文件中。 - css-loader:用于解释 @import 和 url(),并通过 import 后进行解析,通常和 style-loader 结合使用。
- less-loader:将 less 语法编译成 css 语法。
问题:
eslint-loader 配置:“0” “1” "2"代表什么?
- eslint 要想工作,必须定义配置文件,配置文件有两种写法
.eslintrc.*
- package.json 文件中
eslintConfig
- 我们以第一种为例,项目根目录新建
.eslintrc.js
-
plugin常用插件
-
【extract-text-webpack-plugin】:用于提取项目中的css, 最终合并为一个单独的文件。
备注:上述插件需要配合:css-loader、less-loader两个loader使用,css-loader、less-loader处理之后,交给extract-text-webpack-plugin处理。
-
【html-webpack-plugin】:自动创建html文件,且自动引入外部资源。配置项如下:
-
title:"webpack",
filename:"index.html",
template:"./src/index.html"
//用于压缩html
minify: {
removeComments: true, //移除注释
collapseWhitespace: true
} //移除换行
-
【clean-webpack-plugin】:清空webpack的输出目录,防止其他文件“乱入”。
-
【MiniCssExtractPlugin 】:用于提取css为单独文件
21. 数组扁平化算法
如:let arr = [1, [2], [3], [4]]
多维数组转成一维数组,递归:逐层遍历数组,数组的每个元素取出,拼接成新数组
// 数组扁平化
let arr = [1, [2, [3, [4]]]]
// 方法一:使用es6的flat()
// console.log(arr.flat(Infinity))
// 方法二:使用递归
function flat(arr) {
let result = []
// 判断是否传进来是数组
if (!Array.isArray(arr)) {
// 不是数组,就把数据压入result中
result.push(arr)
} else {
for (let i = 0; i < arr.length; i++) {
// console.log(arr[i])
// 第一次进来这样写:result = result.concat(arr[0])
// 第二次进来,又是一个数组,那又得调用flat(),重新从上往下执行函数:result = result.concat(arr[1])
result = result.concat(flat(arr[i]))
}
}
return result
}
console.log(flat(arr))
22. setTimeout时间为0_,程序发生了什么?
// setTimeout(fun, 时间)
setTimeout(() => {
console.log(11)
}, 0)
console.log(22)
分析:
- 代码从上往下执行,当执行到setTimeout的时候,setTimeout是同步代码,setTimeout里面的回调是异步代码。
- 此时会把setTimeout里面的回到函数放入到计时器管理模块中。
- 同步代码执行完成后,当过了0秒(马上),会把当前的回调放入到宏队列中,如果微队列的代码执行完成后,会执行宏队列中的代码,然后依次执行宏队列中的定时器回调。
事件轮询中有个重要模块:郎金杰
- 事件管理模块
- 定时器管理模块。
- dom 时间管理模块。
- ajax 管理模块。
- MutationsObserver 管理模块。
- 回调对列:
- 宏任务:
- setTimeout,setInterval,ajax,dom事件监听等。
- 微任务
- promise中的.then函数,async/await,mutationsobserver等。
- 宏任务:
23. setTimeout时间误差的解决办法
setTimeout(fun, 1000) 间隔1000毫秒执行
代码的执行:
-
同步代码执行时间:会先等待同步代码执行完成,开始执行异步代码。
-
异步代码执行时间:在执行异步代码的时候,会有等待的时间,因为要等前面队列中的代码都执行完成之后,才会执行这个setTimeout中的回调。
-
响应时间:比如你在美国访问了京东setTimeout的倒计时,那么美国看到的京东倒计时一定有问题,响应过去都得3秒至少,setTimeout没法1秒就执行。
-
在现实中setTimeout在执行的时候:等待时间 + 执行时间 + 响应时间。所以在现实中执行基本是大于1000毫秒的。
统计误差时间:
/*
获取时间戳的四种方式
let start = new Date().valueOf()
let start = new Date().getTime()
let start = Date.now()
let start = +new Date()
*/
setInterval(() => {
let n = 0
while (n++ <= 1000000) {}
}, 1000)
// 统计误差
// 开始时间
let start = new Date().getTime()
// 程序执行的秒数
let count = 0
// 间隔时间
let inits = 1000
let timer = setTimeout(fun, inits)
function fun() {
count++
// 误差时间 = 现在的时间 - (开始时间 + count * 1000)
// 如果 误差时间 = 计算出来的时间,那就没有误差。
let errorTime = new Date().getTime() - (start + count * 1000) + 'ms'
console.log(errorTime)
// 解决思路:误差时间累加(当误差达到1000ms之后,不要count++,而是+2)。
}
24. js面向对象的相关总结
面向对象(Object Oriented Programming, OOP),是一种编程思想
es5:
创建对象:
-
function 函数名(){} ==》new 函数名 ==》得到对象(类的实例对象)
-
字面量 {}
-
new Object()
面型对象的三大特征:
-
封装:将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
-
继承:子类可以继承父类的属性和方法
-
多态:同一个方法,在子类和父类中都可以定义,只是内容不同。
由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自 Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat 方法,程序会自动判断出来应该如何执行 eat
OOP的优势:
- 维护方便:采用面向对象思想设计的结构,可读性高,由于继承的存在,即使改变需求,那么维护也只是在局部模块
- 代码质量高
- 模块化:封装可以定义对象的属性和方法的访问级别,通过不同的访问修饰符对外暴露安全的接口,防止内部数据在不安全的情况下被修改
- 易扩展:通过继承,我们可以大幅减少多余的代码,并扩展现有代码的用途
**遍历对象:**for … in
对象转化成字符串:
-
{} ==> string 使用 JSON.stringify()
-
string ==> {} 使用 JSON.parse()
继承:
-
构造函数继承(属性继承)
/* 属性继承:在子类调用父类函数,用call改变this指向 */ function Father(uname, age){ this.uname = uname; this.age = age; console.log('Father-this',this); } // var o = new Father('张三丰', 22); // 这里的this是Father // console.log('o', o); // 继承 function Son(uname, age, score){ // Father(uname, age);这样写的this指向不同,此时Father里面的this指向的是window // Father(uname, age); // 如果调用Father函数,并且能够把Father里面的this指向儿子的实例对象,此时Father中的this指向的是Son Father.call(this, uname, age); this.score = score; } var obj = new Son('erzi', 19, 66); console.log('obj', obj);
-
原型链继承(方法继承)
/* 原型链:Son.prototype -> Father.prototype -> Object.prorotype -> null ES6中的extends关键字实现继承,底层仍然用的是原型 */ // 方法继承 -- 就是原型继承 function Father() { Father.prototype.say = function () { console.log('say方法'); } } // 继承 function Son() { } Son.prototype = new Father(); Son.prototype.eat = function () { console.log('吃饭'); } // 如果继承下来父类的方法,那么要把父类实例对象赋值给子类的原型对象,指回构造函数本身 Son.prototype.constructor = Son; console.log(Son.prototype); console.log(Father.prototype); var obj = new Son(); obj.say(); obj.eat();
-
组合继承
/* 1. 原型继承:继承属性和方法,属性值会重复 2. 借用构造函数继承:继承属性,解决属性值重复的问题,但是父类的方法不会被继承 3. 组合继承:原型继承 + 借用构造函数继承 */ function Person(name, age, sex) { this.name = name; this.age = age; this.sex = sex; }; Person.prototype.sayHi = function () { console.log('啊捏哈赛有'); }; function Student(name, age, sex, score) { // 借用构造函数 Person.call(this, name, age, sex); this.score = score; }; // 改变原型指向 --- 实现继承 Student.prototype = new Person(); // 不传值 Student.prototype.eat = function () { console.log('吃东西'); }; // 如果继承下来父类的方法,那么要把父类实例对象赋值给子类的原型对象,指回构造函数本身 Student.prototype.constructor = Student; var stu = new Student('小黑', 20, '男', 100); console.log(stu.name, stu.age, stu.sex, stu.score); stu.sayHi(); stu.eat(); var stu2 = new Student('小黑2', 20, '女', 1010); console.log(stu2.name, stu2.age, stu2.sex, stu2.score); stu.sayHi(); stu.eat(); // 这样就做到了属性和方法都被继承了
设计模式:
沿用了java中的设计模式,一共有23种。
- 单例模式:一个类只能有一个实例,假如这个实例存在,那么就会沿用当前实例,如果不存在那就创建。
- 工厂模式:批量创建对象。
- 观察者模式:设立一个观察员或者哨兵,观察是否有值更新。通过Object.defineProperty方法来修改其它的属性。
- 订阅模式:发布者通过中间层通知那些订阅者,订阅者收到通知,然后更新相应的数据变化。
es6:
用class作为类名称。主要是js向后台语言靠近。
static 定义静态属性
constructor 构造函数
super 父类构造函数,super必须写在子类的属性前面
extends 继承关键字
25. 高阶函数
定义:指一个函数的参数是函数或者返回值是函数,满足其中一种情况就算是高阶函数。
funA可以接受funB为参数,那么funA就是一个高阶函数。
// add() 就是一个高阶函数
function add(x, y, fun) {
return fun(x) + fun(y)
}
function fun(x) {
return Math.abs(x)
}
console.log(add(-2, 3, fun))
常见的高阶函数:map, reduce, filter, sort
还有一个函数柯里化的问题:
定义:柯里化是把接收多个参数的函数转换成多个只接收一个参数的函数。
作用:参数复用
function add(a, b) {
return a + b;
}
add(1, 2) // 3 普通做法 一次传入两个参数
// 假设有一个 curring 函数可以做到柯里化
function curring(){}
curring(1)(2) // 我们通过这样的方式来接受参数,这样就实现了柯里化
function curring(x) {
return y => x + y // 返回一个函数,然后函数的x+y作为最终的返回值
}
curring(1)(2) // => 3
参数复用:
// 我们平时的写法 --- 非参数复用的写法
function myInfo(inf, name, age) {
return `${inf}:${name}${age}`
}
const myInfo1 = myInfo('个人信息', 'ljc', '19')
console.log(myInfo1); // 个人信息:ljc19
// 函数柯里化的写法 --- 参数复用的写法 复用了inf
function myInfoCurry(inf) {
return (name, age) => {
return `${inf}:${name}${age}`
}
}
let myInfoName = myInfoCurry('个人信息')
const myInfo1 = myInfoName('ljc', '19')
const myInfo2 = myInfoName('ljcc','19')
console.log(myInfo2); // 个人信息:ljcc119
console.log(myInfo1); // 个人信息:ljc19
26. 浏览器引擎Chrome(v8)
v8引擎对内存有限制:
- v8引擎是对浏览器设计的,不太可能大规模使用内存
- v8垃圾回收也限制了内存
nodejs中对v8引擎的内存限制
- 64位的操作系统是1.4G
- 32位操作系统是0.7G
拿官方的一个案例为例,官方是以1.5G内存来进行参考的,1.5G如果要进行垃圾回收,大概是时间是50ms。
如果node要进行一些大内存对象的操作,可以使用buffer来缓冲对象的内存。因为buffer不受限制,因为buffer基于的逻辑是c++的,c++的内存是不受v8引擎限制的。
v8 如何来划分内存和垃圾回收的?
分代回收机制,新生代和老生代
新生代 | 老生代 | |
---|---|---|
32位 | 内存大小限制是16M | 内存大小限制是700M |
64位 | 内存大小限制是32M | 内存大小限制是1.4G |
适合 | 周期短的对象(如:操作某个DOM对象)。一般短期对象较多,占用80%以上 | 周期长的对象(如:new Vue() , document, window等,它就一直存在) |
垃圾回收 | 采用了Scavenge算法进行操作的。在算法实现时主要采用Cheney算法。Scavenge的缺点:可用内存只有一半,另一半永远是等待的。Scavenge的优点:只保留活跃的对象,不活跃的就消灭了。就是部分清空。 | 先用Mark-Sweep(标记清除,就是现在不清楚,等下一次进行扫描的时候发现还在,就会被清除掉,内容清除掉后,会留下一个空间),后用Mark-Compact(标记整理,就是把标记清楚后的空间合并起来。) |
27. 浏览器是如何渲染页面的
cssom: Css Object Model 采用css代码,并将每个选择器呈现为树状结构,是对css样式表的对象化表示。同时还提供了api来操作css。
w3c标准中:
cssom 包括两个部分
- Model:描述样式表和规则的模型。如style标签、link标签都定义了css Model
- View:和元素视图相关的api
下面是熊键的笔记
- DNS 查询/解析
- 作用:将域名地址解析为 ip 地址
- 过程:
- 读取 4 个缓存
- 浏览器 DNS 缓存
- 计算机 DNS 缓存
- 路由器 DNS 缓存
- 网络运营商 DNS 缓存
- 1 个递归查询
- 13 个根域名服务器
- 读取 4 个缓存
- TCP 连接:TCP 三次握手
-
第一次握手:客户端发起,我将要发送请求了,你准备一下
-
第二次握手:服务端发起,我知道了,准备好了,放马过来
-
第三次握手:客户端发起,我也知道了,等着
-
为什么要三次握手?
- 目的:确保客户端和服务端收、发能力都是正常的
- 第一次握手:服务端知道客户端发送能力是正常的
- 第二次握手:客户端知道服务端发送和接受能力是正常的
- 第三次握手:服务端知道客户端接受能力是正常的
- 发送请求
-
客户端发送请求给服务器
-
请求报文
- 请求首行
- 请求地址
- 请求方式
- 查询字符串参数
- 请求头
- 携带 cookie(cookie)
- 携带 token(authorization)
- 空行
- 请求主体
- 请求体参数(POST)
- 请求首行
- 接收响应
-
服务端返回响应给客户端
-
响应报文
- 响应首行
- 响应状态码
- 1xx
- 2xx
- 3xx
- 4xx
- 5xx
- 响应状态码
- 响应头
- set-cookie 设置 cookie
- 缓存 cache-control
- 空行
- 响应主体
- 响应数据
- 响应首行
- 渲染页面
- 最开始返回 index.html 页面,浏览器解析 html 文件
- 遇到 DOM 标记,调用 HTML 解析器,将元素解析 DOM 树
- 遇到 css 标记,调用 css 解析器,解析成 CSSOM 树
- style 不阻塞
- link 阻塞(防止闪屏)
- 遇到 js 标记(script 阻塞),调用 js 引擎,解析 js 代码
- 可能会操作 DOM,重新调用 HTML 解析器,将元素解析 DOM 树
- 可能会操作 CSS,重新调用 css 解析器,解析成 CSSOM 树
- 这个过程就会导致页面重排重绘
- 将 DOM 树和 CSSOM 树组合生成 render 树
- 布局 layout
- 渲染 render
- TCP 断开连接:TCP 四次挥手
- 断开 TCP 连接需要 4 次:客户端发送请求断开两次,服务端返回响应断开两次
- 第一次挥手:客户端发起,客户端数据发送完毕了,准备断开了
- 第二次挥手:服务端发起,服务端数据接收完毕了,可以断开了
- 第三次挥手:服务端发起,服务端数据发送完毕了,准备断开了
- 第四次挥手: 客户端发起,客户端数据接收完毕了,可以断开了
28. js程序的执行顺序是怎样的?
外部js文件先加载还是window.onload先加载?
外部js文件与body的位置有关
-
如果js是放在body里,是按照body的先后顺序进行加载的。
-
如果js在body外(一般指head中),是在body加载完毕后,才加载js。
外部js文件不论是在head中还是在body中,都要优先于window.onload = function(){}
执行。如果外部js有定时器等异步代码,就会滞后执行。
29. vue-loader的作用
vue-loader的用法:用来解析和转换vue文件,提取出其中的script、style、template,把他们交给对应的loader去处理。
vue-loader的特性:
- 允许为vue的组件的每个部分去使用其他loader。
- 允许在vue文件中使用自定义模块和自定义的loader。
- 将style和template引用的资源当做模块依赖处理。为每个这样的组件,都模拟出一个scope。
- 在开发过程中允许使用热重载。
热重载:"热重载"不是当你修改文件的时候简单重新加载页面。启用热重载后,当你修改 .vue
文件时,所有该组件的实例会被替换,并且不需要刷新页面。它甚至保持应用程序和被替换组件的当前状态!当你调整模版或者修改样式时,这极大的提高了开发体验。
使用场景:
- 编译template将编译用作vue的template选项,url转换成webpack的模块请求。
- 可以用loader进行预处理(babel scss less等)
- 可以允许热重载
30. node可以通过哪些方法可以防止程序奔溃
node 速度比较快,node是单线程的,不稳定,不适合处理复杂业务。
单线程,产生未处理异常,会捯饬程序奔溃。
问题:
调用了req.params
let http = require('http')
let server = http.creawteServer(function(req, res){
let ok = req.params.ok; // params 是undefined。如果是未定义就会导致程序奔溃
res.writeHead(200,{'content-type': 'text/plain'})
res.end('hello world')
}).listen(8080, '127.0.0.1')
解决方案1:
uncaughtException 来全局捕获Error,捕获之后,可以防止node奔溃
process.on(‘uncaughtException’, function(err){
打印错误
})
解决方案2:加上try-catch
let http = require('http')
let server = http.creawteServer(function(req, res){
// let ok = req.params.ok; // 把这个未定义的代码拿到外面去
try {
handler(req, res)
} catch(e) {
console.log(e)
}
res.writeHead(200,{'content-type': 'text/plain'})
res.end('hello world')
}).listen(8080, '127.0.0.1')
let handler = function(req, res) {
let ok = req.params.ok; // 同样是未定义,会报错
}
31. http 和 https的区别
http:
http(Hyper Text Transfer Protocol) 超文本传输协议,用于在浏览器和服务器之间传递信息的,http协议以明文的形式传输内容的,数据是不加密的。不适合传输敏感信息。比如:身份证、账号、密码等。
https:
https:(Hyper Text Transfer Protocol over Secure Socket Layer) 即基于SSL的HTTP协议,简单地说就是HTTP的安全版。https是在http的基础上加上了SSL协议。SSL是依靠证书来验证服务器的身份的,它对浏览器和服务器之间通信是加密的。
https协议是由SSL+ http构成的,是可加密的,身份认证的网络安全,比http更安全。
区别:
- https协议需要Ca(Certificate Authority 数字认证机构)申请证书,一般情况下免费的证书很少,证书是需要费用的。
- http是明文传输的,https是加密传输的。
- 端口不同,http是80,https是443。
32. XSS攻击是什么?如何避免?
定义:
XSS攻击又叫CSS(Cross Site Script)跨站脚本攻击。恶意攻击者往web页面中插入html代码,用户请求该页面,嵌入其中的该代码将会被执行,从而达到攻击用户的目的。
一般有三种攻击类型:
- 存储型(这个攻击危害很严重)
- 反射型
- DOM型
解决:
- 对用户输入的内容进行过滤,过滤所有的非法字符。
- html转义(html转义是将特殊字符或html标签转换为与之对应的字符),内容太多的话要转义的内容会过多,所以有局限性。
- 对于链接跳转,要进行内容检测。如:
<a href="xxx"/>
- 限制输入长度。
- 数据进行加密。
- http only 只允许http请求的方式。
33. csrf 攻击(非胡哥)
CSRF概念:CSRF跨站点请求伪造(Cross—Site Request Forgery),跟XSS攻击一样,存在巨大的危害性,你可以这样来理解:
攻击者盗用了你的身份,以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,甚至于购买商品、虚拟货币转账等。如下:其中Web A为存在CSRF漏洞的网站,Web B为攻击者构建的恶意网站,User C为Web A网站的合法用户。
解决:
为了防止CSRF攻击,Token要求不能重复,需要含有时间戳信息、签名信息。
如:token = base64(msg)格式化…base64(sha256(“密锁”, msg))
token产生:
- token构成。Token要求不能重复,需要含有时间戳信息、签名信息。
- token加密:当用户向服务提出访问请求时,产生Token再提交给服务器的时候,服务器需要判断token的有效性(是否过期,签名有效)
- token校验
- token解包
- 比对签名
- 判断时间是否过期
34. css hack
条件Hack:用来调整页面的兼容性的。css hack 这个兼容性主要是针对IE来说的,因为IE一开始没有遵循W3C的标准。
// 条件Hack的写法
// if条件共包含6种选择方式:是否、大于、大于或等于、小于、小于或等于、非指定版本
<!--[if <keywords>? IE <version>?]>
HTML代码块
<![endif]-->
兼容性:浏览器品牌不同,在解释css的时候,解释方式不一样,所以样式不兼容的问题。
注意:尽可能减少对CSS Hack的使用。Hack有风险,使用需谨慎
35. src和href的区别
请求资源类型不同
- href是Hypertext Reference的缩写,表示超文本引用。用来建立当前元素和文档之间的链接。常用的有:link、a。
<link href="reset.css" rel=”stylesheet“/>
- src是source的缩写,在请求src资源时会将其指向的资源下载并应用到文档中,常用的有script、img、iframe。
作用结果不同
- href 用于文档和资源之间确立联系。
- src 用于替换当前内容。
浏览器解析方式不同
- 用href,浏览器会识别该文档为css文件,就会并行下载资源并且不会停止对当前文档的处理。
- 用src,会暂停其他资源的下载和处理,知道将该资源加载、编译、执行完毕,将所指向资源应用到当前内容。这也是为什么建议把js脚本放在底部而不是头部的原因。
36. link和@import的区别?(非胡哥)
- link属于html标签,而@import是css提供的。
- 页面被加载时,link会同时被加载,而@import引用的css会等到页面被加载完再加载的。
- 兼容性问题:@import只在IE5以上才能识别,而link是html标签,无兼容性问题。
- 权重问题: @import的权重要高于link。
- DOM操作: DOM可以操作link中的样式,而不可以操作@import中的样式。
37. 胡哥快排
function quickSort(arr) {
if (arr.length < 1) {
return arr
}
// 基准值(得到一个整数) --- 使用Math.floor是为了处理除不尽的情况
let provotIndex = Math.floor(arr.length / 2)
// 求得基准值对应的元素 --- 从基准下标开始截取,截取一个
var provot = arr.splice(provotIndex, 1)[0]
// 遍历数组和基准数进行对比
let left = []
let right = []
for (let i = 0; i < arr.length; i++) {
if (arr[i] < provot) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
// console.log(left, right)
return quickSort(left).concat([provot], quickSort(right))
}
let arr = [2, 3, 4, 5, 3, 2, 1, 5]
const result = quickSort(arr)
console.log(result)
38. 边界塌陷(外边距重合)
同时给兄弟/父子设置上下边距,理论上是两者之和,实际上只有一半,这种现象叫作边界塌陷。
如:给box1的margin-bottom: 20px,给box2的margin-top: 20px; 理论上应该是40的边距,实际上只有20px的边距。
<head>
<style>
.box1 {
width: 100px;
height: 100px;
border: 1px solid red;
margin-bottom: 20px;
}
.box2 {
width: 100px;
height: 100px;
border: 1px solid green;
margin-top: 20px;
}
</style>
</head>
<body>
<div class="box1"></div>
<div class="box2"></div>
</body>
注意:
-
浮动元素和定位元素是不会发生边界塌陷的。
-
这种现象一般发生在块级元素的垂直方向。
解决方案:
-
margin 同为正数或者同为负数的时候,取的是绝对值大的数。
-
如果一正一负的时候,求的是和,如给box1的margin-bottom: 20px,给box2的margin-top: -20px; 那么最终边界值为0。
2022年8月24日10: 17: 30