混子前端分享一些面试常问干货,同时也帮助大家复习和巩固一波基础ps:文章全部由个人总结,知识点分析不全面请自行查漏补缺
页面布局
你必须知道的经典问题:
假设高度已知,写出三栏布局,其中左栏/右栏宽度各为300px;中间自适应。
(ps:此题作为一个小白也要至少答出3种方法,以下分享连混子前端都知道的5种方法)
- 浮动float [优点:兼容性好;缺点:脱离文档流]
HTML部分:
<section class="layout float">
<article class='left-right-center'>
<div class="left"></div>
<div class="right"></div>
<div class="center"></div>
</article>
</section>
CSS部分:
.layout article div{
height: 100px;
}
.layout.float .left{
float: left;
width: 300px;
background: red;
}
.layout.float .right{
float: right;
width: 300px;
background: blue;
}
.layout.float .center{
background: yellow;
}
- 定位absolute [优点:快捷配合JS使用;缺点:子元素脱离文档流,可使用性差]
HTML部分:
<section class="layout absolute">
<article class='left-right-center'>
<div class="left"></div>
<div class="center"></div>
<div class="right"></div>
</article>
</section>
CSS部分:.layout article div{
height: 100px;
}
.layout.absolute .left,.layout.absolute .right,.layout.absolute .center{
position: absolute;
}
.layout.absolute .left{
left: 0px;
width: 300px;
background: red;
}
.layout.absolute .right{
right: 0px;
width: 300px;
background: blue;
}
.layout.absolute .center{
left: 300px;
right: 300px;
background: yellow;
}
- 弹性盒flex [完美解决上面2个布局的缺点]
HTML部分:
<section class="layout flexbox">
<article class='left-right-center'>
<div class="left"></div>
<div class="center"></div>
<div class="right"></div>
</article>
</section>
CSS部分:
.layout article div{
height: 100px;
}
.layout.flexbox .left-right-center{
display: flex;
}
.layout.flexbox .left{
width: 300px;
background: red;
}
.layout.flexbox .center{
flex: 1;
background: yellow;
}
.layout.flexbox .right{
width: 300px;
background: blue;
}
- 表格table [优点:兼容性好;缺点:其中某个单元格高度超出,其它两栏也超出]
HTML部分:
<section class="layout table">
<article class='left-right-center'>
<div class="left"></div>
<div class="center"></div>
<div class="right"></div>
</article>
</section>
CSS部分:
.layout article div{
height: 100px;
}
.layout.table .left-right-center{
width: 100%;
display: table;
height: 100px;
}
.layout.table .left-right-center>div{
display: table-cell;
}
.layout.table .left{
width: 300px;
background: red;
}
.layout.table .center{
background: yellow;
}
.layout.table .right{
width: 300px;
background: blue;
}
- 网格grid [不用栅格来模拟网格布局 / 代码量简化很多]
HTML部分:
<section class="layout grid">
<article class='left-right-center'>
<div class="left"></div>
<div class="center"></div>
<div class="right"></div>
</article>
</section>
CSS部分:
.layout article div{
height: 100px;
}
.layout.grid .left-right-center{
display: grid;
width: 100%;
grid-template-rows: 100px;
grid-template-columns: 300px auto 300px;
}
.layout.grid .left{
background: red;
}
.layout.grid .center{
background: yellow;
}
.layout.grid .right{
background: blue;
}补充:未知高度时,弹性盒flex / 表格table很适用,不修改代码
思考:上下高度固定,中间自适应(ps:混子前端都会的,你也必须会!)
CSS盒模型
你必须知道的盒子模型:
标准盒模型 + IE盒模型
标准模型:不包含padding和border值,content即盒子width / height
IE盒模型:盒子width / height包含padding和border值
CSS如何设置这两种模型:
box-sizing: content-box; // 标准盒模型(默认)
box-sizing: border-box; // IE盒模型JS如何设置盒模型对应宽和高
1、dom.style.width/height; // 获取内联样式宽/高[局限性]
2、dom.currentStyle.width/height; // 获取是渲染后的宽/高 [只有ie支持]3、window.getComputedStyle(dom).width/height; // 支持chrome、firefox
4、dom.getBoundingClientRect().width/height[left/right/top/bottom]; // 很少人知道,获取渲染后width/height,计算一个元素绝对位置,根据视窗(viewport),常用获取left/right/top/bottom边距重叠
常见问题:子元素高度100px,margin-top: 10px,此时父元素高度 ?
解:父元素是100px;如果父元素设置overflow: hidden,就是110px;
[就是给父元素创建BFC(块级格式化)]
BFC:边距重叠解决方案
基本概念:块级格式化上下文
原理(BFC渲染规则):
- BFC垂直方向边距不发生重叠
- BFC区域不会与浮动元素重叠(清除浮动)
- BFC在页面是独立容器,外面元素不会影响里面元素,里面元素不会影响外面元素
- 计算BFC高度时候,浮动元素也会参与计算
怎样创建BFC,除了给父元素添加overflow: hidden创建BFC,还有那些?
- float值除none以外
- position值不是static或relative就创建了BFC
- display: inline-block,table-cell和table相关的几个属性都可以创建
- overflow不是visiable也可以创建
DOM事件
你必须知道几个关于DOM的知识点
DOM事件级别(标准级别)
一代:DOM 0级
element.onClick = function(){
// TODO
}为什么从DOM 0级直接到DOM 2级了?DOM 1级标准没有设定与事件相关的东西
二代:DOM 2级
element.addEventListener('click', function(){
// TODO
}, false);三代:DOM 3级
新增了鼠标,键盘等事件
element.addEventListener('keyup', function(){
// TODO
}, false);事件模型
DOM事件流
浏览器在为当前页面与用户交互过程中,如:点击鼠标是怎样传到页面上(如何响应的)?混子前端认为分为三个阶段:
- 捕获[事件通过捕获到达目标元素]
- 目标阶段[点击按钮]
- 冒泡到window对象[目标阶段上传到window对象]
DOM事件捕获的具体流程
捕获:window > document > HTML > body > HTML结构 > 一层层传递到目标元素
冒泡:捕获反过来就是冒泡,从目标元素传递到window
Event对象
- event.perventDefault()阻止默认事件,如:<a>标签绑定click事件,点击会阻止跳转默认行为
- event.stopPropagation()阻止冒泡行为
- event.stopImmediatePropagation(),如:button绑定两个click事件(a,b),现在想a执行后不执行b[在a响应函数添加event.stopImmediatePropagation(),会阻止b的执行],用来阻断
- event.currentTarget当前绑定事件元素
- event.target当前被点击元素
如:ul > li 点击触发,给ul绑定onclick方法,此时:
target:<li>
currentTarget:<ul>
自定义事件
var eve = new Event('custome'); // 声明自定义事件
ev.addEventListener('custome', function(){
console.log('custome');
});
ev.dispatchEvent(eve); // dispatchEvent api触发HTTP协议
协议主要特点
- 简单快速
- 灵活
- 无连接
- 无状态
- uri是固定的,图片等(简单快速)
- http头部分有类型,通过http协议完成不同数据类型传输(灵活)
- 链接一次会断掉,不会保持链接(无连接)
- 客户端和服务端请求图片,http协议帮助建立链接和传输,完成后断开,下次客户端在过来,服务端是没办法区分上次和这次是不是同一个人,即服务端没记录(无连接)
补充:网站现在记住的是身份,不是http协议做到的,而是通过服务端加session做到的
HTTP报文
客户端输入url,服务端响应;
报文分为2部分,即:请求报文 响应报文,如图所示:
HTTP方法
GET:获取资源
POST:传输资源
PUT:更新资源
DELETE:删除资源
HEAD:获得报文首部
POST和GET的区别
- GET在浏览器回退时是无害的,而POST会再次提交请求
- GET产生的URL地址可以被收藏,而POST不可以
- GET请求会被浏览器主动缓存,而POST不会,除非手动设置
- GET请求只能进行URL编码,而POST支持多种编码方式
- GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留
- GET请求在URL中传递的参数是有长度限制的(2kb),而POST没有限制
- 对参数的数据类型,GET只接受ASCII字符,而POST没有限制
- GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息
- GET参数通过URL传递,而POST放在Request body中
HTTP状态码
- 1xx:指示信息
- 2xx:成功
- 3xx:重定向
- 4xx:客户端错误
- 5xx:服务器错误
如果你对自己有追求,那以下你也要知道:
200:客户端请求成功
206:客户发送了一个带有Range头的GET请求,服务器完成;如:客户端发起0-10000字节请求,服务器返回206,服务端会根据Range头部截一部分,响应回客户端(客户端要求的Range范围)
301:所请求页面已经转移至新的url(永久重定向)
302:所请求页面已经临时转移至新的url(临时重定向)
304:客户端口有缓冲的文档并发出了一个条件性的请求,服务器告诉客户端,原来缓冲的文档还可以继续使用 [ 服务器告诉客户端有缓存,可以取缓存用,不用在服务器取 ]
400:客户端有语法错误,不能被服务器所理解
401:请求未经授权
403:对被请求页面访问禁止
404:资源不存在
500:服务器错误
503:服务器临时过载或宕机
HTTP持久连接
HTTP协议采用“请求 -- 应答”模式,当使用普通模式,即非keep-alive(持久连接)时,每个请求/应答客户端和服务器都要新建一个连接,完成之后立即断开连接(HTTP无连接协议)
当使用keep-alive模式(持久连接)时,keep-alive功能使客户端到服务端的连接持久有效,当出现对服务器的后续请求时,keep-alive功能避免了建立或者重新建立连接
注意:持久连接1.1版本才支持,1.0版本不支持
管线化
当使用持久连接的情况下,某个连接上消息的传递类似于:
请求1 > 响应1 > 请求2 > 响应2 > 请求3 > 响应3
某个连接上的消息变成了类似以下这样(管线化):
[请求1 > 请求2 > 请求3 ] > [响应1 > 响应2 > 响应3]
注意:
- 只有GET和HEAD请求可以管线化,POST有所限制
- 服务端支持才可以,chrome和firefox默认并未开启管线化
原型链
创建对象的几种方法
- 字面量
var o1 = {name: 'o1'}; var o11 = new Object({name: 'o11'}); - 显示构造函数
var M = function(){this.name='o2'}; var o2 = new M(); - 借用Object.create()
var P = {name: 'o3'}; var o3 = Object.create(P)
Object.create()创建对象是用原型链连接的;o3的__proto__指向P对象,下文会说o3.__proto__指向构造函数的原型对象,也就是说原型就是P
o3.__proto__ === P; // trueps:Object.create()方法是把参数中对象作为一个新对象原型对象赋给o3的,o3本身不具备name属性,是通过原型链连接它的原型对象;
原型 / 构造函数 / 实例 / 原型链
ps:通过prototype 和 __proto__完成原型链向上找的过程,Object.prototype是整个原型链的顶端,到这里未知
问题来了:原型对象和原型链到底起什么作用?
解答:构造函数中增加很多属性和方法,实例就可以共用,当有很多个实例想去公用方法时,不可能每个都拷贝一份,现在考虑应该有个东西存,这个东西就是原型对象
注意:
- 构造函数 > 函数才会有prototype,对象是没有的
- 只有实例对象有__proto__ [ 为什么函数也有,函数也是对象 ]
- 构造函数prototype属性使实例和原型对象产生关联,修改了prototype就是修改实例上一级原型对象
instanceof原理
ps:主要在这条原型链上的构造函数都是实例对象构造函数
instanceof原理是 实例对象__proto__属性 和 构造函数 prototype判断是不是同一个引用,返回true / false
JS code:
function M (){ this.name = name };
var o = new M()
o instanceof M; // true
o instanceof Object; // true注意为什么 o instanceof Object 也返回true ?
JS code:
o.__proto__ === M.prototype; // true
M.prototype.__proto__ === Object.prototype; // trueJS code:
o.__proto__.constructor === M; // true
o.__proto__.constructor === Object; // false所以说使用constructor判断比instanceof更严谨
new运算符(原理)
- 一个对象被创建(理解成字面量),它继承构造函数prototype [ 构造函数被执行,执行时候,相同的传参被传入 ]
- 同时上下文this会被指定为这个新实例
- 构造函数如果返回了一个新对象,那这个对象会取代new出来结果 [ 如果构造函数没有返回对象,那new出来的结果为步骤1创建的对象 ]
JS code:
var newF = function(func){
var o = Object.create(func.prototype); // 1.创建对象关联构造函数原型对象
var k = func.call(o); // 2.执行构造函数
if(typeof k === 'object'){ // 3.判断构造函数运行结果,是否为对象类型
return k;
}else{
return o;
}
}通信类
同源策略及限制
源:协议 / 域名 / 端口 80(默认),这三个有一项不同就跨域了
限制:不是同一个源文档没有权利修改另一个源文档
- cookie、localStorage 和 indexDB无法读取
- DOM无法获得
- Ajax请求无法获得(只适合同源策略)
前后端如何通信
- Ajax [ 同源策略 ]
- WebSocket [ 不限制同源策略 ]
- CORS [ 支持同源和跨域 ]
- ..
如何创建Ajax
var xhr = XMLHttpRequest ? new XMLHttpRequest() : new Window.ActiveXObject("Microsoft.XMLHTTP"); // 兼容写法
xhr.setRequestHeader(); // header头部信息
xhr.open("GET","/try/ajax/ajax_info.txt", true);
xhr.send(); // 发送
// xhr.onload判断xhr.status = 200 || 206 || 304 跨域通信几种方式
Ajax是不能发送跨域通信的,浏览器在识别用Ajax发送一个跨域请求时,它会在http头部加origin来允许跨域通信,如果不加就被拦截,这也是前端混子所理解CORS原理
JSONP
原理:在出现postMessage和CORS前,用script标签的异步加载实现;
JS code:
// 发送
<script src="http://www.abc.com/?data=name&callback=jsonp"></script>
// 返回
<script>
jsonp({
data:{}
}) </script>
// 发送
document.getElementByTagName('head')[0].appendChild('script');实现:
1.告诉服务端callback名称,将来是作为函数名返回的
2.本地必须有jsonp全局函数,后面才能把数据执行出来
Hash
应用场景:页面A通过iframe潜入跨域页面B并发送消息
var B = document.getElementByTagName('iframe');
B.src = B.src + '#' + 'data';
window.onhashchange = function(){ // 写在B页面中, 监听hash有没有改变
var data = window.location.hash;
}postMessage
应用场景同上
// A页面发送到B
B.window.postMessage('data', 'http://B.com');
// B页面接收A
window.addEventListener('message',function(event){
console.log(event.origin); // 拿到A的源
console.log(event.source); // Awindow的对象
console.log(event.data); // 发送的消息拿到了
}, false);Websocket
var ws = new Websocket('wss: echo.websocket.org'); // 指向服务器地址, ws不加密, wss加密
ws.onopen = function(){ ws.send(); }; // 发送出去
ws.onmessage = function(ev){
ev.data;
ws.close; // close断开
}CORS
新型的Ajax通信api:fetch 实现CORS通信
fetch('/some/url',{
method: 'get'
}).then(function(response){
}).catch(function(error){
});安全类
CSRF
CSRF:称为跨站请求伪造
ps:引诱点击后访问网站A后,浏览器自动上传cookie,A网站对身份重新确认(合法),就执行了动作,完成CSRF攻击
实现CSRF不可缺少的条件:
- 访问A网站是登陆的
- 接口存在漏洞
CSRF防御措施:
- token验证:刚才浏览器只自动上传cookie,没有自动上传token,token是注册成功后服务器会往本地存储token,在访问接口时携带token,如果是上图场景,只会自动上传cookie,不会上传token
- Refer验证(refer是页面来源):服务器判断页面来源是不是站点下页面
- 隐藏令牌(类似token):隐藏才http header头中,不会放在连接上,本质与token无区别,只是使用方式
XSS
XSS称为跨域脚本攻击
攻击原理:不需要做登陆验证,核心原理是向页面注入脚本 [ 评论区是最好最入css攻击的,在提交区里面写script标签,img标签等 ],xss是利用合法渠道注入JS
防御措施:宗旨是不让页面执行插入的JS脚本
XSS与CSRF对比区别
- XSS是向页面注入JS运行,JS函数体做事情
- CSRF是利用本身漏洞帮我执行接口
- 方式不一样,CSRF依赖用户登陆网站
渲染机制类
DOCTYPE的作用
你要知道的:DTD(document type definition)告诉浏览器我是什么类型,浏览器根据类型来判断用什么引擎优化它;如:XML / HTML
这里是作用:DOCTYPE是声明文档类型和DTD规范的,直接告诉浏览器当前文档包含哪些DTD(文档类型)
<!DOCTYPE HTML> // html5
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//en" "http://www.w3. org/TR/html4/strict.dtd"> // HTML4严格:不包含展示性和弃用元素(front)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//en" "http://www.w3. org/TR/html4/loose.dtd"> // HTML4传统:包含展示性和弃用元素(front)
浏览器渲染过程
重排Reflow
浏览器根据各种样式计算放在它该出现的问题,如:JS修改div中内容(即触发Reflow)
常见触发Reflow
- 增加/删除/修改 导致Reflow或Repaint
- 移动DOM未知(CSS3动画)
- 修改CSS样式(宽高/display)
- Resize窗口有可能(移动端没有这个问题)或滚动时
- 修改字体
重绘Repaint
屏幕上页面的呈现内容
常见触发Repaint
- DOM移动
- CSS改动
- ... ( 太多了,只要判断现在呈现内容有没有改变 )
JS运行机制
JS是单线程的(同一事件只能做一件事);这里混子前端拿道面烂了的面试题来举例:
JS代码:
for (var i = 1;i <= 5;i ++) {
setTimeout(function timer() {
console.log(i)
},i * 1000)
}对JS作用域、闭包以及事件循环等概念不了解的伙伴会想当然的回答:
第一次循环,隔一秒输出1;
第二次循环,隔一秒输出2;
第三次循环,隔一秒输出3;
第四次循环,隔一秒输出4;
第五次循环,隔一秒输出5;
或者还有伙伴预期结果是分别输出数字1~5,每秒依次,每次一个。
但实际结果大家粘贴到控制台就都知道,以一秒的频率输出五个6。
下面来过一下上面提到的知识点:
作用域:这里我引用《你不知道的javascript》中的一个比喻,可以把作用域链想象成一座高楼,第一层代表当前执行作用域,楼的顶层代表全局作用域。我们在查找变量时会先在当前楼层进行查找,如果没有找到,就会坐电梯前往上一层楼,如果还是没有找到就继续向上找,以此类推。到达顶层后(全局作用域),可能找到了你所需的变量,也可能没找到,但无论如何查找过程都将停止。
闭包:我的理解是在传递函数类型的变量时,该函数会保留定义它的所在函数的作用域。读起来可能比较绕,或者可以简单的这么理解,A函数中定义了B函数并且它返回了B函数,那么不管B函数在哪里被调用或如何被调用,它都会保留A函数的作用域。
事件循环:这个概念深入起来很复杂,下面新开一个段落详谈:
说起事件循环,不得不提起任务队列。事件循环只有一个,但任务队列可能有多个,任务队列可分为宏任务(macro-task)和微任务(micro-task)。
XHR回调、事件回调(鼠标键盘事件)、setImmediate、setTimeout、setInterval、indexedDB数据库操作等I/O以及UI rendering都属于宏任务(也有文章说UI render不属于宏任务,目前还没有定论)
process.nextTick、Promise.then、Object.observer(已经被废弃)、MutationObserver(html5新特性)属于微任务。注意进入到任务队列的是具体的执行任务的函数。
比如上述例子setTimeout()中的timer函数。另外不同类型的任务会分别进入到他们所属类型的任务队列,比如所有setTimeout()的回调都会进入到setTimeout任务队列,所有then()回调都会进入到then队列。当前的整体代码我们可以认为是宏任务。事件循环从当前整体代码开始第一次事件循环,然后再执行队列中所有的微任务,当微任务执行完毕之后,事件循环再找到其中一个宏任务队列并执行其中的所有任务,然后再找到一个微任务队列并执行里面的所有任务,就这样一直循环下去。这就是我所理解的事件循环。
继续看个栗子:
JS代码:
console.log('global')
setTimeout(function () {
console.log('timeout1')
new Promise(function (resolve) {
console.log('timeout1_promise')
resolve()
}).then(function () {
console.log('timeout1_then')
})
},2000)
for (var i = 1;i <= 5;i ++) {
setTimeout(function() {
console.log(i)
},i*1000)
console.log(i)
}
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('then1')
})
setTimeout(function () {
console.log('timeout2')
new Promise(function (resolve) {
console.log('timeout2_promise')
resolve()
}).then(function () {
console.log('timeout2_then')
})
}, 1000)
new Promise(function (resolve) {
console.log('promise2')
resolve()
}).then(function () {
console.log('then2')
})
来一步一步分析以上代码:
- 首先执行整体代码,“global”会被第一个打印出来。这是第一个输出
- 执行到第一个setTimeout时,发现它是宏任务,此时会新建一个setTimeout类型的宏任务队列并派发当前这个setTimeout的回调函数到刚建好的这个宏任务队列中去,并且轮到它执行时要延迟2秒后再执行。
- 代码继续执行走到for循环,发现是循环5次setTimeout(),那就把这5个setTimeout中的回调函数依次派发到上面新建的setTimeout类型的宏任务队列中去,注意,这5个setTimeout的延迟分别是1到5秒。此时这个setTimeout类型的宏任务队列中应该有6个任务了。再执行for循环里的console.log(i),很简单,直接输出1,2,3,4,5,这是第二个输出。
- 再执行到new Promise,Promise构造函数中的第一个参数在new的时候会直接执行,因此不会进入任何队列,所以第三个输出是"promise1",上面有说到Promise.then是微任务,那么这里会生成一个Promise.then类型的微任务队列,这里的then回调会被push进这个队列中。
- 再继续走,执行到第二个setTimeout,发现是宏任务,派发它的回调到上面setTimeout类型的宏任务队列中去。
- 再走到最后一个new Promise,很明显,这里会有第四个输出:"promise2",然后它的then中的回调也会被派发到上面的Promise.then类型的微任务队列中去。
- 第一轮事件循环的宏任务执行完成(整体代码可以看做宏任务)。此时微任务队列中只有一个Promise.then类型微任务队列,它里面有两个任务。宏任务队列中也只有一个setTimeout类型的宏任务队列。
- 下面执行第一轮事件循环的微任务,很明显,会分别打印出"then1",和"then2"。分别是第五和第六个输出。此时第一轮事件循环完成。
- 开始第二轮事件循环:执行setTimeout类型队列(宏任务队列)中的所有任务。发现都有延时,但延时最短的是for循环中第一次循环push进来的那个setTimeout和上面第5个步骤中的第二个setTimeout,它们都只延时1s。它们会被同时执行,但前者先被push进来,所以先执行它!它的作用就是打印变量i,在当前作用域找变量i,木有!去它上层作用域(这里是全局作用域)找,找到了,但此时的i早已是6了。(为啥不是5,那你得去补补for循环的执行流程了)所以这里第七个输出是延时1s后打印出6。
- 紧接着执行第二个setTimeout,它会先后打印出"timeout2"和"timeout2_promise",这分别是第八和第九个输出。但这里发现了then,又把它push到上面已经被执行完的then队列中去。
- 这里要注意,因为出现了微任务then队列,所以这里会执行该队列中的所有任务(此时只有一个任务),即打印出"timeout2_then"。这是第十个输出。
- 继续回过头来执行宏任务队列,此时是执行延时为2s的第一个setTimeout和for循环中第二次循环的那个setTimeout,跟上面一样,前者是第一个被push进来的,所以它先执行。这里会延时1秒(原因下面会解释)分别输出“timeout1”和“timeout1_promise”,但发现了里面也有一个then,于是push到then微任务队列并立即执行,输出了"timeout1_then"。紧接着执行for中第二次循环的setTimeout,输出6。注意这三个几乎是同时被打印出来的。他们分别是第十一到十三个输出。
- 再就很简单了,把省下的for循环中后面三次循环被push进来的setTimeout依次执行,于是每隔1s输出一个6,连续输出3次。
- 第二轮事件循环结束,全部代码执行完毕。
再回过头来看上面那个问题,理解了事件循环的机制,问题就很简单了。for循环时setTimeout()不是立即执行的,它们的回调被push到了宏任务队列当中,而在执行任务队列里的回调函数时,变量i早已变成了6。那如何得到想要的结果呢?很简单,原理就是需要给循环中的setTimeout()创建一个闭包作用域,让它执行的时候找到的变量i是正确的。
混子前端给出如下5种解决方案:
- 引入IIFE
JS代码 for(var i = 0;i < 5; i++){ (function(i){ setTimeout(function timer(){ console.log(i); }, i * 1000) })(i); } - 利用ES 6引入的let关键字
JS代码 for(let i = 0;i<5;i++) { setTimeout(function timer(){ console.log(i); }, i * 1000); } 利用ES 5引入的bind函数
JS代码 for (var i=1; i<=5; i++) { setTimeout( function timer(i) { console.log(i); }.bind(null,i), i*1000 ); }利用setTimeout第三个参数
JS代码 for (var i=1; i<=5; i++) { setTimeout(function timer(i) { console.log(i); }, i*1000,i ); }把setTimeout用一个方法单独出来形成闭包
JS代码 var loop = function (i) { setTimeout(function timer() { console.log(i); }, i*1000); }; for (var i = 1;i <= 5; i++) { loop(i); }
页面性能
- 资源压缩合并,减少HTTP请求(把文件变小)
- 非核心代码异步加载
异步加载方式:
1、动态脚本加载:document.creatElement创建标签/节点标签加到head/body
2、defer:script上添加
3、async:script上添加
对比defer/async区别:
defer是在HTML解析完才会执行,如果是多个按加载顺序执行
async是在加载完之后立即执行,多个async的话,执行顺序和加载顺序无关 - 利用浏览器缓存
强缓存 / 协商缓存 - 利用CDN[内容分发]让网络快速到服务端把文件下载下来
- 预解析DNS
高级浏览器在页面里a标签默认打开了DNS预解析,不加这句话,a标签也做了DNS预解析,但页面如果是HTTPS协议,浏览器是关闭预解析的,通过meta标签强制打开预解析:<meta http-equiv="x-dns-prefetch-control" content="on"> <link rel="dns-prefetch" href="//host_name_to_prefetch.com">
错误监控
首先我们要知道错误分类:
- 即时运行错误:代码错误
- 资源加载错误:img / js / css 加载失败
(一)如果是即时性错误
- try...catch
- window.onerror
- addEventListener
(二)如果是资源加载性错误
- Object.onerror 对 img / js 添加 onerror事件
- performance.getEntries() 获取所有已加载的资源,返回一个数组
- Error事件捕获window上[冒泡不会阻止捕获]
JS代码 window.addEventListener('error', function(){ // **** }, true);
ps:如果JS错误是跨域的,可以在script标签添加crossorigin属性,同时在响应JS资源时,设置JS资源头Access-Control-Allow-Origin就可以拿到错误信息了
这里上报错误的原理:利用ajax通讯上报 / 也可以利用image对象上报
JS代码
(new Image()).src = "http://baidu.com/tesjk?r=tksjk";利用这种方式比ajax简单,也能实现资源上报
算法类
由于本人也是混子前端,不一一列举,但作为一名合格的前端,算法是必不可少的。
ps:请随意吐槽本混子,会虚心接受
好了,混子前端就总结到这里,还请留意混子前端不定时的更新....