说啥也不能浪费金三银四啊
最近出去面试了几次,想着出去试试这一年的学习成果,结果。。。
html、css相关
- 盒模型
- 标准盒模型:盒子总宽度/高度 = width/height + padding + border + margin。( 即 width/height 只是内容高度,不包含 padding 和 border 值 )
- IE盒子模型:盒子总宽度/高度 = width/height + margin = (内容区宽度/高度 + padding + border) + margin。( 即 width/height 包含了 padding 和 border 值 )
- 元素居中的方式
/* 第一种居中方式 */
.box1 {
position:relative;
}
.box1 .dv {
position:absolute;
left:50%;
top:50%;
margin-top: -75px;
margin-left: -75px;
}
/* 第二种居中方式 */
.box1 {
position:relative;
}
.box1 .dv {
position:absolute;
left:0
right:0
top:0
bottom:0
margin:auto;
}
/* 第三种居中方式 */
.box1 {
display:flex;
justify-content:center;
align-items:center;
}
/* 第四种居中的方式 */
.box {
width:600px;
position:relative;
}
.box .dv {
position:absolute;
left:50%;
top:50%;
tarnsform:tarnslate(-50%,-50%)
}
/* 第五种居中方式 */
.box1 {
display:grid;
place-items:center垂直 center水平
}
- 两列布局,左边固定右边自适应有几种实现方式
- flexbox
- calc函数
js相关
-
什么是闭包,闭包的实际应用,闭包的缺点
- 什么是闭包
- js高程一句话概括,一个使用了另一个函数内部变量的函数
- 由于 javascript 的特性,外层的函数无法访问内部函数的变量;而内部函数可以访问外部函数的变量(即作用域链)
- 函数嵌套,内部函数使用外部函数的变量就会形成闭包的条件
- 闭包的实际应用
- 作用域隔离,避免污染全局作用域
- 模块化,AMD CMD Conmonjs
- 闭包的缺点
- 大量的闭包会造成内存泄漏,如果不使用应及时释放,让内部函数变成垃圾对象,赋值为null,让浏览器回收闭包
- 什么是闭包
-
变量提升,function声明和var提升的优先级
- js在执行的时候,会进行预解析,当前作用域中使用var和function声明的变量和函数提升到作用域的顶端,var声明的变量只提升变量声明,函数整个提升并分配内存空间,所有js在函数声明之前可以使用函数
- 函数的优先级要高于var声明的,因为js在预解析的时候如果解析到有值的变量并不会做处理
-
原型和原型链
- 每个对象都有一个隐式原型(proto)它指向创建该对象构造函数的显示原型(prototype)
- js对象在使用一个属性的时候,会在自己身上找,有就使用,没有会到__proto__上查找,有就使用,因为__proto__也是一个对象,它也有它的隐式原型(proto),这个查找属性的过程就是原型链
- 原型链的顶端是Object的__proto__它指向null,如果查到这都没有找到的话,就返回undefind
-
说一说defer 和 async
- 首先在正常情况下,script 标签是会阻塞 DOM 的解析的,所以我们要尽量不要把script 放到 head 里,要尽量放到 body 的最下方。这样不至于会有白屏的效果;然后是 defer和 async;他们两个是异步加载 js 代码的。
- defer
- 给 script 标签添加 defer 属性,就不会阻塞 dom 的解析了,等 dom 渲染完之后才会运行该 js 代码,如果是多个 script 标签都添加了 defer 属性的话,那么它们是按照顺序执行的(第一个全部执行完毕之后才能执行第二个),defer 的 script 是在 DOMContentLoaded 之前运行的。
- async
- 给 script 添加 async 属性之后,会异步下载 js 代码,等下载完成之后会立即运行js 代码。多个 script 标签同时设置了 async 是没有先后顺序的,谁先加载完谁就先运行。
- 如果 script 标签没有操作任何 dom 信息,且不彼此依赖的话,可以使用 async
-
如何把异步变为同步
- Promise+async/await
- generator生成器 ,yield表达式
-
Promise.all的实现
- 返回一个promise,循环数组中的每个promise,调用then方法获取结果
- 创建一个空数组,使用循环索引的方式为每个promise的结果赋值,可以定义一个变量记录then的次数,如果次数等于传入数组的长度,那么resolve出这个数组
-
为什么promise的then方法要返回一个新的promise而不是返回this
- 因为promise的状态一旦确定就不可改变
-
浅拷贝和深拷贝的区别
- 如果拷贝的对象里的元素只有值,没有引用,那浅拷贝和深拷贝没有差别,都会将原有对象复制一份,产生一个新对象,对新对象里的值进行修改不会影响原有对象,新对象和原对象完全分离开
- 如果拷贝的对象里的元素包含引用(像一个对象里储存着另一个对象,存的就是另一个对象的引用),那浅拷贝和深拷贝是不同的,浅拷贝虽然将原有对象复制一份,但是依然保存的是引用,所以对新对象里的引用里的值进行修改,依然会改变原对象里的值,新对象和原对象完全分离开并没有完全分离开。而深拷贝则不同,它会将原对象里的引用也新创建一个,即新建一个对象,然后放的是新对象的属性,这样就可以将新对象和原对象完全分离开
-
手写深拷贝
function deepClone(target, map=new Map()) {
// 被处理的目标是数组和对象
if(target instanceof Array || (target!==null && typeof target === 'object')) {
// map中存着对应的克隆对象,直接将其返回
let cloneTarget = map.get(target)
if(cloneTarget) {
return cloneTarget // 不要对同一个对象进行多次克隆
}
// 创建克隆对象
cloneTarget = target instanceof Array ? []:{}
// 保存到map中
map.set(target,cloneTarget)
// 循环遍历被克隆对象
for(const key in target) {
if(target.hasOwnProperty(key)) {
// 进行递归拷贝
cloneTarget[key] = deepClone(target[key],map)
}
}
// 返回拷贝对象
return cloneTarget
} else {
// 如果不是对象或数组就直接返回被拷贝对象
return target
}
}
浏览器相关
-
输入url地址后都做了什么
- 浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容。若没有,则跳到下步操作。
- 在发送http请求前,需要域名解析(DNS解析),解析获取相应的IP地址。
- 浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手。
- 握手成功后,浏览器向服务器发送http请求,请求数据包。
- 服务器处理收到的请求,将数据返回至浏览器
- 浏览器收到HTTP响应
- 读取页面内容,浏览器渲染,解析html元素生成dom树,遇到link或style启动css解析器解析样式生成cssom树,遇到script启动js引擎执行js代码,最后根据dom树和cssom树生成渲染树,浏览器根据渲染树绘制页面
-
浏览器缓存
- 缓存分为强缓存和协商缓存
- 强缓存
- 在客户端发起请求之前,先检查强缓存,查看强缓存的cache-control里的max-age,判断数据有没有过期,如果没有直接使用该缓存 ,有些用户可能会在没有过期的时候就点了刷新按钮,这个时候浏览器就回去请求服务端,要想避免这样做,可以在cache-control里面加一个immutable.这样用户再怎么刷新,只要 max-age 没有过期就不会去请求资源。
- public 因为服务端返回数据的过程中可能会经过很多虚拟代理服务器,他们可以进行缓存,使用 public 就是允许它们缓存
- private 不允许代理服务器缓存,允许客户端缓存
- no-cache 不允许强缓存,可以协商缓存
- no-store 不允许缓存
- 协商缓存
- 浏览器加载资源时,没有命中强缓存,这时候就去请求服务器,去请求服务器的时候,会带着两个参数,一个是If-None-Match,也就是响应头中的etag属性,每个文件对应一个etag;另一个参数是If-Modified-Since,也就是响应头中的Last-Modified属性,带着这两个参数去检验缓存是否真的过期,如果没有过期,则服务器会给浏览器返回一个304状态码,表示缓存没有过期,可以使用旧缓存。
- etag的作用
- 有时候编辑了文件,但是没有修改,但是last-modified属性的时间就会改变,导致服务器会重新发送资源,但是etag的出现就完美的避免了这个问题,他是文件的唯一标识
- last-modified和etag各有各自的优点和缺点:
- 每个文件都有一个 etag 和 last-modified 属性,etag 要优先于 last-modified,两个属性各有优缺点,比如 last-modified 很多时候不能感知文件是否改变,但 etag 能;last-modified 仅仅记录了时间点,性能肯定要高于etag,etag 记录的是哈希值
- 缓存位置:
- 内存缓存Memory-Cache
- 离线缓存Service-Worker
- 磁盘缓存Disk-Cache
- 推送缓存Push-Cache
vue2/3相关
-
vue双向绑定
- vue在创建的时候会遍历data中所有的属性,通过Object.defineProperty()来劫持各个属性的setter,getter,在getter中收集依赖也就是一个个watcher(哪些地方使用该属性),在setter中(数据修改时)触发依赖并执行监听的回调。如果属性是对象类型,就递归去为该属性注册
-
vue组件通信的方式
- props+事件
- provide/inject
- eventbus
- mitt
- vuex
-
Vue中的nextTick
- 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
- 想要在Vue生命周期函数中的created()操作DOM可以使用Vue.nextTick()回调函数
- 在数据改变后要执行的操作,而这个操作需要等数据改变后而改变DOM结构的时候才进行操作,需要用到nextTick
-
可以说说vue中的diff算法吗
- diff算法是指对新旧虚拟节点进行对比,并返回一个patch对象,用来存储两个节点不同的地方,最后利用patch记录的消息局部更新DOM
- 步骤
- diff算法是虚拟节点的比较
- 先进行key值的比较
- 先进行同级比较
- 然后再比较是不是一方有儿子,一方没儿子,如果是这样,直接在旧节点中插入或删除儿子即可
- 在比较两方都有儿子的情况,有四种优化的比较(新前与旧前、新后与旧后、旧后与新前、新后与旧前)
- 递归比较子节点
-
vue2于vue3有什么不同,vue3有什么优势
- vue3相比vue2更灵活了,因为vue3使用ts编写的对ts的支持更友好了
- 新增了composition API,可以抽离独立的业务逻辑,易于封装
- vue3使用proxy重写了响应式系统
- 模板不用必须用根节点了
- vue3内部使用了tree-shaking,在打包项目的时候只会把我们使用的功能打包进去,而不是将整个vue源码打包
node、webpack相关
- node事件循环
- 我大致说了nodejs实现循环的六个阶段,具体的请参考blog.csdn.net/local_peopl…
- 讲讲koa洋葱圈模型,手写compose函数
- 中间件自顶向下执行,每次调用next方法,就会暂停当前中间件的执行,去执行下一个中间件,相当于next有调用下一个中间件的权利,执行到最后一个中间件的时候,又会回到上一个中间件并执行next后面的代码,最终回溯到最顶层中间件 参考:juejin.cn/post/684490…
- webpack的Loader和Plugin的区别
- Loader直译为"加载器"。Webpack将一切文件视为模块,但是webpack原生是只能解析js文件,如果想将其他文件也打包的话,就会用到loader。 所以Loader的作用是让webpack拥有了加载和解析非JavaScript文件的能力。
- Plugin直译为"插件"。Plugin可以扩展webpack的功能,让webpack具有更多的灵活性。 在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
算法相关
- 讲讲冒泡排序的原理
- 依次比较相邻的两个数,如果前面的数大于后面的数就交换位置,这样第一次循环后最后一个数就是最大的,在下一轮循环中就无需比较,每次找出未排序列中最大的数,第二次找到第二大的数,以此类推...
- 手写归并排序
-
归并排序算法有两个基本的操作,一个是分,也就是把原数组划分成两个子数组的过程。另一个是治,它将两个有序数组合并成一个更大的有序数组。
-
它将数组平均分成两部分: mid = (left + right)/2,当数组分得足够小时---数组中只有一个元素时,只有一个元素的数组自然而然地就可以视为是有序的,此时就可以进行合并操作了。因此,上面讲的合并两个有序的子数组,是从 只有一个元素 的两个子数组开始合并的。
-
const mergeObj = {
mergeSort(arr) {
// 数组第一个位置
let lo = 0
// 数值最后一个位置
let hi = arr.length - 1
// 开始排序,拆分数组
this.sort(arr, lo, hi)
},
sort(arr, lo, hi) {
// 如果数组不能再拆分了,就返回
if (lo >= hi) return
// 得到每次拆分的数组的中间位置
let mid = Math.floor(lo + (hi - lo) / 2)
// 使用得到的中间位置继续拆分,左边的序列
this.sort(arr, lo, mid)
// 使用得到的中间位置继续对右边序列进行拆分
this.sort(arr, mid + 1, hi)
// 每次拆分过后需要合并
this.merge(arr, lo, mid, hi)
},
merge(arr, lo, mid, hi) {
// 定义左边初始下标
let left = lo
// 定义右边初始下标
let right = mid + 1
let i = lo
// 辅助数组
let newArr = []
// 当left或right其中有一个越界,就停止循环
while(left <= mid && right <= hi) {
// 每次移动都需要将i递增
// 如果左边的元素比右边的元素小,添加到辅助数组中,左下标右移
if (arr[left] < arr[right]) {
newArr[i++] = arr[left++]
} else {
// 如果右边元素比左边元素小,将右边元素添加到辅助数组中,右下标右移
newArr[i++] = arr[right++]
}
}
// 到这里只会有一个序列还有剩余的元素
// 如果左边序列还有元素,将左边剩余的元素添加到辅助数组中
while(left <= mid) {
newArr[i++] = arr[left++]
}
// 如果右边序列还有元素,将左边剩余的元素添加到辅助数组中
while(right <= hi) {
newArr[i++] = arr[right++]
}
// 此时辅助数组中的元素就是已经排序好的了,将辅助数组的元素逐个添加到原数组中
for(let j = lo; j <= hi; j++) {
arr[j] = newArr[j]
}
}
}
- 手写快速排序
- 选择数组中间数作为基数,并从数组中取出此基数;
- 准备两个数组容器,遍历数组,逐个与基数比对,较小的放左边容器,较大的放右边容器;
- 递归处理两个容器的元素,并将处理后的数据与基数按大小合并成一个数组,返回。
function quickSort(arr) {
if (arr.length <= 1) { return arr }
var pivotIndex = Math.floor(arr.length / 2)
var pivot = arr.splice(pivotIndex, 1)[0]
var left = []
var right = []
for (var i = 0; i < arr.length; i++){
if (arr[i] < pivot) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return quickSort(left).concat([pivot], quickSort(right))
}
- 树的遍历
function preOrder(tree) {
console.log(tree.data)
if (tree.left) {
preOrder(tree.left)
}
if (tree.right) {
preOrder(tree.right)
}
}
想起来在更新吧