libin1991 / libin_Blog

爬虫-博客大全
https://libin.netlify.com/
124 stars 17 forks source link

前端面试系统准备 #607

Open libin1991 opened 6 years ago

libin1991 commented 6 years ago

CSS三列布局

CSS盒模型

  1. 标准模式/IE模型的区别
  • 标准模型:Width = Content
  • IE模型:Width = Content + Padding + Border
  1. CSS如何设置两种模型
  • box-sizing属性 ==> content-box(标准模型,默认),border-box(IE模型)
  1. 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
复制代码
  1. 实例:根据盒模型解释边距重叠
  • 当两个垂直外边距相遇时,它们将形成一个外边距。合并后的外边距的高度等于两个发生合并的外边距的高度中的较大者

  • 一个元素包含在另一个元素中时(假设没有内边距或边框把外边距分隔开),它们的上和/或下外边距也会发生合并

  • 只有普通文档流中块框的垂直外边距才会发生外边距合并。行内框、浮动框或绝对定位之间的外边距不会合并。

  1. BFC(边距重叠解决方案)
  • 基本概念:块级格式化上下文

  • 渲染规则:

    • BFC元素的垂直方向的边距会发生重叠
    • BFC区域不会与浮动元素的Box重叠(可用于清除浮动)
    • BFC是一个独立的容器,外面的元素不会影响里面的元素,里面的元素也不会影响外面的元素
    • 计算BFC高度时,浮动元素也会参与计算
  • 创建BFC:

    • float值不为none
    • position值不为static/relative
    • overflow值不为visible(等于auto/hidden)
    • display值为table-cell
  • 实例:BFC垂直方向重叠问题(创建包裹div,设置overflow属性为hidden)

<section id="margin">
    <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 id="layout">
    <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 id="float">
    <div>我是浮动元素</div>
</section>

* {
    margin: 0;
    padding: 0;
}
#float {
    background: red;
    // overflow: auto;
    float: left;
}
#float .float {
    float: left;
    font-size: 30px;
}
复制代码

原型链

  1. 创建对象的几种方法
  • 字面量
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);
复制代码
  1. 原型链概念
  • 原型链是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
复制代码
  1. instanceof的原理

  • 判断实例对象的__proto__和构造函数的prototype是否是同一引用
  • A instanceof B:查看对象B的prototype属性指向的原型对象是否在对象A的原型链上,若在则返回true,若不在则返回false
  1. 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;
    }
}
复制代码

面向对象

  1. 借助构造函数实现继承(部分继承)
  • 缺点:该方法无法继承父类的原型方法
function Parent() {
    this.name = 'parent';
    this.arr = [1];
}
function Child() {
    Parent.call(this);
    this.type = 'child';
}

复制代码
  1. 借助原型链实现继承
  • 缺点:原型链中的原型对象是共用的(同一对象)
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

复制代码
  1. 组合方式
  • 缺点:父类构造函数执行了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;

复制代码
  1. 原型式继承
  • 该方法借助原型可以基于已有的对象创建新对象,从而不必因此创建自定义类型
function object(o) {
    function F(){};
    F.prototype = o;
    return new F();
}
复制代码
  • Object.create()规范了原型式继承
  1. 寄生式继承
  • 创建一个仅仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象
function createAnother(originalObj) {
    var clone = object(originalObj);
    clone.sayHi = function() {
        // ...
    }
    return clone;
}
复制代码

DOM事件

  1. DOM事件类
  • DOM0
element.onclick = function() {}
复制代码
  • DOM2
// W3C
element.addEventListener('click', function(){}, false);
// IE
element.attachEvent('click', function(){}, false);
复制代码
  • DOM3(增加了许多事件)
element.addEventListener('keyup', function(){}, false);
复制代码
  1. DOM事件捕获/冒泡过程
  • window ==> document ==> html(通过document.docuemntElement获得) ==> ... ==> 目标元素
  • 反向为冒泡
  1. Event对象的常见应用
  • event.preventDefault();
表示阻止默认事件
复制代码
  • event.stopPropagation();
表示阻止冒泡行为
复制代码
  • event.stopImmediatePropagation();
应用于:事件响应的优先级
场景:一个按钮绑定两个click事件A和B,要求让点击A时,不执行B
复制代码
  • event.currentTarget;
表示:当前绑定事件的对象,恒等于this

特殊情况:
在父子嵌套关系中,父元素绑定了事件,单击子元素
此时,currentTarget指向的是父元素(因为它是绑定事件的对象);
此时,target指向了子元素(因为它是触发事件的具体对象)
复制代码
  • event.target;
表示:触发事件的某个具体对象
// IE
event.sourceElement
复制代码
  1. 自定义事件(无需使用回调)
  • 使用Event类,但是只能指定事件名称,无法添加数据
var eve = new Event('custom');
ev.addEventListener('custom', function(){
    console.log('custom'); 
});
// 触发
ev.dispatchEvent(eve);
复制代码
  • 使用CustomEvent不但可以指定事件名称,还可以指定一个object类型的参数

类型转换

  1. 7中数据类型
  • Boolean, Null, Undefined, Number, String, Symbol
  • Object
  1. 显示类型转换
  • 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
复制代码
  1. 隐式类型转换
  • 四则运算
  • 判断语句
  • Native调用
    • console.log自动转换为字符串
    • alert自动转换为字符串
  1. 常见题目
[] + [] ==> 
[] + {} ==>

{} + [] ==> 0
- 第一个大括号被作为代码块
- 若被console.log()包括的话结果为 [object Object]

{} + {} ==> "[object Object][object Object]"
- 

true + true ==>
1 + {a: 1} ==>
复制代码
  1. typeof返回值
  • undefined, boolean, number, string, symbol
  • function, object

设计模式

  1. 单例模式
  2. 观察者模式
  3. 适配器模式
  4. 工厂模式

通信类

  1. 同源政策及其限制
  • :协议 + 域名 + 端口
  • Cookie、LocalStorage和IndexDB无法读取
  • DOM无法获取
  • Ajax无法请求
补充:cookie,localStorage和sessionStorage区别
复制代码
  1. 前后端如何通信
  • Ajax(同源下通信方式)
  • WebSocket(不受同源策略限制)
  • CORS(支持跨域,支持同源)
  • Fetch(兼容性不好)
  1. 如何创建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;
}
复制代码
  1. 跨域通信的几种方式
  • 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>
    //动态创建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);
    };
</script>

// 服务器端
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){

});
复制代码

安全类

  1. XSS 跨站脚本攻击
- 它允许恶意用户将代码注入到网页上,其他用户在观看网页时就会受到影响。
- 这类攻击通常包含了HTML以及用户端脚本语言

举例:
通过客户端脚本(JS)在一个论坛发帖中发布一段恶意的JS代码就是脚本注入,如果这个代码内容有请求外部服务器,那么就叫做XSS!

防护:
- 如果您在cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击
- 对用户表单输入的数据进行过滤,对javascript代码进行转义
复制代码
  1. CSRF 跨站请求伪造
- 冒充用户发起请求(在用户不知情的情况下),完成一些违背用户意愿的请求
1.登录受信任网站A,并在本地生成Cookie 。
2.在不退出A的情况下,访问危险网站B。

防护:
token防御的整体思路是:
1. 后端随机产生一个token,把这个token保存在SESSION状态中;同时,后端把这个token交给前端页面
2. 下次前端需要发起请求(比如发帖)的时候把这个token加入到请求数据或者头信息中,一起传给后端;
3. 后端校验前端请求带过来的token和SESSION里的token是否一致;
复制代码

HTTP协议类

  1. HTTP协议的主要特点
  • 简单快速
    • 输入uri进行访问资源
  • 灵活
    • 通过不用的http协议完成不同类型数据传输
  • 无连接
    • 一次连接,无需再次连接
  • 无状态
    • 两次连接,无法区分浏览器端的身份
  1. HTTP报文的组成部分
  • 请求报文
    • 请求行(HTTP方法 + 请求地址 + HTTP协议及版本)
    • 请求头(key/value值)
    • 空行
    • 请求体
  • 响应报文
    • 状态行(HTTP协议及版本 + 状态码 + 原因)
    • 响应头
    • 空行
    • 响应体
  1. HTTP方法
  • GET
  • POST
  • PUT
  • DELETE
  • HEAD
  1. POST和GET的区别
  • GET回退是无害的,POST会再次提交请求
  • GET请求会被浏览器主动缓存,POST不会
  • GET请求只能url编码,POST支持多种编码方式
  • GET请求参数会被完整保留在浏览器历史记录里,POST中的参数不会被保留
  • GET请求在URL中传送的参数有长度限制(2KB),POST没有限制
  • GET请求参数通过URL传递,POST放在Request Body中
  1. 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: 请求未完成,服务器临时过载
  1. 什么是持久连接
  • 无连接:每个请求/应答客户端和服务器都要新建一个连接(HTTP/1.0)
  • Keep-Alive模式:避免重新建立连接(HTTP/1.1)
  1. 什么是管线化
  • 管线化机制通过持久连接完成
  • 只有GET和HEAD请求可以进行管线化,POST有限制
  • 管线化不会影响响应到来的顺序
请求1 - 响应1 - 请求2 - 响应2 - 请求3 - 响应3

// 管线化,请求打包发送,依次进行响应(单独),并保持顺序

请求1 - 请求2 - 请求3 - 响应1 - 响应2 - 响应3
复制代码
  1. https的介绍
1. HTTP的缺点
- 使用明文(不加密)通信,内容可能被窃听
- 不验证通信方的身份,可能遭遇伪装
- 无法证明报文的完整性,可能已遭篡改

2. HTTPS介绍

- HTTPS = HTTP + 加密 + 认证 + 完整性保护

1)HTTPS使用SSL,先和SSL通信,再由SSL和TCP通信

补充:

对称密钥加密:加密和解密用同一个密钥的方式(共享)
- 弊端:加密时密钥也会被发给对方

非对称密钥加密:
- 发送方使用接收方的公开密钥进行加密处理,接收方使用自己的私有密钥解密
- 弊端:处理速度过慢

2)HTTPS采用混合加密机制
- 交换密钥时,使用非对称加密方式;
- 之后的建立通信交换报文阶段,使用对称加密方式(共享)

3)证明公开密钥的正确性 --> 使用由数字证书认证机构颁发的公开密钥证书

4)HTTPS还可以使用客户端证书
复制代码
  1. HTTP2.0与HTTP1.1的区别
- HTTP/2采用二进制格式而非文本格式
- HTTP/2是完全多路复用的,同时发起无数个请求,并且响应可以同时返回
- 使用报头压缩,HTTP/2降低了开销
- HTTP/2让服务器可以将响应主动“推送”到客户端缓存中
复制代码
  • 因为“所有的HTTP2.0的请求都在一个TCP链接上”,“资源合并减少请求”,比如CSS Sprites,多个JS文件、CSS文件合并等手段没有效果
  1. 3次挥手 && 4此握手
  • TCP建立连接

  • TCP关闭连接
    • TCP连接是全双工的,因此,每个方向都必须要单独进行关闭
    • 当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了


补充问题:


渲染机制

  1. 什么是DOCTYPE及作用

DTD 文档类型定义

  • 告诉浏览器自身的文档类型

DOCTYPE

  • 声明文档类型和DTD规范的,主要用于文件的合法性验证
常见的DOCTYPE:

HTML 5
    <!DOCTYPE html>

HTML 4.01 Strict 该DTD包含所有HTML元素和属性,但不包括展示型的和弃用的元素(如:font)

HTML 4.01 Transitional 该DTD包含所有HTML元素和属性,包括展示型和弃用的元素
复制代码
  1. 浏览器的渲染过程

  • HTML经过HTML Parser变为DOM Tree
  • CSS经过CSS Parser变为CSSOM Tree(计算后的)
  • DOM Tree和CSSOM Tree结合后成为Render Tree(不知道具体HTML元素的具体位置)
  • Render Tree和Layout(精确计算位置、宽高、颜色等)结合后,进行绘制

  1. 什么是重排Reflow
  • 定义:DOM结构中的各个元素都有自己的盒子模型,这些都需要浏览器根据各种样式计算后将元素放到它该出现的位置,这个过程称为Reflow重排

  • 触发Reflow:

    • 增加、删除、修改或移动DOM节点
    • 修改CSS样式
    • 修改网页的默认字体(极其不推荐!白屏闪现)
  1. 什么是重绘Repaint
  • 定义:将页面要呈现的内容都画在页面上,这个过程称之为Repaint

  • 触发Repint:

    • DOM改动
    • CSS改动 (只要页面显示内容不同就会Repaint)
  • 如何减少Repaint的频率?

    • 通过DocumentFragment,将新创建的节点添加进去,向浏览器中一次性加入
DocumentFragment:

- 表示一个没有父级文件的最小文档对象。它被当做一个轻量版的Document使用,用于存储已排好版的或尚未打理好格式XML片段。
- DocumentFragment不是真实DOM树的一部分,它的变化不会引起DOM树的重新渲染的操作(reflow),且不会导致性能等问题。

- 可以使用document.createDocumentFragment()方法创建一个空的DocumentFragment

复制代码

JS运行机制 -- 任务队列和EventLoop

  1. 异步任务的类型:
  • setTimeout和setInterval
  • DOM事件(addEventListener)
  • Promise
  1. 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
复制代码
  1. setTimeout(fn, 0)的含义
  • 指定某个任务在主线程最早可得的空闲时间执行,意思就是不用再等多少秒了,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行
console.log('abc');
setTimeout(() => {
    console.log('执行啦')
},3000);setTimeout(() => {
    console.log('执行啦123')
},0);

// 'abc'
// 执行啦123
// 执行啦
复制代码
  1. process.nextTick(callback)的含义
  • 类似node.js版的"setTimeout",在事件循环的下一次循环中调用 callback 回调函数。
  • 在同一EventLoop中,属于Microtask,优先级高于Promise
  1. 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

复制代码
  1. 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')
    })
})
复制代码

参考1:juejin.im/post/59e85e…
参考2:juejin.im/post/5a6155…

  1. 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
复制代码

页面性能

  1. 提升页面性能的方法有哪些?
1. 资源压缩合并,减少HTTP请求

2. 非核心代码的异步加载 --> 异步加载的方式 --> 异步加载的区别

3. 利用浏览器缓存 --> 缓存的分类 --> 缓存的原理

4. 使用CDN

5. 预解析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只是规定了包含哪个域名的就对它进行预解析
复制代码
  1. 异步加载的方式
  • 动态脚本加载
  • defer
  • async
  1. 异步加载的区别
  • defer在HTML解析完之后才执行,如果是多个含defer属性的script标签,按照加载顺序执行
  • async是在加载完之后立即执行,如果是多个含async属性的script标签,执行顺序和加载顺序无关
  1. 浏览器缓存分类
  • 强缓存(直接使用浏览器的缓存,不再请求服务器)

    • 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:让浏览器上的强缓存事件变短

复制代码
  1. Yahoo军规的其他优化方法

错误监控

  1. 前端错误的分类
  • 即时运行错误(代码错误)
  • 资源加载错误
  1. 错误的捕获方式
  • 即时运行错误
    • try...catch
    • window.onerror(属于DOM0)
  • 资源加载错误
    • object.onerror(不会向上冒泡到window
    • performance.getEntries()
      • 返回一个数组,包含所有成功加载的资源(一种间接方法)
    • Error事件捕获
      window.addEventListener('error', (e) => {
          console.log('捕获', e);
      }, true);
      
      // true表示捕获,false表示冒泡
      复制代码
跨域的JS运行错误可以捕获吗?错误提示是什么?应该怎么处理?

Answer:

跨域错误只提示Script Error
解决方法:script标签增加crossorigin属性,设置js资源响应头为Access-Control-Allow-Origin: *
复制代码
  1. 上报错误的基本原理
  • 采用Ajax通信的方式上报
  • 利用Image对象上报(最佳)
(new Image()).src = 'http://...';       该请求可以直接被发出
复制代码

笔试题

  1. 写出下面的输出
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. 数组扁平化
  • 方法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);
复制代码
  1. 实现如下功能
  • 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;
        }
    })

}
复制代码

三面/终面

  1. 一个团队最重要的是什么?
  • 服从管理
  • 团队中层次分明,既有优秀的份子,也有做业务的中坚力量
  • 有对技术、对产品体验、对生活的追求
  • 综上,团队是健康的,氛围也应该是融洽的,协作也就是自然而然的
  1. 业务能力的描述
  • 做过什么业务
  • 有什么业绩
  • 使用什么技术方案
  • 突破什么技术难点
  • 遇到什么问题
  • 最大的收获是什么