前端面试前的准备
CSS 三列布局
CSS 盒模型
标准模式/IE模型的区别
- 标准模型:Width = Content
- IE模型:Width = Content + Padding + Border
CSS如何设置两种模型
- box-sizing 属性 ==> content-box(标准模型,默认),border-box(IE 模型)
JS 如何设置获取盒模型对应的宽和高
1) domElem.style.width/height // 只能取到内联样式的宽和高
2) domElem.currentStyle.width/height // 获取到即时的宽和高,但只有IE支持,通用使用第三种
3) window.getComputedStyle(domElem).width/height
4) domElem.getBoundingClientRect().width/height // 根据视窗,计算元素绝对位置,还可获取top和left
实例:根据盒模型解释边距重叠
- 当两个垂直外边距相遇时,它们将形成一个外边距。合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者
- 一个元素包含在另一个元素中时(假设没有内边距或边框把外边距分隔开),它们的上和/或下外边距也会发生合并
- 只有普通文档流中块框的垂直外边距才会发生外边距合并。行内框、浮动框或绝对定位之间的外边距不会合并。
BFC(边距重叠解决方案)
基本概念:块级格式化上下文
渲染规则:
- BFC元素的垂直方向的边距会发生重叠
- BFC区域不会与浮动元素的Box重叠(可用于清除浮动)
- BFC是一个独立的容器,外面的元素不会影响里面的元素,里面的元素也不会影响外面的元素
- 计算BFC高度时,浮动元素也会参与计算
创建 BFC:
- float值不为none
- position值不为static/relative
- overflow值不为visible(等于auto/hidden)
- display值为table-cell
实例:BFC垂直方向重叠问题(创建包裹div,设置overflow属性为hidden)
<section> <p>1</p> <div style="overflow:hidden"> <p>2</p> </div> <p>3</p> </section>
{
margin: 0;
padding: 0;
}
#margin {
background: pink;
overflow: hidden;
}
#margin > p {
background: red;
margin: 5px auto 25px;
}
- 实例:两栏浮动布局,高度较低一侧会被侵染
<section>
<div class="left"></div>
<div class="right"></div>
</section>
{
margin: 0;
padding: 0;
}
#layout {
background: red;
}
#layout .left {
background: pink;
width: 100px;
height: 100px;
float: left;
}
#layout .right {
background: #ccc;
height: 110px;
overflow: auto;
}
- 实例:清除浮动(BFC即使子元素是浮动元素,高度也会参与计算)
<section>
<div>我是浮动元素</div>
</section>
{
margin: 0;
padding: 0;
}
#float {
background: red;
// overflow: auto;
float: left;
}
#float .float {
float: left;
font-size: 30px;
}
原型链
创建对象的几种方法
- 字面量
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);
原型链概念
- 原型链是JS实现类与继承的基础,从实例对象向上找,直Object.prototype(原型链顶端)
- 函数都具有prototype属性,构造函数的prototype属性指向原型对象,该原型对象的constructor属性指回构造函数
Func.prototpye.constructor === Func; // true
- 实例对象的__proto__属性指向构造函数的原型对象
obj.__proto__ === Func.prototype; // true
- 函数也具有__proto__属性(函数也是对象),指向Function的原型对象,说明该函数的构造函数Function
Func.__proto__ === Function.prototype; // true
instanceof 的原理
- 判断实例对象的__proto__和构造函数的prototype是否是同一引用
- A instanceof B:查看对象B的prototype属性指向的原型对象是否在对象A的原型链上,若在则返回true,若不在则返回false
new 运算符
- 一个新对象被创建,它继承自Func.prototype(构造函数的原型对象)
- 构造函数Func被执行,相应的参数被传入,上下文(this)会被指定为这个新实例
- 若构造函数Func返回一个“对象”,那么这个对象会取代整个new出来的结果;
- 若构造函数Func没有返回“对象”,那么new出来的结果为步骤1创建的对象。
// 实现方法 _new,模拟 new 的作用
function _new(f) {
// 返回一个function
return function() {
var obj = {"proto": f.prototype};
f.apply(obj, arguments);
return obj;
}
}
// 测试
function Person(name, age) {
this.name = name;
this.age = age;
}
var pp = _new(Person)("Jialin", 25);
pp instanceof Person; // true;
// 使用 Object.create 模拟 new 方法
var _new2 = function(func) {
var obj = Object.create(func.prototype);
var k = func.call(obj);
if(typeof k === 'object') {
return k;
} else {
return obj;
}
}
面向对象
借助构造函数实现继承(部分继承)
- 缺点:该方法无法继承父类的原型方法
function Parent() {
this.name = 'parent';
this.arr = [1];
}
function Child() {
Parent.call(this);
this.type = 'child';
}
借助原型链实现继承
- 缺点:原型链中的原型对象是共用的(同一对象)
function Parent() { this.name = 'parent'; this.arr = [1, 2, 3]; } function Child() { this.type = 'child'; } Child.prototype = new Parent(); var c1 = new Child(); var c2 = new Child(); console.log(c1.arr); // [1, 2, 3] c1.arr.push(4); console.log(c2.arr); // [1, 2, 3, 4]
c1.proto === c2.proto; // true
组合方式
- 缺点:父类构造函数执行了2次,子类的 protype 被修改(共用),继而子类的构造函数将指向父类构造函数(子类的原型方法也丢失)
function Parent() { this.name = 'parent'; this.arr = [1, 2, 3]; } function Child() { Parent.call(this); this.type = 'child'; } Child.prototype = new Parent();
var cc = new Child();
cc.constructor; // Parent(){...}
- 优化1:将父类构造函数的执行次数减少至1次
function Parent() { this.name = 'parent'; this.arr = [1, 2, 3]; } function Child() { Parent.call(this); this.type = 'child'; } Child.prototype = Parent.prototype;
var cc = new Child();
cc.constructor; // Parent(){...}
- 优化2:使用 Object.create 创建 Child.prototype,之后再修改子类原型的 constructor
function Parent() {
this.name = 'parent';
this.arr = [1, 2, 3];
}
function Child() {
Parent.call(this);
this.type = 'child';
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
原型式继承
- 该方法借助原型可以基于已有的对象创建新对象,从而不必因此创建自定义类型
function object(o) {
function F(){};
F.prototype = o;
return new F();
}
复制代码
- Object.create() 规范了原型式继承
寄生式继承
- 创建一个仅仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象
function createAnother(originalObj) {
var clone = object(originalObj);
clone.sayHi = function() {
// ...
}
return clone;
}
DOM事件
DOM事件类
- DOM0
element.onclick = function() {}
- DOM2
// W3C
element.addEventListener('click', function(){}, false);
// IE
element.attachEvent('click', function(){}, false);
- DOM3(增加了许多事件)
element.addEventListener('keyup', function(){}, false);
DOM事件捕获/冒泡过程
- window ==> document ==> html(通过 document.docuemntElement 获得) ==> ... ==> 目标元素
- 反向为冒泡
Event 对象的常见应用
- event.preventDefault(); 表示阻止默认事件
- event.stopPropagation(); 表示阻止冒泡行为
- event.stopImmediatePropagation(); 应用于:事件响应的优先级,场景:一个按钮绑定两个click事件A和B,要求让点击A时,不执行B
- event.currentTarget;表示:当前绑定事件的对象,恒等于 this,特殊情况:在父子嵌套关系中,父元素绑定了事件,单击子元素,此时,currentTarget指向的是父元素(因为它是绑定事件的对象);此时,target指向了子元素(因为它是触发事件的具体对象)
- event.target; 表示:触发事件的某个具体对象
// IE
event.sourceElement
自定义事件(无需使用回调)
- 使用 Event 类,但是只能指定事件名称,无法添加数据
var eve = new Event('custom');
ev.addEventListener('custom', function(){
console.log('custom');
});
// 触发
ev.dispatchEvent(eve);
复制代码
- 使用CustomEvent不但可以指定事件名称,还可以指定一个object类型的参数
类型转换
7 种数据类型
- Boolean, Null, Undefined, Number, String, Symbol
- Object
显示类型转换
- Number函数
// 原始类型
数值 ==> 原始值
字符串 ==> 数值(空字符为0) or NaN
布尔值 ==> true为1,false为0
undefined ==> NaN
null ==> 0
对象,先调用该对象自身的 valueOf 方法,
- 若返回原始类型的值,则执行Number方法转换;
- 若返回符合类型的值,调用自身的toString方法,
- 若返回原始类型的值,使用Number方法转换
- 若仍返回复合类型的值,则报错失败
var obj = {a:1};
console.log(Number(obj));
其中,
obj.valueOf()返回{a: 1}, {a: 1}的toString()返回'[object Object]'
- String 函数
// 原始类型
数值 ==> 对应字符串
字符串 ==> 原始值
布尔值 ==> true变为'true',false变为'false'
undefined ==> 'undefined'
null ==> 'null'
复合类型,先调用该对象自身的 toString 方法,
- 若返回原始类型的值,则执行String方法转换;
- 若返回符合类型的值,调用自身的valueOf方法,
- 若返回原始类型的值,使用String方法转换
- 若仍返回复合类型的值,则报错失败
复制代码
- 若仍返回复合类型的值,则报错失败
- Boolean 函数
// 原始类型
undefined, null, -0, +0, NaN, 空字符串 均转为 false
隐式类型转换
- 四则运算
- 判断语句
- Native调用
- console.log自动转换为字符串
- alert自动转换为字符串
常见题目
[] + [] ==> [] + {} ==>
{} + [] ==> 0
- 第一个大括号被作为代码块
- 若被console.log()包括的话结果为 [object Object]
{} + {} ==> "[object Object][object Object]"true + true ==>
1 + {a: 1} ==>
- typeof返回值
- undefined, boolean, number, string, symbol
- function, object
设计模式
- 单例模式
- 观察者模式
- 适配器模式
- 工厂模式
通信类
同源政策及其限制
- 源:协议 + 域名 + 端口
- Cookie、LocalStorage和IndexDB无法读取
- DOM无法获取
- Ajax无法请求
补充:cookie,localStorage和sessionStorage区别
复制代码
前后端如何通信
- Ajax(同源下通信方式)
- WebSocket(不受同源策略限制)
- CORS(支持跨域,支持同源)
- Fetch(兼容性不好)
如何创建 Ajax
- XHR对象的工作流程
- 兼容性处理(IE, FireFox)
- 事件触发条件
- 事件的触发顺序
var xhr = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP');
var data = opt.data;
var url = opt.url;
var type = opt.type.toUpperCase();
var dataArr = [];
for(var k in data) {
dataArr.push(k + '=' + data[k]);
}
if(type === 'GET') {
url = url + '?' + dataArr.join('&');
xhr.open(type, url.replace(/?$/g, ''), true);
xhr.send();
}
if(type === 'POST') {
// 第三个参数:默认为true,表示异步发送
xhr.open(type, url, true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send(dataArr.join('&'));
}
xhr.onload = function(){
if(xhr.status === 200 || xhr.status === 304) {
var res;
if(opt.success && opt.success instanceof Function) {
res = xhr.responseText;
if(typeof res === 'string') {
res = JSON.parse(res);
opt.success.call(xhr, res)
}
}
} else {
if(opt.error && opt.error instanceof Function) {
opt.error.call(xhr, res);
}
}
}
// Promise封装
var getJSON = function(url) {
var promise = new Promise((resolve, reject) => {
var client = new XMLHttpRequest();
client.open('GET', url);
client.onreadystatechange = handler;
client.responseType = 'json';
client.setRequestHeader('Accept', 'application/json');
client.send();
function handler() {
if(this.readyState !== 4) {
return;
}
if(this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
});
return promise;
}
跨域通信的几种方式
- JSONP
- 利用 Script 标签的异步加载来实现
util.jsonp = function(url, onsuccess, onerror, charset) {
var callbackName = util.getName('name');
window[callbackName] = function() {
if(onsuccess && util.isFunction(onsuccess)) {
onsuccess(arguments[0]);
}
}
var script = util.createScript(url + '&callback=' + callbackName, charset);
script.onload = script.onreadystatechange = function() {
if(!script.readyState || /loaded|complete/.test(script.readyState)) {
script.onload = script.onreadystatechange = null;
if(script.parentNode) {
script.parentNode.removeChild(script);
}
window[callbackName] = null;
}
};
script.onerror = function() {
if(onerror && util.isFunction(onerror)) {
onerror();
}
}
}
//动态创建 script 标签,并请求
function addScriptTag(src){
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.src = src;
document.body.appendChild(script);
};
// 在 onload 后,跨域请求
window.onload = function(){
addScriptTag('http://127.0.0.1:8080/jsonp?callback=test');
};
//回调函数,必须为全局,不然会报错
function test(data){
alert(data.name);
};
server.on('request',function(req, res){
var urlPath = url.parse(req.url).pathname;
var qs = querystring.parse(req.url.split('?')[1]);
if(urlPath === '/jsonp' && qs.callback){
res.writeHead(200,{'Content-Type':'application/json;charset=utf-8'});
var data = {
"name": "Monkey"
};
data = JSON.stringify(data);
var callback = qs.callback+'('+data+');';
res.end(callback);
} else {
res.writeHead(200, {'Content-Type':'text/html;charset=utf-8'});
res.end('Hell World\n');
}
});
- Hash
- hash 改变页面不刷新,search 改变页面刷新
// 当前页面A通过iframe嵌入了跨域页面B
// A中:
var B = document.getElementsByTagName('iframe');
B.src = B.src + '#' + 'data';
// B中
window.onhashchange = function() {
var data = window.location.hash;
}
- postMessage
// 窗口A(http://A.com)向窗口B(http://B.com)发送消息
Bwindow.postMessage('data', 'http://B.com');
// 在B窗口中
window.addEventListener('message', function(evet) {
console.log(event.origin); // http://A.com
console.log(event.source); // Bwindow
console.log(event.data); // data
}, false);
- WebSocket
var ws = new WebSocket('wss://echo.websocket.org');
ws.onopen = function(evt) {
console.log('Connection open...');
ws.send('Hello WebSockets');
}
ws.onmessage = function(evt) {
console.log('Received Message: ', evt.data);
ws.close();
}
ws.onclose = function(evt) {
console.log('Connection closed.');
}
- CORS
- 浏览器会拦截ajax请求,如果被认为是跨域,则会加origin字段
- 服务器端会在响应中加入Access-Control-Allow-Origin字段
- CORS请求默认不发送Cookie和HTTP认证信息,如需要发送Cookie,可使用
// 客户端 var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
// 服务器端 Access-Control-Allow-Credentials: true
补充:fetch API
// url必须,options可选 fetch('/some/url', { method: 'get', }).then(function(response) {
}).catch(function(err){});
安全类
- XSS 跨站脚本攻击
- 它允许恶意用户将代码注入到网页上,其他用户在观看网页时就会受到影响。
- 这类攻击通常包含了HTML以及用户端脚本语言
举例:通过客户端脚本(JS)在一个论坛发帖中发布一段恶意的JS代码就是脚本注入,如果这个代码内容有请求外部服务器,那么就叫做XSS!
防护:
- 如果您在cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击
- 对用户表单输入的数据进行过滤,对javascript代码进行转义
CSRF 跨站请求伪造
- 冒充用户发起请求(在用户不知情的情况下),完成一些违背用户意愿的请求
1.登录受信任网站A,并在本地生成Cookie 。
2.在不退出A的情况下,访问危险网站B。
防护:token防御的整体思路是:
- 后端随机产生一个token,把这个token保存在SESSION状态中;同时,后端把这个token交给前端页面
- 下次前端需要发起请求(比如发帖)的时候把这个token加入到请求数据或者头信息中,一起传给后端;
- 后端校验前端请求带过来的token和SESSION里的token是否一致;
HTTP协议类
HTTP协议的主要特点
- 简单快速
- 输入uri进行访问资源
- 灵活
- 通过不用的http协议完成不同类型数据传输
- 无连接
- 一次连接,无需再次连接
- 无状态
- 两次连接,无法区分浏览器端的身份
HTTP报文的组成部分
- 请求报文
- 请求行(HTTP方法 + 请求地址 + HTTP协议及版本)
- 请求头(key/value值)
- 空行
- 请求体
- 响应报文
- 状态行(HTTP协议及版本 + 状态码 + 原因)
- 响应头
- 空行
- 响应体
HTTP方法
- GET
- POST
- PUT
- DELETE
- HEAD
POST和GET的区别
- GET回退是无害的,POST会再次提交请求
- GET请求会被浏览器主动缓存,POST不会
- GET请求只能url编码,POST支持多种编码方式
- GET请求参数会被完整保留在浏览器历史记录里,POST中的参数不会被保留
- GET请求在URL中传送的参数有长度限制(2KB),POST没有限制
- GET请求参数通过URL传递,POST放在Request Body中
HTTP状态码
- 200 OK: 客户端请求成功
- 206 Partial Content: 客户端发送一个带有Range头的GET请求
- 301 Moved Permanetly: 所请求的页面已经转移到新的URL
- 302 Found: 所请求的页面已经临时转移到新的URL
- 304 Not Modified: 原缓存可用
- 400 Bad Request: 客户端请求有语法错误
- 401 Unauthorized: 请求未经授权,必须和www-authenticate一起使用
- 403 Forbidden: 请求页面被禁止访问
- 404 Not Found: 请求资源不存在
- 500 Internal Server Error: 服务器发生不可预期的错误,元缓存文档还可继续使用
- 503 Server Unavailable: 请求未完成,服务器临时过载
- 什么是持久连接
- 无连接:每个请求/应答客户端和服务器都要新建一个连接(HTTP/1.0)
- Keep-Alive模式:避免重新建立连接(HTTP/1.1)
- 什么是管线化
- 管线化机制通过持久连接完成
- 只有GET和HEAD请求可以进行管线化,POST有限制
- 管线化不会影响响应到来的顺序
请求1 - 响应1 - 请求2 - 响应2 - 请求3 - 响应3
// 管线化,请求打包发送,依次进行响应(单独),并保持顺序请求1 - 请求2 - 请求3 - 响应1 - 响应2 - 响应3
https 的介绍
1. HTTP的缺点
- 使用明文(不加密)通信,内容可能被窃听
- 不验证通信方的身份,可能遭遇伪装
- 无法证明报文的完整性,可能已遭篡改
HTTPS 介绍
HTTPS = HTTP + 加密 + 认证 + 完整性保护
1)HTTPS使用SSL,先和SSL通信,再由SSL和TCP通信
补充:
对称密钥加密:加密和解密用同一个密钥的方式(共享)
- 弊端:加密时密钥也会被发给对方
非对称密钥加密:
- 发送方使用接收方的公开密钥进行加密处理,接收方使用自己的私有密钥解密
- 弊端:处理速度过慢
2)HTTPS采用混合加密机制
- 交换密钥时,使用非对称加密方式;
- 之后的建立通信交换报文阶段,使用对称加密方式(共享)
3)证明公开密钥的正确性 --> 使用由数字证书认证机构颁发的公开密钥证书
4)HTTPS还可以使用客户端证书
HTTP 2.0 与 HTTP1.1 的区别
- HTTP/2采用二进制格式而非文本格式
- HTTP/2是完全多路复用的,同时发起无数个请求,并且响应可以同时返回
- 使用报头压缩,HTTP/2降低了开销
- HTTP/2让服务器可以将响应主动“推送”到客户端缓存中
复制代码
- 因为“所有的HTTP2.0的请求都在一个TCP链接上”,“资源合并减少请求”,比如CSS Sprites,多个JS文件、CSS文件合并等手段没有效果
3 次挥手 && 4 此握手
- TCP建立连接
- TCP关闭连接
- TCP连接是全双工的,因此,每个方向都必须要单独进行关闭
- 当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了
渲染机制
- 什么是 DOCTYPE 及作用
DTD 文档类型定义
- 告诉浏览器自身的文档类型
DOCTYPE
- 声明文档类型和DTD规范的,主要用于文件的合法性验证
常见的 DOCTYPE:
HTML 5
<!DOCTYPE html>
HTML 4.01 Strict 该DTD包含所有HTML元素和属性,但不包括展示型的和弃用的元素(如:font)
HTML 4.01 Transitional 该DTD包含所有HTML元素和属性,包括展示型和弃用的元素
- 浏览器的渲染过程
- HTML经过HTML Parser变为DOM Tree
- CSS经过CSS Parser变为CSSOM Tree(计算后的)
- DOM Tree和CSSOM Tree结合后成为Render Tree(不知道具体HTML元素的具体位置)
- Render Tree和Layout(精确计算位置、宽高、颜色等)结合后,进行绘制
- 什么是重排 Reflow
定义:DOM结构中的各个元素都有自己的盒子模型,这些都需要浏览器根据各种样式计算后将元素放到它该出现的位置,这个过程称为 Reflow 重排
触发Reflow:
- 增加、删除、修改或移动DOM节点
- 修改CSS样式
- 修改网页的默认字体(极其不推荐!白屏闪现)
- 什么是重绘 Repaint
定义:将页面要呈现的内容都画在页面上,这个过程称之为 Repaint
触发Repint:
- DOM改动
- CSS改动 (只要页面显示内容不同就会Repaint)
如何减少Repaint的频率?
- 通过DocumentFragment,将新创建的节点添加进去,向浏览器中一次性加入
DocumentFragment:
- 表示一个没有父级文件的最小文档对象。它被当做一个轻量版的 Document 使用,用于存储已排好版的或尚未打理好格式XML片段。
- DocumentFragment 不是真实DOM树的一部分,它的变化不会引起 DOM 树的重新渲染的操作(reflow),且不会导致性能等问题。
- 可以使用 document.createDocumentFragment() 方法创建一个空的 DocumentFragment
JS运行机制 -- 任务队列和 EventLoop
- 异步任务的类型:
- setTimeout 和 setInterval
- DOM 事件(addEventListener)
- Promise
- EventLoop
整个最基本的 Event Loop 如图所示:
- queue可以看做一种数据结构,用以存储需要执行的函数
- timer类型的API(setTimeout/setInterval)注册的函数,等到期后进入task队列(这里不详细展开timer的运行机制)
- 其余API注册函数直接进入自身对应的task/microtask队列
- Event Loop执行一次,从task队列中拉出一个task执行
- Event Loop继续检查microtask队列是否为空,依次执行直至清空队列
task主要包含:setTimeout、setInterval、setImmediate、I/O、UI交互事件
microtask主要包含:Promise、process.nextTick、MutaionObserver
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
Promise.resolve().then(() => {
console.log(3)
}).then(() => {
console.log(4)
})
console.log(5)
// 1 5 3 4 2
分析如下:
同步运行的代码首先输出:1、5,接着,清空microtask队列:3、4
setTimeout(fn, 0) 的含义
- 指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。
console.log('abc'); setTimeout(() => { console.log('执行啦') },3000);setTimeout(() => { console.log('执行啦123') },0);
// 'abc'
// 执行啦123
// 执行啦
process.nextTick(callback) 的含义
- 类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。
- 在同一EventLoop中,属于Microtask,优先级高于Promise
setTimeout 与 Promise 混合
console.log(1)
setTimeout(() => {
console.log(2)
new Promise(resolve => {
console.log(4)
resolve()
}).then(() => {
console.log(5)
})
})
new Promise(resolve => {
console.log(7)
resolve()
}).then(() => {
console.log(8)
})
setTimeout(() => {
console.log(9)
new Promise(resolve => {
console.log(11)
resolve()
}).then(() => {
console.log(12)
})
});
分析如下:
同步运行的代码首先输出:1、7
接着,清空microtask队列:8
第一个task执行:2、4
接着,清空microtask队列:5
第二个task执行:9、11
接着,清空microtask队列:12
setTimeout,Promise 与 process.nextTick 的混合
console.log('1');
setTimeout(function() {
console.log('2');
process.nextTick(function() {
console.log('3');
})
new Promise(function(resolve) {
console.log('4');
resolve();
}).then(function() {
console.log('5')
})
})
process.nextTick(function() {
console.log('6');
})
new Promise(function(resolve) {
console.log('7');
resolve();
}).then(function() {
console.log('8')
})
setTimeout(function() {
console.log('9');
process.nextTick(function() {
console.log('10');
})
new Promise(function(resolve) {
console.log('11');
resolve();
}).then(function() {
console.log('12')
})
})
复制代码
setTimeout 函数中,0ms 和 1ms 的效果一样(结果都是 1ms 后,传入 task 队列)
- Chrome 和 Node 中的 timer 都使用这一规则
setTimeout(() => {
console.log(2)
}, 2)
setTimeout(() => {
console.log(1)
}, 1)
setTimeout(() => {
console.log(0)
}, 0)
// 1, 0, 2
页面性能
- 提升页面性能的方法有哪些?资源压缩合并,减少HTTP请求
- 非核心代码的异步加载 --> 异步加载的方式 --> 异步加载的区别
- 利用浏览器缓存 --> 缓存的分类 --> 缓存的原理
- 使用CDN
- 预解析 DNS,<meta http-equi="x-dns-prefetch-control" content="on">
- a 标签自带了预解析的功能,那就不需要link开启预解析的功能,但是如果是https协议的话,a标签就不带有预解析的功能,因此使用 meta,<link rel="dns-prefetch" href="//host_name_to_prefetch.com">
- 普通方法,link 只是规定了包含哪个域名的就对它进行预解析
- 异步加载的方式
- 动态脚本加载
- defer
- async
- 异步加载的区别
- defer 在 HTML 解析完之后才执行,如果是多个含 defer 属性的 script 标签,按照加载顺序执行
- async 是在加载完之后立即执行,如果是多个含 async 属性的 script 标签,执行顺序和加载顺序无关
- 浏览器缓存分类
强缓存(直接使用浏览器的缓存,不再请求服务器)
- Expires: Thu, 21 Jan 2017 23:39:02 GMT(绝对时间)
- Cache-Control: max-age=3600(相对时间,单位为妙)
- 其他值:no-store(不缓存) no-cache(不缓存过期的,需revalidation)
- 若服务器响应同时包含Expires和Cache-Control,以Cache-Control为准
协商缓存(询问服务器,浏览器的缓存是否可以使用)
- If-Modified-Since:Last-Modified ...
- Etag/If-None-Match
使用了缓存后, 更改js文件内容但未更改js文件名。 重新发布后,怎么保证浏览器加载的是最新的js文件?
A:让浏览器上的强缓存事件变短
- Yahoo 军规的其他优化方法
错误监控
- 前端错误的分类
- 即时运行错误(代码错误)
- 资源加载错误
- 错误的捕获方式
- 即时运行错误
- try...catch
- window.onerror(属于DOM0)
- object.onerror(不会向上冒泡到window)
- performance.getEntries()
- 返回一个数组,包含所有成功加载的资源(一种间接方法)
- Error事件捕获
window.addEventListener('error', (e) => { console.log('捕获', e); }, true);
// true表示捕获,false表示冒泡
跨域的JS运行错误可以捕获吗?错误提示是什么?应该怎么处理?
跨域错误只提示 Script Error,解决方法:script 标签增加 crossorigin 属性,设置 js 资源响应头为 Access-Control-Allow-Origin: *
- 上报错误的基本原理
- 采用 Ajax 通信的方式上报
- 利用 Image 对象上报(最佳)
(new Image()).src = 'http://...'; 该请求可以直接被发出
笔试题
写出下面的输出
function Foo() {
getName = function(){console.log(1);}
return this; // 指window对象
}
Foo.getName = function(){console.log(2);}
Foo.prototype.getName = function(){console.log(3);}
var getName = function(){console.log(4);} // 变量定义提升
function getName(){console.log(5);} // 函数定义提升
Foo.getName(); // 2
getName(); // 4
Foo().getName();
// 1,返回window对象,调用window对象的getName方法(已经被从4修改为1)
getName(); // 1
new Foo.getName(); // 2
// 等价于
new function(){console.log(2);}();
new Foo().getName(); // 3
// 等价于
新建一个Foo函数的实例,调用该实例上(或原型上)的getName方法
new new Foo().getName();// 3
// 等价于
new foo.getName();
先执行new Foo(),得到Foo函数的实例;
由于点操作符的优先级高于new(不带参数),故先执行foo.getName(),返回3
- 优先级:
- 点操作符 高于 new(带参数)和new(不带参数)
- new(带参数) 高于 new(不带参数)
算法类
数组扁平化
- 方法1:toString() 类型转换
var flag = function(arr) {
let toString = Array.prototype.toString;
Array.prototype = function() {
return this.join(',');
}
// 隐式调用toString()
let result = arr + '';
Array.prototype.toString = toString;
return result;
}
复制代码
- 方法2:valueOf() 格式转换
Array.prototype.valueOf = function() { return this.join(','); } var flat = function(arr) { return arr + ''; };
flat(arr);
方法3:[Symbol.iterator] 遍历器
Array.prototype.[Symbol.iterator] = function() { let arr = [].concat(this); let getFirst = function(array) { let first = array.shift(); return first; } return { next: function() { let item = getFirst(arr); if(item) { return { value: item, done: false }; } else { return { done: true } } } } }
var flat = function(arr) { let r = []; for(let i of arr) { r.push(i); } return r.join(','); }
- 方法4:JSON序列化
function flat (arr) {
let jsonArr = JSON.stringify(arr); // 将数组转化成json
return jsonArr.replace(/(\[|\])/g, ''); // 用正则去掉json中所有的中括号并返回
};
flat(arr);
实现如下功能
- destructuringArray( [1,[2,4],3], "[a,[b],c]" );
- result: { a:1, b:2, c:3 }
// 递归实现(深拷贝) function func(arr, str) { str = Array.isArray(str) ? str : JSON.parse(str.replace(/([a-z]+)/g, '"$1"')); let obj = {}; str.forEach((val, index) => { let res = arr[index]; if(Array.isArray(val)) { Object.assign(obj, func(res, val)) } else { obj[val] = res; } }}
}
三面/终面
- 一个团队最重要的是什么?
- 服从管理
- 团队中层次分明,既有优秀的份子,也有做业务的中坚力量
- 有对技术、对产品体验、对生活的追求
- 综上,团队是健康的,氛围也应该是融洽的,协作也就是自然而然的
- 业务能力的描述
- 做过什么业务
- 有什么业绩
- 使用什么技术方案
- 突破什么技术难点
- 遇到什么问题
- 最大的收获是什么
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论