前端面试常见问题汇总
前端面试常见问题汇总
CSS
1. @mixin 中的 @include 和 @extend 的区别(sass)
列举不同的清除浮动的技巧
/* 1. 添加新元素 */
<div class="outer">
<div class="div1"></div>
<div class="div2"></div>
<div class="div3"></div>
<div class="clearfix"></div>
</div>
.clearfix {
clear: both;
}
/* 2. 为父元素增加样式 */
.clearfix {
overflow: auto;
zoom: 1; // 处理兼容性
}
/* 3. :after 伪元素方法(作用于父元素) */
.outer {
zoom: 1;
&:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
}
CSS 实现移动端 1px 线条的绘制
- 先放大,再缩小
方法1:
.box:before {
position: absolute;
top: 0;
left: 0;
width: 100%;
right: 0;
height: 1px;
content: '';
-webkit-transform: scaleY(.5);
transform: scaleY(.5);
background-color: #e2e2e2;
}
方法2:
.box:before{
content: "";
pointer-events: none; /* 防止点击触发 */
box-sizing: border-box;
position: absolute;
width: 200%;
height: 200%;
left: 0;
top: 0;
border:1px solid #000;
-webkit-transform(scale(0.5));
-webkit-transform-origin: 0 0;
transform(scale(0.5));
transform-origin: 0 0;
}
缺点:
- 占据了该元素的伪元素,代码量相对较多
- input元素没有伪元素
2. 用 css 实现垂直居中(子元素宽高不确定?)(兼容ie8?)
- 方案一:使用css3的新属性transform:translate(x,y)属性
.eight {
position: absolute;
width: 80px;
height: 80px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
-webkit-transform: translate(-50%, -50%);
-moz-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
background: green;
}
这个方法可以不需要设定固定的宽高,在移动端用的会比较多,在移动端css3兼容的比较好
3. 如果设计中使用了非标准的字体,你该如何去实现?
- 图片替代
- web: fonts在线字库
- @font-face
4. 行内元素有哪些?块级元素有哪些?空元素有哪些?
- 行内元素有:span、img、input、select、strong
- 块级元素有:div、ul、ol、dl、dt、dd、h1、h2、h3、h4、p...
- 常见的空元素有:br、hr、img、input、link、meta、、base、area、command、embed、keygen、param、source、track、wbr...
5. 两个div进行两列布局,要求高度不定(父元素也是),我需要两个div实时等高,即左边div高度被其内子元素撑高时,右边的div高度和左边同步【等高布局】
- 父元素 display: table; 左边定宽 width: 200px; display: table-cell;右边 width: 100%;
- 父元素 display: flex; align-item: stretch; 左边定宽 width: 200px; 右边设置项伸展属性: flex-grow: 1;
6. 按照下图,说说flexbox的原理
JavaScript
1、编写一个函数将列表子元素顺序反转
2、请实现一个Event类,继承自此类的对象都会拥有两个方法on,off,once和trigger
3、如何创建一个ajax请求(https://zhuanlan.zhihu.com/p/27776535)
创建Ajax的过程: 1)创建XMLHttpRequest对象(异步调用对象)
var xhr = new XMLHttpRequest();
2)创建新的Http请求(方法、URL、是否异步)
xhr.open('get', 'example.php', false);
3)设置响应HTTP请求状态变化的函数 onreadystatechange事件中readyState属性等于4。响应的HTTP状态为 200(OK)或者304(Not Modified)。 4)发送http请求
xhr.send(data);
5)获取异步调用返回的数据
详细过程:
const xhr = new XMLHttpRequest();
xhr.open(method, url, async);
// send 方法发送请求,并接受一个可选参数
// 当请求方式为 post 时,可以将请求体的参数传入
// 当请求方式为 get 时,可以不传或传入 null
// 不管是 get 还是 post,参数都需要通过 encodeURIComponent 编码后拼接
xhr.send(data);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
// HTTP 状态在 200 -300 之间表示请求成功
// HTTP 状态为 304 表示内容未发生改变,可直接从缓存中获取
if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
console.log('请求成功', xhr.responseText);
}
}
}
// 超时时间单位为毫秒
xhr.timeout = 1000
// 当请求超时时,会触发 ontimeout 方法
xhr.ontimeout = () => console.log('请求超时')
4. javascript 连等赋值问题
var a = { n: 1 };
var b = a;
a.x = a = { n: 2 }
console.log(a.x); // undefined
console.log(b.x); // {n:2}
解析:
解析器在接收到 a = a.x = { n: 2 } 这个语句后,会这样子做:
-
在找到 a 和 a.x 的指针。如果已有指针,那么不改变它。如果没有指针, 即那个变量还没被申明,那么就创建它,指向 null。 a 是有指针的,指向
{n: 1}; a.x 是没有指针的,所以创建它,指向 null。 -
然后把上面找到的指针,都指向最右赋值的那个指,即是
{n: 2}。
5. JS数组深浅复制
浅拷贝:
-
slice实现
var arr = ['old', '1' , true, null, undefined];
var new_arr = arr.slice();
new_arr[0] = 'new';
console.log(arr);
console.log(new_arr);
-
concat实现
var arr = ['old', '1' , true, null, undefined];
var new_arr = arr.concat();
new_arr[0] = 'new';
console.log(arr);
console.log(new_arr);
注释:如果数组元素是基本类型,就会拷贝一份新的;但是如果数组元素是对象或者数组,就只会拷贝引用(类似指针),修改其中一个就会影响另外一个。例如:
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}];
var new_arr = arr.concat();
new_arr[0] = 'new';
new_arr[3][0] = 'new1';
console.log(arr) // ["old", 1, true, ['new1', 'old2'], {old: 1}]
console.log(new_arr) // ["new", 1, true, ['new1', 'old2'], {old: 1}]
深拷贝
-
JSON.stringify实现数组深拷贝
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}];
var new_arr = JSON.parse(JSON.stringify(arr));
new_arr[0] = 'new';
new_arr[3][0] = 'new1';
console.log(arr) // ["old", 1, true, ['old1', 'old2'], {old: 1}]
console.log(new_arr) // ["new", 1, true, ['new1', 'old2'], {old: 1}]
缺点:简单粗暴,但是不能拷贝函数,不推荐。
手动实现深浅拷贝
- 浅拷贝
var shallowCopy = function (obj) {
// 判断是否是数组或者对象
if (typeof obj !== 'object') {
return obj;
}
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key]
}
}
return newObj;
}
- 深拷贝
var deepCopy = function (obj) {
// 判断是否是数组或者对象
if (typeof obj !== 'object') {
return obj;
}
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
6. 实现dom的增删改查复制
// 创建节点
createDocumentFragment()
createElement()
createTextNode()
// 添加 移除 替换 插入
appendChild()
removeChild()
replaceChild()
insertBefore()
// 查找
document.getElementsByTagName()
document.getElementsByName()
document.getElementsByClassName()
document.getElementById()
document.querySelector()
document.querySelectorAll()
7. 谈谈您对 MVC、MVVM 的理解
MVC
- 视图(View):用户界面。
- 控制器(Controller):业务逻辑。
- 模型(Model):数据保存。
各部分之间的通讯方式如下:

1、View 传送指令到 Controller
2、Controller 完成业务逻辑后,要求 Model 改变状态
3、Model 将新的数据发送到 View,用户得到反馈
特点:
所有通讯都是单向的。
MVP
MVP 模式将 Controller 改名为 Presenter,同时改变了通信方向。

1、各部分之间的通讯,都是双向的。
2、View 与 Model 不发生联系,都通过 Presenter 传递。
3、View 非常薄,不部署任何业务逻辑,被称为 “被动视图”(Passive View),即没有任何主动性,而 Presenter 非常厚,所有逻辑都部署在那里。
MVVM
MVVM 模式将 Presenter 改名为 ViewModel,基本上与 MVP 模式完全一致。

唯一的区别是,它采用双向绑定(data-binding):View的变动,自动反应在 ViewModel,反之亦然。
8. vue 的生命周期钩子函数
-
- beforeCreate
-
- created
-
- beforeMount
-
- mounted
-
- beforeUpdate
-
- updated
-
- actived
-
- deactivated
-
- beoforeDestory
-
- destoryed
9. 使用正则实现电话号码和邮箱的验证
10. 使用正则实现trim函数删除字符串前后空格
function trim(str) {
return str.replace(/(^\s*)|(\s*$)/g, '');
}
11. BOM对象,DOM对象
BOM对象:
- window对象
- location对象
- navigator对象
- screen对象
- history对象
DOM对象
-
常用的DOM方法:
- getElementById(id)
- getElementsByTagName()
- appendChild(node)
- removeChild(node)
- replaceChild()
- insertChild()
- createELement()
- createTextNode()
- getAttribute()
- setAttribute()
-
常用的DOM属性:
- innerHTML 节点(元素)的文本值
- parentNode 节点(元素)的父节点
- childNodes
- attributes 节点(元素)的属性节点
参考: HTML DOM 方法
12. 任务队列优先级(Promise、setTimout、setImmediate)
下面代码输出结果?为什么?
demo1:
setTimeout(() => console.log('a'), 0);
var p = new Promise((resolve) => {
console.log('b');
resolve();
});
p.then(() => console.log('c'));
p.then(() => console.log('d'));
console.log('e');
// 结果:b e c d a
// 任务队列优先级:promise.Trick()>promise的回调>setTimeout>setImmediate
demo2:
async function async1() {
console.log("a");
await async2(); //执行这一句后,await会让出当前线程,将后面的代码加到任务队列中,然后继续执行函数后面的同步代码
console.log("b");
}
async function async2() {
console.log( 'c');
}
console.log("d");
setTimeout(function () {
console.log("e");
},0);
async1();
new Promise(function (resolve) {
console.log("f");
resolve();
}).then(function () {
console.log("g");
});
console.log('h');
// 谁知道为啥结果不一样?????????????
// 直接在控制台中运行结果: d a c f h g b e
// 在页面的script标签中运行结果:d a c f h b g e
13. js中事件代理:
<ul id="ui-test">
<li>item1</li>
<li>item2</li>
<li>item3</li>
<li>item4</li>
<li>item5</li>
<li>item6</li>
<li>item7</li>
<li>item8</li>
<li>item9</li>
<li>item10</li>
</ul>
<script>
document.addEventListener('DOMContentLoaded', function(event) {
var oUL = document.querySelector('#ui-test')
// var oLI = document.querySelectorAll('#ui-test > li')
// for (var i = 0, len = oLI.length; i < len; i++) {
// console.log(oLI[i]);
// oLI[i].addEventListener('click', function() {
// console.log(this.innerHTML)
// })
// }
// 代理模式
oUL.addEventListener('click', function(ev) {
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if (target.nodeName.toLowerCase() === 'li') {
console.log(target);
console.log(target.innerHTML)
}
})
});
</script>
14. js实现数组去重怎样实现?
- 1、创建一个临时数组来保存数组中已有的元素
- 2、使用哈希表存储已有的元素
- 3、使用
indexOf判断数组元素第一次出现的位置是否为当前位置 - 4、先排序再去重
- 5、使用
Set
找出数组中的最大值
- reduce
var arr = [6, 4, 1, 8, 2, 11, 3];
function max (prev, next) {
return Math.max(prev, next)
}
console.log(arr.reduce(max));
- apply
var arr = [6, 4, 1, 8, 2, 11, 3];
console.log(Math.max.apply(null, arr)); // 11
- ES6
var arr = [6, 4, 1, 8, 2, 11, 3];
function max (arr) {
return Math.max(...arr);
}
console.log(max(arr));
打乱数组的方法
var arr = [];
for(var i = 0; i < 100; i++) {
arr[i] = i;
}
arr.sort(function() {
return 0.5 - Math.random();
})
console.log(arr)
数组扁平化
var arr = [1, [2, [3, 4]]]
function flatten(arr) {
while(arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console.log(flatten(arr)); // [1, 2, 3, 4]
简单的字符串模板
var TemplateEngine = function(tpl, data) {
var re = /<%([^%>]+)?%>/g,
match;
while(match = re.exec(tpl)) {
console.log('match0: ', match[0]);
console.log('match1: ', match[1]);
tpl = tpl.replace(match[0], data[match[1]])
}
return tpl;
}
var template = '<p>Hello, my name is <%name%>. I\'m <%age%> years old.<p>';
console.log(TemplateEngine(template, {
name: 'ly2011',
age: 16
}))
函数函数柯里化
函数柯里化:在一个函数中首先填充几个参数(然后再返回一个新函数)的技术称为柯里化(Currying)。
- 通用的柯里化方法
// 通用的函数柯里化构造方法
function curry(fn) {
// 新建 args 保存参数,注意,第一个参数应该是要科利华的函数,所以 args 里面去掉第一个
var args = [].slice.call(arguments, 1);
/* // 新建 _fn 函数作为返回值
var _fn = function() {
// 参数长度为 0, 执行 fn 函数,完成该函数的功能
if(arguments.length === 0) {
return fn.apply(this, args)
} else {
// 否则,存储参数到闭包中,返回本函数
[].push.apply(this, arguments);
return _fn;
}
}
return _fn;*/
return function() {
var newArgs = args.concat([].slice.call(arguments))
return fn.apply(null, newArgs)
}
}
function add() {
return [].reduce.call(arguments, function(a, b) {
return a + b;
} )
}
function multiply() {
return [].reduce.call(arguments, function(a, b) {
return a * b;
})
}
console.log(curry(add, 1, 2, 3, 4, 5)(1)) // 16
console.log(curry(multiply, 1, 2, 3, 4)(5)) // 120
- 简单的用法
function multiply(x, y) {
if (y === undefined) {
return function(z) {
return x * z
}
} else {
return x * y
}
}
console.log(multiply(3, 4)); // 12
console.log(multiply(3)(4)); // 12
柯里化的作用
- 延迟计算
- 参数复用
- 动态创建函数
多个标签页之间通信(如何实现浏览器中多个标签页之间的通信?)
websocket协议、localstorage、以及使用 html5浏览器的新特性 SharedWorker。
请描述一下 cookies, sessionStorage 和 localStorage 的区别?
-
cookie是网站为了标示用户身份而储存在用户本地终端(Client Slide) 上的数据(通常经过加密) -
cookie数据始终在同源的 http 请求中携带(即使不需要),既会在浏览器和服务器间来回传递 -
sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存
存储大小:
-
cookie数据大小不能超过4k -
sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到 5M 或 更大
有效时间:
-
localStorage存储持久数据,浏览器关闭后数据不丢失除非浏览器主动删除数据。 -
sessionStorage数据在当前浏览器窗口关闭后自动删除 -
cookie设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
js实现循环 setTimeout输出 0, 1, 2, 3, 4
for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
结果为: 5,5,5,5,5
解决办法
- 第一种:闭包
for(var i = 0; i < 5; i++) {
(function(j) { // j = i
setTimeout(function() {
console.log(j)
}, 1000)
})(i)
}
- 第二种:值类型传递
var output = function(i) {
setTimeout(function() {
console.log(i)
}, 1000)
}
for(var i = 0; i < 5; i++) {
output(i) // 这里传过去的 i 被复制了
}
- 第三种:es6 let
for(let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, 1000);
}
如果要让0-4一秒一秒地输出来呢?
- 第一种:
for(var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j)
}, 1000 * j ) // 这里修改 0-4 的定时器时间
})(i)
}
第二种:
const tasks= []
const output = (i) => new Promise((resolve) => {
setTimeout(() => {
console.log(i)
resolve() // 这里一定要 resolve, 否则代码不会按预期 work
}, 1000 * i)
})
// 生成全部的异步操作
for(var i = 0; i < 5; i++) {
tasks.push(output(i))
}
// 异步操作完成之后,输出最后的i
Promise.all(tasks).then(() => {
setTimeout(() => {
console.log(i)
}, 1000)
})
- 第三种
// 模拟其他语言中的 sleep,实际上可以是任何异步操作
const sleep = (timeountMS) => new Promise((resolve) => {
setTimeout(resolve, timeountMS);
});
(async () => { // 声明即执行的 async 函数表达式
for (var i = 0; i < 5; i++) {
await sleep(1000);
console.log(new Date, i);
}
await sleep(1000);
console.log(new Date, i);
})();
15. 谈谈对this的理解
1)this总是指向函数的直接调用者(而非间接调用者) 2)如果有new关键字,this指向new出来的那个对象 3)在事件中,this指向目标元素,特殊的是IE的attachEvent中的this总是指向全局对象window。
16. eval是做什么的?
它的功能是把对应的字符串解析成JS代码并运行;应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)。
17. ['1', '2', '3'].map(parseInt)答案是多少?
[1, NaN, NaN]
解析:
-
Array.prototype.map() array.map(callback[, thisArg]) callback函数的执行规则 参数:自动传入三个参数 currentValue(当前被传递的元素) index(当前被传递的元素的索引) array(调用map方法的数组)
-
parseInt parseInt方法接收两个参数 第三个参数['1', '2', '3']将被忽略。parseInt方法将会通过以下方式被调用 parseInt('1', 0) parseInt('2', 1) parseInt('3', 2)
parseInt的第二个参数radix为0时,ECMAScript5将string作为十进制数字的字符串解析。 parseInt的第二个参数radix为1时,解析结果为NaN; parseInt的第二个参数radix在2-36之间时,如果string参数的第一个字符(除空白以外),不属于radix指定进制下的字符,解析结果为NaN。 parseInt('3', 2)执行时,由于 '3' 不属于二进制字符,解析结果为 NaN。
18. 什么是闭包(closure),为什么要用它?
闭包指的是一个函数可以访问另一个函数作用域中变量。常见的构造方法,是在一个函数内部定义另外一个函数。内部函数可以引用外层的变量;外层变量不会被垃圾回收机制回收。 注意:闭包的原理是作用域链,所以闭包访问的上级作用域中的变量是个对象,其值为其运算结束后的左后一个值。 优点:避免全局变量污染。 缺点:容易造成内存泄漏。
例子:
function makeFunc() {
var name = 'Mozilla'
function displayName() {
console.log(name)
}
return displayName
}
var myFunc = makeFunc()
myFunc() // 输出Mozilla
myFunc变成一个闭包。闭包是一种特殊的对象。它由两部分构成: 函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。在我们的例子中,myFunc是一个闭包,由displayName函数和闭包创建时存在的 'Mozilla' 字符串组成。
19. JS延迟加载的方式有哪些?
defer和async、动态创建DOM方式、按需异步载入JS defer: 延迟脚本。立即下载,但延迟执行(延迟到整个页面都解析完毕后再运行),按照脚本出现的先后顺序执行。 async: 异步脚本。下载完立即执行,但不保证按照脚本出现的先后顺序执行。
20. 什么是跨域问题,如何解决跨域问题?
21. 页面编码和被请求的资源编码不一致如何处理?
若请求的资源编码,如外引js文件编码与页面编码不同。可根据外引资源编码方式定义为 charset="utf-8"或"gbk"。 比如:http://www.yyy.com/a.html 中嵌入了一个http://www.xxx.com/test.js a.html 的编码是gbk或gb2312的。 而引入的js编码为utf-8的 ,那就需要在引入的时候
22. 渐进增强与优雅降级
渐进增强:针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进,达到更好的用户体验。 优雅降级:一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。
23. 在一个DOM上同时绑定两个点击事件:一个用捕获,一个用冒泡。事件会执行几次,先执行冒泡还是捕获?
- 该DOM上的事件如果被触发,会执行两次(执行次数等于绑定次数)
- 如果该DOM是目标元素,则按事件绑定顺序执行,不区分冒泡/捕获
- 如果该DOM是处于事件流中的非目标元素,则先执行捕获,后执行冒泡
24. 事件的代理/委托
- 事件委托是指将事件绑定目标元素到父元素上,利用冒泡机制触发该事件
优点:
- 可以减少事件注册,节省大量内存占用
- 可以将事件应用于动态添加的子元素上
缺点:
- 使用不当会造成事件在不应该触发时触发
26. javascript如何实现继承?
27. 给出一个字符串 'abfecg', 如何快速反转?
28.给出一个不存在重复元素的数组 [...'item'...], 如何最快速插入一个新元素 'itemNew'?
29. 说说普通函数与箭头函数的区别?
30. 说说循环数组和对象,你用过哪些方法?
-
for
-
forEach
-
map
-
filter
-
some
对象循环: for ... in
- every
31. 你对浏览器的兼容性有了解过吗?
32. 哪些操作会引起浏览器重绘(repaint)和重排(reflow), 延伸:重绘和重排谁更消耗性能?
- postion:absolute; left:100px;会不会引起?
- translateX:100px;会不会引起?
- getBoundingClientRect会不会引起?
- getClientWidth、getClientHeight会不会引起?
原理:
触发重排 页面布局和元素几何属性的改变就会导致重排 下列情况会发生重排
- 页面初始渲染
- 添加/删除可见DOM元素
- 改变元素位置
- 改变元素尺寸(宽、高、内外边距、边框等)
- 改变元素内容(文本或图片等)
- 改变窗口尺寸
以下属性或方法会刷新渲染队列
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- clientTop、clientLeft、clientWidth、clientHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- getComputedStyle()(IE中currentStyle)
减少重绘和重排的原理很简单:
- 元素脱离文档
- 改变样式
- 元素回归文档
HTTP
1、从浏览器地址栏输入url到显示页面的步骤(以HTTP为例)
主要过程是: 浏览器解析->查询缓存->dns查询->建立链接->服务器处理请求->服务器发送响应->客户端收到页面->解析HTML->构建渲染树->开始显示内容(白屏时间)->首屏内容加载完成(首屏时间)->用户可交户(DOMContentLoaded)->加载完成(load)
- 在浏览器地址栏输入URL
- 浏览器查看
缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤- 如果资源未缓存,发起新请求
- 如果已缓存,检验是否足够新鲜,足够新鲜直接提供给客户端,否则与服务器进行验证。
- 检验新鲜通常有两个HTTP头进行控制
Expires和Cache-Control:- HTTP1.0提供Expires,值为一个绝对时间表示缓存新鲜日期
- HTTP1.1增加了Cache-Control: max-age=,值为以秒为单位的最大新鲜时间
- 浏览器解析URL获取协议,主机,端口,path
- 浏览器组装一个HTTP(GET)请求报文
- 浏览器获取主机ip地址,过程如下:
- 浏览器缓存
- 本机缓存
- hosts文件
- 路由器缓存
- ISP DNS缓存
- DNS递归查询(可能存在负载均衡导致每次IP不一样)
-
打开一个socket与目标IP地址,端口建立TCP链接,三次握手如下:
- 客户端发送一个TCP的SYN=1,Seq=X的包到服务器端口
- 服务器发回SYN=1, ACK=X+1, Seq=Y的响应包
- 客户端发送ACK=Y+1, Seq=Z
- TCP链接建立后发送HTTP请求
- 服务器接受请求并解析,将请求转发到服务程序,如虚拟主机使用HTTP Host头部判断请求的服务程序
- 服务器检查HTTP请求头是否包含缓存验证信息如果验证缓存新鲜,返回304等对应状态码
- 处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作
- 服务器将响应报文通过TCP连接发送回浏览器
- 浏览器接收HTTP响应,然后根据情况选择 关闭TCP连接或者保留重用,关闭TCP连接的四次握手如下:
- 主动方发送Fin=1, Ack=Z, Seq= X报文
- 被动方发送ACK=X+1, Seq=Z报文
- 被动方发送Fin=1, ACK=X, Seq=Y报文
- 主动方发送ACK=Y, Seq=X报文
- 浏览器检查响应状态吗:是否为1XX,3XX, 4XX, 5XX,这些情况处理与2XX不同
- 如果资源可缓存,进行缓存
- 对响应进行解码(例如gzip压缩)
- 根据资源类型决定如何处理(假设资源为HTML文档)
- 解析HTML文档,构建DOM树,下载资源,构造CSSOM树,执行js脚本,这些操作没有严格的先后顺序,以下分别解释
-
构建DOM树:
- Tokenizing:根据HTML规范将字符流解析为标记
- Lexing:词法分析将标记转换为对象并定义属性和规则
- DOM construction:根据HTML标记关系将对象组成DOM树
- 解析过程中遇到图片、样式表、js文件,启动下载
-
构建CSSOM树:
- Tokenizing:字符流转换为标记流
- Node:根据标记创建节点
- CSSOM:节点创建CSSOM树
-
根据DOM树和CSSOM树构建渲染树:
- 从DOM树的根节点遍历所有可见节点,不可见节点包括:1)
script,meta这样本身不可见的标签。2)被css隐藏的节点,如display: none - 对每一个可见节点,找到恰当的CSSOM规则并应用
- 发布可视节点的内容和计算样式
- 从DOM树的根节点遍历所有可见节点,不可见节点包括:1)
-
js解析如下:
- 浏览器创建Document对象并解析HTML,将解析到的元素和文本节点添加到文档中,此时document.readystate为loading
- HTML解析器遇到没有async和defer的script时,将他们添加到文档中,然后执行行内或外部脚本。这些脚本会同步执行,并且在脚本下载和执行时解析器会暂停。这样就可以用document.write()把文本插入到输入流中。同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作script和他们之前的文档内容
- 当解析器遇到设置了async属性的script时,开始下载脚本并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器不会停下来等它下载。异步脚本禁止使用document.write(),它们可以访问自己script和之前的文档元素
- 当文档完成解析,document.readState变成interactive
- 所有defer脚本会按照在文档出现的顺序执行,延迟脚本能访问完整文档树,禁止使用document.write()
- 浏览器在Document对象上触发DOMContentLoaded事件
- 此时文档完全解析完成,浏览器可能还在等待如图片等内容加载,等这些内容完成载入并且所有异步脚本完成载入和执行,document.readState变为complete,window触发load事件
- 显示页面(HTML解析过程中会逐步显示页面)
安全
综合
1、如何进行网站性能优化
- content 方面
- 减少
HTTP请求:合并文件、CSS精灵、inline Image - 减少
DNS查询:DNS缓存、将资源分布到恰当数量的主机名 - 减少
DOM元素数量
- server 方面
- 使用
CDN - 配置
ETag - 对组件使用
Gzip压缩
- cookie 方面
- 减少
Cookie大小
- css 方面
- 将样式表放到页面顶部
- 不使用 CSS 表达式
- 使用 不使用 @import
- javascript 方面
- 将脚本放到页面底部
- 将 javascript 和 css 从外部引入
- 压缩 javascript和css
- 删除不需要的脚本
- 减少 DOM 访问
- 图片方面
- 优化图片:根据实际颜色需要选择色深、压缩
- 优化 css 精灵
- 不要在 HTML 中拉伸图片
渲染
如何提高webpack的编译速度?
- 版本升级
- 利用多核(happypack)
- 利用缓存(dll)
- 开发环境去掉组件懒加载、不压缩图片、不分离css文件
算法
- 冒泡排序
解析:
1. 比较相邻的两个元素,如果前一个比后一个大,则交换位置。
2. 第一轮的时候最后一个元素应该是最大的一个。
3. 按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。
function sort(elements) {
for(var i = 0; i < elements.length - 1; i++) {
for(var j = 0; j < elements.length - i - 1; j++) {
if (elements[j] > elements[j + 1]) {
var swap = elements[j];
elements[j] = elements[j + 1];
elements[j + 1] = swap;
}
}
}
}
var elements = [3, 1, 5, 7, 2, 4, 9, 6, 10, 8];
console.log('before: ', elements)
sort(elements)
console.log('after: ', elements)
- 快速排序
解析:
快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。然后递归调用,在两边都实行快速排序。
function quickSort(elements) {
if (elements.length <= 1) {
return elements;
}
var pivotIndex = Math.floor(elements.length / 2);
var pivot = elements.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
for (var i = 0; i < elements.length; i++) {
if (elements[i] < pivot) {
left.push(elements[i])
} else {
right.push(elements[i])
}
}
return quickSort(left).concat([pivot] , quickSort(right))
}
var elements = [5, 6, 2, 1, 3, 8, 7, 1.2, 5.5, 4.5]
console.log(quickSort(elements))
- 查找字符串中出现次数最多的字符和次数
例如:sdddrtkjsfkasjdddj中出现最多的字符是d,出现了6次
var str = "sdddrtkjsfkkkasjdddj";
var max = 0;
var char;
function Search(str) {
var json = {};
for (var i = 0; i < str.length; i++) {
if (!json[str[i]]) {
json[str[i]] = str[i];
} else {
json[str[i]] += str[i];
}
}
for (var i = 0; i < str.length; i++) {
if (json[str[i]].length > max) {
max = json[str[i]].length;
char = str[i];
}
}
console.log('json: ', json)
console.log("出现次数最多的字符是" + char + ",出现了" + max + "次")
}
Search(str);
- 计算数组中每个元素出现的次数
// 计算数组中每个元素出现的次数
var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice']
var countedNames = names.reduce(function(allNames, name) {
if(name in allNames) {
allNames[name]++
} else {
allNames[name] = 1
}
return allNames;
}, {})
console.log(countedNames)
- for(var i=0,j=0;i<10,j<6;i++,j++) { sum = i+j }, sum值最后是多少?
var sum = 0;
for(var i=0,j=0;i<10,j<6;i++,j++) {
sum = i+j
}
console.log(sum)
解析: 答案是: 10, 首先每次for循环的i和j值是相等的: 第一次:i=0, j =0; 符合条件, sum = i + j = 0; 第二次:i=1,j=1;符合条件, sum = i + j = 2; 第三次:i=2,j=2;符合条件, sum = i + j = 4; 第四次:i=3,j=3;符合条件,sum = i + j = 6; 第五次:i=4,j=4;符合条件, sum = i + j = 8; 第六次:i=5,j=5;符合条件,sum = i + j =10; 第七次:i=6,j=6;不符合条件((这里需要注意,循环继续的判断依据以分号前的最后一项为准,即判断j<6符不符合条件),循环结束。
注意: 这里值得一提的是 如果把条件i<10,j<6;改成i<6,j<10; 结果将完全不同,此时循环执行到j<10才会结束,此时sum=18。 参考地址
- 说下你所知道的排序方法? 冒泡排序、快排、二分法、广度遍历、深度遍历...
情景题
如何限制用户在一定时间内(例如2~3s)重复点击按钮?
- 利用防抖/节流(推荐)
- 利用setTimeout定时器
- rxjs