1. 万能居中
1.margin: 0 auto;水平
2.text-align: center;水平
3.行高,垂直
4.表格,center,middle;水平垂直
5.display:table-cell;模拟表格,all
6.绝对定位,50%减自身宽高
7.绝对定位,上下左右全0,margin:auto
8.绝对定位加相对定位。不需要知道宽高
9.IE6,IE7:给父元素设一个font-size:高度/1.14,vertical-align:middle
2. BFC优化
块格式化上下文, 特性:
3. 盒模型哪两种模式?什么区别?如何设置
4. 常用清除浮动的方法,如不清除浮动会怎样?
当父元素不给高度的时候,内部元素不浮动时会撑开, 而浮动的时候,父元素变成一条线, 造成塌陷.
5. 栅格化的原理
比如antd的row和col, 将一行等分为24份, col是几就占几份, 底层按百分比实现; 结合媒体查询, 可以实现响应式
6. 纯css实现三角形
// 通过设置border
.box
{
width:0px;
height:0px;
border-top:50px solid rgba(0,0,0,0);
border-right:50px solid rgba(0,0,0,0);
border-bottom:50px solid green;
border-left:50px solid rgba(0,0,0,0);
}
7. 高度不定,宽100%,内一p高不确定,如何实现垂直居中?
8. 至少两种方式实现自适应搜索
9. 设置一段文字的大小为6px
10. css菊花图
四个小圆点一直旋转
// 父标签
animation: antRotate 1.2s infinite linear;
// 子标签
animation: antSpin 1s infinite linear;
@keyframe antSpin {
to {
opacity: 1
}
}
@keyframe antRotate {
to {
transform: rotate(405)
}
}
// animation-delay: 逐个延迟0.4s
11. 关于em
"font-size: 20px">
123
"font-size: 2em;width: 2em">456
// 此时子元素的font-size为40px, 宽度为80px(还要乘以子元素font-size的系数)
12. 关于vh, vw
vw:viewpoint width,视窗宽度,1vw等于视窗宽度的1%。
vh:viewpoint height,视窗高度,1vh等于视窗高度的1%。
vmin:vw和vh中较小的那个。
vmax:vw和vh中较大的那个。
13. Flex布局
14. overflow原理
overflow: hidden
能清除块内子元素的浮动影响. 因为该属性进行超出隐藏时需要计算盒子内所有元素的高度, 所以会隐式清除浮动
创建BFC条件(满足一个):
15. 实现自适应的正方形:
width
百分比, height: 0
, padding-top(bottom): 50%
16. 标准模式和怪异模式
box-sizing: border-box
; 标准模式: box-sizing: content-box
17. CSS3实现环形进度条
两个对半矩形遮罩, 使用rotate
以及overflow: hidden
进行旋转
18. css优先级
选择器的特殊性值表述为4个部分,用0,0,0,0表示。
19.什么是 BFC机制
BFC(Block Formatting Context),块级格式化上下文,是一个独立的渲染区域,让处于 BFC 内部的元素与外部的元素相互隔离,使内外元素的定位不会相互影响。
触发条件 (以下任意一条)
在IE下, Layout,可通过
zoom:1
触发
BFC布局与普通文档流布局区别:
BFC布局规则:
开发中的应用
20.CSS3中新增的选择器以及属性
这里只是列出来, 具体的使用,请查看我的关于css3新增选择器与属性文章
属性选择器 | 含义描述 |
---|---|
E[att^=“val”] | 属性att的值以"val"开头的元素 |
E[att$=“val”] | 属性att的值以"val"结尾的元素 |
E[att*=“val”] | 属性att的值包含"val"字符串的元素 |
选择器 | 含义描述 |
---|---|
E:root | 匹配文档的根元素,对于HTML文档,就是HTML元素 |
E:nth-child(n) | 匹配其父元素的第n个子元素,第一个编号为1 |
E:nth-last-child(n) | 匹配其父元素的倒数第n个子元素,第一个编号为1 |
E:nth-of-type(n) | 与:nth-child()作用类似,但是仅匹配使用同种标签的元素 |
E:nth-last-of-type(n) | 与:nth-last-child() 作用类似,但是仅匹配使用同种标签的元素 |
E:last-child | 匹配父元素的最后一个子元素,等同于:nth-last-child(1) |
E:first-of-type | 匹配父元素下使用同种标签的第一个子元素,等同于:nth-of-type(1) |
E:last-of-type | 匹配父元素下使用同种标签的最后一个子元素,等同于:nth-last-of-type(1) |
E:only-child | 匹配父元素下仅有的一个子元素,等同于:first-child:last-child或 :nth-child(1):nth-last-child(1) |
E:only-of-type | 匹配父元素下使用同种标签的唯一一个子元素,等同于:first-of-type:last-of-type或 :nth-of-type(1):nth-last-of-type(1) |
E:empty | 匹配一个不包含任何子元素的元素,注意,文本节点也被看作子元素 |
属性 | 含义描述 | 兼容 |
---|---|---|
transition | 设置过渡效果 | |
transform | 变换效果(移动、缩放、转动、拉长或拉伸) | |
animation | 动画效果 | |
box-shadow | 阴影效果 | FF3.5, Safari 4, Chrome 3 |
text-shadow | 文本阴影 | FF 3.5, Opera 10, Safari 4, Chrome 3 |
border-colors | 为边框设置多种颜色 | FF3+ |
boder-image | 图片边框 | FF 3.5, Safari 4, Chrome 3 |
text-overflow | 文本截断 | IE6+, Safari4, Chrome3, Opera10 |
word-wrap | 自动换行 | IE6+, FF 3.5, Safari 4, Chrome 3 |
border-radius | 圆角边框 | FF 3+, Safari 4 , Chrome 3 |
opacity | 不透明度 | all |
box-sizing | 控制盒模型的组成模式 | FF3+, Opera 10, Safari 4, Chrome 3 |
outline | 外边框 | FF3+, safari 4, chrome 3, opera 10 |
background-size | 不指定背景图片的尺寸 | safari 4, chrome 3, opera 10 |
background-origin | 指定背景图片从哪里开始显示 | safari 4, chrome 3, FF 3+ |
background-clip | 指定背景图片从什么位置开始裁切 | safari 4, chrome 3 |
rgba | 基于r,g,b三个颜色通道来设置颜色值, 通过a来设置透明度 | safari 4, chrome 3, FF3, opera 10 |
21.清除浮动有哪些方法, 各有什么优缺点
,并在CSS中赋予`.clear{clear:both;}`属性即可清理浮动。亦可使用` `或
来进行清理。优点: 简单, 写少量代码, 兼容性也好 缺点: 添加无语义html元素, 不利于代码语义化, 后期维护成本大
overflow:hidden;
或overflow:auto;
可以清除浮动,另外在 IE6 中还需要触发 hasLayout ,例如为父元素设置容器宽高或设置zoom:1
。在添加overflow属性后,浮动元素又回到了容器层,把容器高度撑起,达到了清理浮动的效果。优点: 简单, 代码少, 浏览器支持好 缺点: 不能和position配合使用, 因为超出的尺寸会被隐藏
overflow:hidden
clearfix
的class,然后给这个class添加一个:after伪元素实现元素末尾添加一个看不见的块元素(Block element)清理浮动。通过CSS伪元素在容器的内部元素最后添加了一个看不见的空格"020"或点".",并且赋予clear属性来清除浮动。需要注意的是为了IE6和IE7浏览器,要给clearfix这个class添加一条zoom:1;触发haslayout。优点: 浏览器支持好,不容易出现怪问题(目前:大型网站都有使用,如:腾迅,网易,新浪等等) 缺点: 代码多,要两句代码结合使用,才能让主流浏览器都支持
简单, 代码少,好掌握 缺点: 只适用于高度固定的布局
22.用纯CSS创建一个三角形的原理是什么
之前写三角形, 都是直接记住代码,没有探究原因,我也是直到有一次面试时,面试大哥让我说说css创建三角形的原理,我就…回来就赶紧翻资料.接下来我就将当时我理解的过程列举出来:
.box{ width:100px; height:100px; border: 3px solid; border-color:#1b93fb #1bfb24 #efad48 #ef4848;}
.box{ width:100px; height:100px; border: 50px solid; border-color:#1b93fb #1bfb24 #efad48 #ef4848;}
.box{ width:0px; height:0px; border: 50px solid; border-color:#1b93fb #1bfb24 #efad48 #ef4848;}
四个三角形拼合成的矩形呈现在我们眼前,那如如果我们只想要一个三角形, 我们是不是可以设想将其他三个设为不可见;
.box{ width:0px; height:0px; border: 50px solid; border-color:transparent transparent transparent #ef4848;}
三角形这样就出来, 有木有很简单, 当然我们也可以采用逆向思维来写这个效果, 就是先将所有边框设为透明, 然后需要哪边再对其设置颜色, 效果是一样的
.box{ width:0px; height:0px; border: 50px solid transparent; border-left:50px solid #ef4848;}
这样给面试你的人讲,讲明白应该不是问题., 重点就是要理解border的应用
23.实现三栏布局有哪些方法, 分别描述一下
三栏布局,顾名思义就是两边固定,中间自适应。三栏布局在开发十分常见,那么什么是三栏布局?即左右模块固定宽度,中间模块随浏览器变化自适应,想要完成的最终效果如下图所示:下面列出四种实现方式, 在开发中可以根据实际需求选择适合自己的方法进行编码:
<style>
.container{
display:flex;
justify-content: center;
height: 200px;
background: #eee;
}
.left {
width: 200px;
background-color: red;
height: 100%;
}
.main {
background-color: yellow;
flex: 1;
}
.right {
width: 200px;
background-color: green;
}
style>
<div class="container">
<div class="left">1div>
<div class="main">2div>
<div class="right">3div>
div>
简单实用,现在比较流行的方案,但是需要考虑浏览器的兼容性。
<style>
.container {
position: relative;
background:#eee;
height:200px;
}
.main {
height: 200px;
margin: 0 120px;
background-color: yellow;
}
.left {
position: absolute;
width: 100px;
height: 200px;
left: 0;
top: 0;
background-color: red;
}
.right {
position: absolute;
width: 100px;
height: 200px;
background-color: green;
right: 0;
top: 0;
}
style>
<div class="container">
<div class="left">1div>
<div class="main">2div>
<div class="right">3div>
div>
这种方案也简单实用, 并且可以将 ``元素放到第一位,使得主要内容优先加载!
<style>
.content {
float: left;
width: 100%;
}
.main {
height: 200px;
margin-left: 110px;
margin-right: 220px;
background-color: yellow;
}
.left {
float: left;
height: 200px;
width: 100px;
margin-left: -100%;
background-color: red;
}
.right {
width: 200px;
height: 200px;
float: right;
margin-left: -200px;
background-color: green;
}
style>
<div class="content">
<div class="main">div>
div>
<div class="left">div>
<div class="right">div>
<style>
.container {
margin-left: 120px;
margin-right: 220px;
}
.main {
float: left;
width: 100%;
height: 300px;
background-color: yellow;
}
.left {
float: left;
width: 100px;
height: 300px;
margin-left: -100%;
position: relative;
left: -120px;
background-color: blue;
}
.right {
float: left;
width: 200px;
height: 300px;
margin-left: -200px;
position: relative;
right: -220px;
background-color: green;
}
style>
<div class="container">
<div class="main">div>
<div class="left">div>
<div class="right">div>
div>
圣杯布局和双飞翼布局解决问题的方案在前一半是相同的,也就是三栏全部float浮动,但左右两栏加上负margin让其跟中间栏div并排,以形成三栏布局。
24.css3实现0.5px的细线
<style>
.line {
position: relative;
}
.line:after {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 1px;
background-color: #000000;
-webkit-transform: scaleY(.5);
transform: scaleY(.5);
}
style>
<div class="line">div>
25.link 与 @import 的区别
@import是 CSS 提供的语法规则,只有导入样式表的作用;link是HTML提供的标签,不仅可以加载 CSS 文件,还可以定义 RSS、rel 连接属性等
加载页面时,link标签引入的 CSS 被同时加载;@import引入的 CSS 将在页面加载完毕后被加载。
@import是 CSS2.1 才有的语法,故只可在 IE5+ 才能识别;link标签作为 HTML 元素,不存在兼容性问题。
可以通过 JS 操作 DOM ,插入link标签来改变样式;由于DOM方法是基于文档的,无法使用@import的方式插入样式。
css部分就整理到这里, 小伙伴们面试还有什么经常遇到的,可以在评论区给我留言, 我有时间就整理出来, IT(挨踢)都是一大家, 方便你我他
26.开发中为什么要初始化css样式
因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面显示差异。
27.CSS优化、提高性能的方法有哪些
尽量将样式写在单独的css文件里面,在head元素中引用 将代码写成单独的css文件有几点好处:
不使用@import
避免使用复杂的选择器,层级越少越好 建议选择器的嵌套最好不要超过三层,比如:
精简页面的样式文件,去掉不用的样式
利用CSS继承减少代码量
避免!important,可以选择其他选择器
1. ES5和ES6继承方式区别
2. Generator了解
ES6 提供的一种异步编程解决方案, Generator 函数是一个状态机,封装了多个内部状态。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
调用后返回指向内部状态的指针, 调用next()才会移向下一个状态, 参数:
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
3. 手写Promise实现
var myPromise = new Promise((resolve, reject) => {
// 需要执行的代码
...
if (/* 异步执行成功 */) {
resolve(value)
} else if (/* 异步执行失败 */) {
reject(error)
}
})
myPromise.then((value) => {
// 成功后调用, 使用value值
}, (error) => {
// 失败后调用, 获取错误信息error
})
4. Promise优缺点
function promise () {
this.msg = '' // 存放value和error
this.status = 'pending'
var that = this
var process = arguments[0]
process (function () {
that.status = 'fulfilled'
that.msg = arguments[0]
}, function () {
that.status = 'rejected'
that.msg = arguments[0]
})
return this
}
promise.prototype.then = function () {
if (this.status === 'fulfilled') {
arguments[0](this.msg)
} else if (this.status === 'rejected' && arguments[1]) {
arguments[1](this.msg)
}
}
5. 观察者模式
又称发布-订阅模式, 举例子说明.
实现: 发布者管理订阅者队列, 并有新消息推送功能. 订阅者仅关注更新就行
6. 手写实现bind
Function.prototype.bind = function () {
// 保存原函数
var self = this
// 取出第一个参数作为上下文, 相当于[].shift.call(arguments)
var context = Array.prototype.shift.call(arguments)
// 取剩余的参数作为arg; 因为arguments是伪数组, 所以要转化为数组才能使用数组方法
var arg = Array.prototype.slice.call(arguments)
// 返回一个新函数
return function () {
// 绑定上下文并传参
self.apply(context, Array.prototype.concat.call(arg, Array.prototype.slice.call(arguments)))
}
}
7. 手写实现4种继承
function Father () {}
function Child () {}
// 1\. 原型继承
Child.prototype = new Father()
// 2\. 构造继承
function Child (name) {
Father.call(this, name)
}
// 3\. 组合继承
function Child (name) {
Father.call(this, name)
}
Child.prototype = new Father()
// 4\. 寄生继承
function cloneObj (o) {
var clone = object.create(o)
clone.sayName = ...
return clone
}
// 5\. 寄生组合继承
// 6\. ES6 class extend继承
8. css菊花图
四个小圆点一直旋转
// 父标签
animation: antRotate 1.2s infinite linear;
// 子标签
animation: antSpin 1s infinite linear;
@keyframe antSpin {
to {
opacity: 1
}
}
@keyframe antRotate {
to {
transform: rotate(405)
}
}
// animation-delay: 逐个延迟0.4s
9. http状态码
10. Object.create实现(原型式继承,特点:实例的proto指向构造函数本身)
11. async和await:
12. 算法和数据结构:
13. 封装JSONP
function jsonp ({url, param, callback}) {
return new Promise((resolve, reject) => {
var script = document.createElement('script')
window.callback = function (data) {
resolve(data)
document.body.removeChild('script')
}
var param = {...param, callback}
var arr = []
for (let key in param) {
arr.push(`${key}=${param[key]}`)
}
script.src = `${url}?${arr.join('&')}`
document.body.appendChild(script)
})
}
14. 手动实现map(forEach以及filter也类似)
// for循环实现
Array.prototype.myMap = function () {
var arr = this
var [fn, thisValue] = Array.prototype.slice.call(arguments)
var result = []
for (var i = 0; i < arr.length; i++) {
result.push(fn.call(thisValue, arr[i], i, arr))
}
return result
}
var arr0 = [1, 2, 3]
console.log(arr0.myMap(v => v + 1))
// forEach实现(reduce类似)
Array.prototype.myMap = function (fn, thisValue) {
var result = []
this.forEach((v, i, arr) => {
result.push(fn.call(thisValue, v, i, arr))
})
return result
}
var arr0 = [1, 2, 3]
console.log(arr0.myMap(v => v + 1))
15. js实现checkbox全选以及反选
全选
1
2
3
16. 对原型链的理解?prototype上都有哪些属性
17. 为什么使用继承
通常在一般的项目里不需要,因为应用简单,但你要用纯js做一些复杂的工具或框架系统就要用到了,比如webgis、或者js框架如jquery、ext什么的,不然一个几千行代码的框架不用继承得写几万行,甚至还无法维护。
18. setTimeout时间延迟为何不准
单线程, 先执行同步主线程, 再执行异步任务队列
19. 事件循环述,宏任务和微任务有什么区别?
20. let const var作用域
块级作用域, 暂时性死区
21. 节流和防抖
// 函数节流 滚动条滚动
var canRun = true;
document.getElementById("throttle").onscroll = function(){
if(!canRun){
// 判断是否已空闲,如果在执行中,则直接return return; }
canRun = false;
setTimeout(function(){
console.log("函数节流");
canRun = true; }, 300);
};
// 函数防抖
var timer = false;document.getElementById("debounce").onscroll = function(){ clearTimeout(timer); // 清除未执行的代码,重置回初始化状态
timer = setTimeout(function(){
console.log("函数防抖");
}, 300);
};
22. 实现一个sleep函数
// 这种实现方式是利用一个伪死循环阻塞主线程。因为JS是单线程的。所以通过这种方式可以实现真正意义上的sleep()。
function sleep(delay) {
var start = (new Date()).getTime();
while ((new Date()).getTime() - start < delay) {
continue; }}function test() {
console.log('111');
sleep(2000);
console.log('222');
}
test()
23. 闭包
概念: 内层函数能够访问外层函数作用域的变量
缺点: 引起内存泄漏(释放内存)
作用:
24. Immutable.js
Facebook出品, 倡导数据的不可变性, 用的最多就是List和Map.
25. js实现instanceof
// 检测l的原型链(__proto__)上是否有r.prototype,若有返回true,否则false
function myInstanceof (l, r) {
var R = r.prototype
while (l.__proto__) {
if (l.__proto__ === R)
return true
}
return false
}
27. ES6的模块引入和CommonJs区别
28. 严格模式
// 严格模式下, 隐式绑定丢失后this不会指向window, 而是指向undefined
'use strict'
var a = 2
var obj = { a: 1, b: function() {
// console.log(this.a)
console.log(this) } }
var c = obj.b c() // undefined
29. fetch, axios区别
30. typescript缺点
31. 构造函数实现原理
// 模拟构造函数实现
var Book = function(name) { this.name = name; };
//正常用法
var java = new Book(‘Master Java’);
//使用代码模拟,在非IE浏览器中测试,IE浏览器不支持
var python = {};
python.__proto__ = Book.prototype;
Book.call(python, 'Master Python');
32. for in 和 for of区别
for in
遍历数组会遍历到数组原型上的属性和方法, 更适合遍历对象forEach
不支持break, continue, return
等for of
可以成功遍历数组的值, 而不是索引, 不会遍历原型33. JS实现并发控制:
使用消息队列以及setInterval
或promise
进行入队和出队
34. ajax和axios、fetch的区别
35. promise.finally实现
Promise.prototype.finally = function (callback) { let P = this.constructor; return this.then( value => P.resolve(callback()).then(() => value), reason => P.resolve(callback()).then(() => { throw reason }) );};
1. reflow(回流)和repaint(重绘)优化
浏览器渲染过程: DOM tree, CSS tree --> Render tree --> Paint
DOM tree根节点为html
渲染从浏览器左上角到右下角
第一次打开页面至少触发一次重绘和回流, 结构如宽高位置变化时, 触发reflow回流;非结构如背景色变化时, 触发repaint重绘. 二者都会造成体验不佳
如何减少重绘和回流?
2.一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?
3.localStorage 与 sessionStorage 与cookie的区别总结
4.浏览器如何阻止事件传播,阻止默认行为
5.虚拟DOM方案相对原生DOM操作有什么优点,实现上是什么原理?
虚拟DOM可提升性能, 无须整体重新渲染, 而是局部刷新.
JS对象, diff算法
6.浏览器事件机制中事件触发三个阶段
7.什么是跨域?为什么浏览器要使用同源策略?你有几种方式可以解决跨域问题?了解预检请求嘛?
8.了解浏览器缓存机制吗?
9.为什么操作 DOM 慢?
DOM本身是一个js对象, 操作这个对象本身不慢, 但是操作后触发了浏览器的行为, 如repaint和reflow等浏览器行为, 使其变慢
10.什么情况会阻塞渲染?
11.如何判断js运行在浏览器中还是node中?
判断有无全局对象global和window
12.关于web以及浏览器处理预加载有哪些思考?
图片等静态资源在使用之前就提前请求
资源使用到的时候能从缓存中加载, 提升用户体验
页面展示的依赖关系维护
13.http多路复用
14. http和https:
15. CSRF和XSS区别及防御
16. cookie可设置哪些属性?httponly?
chrome控制台的application下可查看:
17. 登录后,前端做了哪些工作,如何得知已登录
18. http状态码
19. # Http请求头缓存设置方法
Cache-control, expire, last-modify
20. 实现页面回退刷新
21. 正向代理和反向代理
(1)访问原来无法访问的资源,如google
(2) 可以做缓存,加速访问资源
(3)对客户端访问授权,上网进行认证
(4)代理可以记录用户访问记录(上网行为管理),对外隐藏用户信息
(1)保证内网的安全,可以使用反向代理提供WAF功能,阻止web攻击大型网站,通常将反向代理作为公网访问地址,Web服务器是内网。
(2)负载均衡,通过反向代理服务器来优化网站的负载
22. 关于预检请求
在非简单请求且跨域的情况下,浏览器会自动发起options预检请求。
23. 三次握手四次挥手
24. TCP和UDP协议
25. 进程和线程的区别
1. 生命周期
2 .双向数据绑定v-model。这个最好也是自己实现一下 理解更深
通过v-model
VUE实现双向数据绑定的原理就是利用了 Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的。
// 依赖收集
// 简化版
var obj = { }
var name
//第一个参数:定义属性的对象。
//第二个参数:要定义或修改的属性的名称。
//第三个参数:将被定义或修改的属性描述符。
Object.defineProperty(obj, "data", {
//获取值
get: function () {
return name
},
//设置值
set: function (val) {
name = val
console.log(val)
}
})
//赋值调用set
obj.data = 'aaa'
//取值调用get
console.log(obj.data)
// 详细版
myVue.prototype._obverse = function (obj) { // obj = {number: 0}
var value;
for (key in obj) { //遍历obj对象
if (obj.hasOwnProperty(key)) {
value = obj[key];
if (typeof value === 'object') { //如果值是对象,则递归处理
this._obverse(value);
}
Object.defineProperty(this.$data, key, { //关键
enumerable: true,
configurable: true,
get: function () {
console.log(`获取${value}`);
return value;
},
set: function (newVal) {
console.log(`更新${newVal}`);
if (value !== newVal) {
value = newVal;
}
}
})
}
}
}
3.vue父子组件传递参数
4.vue传递参数方法
// 1\. 新建eventBus.js
import Vue from 'vue'
export default new Vue
// 或直接在main.js中初始化EventBus(全局)
Vue.prototype.$EventBus = new Vue()
// 2\. 发射与接收
// 如果是定义在eventBus.js中
import eventBus from 'eventBus.js'
eventBus.$emit()
eventBus.$on()
// 如果是定义在main.js中
this.bus.$emit()
this.bus.$on()
// 3\. 移除监听
eventBus.$off()
5.vue自定义组件
可以使用独立可复用的自定义组件来构成大型应用, 采用帕斯卡命名法或横线连接, 通过以上方式进行组件间通信. 每一个组件都是Vue实例, 可以使用生命周期钩子.
6. vue自定义指令
7.vuex组成和原理
8.vue-router的原理,例如hashhistory和History interface这些东西要弄明白。其实看一下源码就好了,看不懂可以直接看解析的相关技术博客。
vue-router用法:
在router.js或者某一个路由分发页面配置path, name, component对应关系
vue-router原理: 通过hash和History interface两种方式实现前端路由
9.vue的seo问题
seo关系到网站排名, vue搭建spa做前后端分离不好做seo, 可通过其他方法解决:
10.预渲染和ssr
以上
11.生命周期内create和mounted的区别
12.监听watch
对应一个对象,键是观察表达式,值是对应回调。值也可以是methods的方法名,或者是对象,包含选项。在实例化时为每个键调用 $watch()
13.登录验证拦截(通过router)
先设置requireAuth:
routes = [
{
name: 'detail',
path: '/detail',
meta: {
requireAuth: true
}
},
{
name: 'login',
path: '/login'
}
]
再配置router.beforeEach:
router.beforeEach((from, to, next) => {
if (to.meta.requireAuth) { // 判断跳转的路由是否需要登录
if (store.state.token) { // vuex.state判断token是否存在
next() // 已登录
} else {
next({
path: '/login',
query: {redirect: to.fullPath} // 将跳转的路由path作为参数,登录成功后跳转到该路由
})
}
} else {
next()
}
})
14. v-for key值
不写key值会报warning, 和react的array渲染类似. 根据diff算法, 修改数组后, 写key值会复用, 不写会重新生成, 造成性能浪费或某些不必要的错误
15. vue3.0的更新和defineProperty优化
15. vue使用this获取变量
正常要通过vm.[图片上传失败…(image-6d2f4e-1570591304185)]
root传参取值
16. jQuery的优缺点,与vue的不同,vue的优缺点?
17. vue解除双向绑定
let obj = JSON.parse(JSON.stringify(this.temp1));
18. vue异步组件
为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染
Vue.component( 'async-webpack-example', () => import('./my-async-component'))
// 这个 `import` 函数会返回一个 `Promise` 对象。
19. MVC与MVVM
20. vue渐进式
小到可以只使用核心功能,比如单文件组件作为一部分嵌入;大到使用整个工程,vue init webpack my-project来构建项目;VUE的核心库及其生态系统也可以满足你的各式需求(core+vuex+vue-route)
1. 新旧生命周期
旧: will, did; mount, update…
新: 16版本之后:
getDerivedStateFromProps
: 虚拟dom之后,实际dom挂载之前, 每次获取新的props或state之后, 返回新的state, 配合didUpdate可以替代willReceivePropsgetSnapshotBeforeUpdate
: update发生的时候,组件更新前触发, 在render之后,在组件dom渲染之前;返回一个值,作为componentDidUpdate的第三个参数;配合componentDidUpdate, 可以覆盖componentWillUpdate的所有用法componentDidCatch
: 错误处理对比: 弃用了三个will, 新增两个get来代替will, 不能混用, 17版本会彻底删除. 新增错误处理
2. react核心
3. fiber核心(react 16)
4. 渲染一个react
5. 高阶组件
高阶组件就是一个函数,且该函数(wrapper)接受一个组件作为参数,并返回一个新的组件。
高阶组件并不关心数据使用的方式和原因,而被包裹的组件也不关心数据来自何处.
export default DragSource(type, spec, collect)(MyComponent)
6. hook(v16.7测试)
在无状态组件(如函数式组件)中也能操作state以及其他react特性, 通过useState
7. redux和vuex以及dva:
8. react和vue的区别
9. react单向数据流怎么理解
React是单向数据流,数据主要从父节点传递到子节点(通过props)。如果顶层(父级)的某个props改变了,React会重渲染所有的子节点。
10. React算法复杂度优化
react树对比是按照层级去对比的, 他会给树编号0,1,2,3,4… 然后相同的编号进行比较。所以复杂度是n,这个好理解。
关键是传统diff的复杂度是怎么算的?传统的diff需要出了上面的比较之外,还需要跨级比较。他会将两个树的节点,两两比较,这就有n2的复杂度了。然后还需要编辑树,编辑的树可能发生在任何节点,需要对树进行再一次遍历操作,因此复杂度为n。加起来就是n3了。
11. React优点
声明式, 组件化, 一次学习, 随处编写. 灵活, 丰富, 轻巧, 高效
1. 移动端兼容适配
2. flexible如何实现自动判断dpr
判断机型, 找出样本机型去适配. 比如iphone以6为样本, 宽度375px, dpr是2
3. 为什么以iPhone6为标准的设计稿的尺寸是以750px宽度来设计的呢?
iPhone6的满屏宽度是375px,而iPhone6采用的视网膜屏的物理像素是满屏宽度的2倍,也就是dpr(设备像素比)为2, 并且设计师所用的PS设计软件分辨率和像素关系是1:1。所以为了做出的清晰的页面,设计师一般给出750px的设计图,我们再根据需求对元素的尺寸设计和压缩。
4. 如何处理异形屏iphone X
safe area
: 默认放置在安全区域以避免遮挡, 但会压缩viewport-fit=cover
: 告诉浏览器要讲整个页面渲染到浏览器中,不管设备是圆角与否,这个时候会造成页面的元素被圆角遮挡padding: constant(env)
: 解决遮挡问题5. 移动端首屏优化
6. PWA全称Progressive Web App,即渐进式WEB应用
一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用. 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能
解决了哪些问题?
7. 离线包方案
现在 web 页面在移动端的地位越来越高,大部分主流 App 采用 native + webview 的 hybrid 模式,加载远程页面受限于网络,本地 webview 引擎,经常会出现渲染慢导致的白屏现象,体验很差,于是离线包方案应运而生。动态下载的离线包可以使得我们不需要走完整的 App 审核发布流程就完成了版本的更新
8. 自适应和响应式布局的区别
1. babel和polyfill
Babel
: Babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码。注意:Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的 APIPolyfill
: Polyfill的准确意思为,用于实现浏览器并不支持的原生API的代码。2. jpg, jpeg和png区别
3. git rebase和merge区别
1.JSBridge通信原理, 有哪几种实现的方式?
JsBridge给JavaScript提供了调用Native功能,Native也能够操控JavaScript。这样前端部分就可以方便使用地理位置、摄像头以及登录支付等Native能力啦。JSBridge构建 Native和非Native间消息通信的通道,而且是 双向通信的通道。
2.实现一个简单的 JSBridge,设计思路?
1. 二分查找和冒泡排序
2. 快速排序
function quickSort (arr) {
if (arr.length < 2) return arr
var middle = Math.floor(arr.length / 2)
var flag = arr.splice(middle, 1)[0]
var left = [],
right = []
for (var i = 0; i < arr.length; i++) {
if (arr[i] < flag) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return quickSort(left).concat([flag], quickSort(right))
}
3. 最长公共子串
function findSubStr(str1, str2) {
if (str1.length > str2.length) {
[str1, str2] = [str2, str1]
}
var result = ''
var len = str1.length
for (var j = len; j > 0; j--) {
for (var i = 0; i < len - j; i++) {
result = str1.substr(i, j)
if (str2.includes(result)) return result
}
}
}
console.log(findSubStr('aabbcc11', 'ppooiiuubcc123'))
4. 最长公共子序列(LCS动态规划)
另一篇
// dp[i][j] 计算去最大长度,记住口诀:相等左上角加一,不等取上或左最大值
function LCS(str1, str2){
var rows = str1.split("")
rows.unshift("")
var cols = str2.split("")
cols.unshift("")
var m = rows.length
var n = cols.length
var dp = []
for(var i = 0; i < m; i++){
dp[i] = []
for(var j = 0; j < n; j++){
if(i === 0 || j === 0){
dp[i][j] = 0
continue
}
if(rows[i] === cols[j]){
dp[i][j] = dp[i-1][j-1] + 1 //对角+1
}else{
dp[i][j] = Math.max( dp[i-1][j], dp[i][j-1]) //对左边,上边取最大
}
}
console.log(dp[i].join(""))//调试
}
return dp[i-1][j-1]
}
//!!!如果它来自左上角加一,则是子序列,否则向左或上回退。
//findValue过程,其实就是和 就是把T[i][j]的计算反过来。
// 求最长子序列
function findValue(input1,input2,n1,n2,T){
var i = n1-1,j=n2-1;
var result = [];//结果保存在数组中
console.log(i);
console.log(j);
while(i>0 && j>0){
if(input1[i] == input2[j]){
result.unshift(input1[i]);
i--;
j--;
}else{
//向左或向上回退
if(T[i-1][j]>T[i][j-1]){
//向上回退
i--;
}else{
//向左回退
j--;
}
}
}
console.log(result);
5. 数组去重,多种方法
6. 实现一个函数功能:sum(1,2,3,4…n)转化为 sum(1)(2)(3)(4)…(n)
// 使用柯里化 + 递归
function curry ( fn ) {
var c = (...arg) => (fn.length === arg.length) ?
fn (...arg) : (...arg1) => c(...arg, ...arg1)
return c
}
7. 反转二叉树
var invertTree = function (root) {
if (root !== null) {
[root.left, root.right] = [root.right, root.left]
invertTree(root.left)
invertTree(root.right)
}
return root
}
8. 贪心算法解决背包问题
var items = ['A','B','C','D']
var values = [50,220,60,60]
var weights = [5,20,10,12]
var capacity = 32 //背包容积
greedy(values, weights, capacity) // 320
function greedy(values, weights, capacity) {
var result = 0
var rest = capacity
var sortArray = []
var num = 0
values.forEach((v, i) => {
sortArray.push({
value: v,
weight: weights[i],
ratio: v / weights[i]
})
})
sortArray.sort((a, b) => b.ratio - a.ratio)
sortArray.forEach((v, i) => {
num = parseInt(rest / v.weight)
rest -= num * v.weight
result += num * v.value
})
return result
}
9. 输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
function FindNumbersWithSum(array, sum)
{
var index = 0
for (var i = 0; i < array.length - 1 && array[i] < sum / 2; i++) {
for (var j = i + 1; j < array.length; j++) {
if (array[i] + array[j] === sum) return [array[i], array[j]]
}
//index = array.indexOf(sum - array[i], i + 1)
// if (index !== -1) {
// return [array[i], array[index]]
//}
}
return []
10. 二叉树各种(层序)遍历
深度广度遍历
// 根据前序和中序重建二叉树
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function reConstructBinaryTree(pre, vin)
{
var result = null
if (pre.length === 1) {
result = {
val: pre[0],
left: null,
right: null
}
} else if (pre.length > 1) {
var root = pre[0]
var vinRootIndex = vin.indexOf(root)
var vinLeft = vin.slice(0, vinRootIndex)
var vinRight = vin.slice(vinRootIndex + 1, vin.length)
pre.shift()
var preLeft = pre.slice(0, vinLeft.length)
var preRight = pre.slice(vinLeft.length, pre.length)
result = {
val: root,
left: reConstructBinaryTree(preLeft, vinLeft),
right: reConstructBinaryTree(preRight, vinRight)
}
}
return result
}
// 递归
// 前序遍历
function prevTraverse (node) {
if (node === null) return;
console.log(node.data);
prevTraverse(node.left);
prevTraverse(node.right);
}
// 中序遍历
function middleTraverse (node) {
if (node === null) return;
middleTraverse(node.left);
console.log(node.data);
middleTraverse(node.right);
}
// 后序遍历
function lastTraverse (node) {
if (node === null) return;
lastTraverse(node.left);
lastTraverse(node.right);
console.log(node.data);
}
// 非递归
// 前序遍历
function preTraverse(tree) {
var arr = [],
node = null
arr.unshift(tree)
while (arr.length) {
node = arr.shift()
console.log(node.root)
if (node.right) arr.unshift(node.right)
if (node.left) arr.unshift(node.left)
}
}
// 中序遍历
function middleTraverseUnRecursion (root) {
let arr = [],
node = root;
while (arr.length !== 0 || node !== null) {
if (node === null) {
node = arr.shift();
console.log(node.data);
node = node.right;
} else {
arr.unshift(node);
node = node.left;
}
}
}
// 广度优先-层序遍历
// 递归
var result = []
var stack = [tree]
var count = 0
var bfs = function () {
var node = stack[count]
if (node) {
result.push(node.value)
if (node.left) stack.push(node.left)
if (node.right) stack.push(node.right)
count++
bfs()
}
}
bfs()
console.log(result)
// 非递归
function bfs (node) {
var result = []
var queue = []
queue.push(node)
while (queue.length) {
node = queue.shift()
result.push(node.value)
node.left && queue.push(node.left)
node.right && queue.push(node.right)
}
return result
}
11. 各种排序
// 插入排序
function insertSort(arr) {
var temp
for (var i = 1; i < arr.length; i++) {
temp = arr[i]
for (var j = i; j > 0 && temp < arr[j - 1]; j--) {
arr[j] = arr[j - 1]
}
arr[j] = temp
}
return arr
}
console.log(insertSort([3, 1, 8, 2, 5]))
// 归并排序
function mergeSort(array) {
var result = array.slice(0)
function sort(array) {
var length = array.length
var mid = Math.floor(length * 0.5)
var left = array.slice(0, mid)
var right = array.slice(mid, length)
if (length === 1) return array
return merge(sort(left), sort(right))
}
function merge(left, right) {
var result = []
while (left.length || right.length) {
if (left.length && right.length) {
if (left[0] < right[0]) {
result.push(left.shift())
} else {
result.push(right.shift())
}
} else if (left.length) {
result.push(left.shift())
} else {
result.push(right.shift())
}
}
return result
}
return sort(result)
}
console.log(mergeSort([5, 2, 8, 3, 6]))
// 二分插入排序
function twoSort(array) {
var len = array.length,
i,
j,
tmp,
low,
high,
mid,
result
result = array.slice(0)
for (i = 1; i < len; i++) {
tmp = result[i]
low = 0
high = i - 1
while (low <= high) {
mid = parseInt((high + low) / 2, 10)
if (tmp < result[mid]) {
high = mid - 1
} else {
low = mid + 1
}
}
for (j = i - 1; j >= high + 1; j--) {
result[j + 1] = result[j]
}
result[j + 1] = tmp
}
return result
}
console.log(twoSort([4, 1, 7, 2, 5]))
12. 使用尾递归对斐波那契优化
递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
// 传统递归斐波那契, 会造成超时或溢出
function Fibonacci (n) {
if ( n <= 1 ) {return 1};
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Fibonacci(10) // 89
Fibonacci(100) // 超时
Fibonacci(500) // 超时
// 使用尾递归优化, 可规避风险
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};
return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}
Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity
13. 两个升序数组合并为一个升序数组
function sort (A, B) {
var i = 0, j = 0, p = 0, m = A.length, n = B.length, C = []
while (i < m || j < n) {
if (i < m && j < n) {
C[p++] = A[i] < B[j] ? A[i++] : B[j++]
} else if (i < m) {
C[p++] = A[i++]
} else {
C[p++] = B[j++]
}
}
return C
}
1. node的router是什么
2. 数据库索引是啥
3. 浏览器的事件循环和node事件循环有什么区别?
4.请介绍一下node里的模块是什么
Node中,每个文件模块都是一个对象,它的定义如下:
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
this.filename = null;
this.loaded = false;
this.children = [];
}
module.exports = Module;
var module = new Module(filename, parent);
所有的模块都是 Module 的实例。可以看到,当前模块(module.js)也是 Module 的一个实例。
5.请介绍一下require的模块加载机制
这道题基本上就可以了解到面试者对Node模块机制的了解程度 基本上面试提到
// require 其实内部调用 Module._load 方法
Module._load = function(request, parent, isMain) {
// 计算绝对路径
var filename = Module._resolveFilename(request, parent);
// 第一步:如果有缓存,取出缓存
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
// 第二步:是否为内置模块
if (NativeModule.exists(filename)) {
return NativeModule.require(filename);
}
/********************************这里注意了**************************/
// 第三步:生成模块实例,存入缓存
// 这里的Module就是我们上面的1.1定义的Module
var module = new Module(filename, parent);
Module._cache[filename] = module;
/********************************这里注意了**************************/
// 第四步:加载模块
// 下面的module.load实际上是Module原型上有一个方法叫Module.prototype.load
try {
module.load(filename);
hadException = false;
} finally {
if (hadException) {
delete Module._cache[filename];
}
}
// 第五步:输出模块的exports属性
return module.exports;
};
接着上一题继续发问
6.加载模块时,为什么每个模块都有__dirname,__filename属性呢,new Module的时候我们看到1.1部分没有这两个属性的,那么这两个属性是从哪里来的
// 上面(1.2部分)的第四步module.load(filename)
// 这一步,module模块相当于被包装了,包装形式如下
// 加载js模块,相当于下面的代码(加载node模块和json模块逻辑不一样)
(function (exports, require, module, __filename, __dirname) {
// 模块源码
// 假如模块代码如下
var math = require('math');
exports.area = function(radius){
return Math.PI * radius * radius
}
});
也就是说,每个module里面都会传入__filename, __dirname参数,这两个参数并不是module本身就有的,是外界传入的
7.我们知道node导出模块有两种方式,一种是exports.xxx=xxx和Module.exports={}有什么区别吗
module.exports vs exports
很多时候,你会看到,在Node环境中,有两种方法可以在一个模块中输出变量:
方法一:对module.exports赋值:
// hello.js
function hello() {
console.log('Hello, world!');
}
function greet(name) {
console.log('Hello, ' + name + '!');
}
module.exports = {
hello: hello,
greet: greet
};
方法二:直接使用exports:
// hello.js
function hello() {
console.log('Hello, world!');
}
function greet(name) {
console.log('Hello, ' + name + '!');
}
function hello() {
console.log('Hello, world!');
}
exports.hello = hello;
exports.greet = greet;
但是你不可以直接对exports赋值:
// 代码可以执行,但是模块并没有输出任何变量:
exports = {
hello: hello,
greet: greet
};
如果你对上面的写法感到十分困惑,不要着急,我们来分析Node的加载机制:
首先,Node会把整个待加载的hello.js文件放入一个包装函数load中执行。在执行这个load()函数前,Node准备好了module变量:
var module = {
id: 'hello',
exports: {}
};
load()函数最终返回module.exports:
var load = function (exports, module) {
// hello.js的文件内容
...
// load函数返回:
return module.exports;
};
var exported = load(module.exports, module);
也就是说,默认情况下,Node准备的exports变量和module.exports变量实际上是同一个变量,并且初始化为空对象{},于是,我们可以写:
exports.foo = function () { return 'foo'; };
exports.bar = function () { return 'bar'; };
也可以写:
module.exports.foo = function () { return 'foo'; };
module.exports.bar = function () { return 'bar'; };
换句话说,Node默认给你准备了一个空对象{},这样你可以直接往里面加东西。
但是,如果我们要输出的是一个函数或数组,那么,只能给module.exports赋值:
module.exports = function () { return 'foo'; };
给exports赋值是无效的,因为赋值后,module.exports仍然是空对象{}。
结论
如果要输出一个键值对象{},可以利用exports这个已存在的空对象{},并继续在上面添加新的键值;
如果要输出一个函数或数组,必须直接对module.exports对象赋值。
所以我们可以得出结论:直接对module.exports赋值,可以应对任何情况:
module.exports = {
foo: function () { return 'foo'; }
};
或者:
module.exports = function () { return 'foo'; };
最终,我们强烈建议使用module.exports = xxx的方式来输出模块变量,这样,你只需要记忆一种方法。
8.请介绍一下Node事件循环的流程
9.在每个tick的过程中,如何判断是否有事件需要处理呢?
10.请描述一下整个异步I/O的流程
11.如何查看V8的内存使用情况
使用process.memoryUsage(),返回如下
{
rss: 4935680,
heapTotal: 1826816,
heapUsed: 650472,
external: 49879
}
heapTotal 和 heapUsed 代表V8的内存使用情况。 external代表V8管理的,绑定到Javascript的C++对象的内存使用情况。 rss, 驻留集大小, 是给这个进程分配了多少物理内存(占总分配内存的一部分) 这些物理内存中包含堆,栈,和代码段。
12.V8的内存限制是多少,为什么V8这样设计
64位系统下是1.4GB, 32位系统下是0.7GB。因为1.5GB的垃圾回收堆内存,V8需要花费50毫秒以上,做一次非增量式的垃圾回收甚至要1秒以上。这是垃圾回收中引起Javascript线程暂停执行的事件,在这样的花销下,应用的性能和影响力都会直线下降。
13.V8的内存分代和回收算法请简单讲一讲
在V8中,主要将内存分为新生代和老生代两代。新生代中的对象存活时间较短的对象,老生代中的对象存活时间较长,或常驻内存的对象。
新生代中的对象主要通过Scavenge算法进行垃圾回收。这是一种采用复制的方式实现的垃圾回收算法。它将堆内存一份为二,每一部分空间成为semispace。在这两个semispace空间中,只有一个处于使用中,另一个处于闲置状态。处于使用状态的semispace空间称为From空间,处于闲置状态的空间称为To空间。
老生代主要采取的是标记清除的垃圾回收算法。与Scavenge复制活着的对象不同,标记清除算法在标记阶段遍历堆中的所有对象,并标记活着的对象,只清理死亡对象。活对象在新生代中只占较小部分,死对象在老生代中只占较小部分,这是为什么采用标记清除算法的原因。
主要问题是每一次进行标记清除回收后,内存空间会出现不连续的状态
14.哪些情况会造成V8无法立即回收内存
闭包和全局变量
15.请谈一下内存泄漏是什么,以及常见内存泄漏的原因,和排查的方法
什么是内存泄漏
一、全局变量
a = 10;//未声明对象。
global.b = 11;//全局变量引用这种比较简单的原因,全局变量直接挂在 root 对象上,不会被清除掉。
二、闭包
function out() {
const bigData = new Buffer(100);
inner = function () {
}}
闭包会引用到父级函数中的变量,如果闭包未释放,就会导致内存泄漏。上面例子是 inner 直接挂在了 root 上,那么每次执行 out 函数所产生的 bigData 都不会释放,从而导致内存泄漏。
需要注意的是,这里举得例子只是简单的将引用挂在全局对象上,实际的业务情况可能是挂在某个可以从 root 追溯到的对象上导致的。
事件监听
Node.js 的事件监听也可能出现的内存泄漏。例如对同一个事件重复监听,忘记移除(removeListener),将造成内存泄漏。这种情况很容易在复用对象上添加事件时出现,所以事件重复监听可能收到如下警告:
emitter.setMaxListeners() to increase limit
例如,Node.js 中 Agent 的 keepAlive 为 true 时,可能造成的内存泄漏。当 Agent keepAlive 为 true 的时候,将会复用之前使用过的 socket,如果在 socket 上添加事件监听,忘记清除的话,因为 socket 的复用,将导致事件重复监听从而产生内存泄漏。
原理上与前一个添加事件监听的时候忘了清除是一样的。在使用 Node.js 的 http 模块时,不通过 keepAlive 复用是没有问题的,复用了以后就会可能产生内存泄漏。所以,你需要了解添加事件监听的对象的生命周期,并注意自行移除。
排查方法
想要定位内存泄漏,通常会有两种情况:
- 对于只要正常使用就可以重现的内存泄漏,这是很简单的情况只要在测试环境模拟就可以排查了。
- 对于偶然的内存泄漏,一般会与特殊的输入有关系。想稳定重现这种输入是很耗时的过程。如果不能通过代码的日志定位到这个特殊的输入,那么推荐去生产环境打印内存快照了。
- 需要注意的是,打印内存快照是很耗 CPU 的操作,可能会对线上业务造成影响。快照工具推荐使用 heapdump 用来保存内存快照,使用 devtool 来查看内存快照。
- 使用 heapdump 保存内存快照时,只会有 Node.js 环境中的对象,不会受到干扰(如果使用 node-inspector 的话,快照中会有前端的变量干扰)。
- PS:安装 heapdump 在某些 Node.js 版本上可能出错,建议使用 npm install heapdump -target=Node.js 版本来安装。
新建Buffer会占用V8分配的内存吗
不会,Buffer属于堆外内存,不是V8分配的。
Buffer.alloc和Buffer.allocUnsafe的区别
Buffer.allocUnsafe创建的 Buffer 实例的底层内存是未初始化的。新创建的 Buffer 的内容是未知的,可能包含敏感数据。使用 Buffer.alloc() 可以创建以零初始化的 Buffer 实例。
Buffer的内存分配机制
为了高效的使用申请来的内存,Node采用了slab分配机制。slab是一种动态的内存管理机制。Node以8kb为界限来来区分Buffer为大对象还是小对象,如果是小于8kb就是小Buffer,大于8kb就是大Buffer。
例如第一次分配一个1024字节的Buffer,Buffer.alloc(1024),那么这次分配就会用到一个slab,接着如果继续Buffer.alloc(1024),那么上一次用的slab的空间还没有用完,因为总共是8kb,1024+1024 = 2048个字节,没有8kb,所以就继续用这个slab给Buffer分配空间。
如果超过8bk,那么直接用C++底层地宫的SlowBuffer来给Buffer对象提供空间。
Buffer乱码问题
例如一个份文件test.md里的内容如下:
```
床前明月光,疑是地上霜,举头望明月,低头思故乡
```
我们这样读取就会出现乱码:
```javascript
var rs = require('fs').createReadStream('test.md', {highWaterMark: 11});// 床前明???光,疑???地上霜,举头???明月,???头思故乡
```
一般情况下,只需要设置rs.setEncoding('utf8')即可解决乱码问题
- 客户端与服务器只需要一个TCP连接,比http长轮询使用更少的连接
- webSocket服务端可以推送数据到客户端
- 更轻量的协议头,减少数据传输量
首先,WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求,格式如下:
```javascript
GET ws://localhost:3000/ws/chat HTTP/1.1Host: localhostUpgrade: websocketConnection: UpgradeOrigin: http://localhost:3000Sec-WebSocket-Key: client-random-stringSec-WebSocket-Version: 13
```
该请求和普通的HTTP请求有几点不同:
- GET请求的地址不是类似/path/,而是以ws://开头的地址;
- 请求头Upgrade: websocket和Connection: Upgrade表示这个连接将要被转换为WebSocket连接;
- Sec-WebSocket-Key是用于标识这个连接,并非用于加密数据;
- Sec-WebSocket-Version指定了WebSocket的协议版本。
随后,服务器如果接受该请求,就会返回如下响应:
```javascript
HTTP/1.1 101 Switching ProtocolsUpgrade: websocketConnection: UpgradeSec-WebSocket-Accept: server-random-string
```
该响应代码101表示本次连接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket指定的WebSocket协议。
- 443端口用来验证服务器端和客户端的身份,比如验证证书的合法性
- 80端口用来传输数据(在验证身份合法的情况下,用来数据传输)
- 密钥:密钥是一种参数,它是在明文转换为密文或将密文转换为明文的算法中输入的参数。密钥分为对称密钥与非对称密钥,分别应用在对称加密和非对称加密上。
- 对称加密:对称加密又叫做私钥加密,即信息的发送方和接收方使用同一个密钥去加密和解密数据。对称加密的特点是算法公开、加密和解密速度快,适合于对大数据量进行加密,常见的对称加密算法有DES、3DES、TDEA、Blowfish、RC5和IDEA。
- 非对称加密:非对称加密也叫做公钥加密。非对称加密与对称加密相比,其安全性更好。对称加密的通信双方使用相同的密钥,如果一方的密钥遭泄露,那么整个通信就会被破解。而非对称加密使用一对密钥,即公钥和私钥,且二者成对出现。私钥被自己保存,不能对外泄露。公钥指的是公共的密钥,任何人都可以获得该密钥。用公钥或私钥中的任何一个进行加密,用另一个进行解密。
- 摘要:摘要算法又称哈希/散列算法。它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。算法不可逆。
为什么需要CA机构对证书签名
如果不签名会存在中间人攻击的风险,签名之后保证了证书里的信息,比如公钥、服务器信息、企业信息等不被篡改,能够验证客户端和服务器端的“合法性”。
https验证身份也就是TSL/SSL身份验证的过程
简要图解如下:
![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X2pwZy9ZQkZWM0RhME53dkQ4R3VVY2RrczYwR2NIY1dNV0xKbEc2aWFORTliZjk2N0hxZzg5WDA4eDFsa3FjOE5laWNkUWg0NUxEdjVCVFA4Sk03QTdjM1RWd3Z3LzY0MA?x-oss-process=image/format,png)
面对node单线程对多核CPU使用不足的情况,Node提供了child_process模块,来实现进程的复制,node的多进程架构是主从模式,如下所示:
![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9ZQkZWM0RhME53dkQ4R3VVY2RrczYwR2NIY1dNV0xKbHFIU2x0Nk1wQUZFVUNMSlpWcWZXNk5Yb0tqdmg4QmV1WExVY2pCMFlkVE1heU51OXJVOW12US82NDA?x-oss-process=image/format,png)
```javascript
var fork = require('child_process').fork;
var cpus = require('os').cpus();
for(var i = 0; i < cpus.length; i++){
fork('./worker.js');
}
```
在linux中,我们通过ps aux | grep worker.js查看进程
![img](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9tbWJpei5xcGljLmNuL21tYml6X3BuZy9ZQkZWM0RhME53dkQ4R3VVY2RrczYwR2NIY1dNV0xKbGs4dGVVQTZObUhuYXEwMEh4OERpYlFaTE9INWJmTjM2MVZiY3BwaldRT2hjZFYwU21KVVBOZncvNjQw?x-oss-process=image/format,png)
这就是著名的主从模式,Master-Worker
创建子进程的方法大致有:
- spawn():启动一个子进程来执行命令
- exec(): 启动一个子进程来执行命令,与spawn()不同的是其接口不同,它有一个回调函数获知子进程的状况
- execFlie(): 启动一个子进程来执行可执行文件
- fork(): 与spawn()类似,不同电在于它创建Node子进程需要执行js文件
- spawn()与exec()、execFile()不同的是,后两者创建时可以指定timeout属性设置超时时间,一旦创建的进程超过设定的时间就会被杀死
- exec()与execFile()不同的是,exec()适合执行已有命令,execFile()适合执行文件。
请问你知道spawn在创建子进程的时候,第三个参数有一个stdio选项吗,这个选项的作用是什么,默认的值是什么。
请问实现一个node子进程被杀死,然后自动重启代码的思路
var createWorker = function(){
var worker = fork(__dirname + 'worker.js')
worker.on('exit', function(){
console.log('Worker' + worker.pid + 'exited'); // 如果退出就创建新的worker createWorker()
})
}
在7.4的基础上,实现限量重启,比如我最多让其在1分钟内重启5次,超过了就报警给运维
- 思路大概是在创建worker的时候,就判断创建的这个worker是否在1分钟内重启次数超过5次
- 所以每一次创建worker的时候都要记录这个worker 创建时间,放入一个数组队列里面,每次创建worker都去取队列里前5条记录
- 如果这5条记录的时间间隔小于1分钟,就说明到了报警的时候了
如何实现进程间的状态共享,或者数据共享
我自己没用过Kafka这类消息队列工具,问了java,可以用类似工具来实现进程间通信,更好的方法欢迎留言
如果使用过koa、egg这两个Node框架,请简述其中的中间件原理,最好用代码表示一下
const Koa = require('koa')
const app = new Koa()
app.use((ctx, next) => {
console.log(1)
next()
console.log(3)
})
app.use((ctx) => {
console.log(2)
})
app.listen(3001)
执行结果是1=>2=>3
koa中间件实现源
// 注意其中的compose函数,这个函数是实现中间件洋葱模型的关键
// 场景模拟
// 异步 promise 模拟
const delay = async () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 2000);
});
}
// 中间间模拟
const fn1 = async (ctx, next) => {
console.log(1);
await next();
console.log(2);
}
const fn2 = async (ctx, next) => {
console.log(3);
await delay();
await next();
console.log(4);
}
const fn3 = async (ctx, next) => {
console.log(5);
}
const middlewares = [fn1, fn2, fn3];
// compose 实现洋葱模型
const compose = (middlewares, ctx) => {
const dispatch = (i) => {
let fn = middlewares[i];
if(!fn){ return Promise.resolve() }
return Promise.resolve(fn(ctx, () => {
return dispatch(i+1);
}));
}
return dispatch(0);
}
compose(middlewares, 1);
现在在重新过一遍node 12版本的主要API,有很多新发现,比如说
fs.watch这个模块,事件的回调函数有一个参数是触发的事件是事件名称,但是呢无论我增删改,都是触发rename事件(如果更改是update事件,删除delete事件该,重命名是rename事件多好)。后来网上找到一个node-watch模块,完美解决此问题,此模块增删改都有对应的事件,然后还高效的支持递归watch 文件
杂想
- crypto模块,可以考察基础的加密学知识,比如摘要算法有哪些(md5, sha1, sha256,加盐的md5,sha256等等),接着可以问如何用md5自己模拟一个加盐的md5算法, 接着可以问加密算法(crypto.createCiphe)中的aes,eds算法的区别,分组加密模式有哪些(比如ECB,CBC,为什么ECB不推荐),node里的分组加密模式是哪种(CMM),这些加密算法里的填充和向量是什么意思,接着可以问数字签名和https的流程(为什么需要CA,为什么要对称加密来加密公钥等等)
- tcp/ip,可以问很多基础问题,比如链路层通过什么协议根据IP地址获取物理地址(arp),网关是什么,ip里的ICMP协议有什么用,tcp的三次握手,四次分手的过程是什么,tcp如何控制重发,网络堵塞TCP会怎么办等等,udp和tcp的区别,udp里的广播和组播是什么,组播在node里通过什么模块实现。
- os,操作系统相关基础,io的流程是什么(从硬盘里读取数据到内核的内存中,然后内核的内存将数据传入到调用io的应用程序的进程内存中),冯诺依曼体系是什么,进程和线程的区别等等(我最近在看马哥linux教程,因为自己不是科班出身,听了很多基础的计算机知识,受益匪浅,建议去bilibili看)
- linux相关操作知识(node涉及到后台,虽然是做中台,不涉及数据库,但是基本的linux操作还是要会的)
- node性能监控
- 测试,因为用的egg框架,有很完善的学习单元测试的文档,省略这部分
- 数据库可以问一些比如事务的等级有哪些,mysql默认的事务等级是什么,会产生什么问题,然后考一些mysql查询的笔试题。。。和常用优化技巧,node的mysql的orm工具使用过没有...