面试小记
CSS部分
css三列布局,两边固定宽度,中间自适应
两栏布局(至少三种,float/absolute/flex)
三栏布局(双飞翼/圣杯/flex)
BFC(产生条件、用途)
产生条件:
- body根元素
- float元素 float不等于none
- 绝对定位 absolute fixed
- display等于inline-block、table-cells、flex
- overflow除了visible以外的值hidden、auto、scroll
BFC的用途/特性:
- 同一个BFC中的margin会重叠
- 浮动的元素会脱离普通文档流,但是BFC却可以包含浮动的元素,即消除浮动。
- BFC可以阻止元素被浮动元素覆盖。
用border画一个半圆/三角形
- 三角形
width: 0;
height: 0;
border-top: 50px solid blue;
border-right: 50px solid red;
border-bottom: 50px solid green;
border-left: 50px solid yellow;
- 半圆
width: 100px;
height: 50px;
border-radius: 50px 50px 0 0;
background: pink;
弹出框
实现向内旋转的效果(transform: rotateY)
css3如何开启动画的GPU加速(translate3d(0, 0, 0))
行内元素padding margin有用吗
- padding有用
- margin左右有用,上下无效。
盒模型有哪两种,区别是啥
- IE盒模型 border-box width = content宽度+padding+border
- W3C盒模型 content-box width = content宽度
常用移动端适配方案,border 1px在dpr比较大的手机上咋解决
- 传送门-掘金-移动端适配总结 感觉这篇文章里的适配代码是个无敌的答案
css水平垂直居中
- table-cell
- absolute
- transform:translate
- flex
- grid
flex align-item align-content的区别
- 容器属性
- flex-direction 主轴的方向
- flex-wrap 主轴一行展示不下,如何换行
- justify-content 项目在主轴上的对齐方式
- align-items 项目在交叉轴上的对齐方式
- align-content 多根轴线如何对齐
- 项目属性
- order 项目的排列顺序 越小越靠前 默认0
- flex-grow 项目的放大比例 默认为0 等于0不会放大。大于0,如果有剩余空间的情况下,项目1为x1,项目2为x2,则项目1占剩余空间的x1/(x1+x2)
- flex-shrink 项目的缩小比例 默认为1 等于0不参与缩小。ul width 100px。两个li宽100px,默认每个li都会被缩小1/(1+1)=1/2。第一个li 2,第二个li 3,则第一个li被缩小2/(2+3)=2/5,被缩小的实际宽度为2/5*100=40,剩余宽度为60。第一个li 为0,如果第二个li为0,那么两者都不缩小,第二个li 大于0,那么li实际宽度,第一个li不缩小。
- flex-basis 项目的占据主轴的空间 默认auto
- flex-self 该项目单独的对齐方式
单行,多行文本超出省略号
- 单行文本溢出
width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
- 多行文本溢出
:和::代表啥
- :伪类
- ::伪元素
JS部分
基本类型和引用类型在内存中是如何存储的
- 基本类型存储在栈内存
- 引用类型存储在堆内存 栈内存中存的是引用类型在堆内存中的地址 或者说存的是引用类型的引用
事件委托和事件代理
一个ul里面有很多个li的时候,如果想让每个li都有一个点击事件,不需要给每个li都去添加,只需要给ul添加一个就行了。
利用事件冒泡的原理。
闭包 会导致的问题 如何解决
闭包使用不当会导致内存溢出。因此,在退出函数之前,将不使用的局部变量全部删除。
js的垃圾回收机制
- 引用计数垃圾收集 概念:语言引擎有一张”引用表”,保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0,就表示这个值不再用到了,因此可以将这块内存释放; 缺点:该算法有一个限制——循环引用。
- 标记-清除垃圾回收算法 2012年起,所有现代浏览器都是用了该算法
原生的ajax
XMLHttpRequest
window.onload和document.onload的区别
- todo
match方法实现,支持?和*通配符
- todo
ES6模块和CommonJs模块的区别
- commonJs是被加载的时候运行,es6是编译的时候运行
- commonJs输出的是值的浅拷贝,es6输出值的引用
- commentJs具有缓存。在第一次被加载时,会完整运行整个文件并输出一个对象,拷贝(浅拷贝)在内存中。下次加载文件时,直接从内存中取值
- 参考链接 传送门-掘金-CommonJs 和 ESModule 的 区别整理 传送门-JavaScript 模块的循环加载
es6新特性列举几个
let/const
,箭头函数
,解构
,模板字符串
,Promise
,Class
var/let/const 区别
声明方式 | 变量提升 | 重复声明 | 暂时性死区 | 初始值 | 作用域 |
---|---|---|---|---|---|
var | 允许 | 允许 | 不存在 | 不需要 | 除块级 |
let | 不允许 | 不允许 | 存在 | 需要 | 块级 |
const | 不允许 | 不允许 | 存在 | 需要 | 块级 |
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。
es6中Object的方法 Object.assign()合并对象是浅拷贝,深拷贝
- 浅拷贝 Object.assign()
- 深拷贝
- JSON.parse(JSON.stringify(obj))
- 手写一个深拷贝
function deepClone(obj){
let result;
if(typeof obj === 'object'){
result = obj.constructor === 'Array' ? [] : {};
for(let i in obj){
result[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i];
}
}
else result = obj;
return result ;
}
es6模板字符串如何实现
function replace(content){
return content.replace(/\$\{([^}]+)\}/g,function(matched,childItem){
return eval(childItem);
})
}
// /\$\{([^}]+)\}/g
/*
这个正则匹配的内容就是${},括号里面是除了}以外所有的字符,并且若干个
*/
箭头函数和普通函数的区别
- 箭头函数声明时绑定this。
- 普通函数执行时绑定this
- 箭头函数不能用于构造函数,不能使用new,不能call,apply,bind,不能当作generator函数,不能使用yield,没有arguments,没有prototype。
var a={
func:function(){
console.log(this)
}
}
var b=a.func;
var c = {
func:()=>this
}
var d=c.func
//a.func 输出a本身 b(),c.func(),c()输出window
继承 原型 原型链
- 使用ES5实现一个继承 核心是用一个 F 空的构造函数去取代执行了 Parent 这个构造函数。
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function() {
console.log('parent name:', this.name);
}
function Child(name, parentName) {
Parent.call(this, parentName);
this.name = name;
}
function create(proto) {
function F(){}
F.prototype = proto;
return new F();
}
Child.prototype = create(Parent.prototype);
Child.prototype.sayName = function() {
console.log('child name:', this.name);
}
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.sayName(); // parent name: father
var child = new Child('son', 'father');
- ES6继承
class Father {
constructor(name){
this.name = name;
}
}
class Son extends Father {
constructor(parentName,age){
super(parentName);
this.age = age;
}
}
数组去重
// 方法1
[...new Set(array)]
// 方法2
handle = array=>array.reduce((p,c)=>{
!p.includes(c) && p.push(c);
return p;
},[])
- 遍历
数组的方法
forEach
map
reduce
filter
some
every
concat
push
pop
unshift
shift
- 用reduce实现map
- 用reduce计算[2, [3, 4], [4, [5, 6]]]里所有的值的和
sum = array=>array.reduce((p,c)=>p+(Array.isArray(c) ? sum(c) : c),0)
- 使用reduce实现flap方法
Flap = function(array){
return array.reduce(function(p,c){
return p.concat(Array.isArray(c) ? myFlap(c) : c);
},[])
}
myFlap = array=>array.reduce((p,c)=>p.concat(Array.isArray(c) ? myFlap(c) : c),[])
for in/for of/for 遍历对象 缺点 只能遍历原型的属性
for…in语句以任意顺序遍历一个对象自有的、继承的、可枚举的、非Symbol的属性。对于每个不同的属性,语句都会被执行。
for…of语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
for in
- 可以遍历数组和对象。可以获取key和value。
- 适用于普通对象,并且做了对应的优化。但是不适用于数组,因此速度要慢 10-100 倍。(适用,适合的意思,而不是不能使用的意思。)
- 循环会遍历 所有属性,不仅仅是这些数字属性。
for of
- 可以遍历数组,不可遍历对象。只能获取value
- 性能更好
- 遍历具有迭代器属性的对象。因此如果某个对象有迭代器属性,也是可以遍历的。
for循环就是遍历数组的方法。
如何判断一个数组
arr instanceof Array
arr.constructor === Array
Object.prototype.toString.call(arr)
Array.isArray(arr)
如何判断一个对象是否为空对象
for in
Object.keys()
如何判断一个类数组对象
- 本质还是一个对象
- 有一个length属性
异步的处理方式都有哪些
- 回调函数
- promise
- generator
- async/await
Promise和async/await
Promise原理 手写
- Promise中存在三个状态 等待态pending 执行态fullfilled 拒绝态rejected
- Promise状态不可逆 pending=>fullfied pending=>rejected
- Promise.then()方法返回一个Promise对象,因此可以链式调用
- Promise接受的函数参数是同步执行的,但then方法中的回调函数则是异步的
//promise声明
var promise = new Promise(function(resolve,reject){
if(){//成功
resolve(value)
}
else{
reject(err)
}
});
//promise调用
promise.then(function(value){
// succ
},function(err){
// fail
})
Promise封装setTimeOut
wait=time=>new Promise(y=>setTimeout(y,time))
async/await
async/await是generator的语法糖。
async/await对generator的改进有以下几点:
- 内置执行器
- Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行。
- 更好的语义
- async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。
- 更广的适用性
- co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。所以,async函数的实现,就是把Generator函数和自动执行器包装在一个函数里。
- 返回值是Promise
- async函数的返回值是 Promise 对象。
Event Loop
Event Loop分为浏览器中的Event Loop和Node中的Event Loop
浏览器中的Event Loop
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。
Node中的Event Loop
(1)V8引擎解析JavaScript脚本。
(2)解析后的代码,调用Node API。
(3)libuv库负责Node API的执行。它将不同的任务分配给不同的线程,形成一个Event Loop(事件循环),任务的执行结果会以异步的方式返回给V8引擎。
(4)V8引擎再将结果返回给用户。
宏任务和微任务的执行顺序
(1)先执行宏任务,执行完后,查看是否有微任务队列
(2)如果有微任务队列,则执行微任务
(3)如果没有,则读取宏任务中排在最前列的任务
(4)执行宏任务的过程中遇到微任务,则加入微任务队列
(5)执行完宏任务后,查看是否有微任务队列
常见的宏任务和微任务
- 宏任务
script、setTimeout、setInterval、setImmediate、requestAnimationFrame、I/O、UI Rendering
- 微任务
new Promise().then(回调)、Process.nextTick、Object.observe、MutationObserver
console.log(1);
async function async1(){
console.log(2);
await console.log(3);
console.log(4);
}
setTimeout(function(){
console.log(5)
},0)
async1();
new Promise(function(resolve){
console.log(6);
resolve();
console.log(9);
}).then(function(){
console.log(7);
});
console.log(8);
// 1 2 3 6 9 8 4 7 5
Node的执行顺序
数据输入 => poll轮询 => check检查 => close callbacks关闭事件回调 => timers定时器 => I/O callbacks I/O事件回调 => idle prepare 闲置 => 轮询
同步任务和异步任务的定义
CPU会立即执行的任务是同步任务。
node属于多线程还是单线程
- 单线程
node的前端模板
- ejs pug
node中间件的理解
node如何遍历整个目录里的所有文件
process
process对象是一个全局变量,它提供有关当前 Node.js 进程的信息并对其进行控制。
类似global全局对象,浏览器中的window。
call apply bind的区别 手写?
- 三者都是改变this的指向。
- 区别call,apply返回函数执行的结果,bind则返回一个函数
- 其中call的参数是展开的数据,apply的参数是数组
//call
Function.prototype.myCall = function(context=window){
context.fn = this;
let args = [...arguments].slice(1);
let result = context.fn(...args);
delete context.fn;
return result;
}
//apply
Function.prototype.myApply = function(context=window){
context.fn = this;
let result;
if(arguments[1]){
result = context.fn(...arguments[1]);
}
else{
result = context.fn();
}
delete context.fn;
return result;
}
//bind
Function.prototype.myBind = function(context){
if(typeof this !== 'function'){
throw Error('not a function')
}
let fn = this;
let args = [...arguments].slice(1);
let bindFn = function(){
return fn.apply(this instanceof bindFn ? this : context, args.concat([...arguments]));
}
let tem = function(){};
tem.prototype = this.prototype;
bindFn.prototype = new tem();
return bindFn;
}
new发生了什么
- 新建一个新对象
- this指向新对象
- 为新对象添加属性
- 返回新对象
函数递归 f(n) 返回1到n的累加和
function sum(n){
let result;
if(n>1){
result = n+sum(n-1);
}
else{
result = n;
}
return result;
}
const sum = n=>n>1 ? n+sum(n-1) : n;
SSR Nuxt Next
- 服务器端渲染
正则
- 匹配所有非空白符 \S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
- 匹配非回车的字符
- 匹配所有字符
- 12345678变成12,345,678的正则
//方法1
(str.replace(/\B(?=(\d{3})+$)/g, ','))
//方法2
str.toLocaleString()
- abc-def-hasdk-asdsa转换成驼峰
let str = 'abc-def-hasdk-asdsa';
str.replace(/[\-]{1}[a-z]{1}/g,sel=>sel[1].toUpperCase())
instanceOf/Object.create/new的实现原理
已知
一个全部装着url的数组urls和一个叫fetch的ajax api,实现一个函数ajaxAll(urls, maxParallel, cb),同时最多并行执行maxParallel个请求,所有url得到结果后执行cb
求以下输出
Object.defineProperty(obj, 'name', {get(){return this.name}})
// 循环引用 导致内存溢出
for(var i=0;i<5;i++){
setTimeout(()=>console.log(i))
}
//输出什么 如何修改
//输出5个5
//想输出1,2,3,4,5,可以使用let,IIFE,setTimeout第三个参数
手写一个div的拖拽功能
<head>
<style>
body{
position: relative;
}
#drag{
position: absolute;
width: 100px;
height: 100px;
background: red;
}
</style>
</head>
<body>
<div id='drag'>
</div>
<script>
let oDiv = document.getElementById('drag');
oDiv.onmousedown = function(e){
e = e || window.event;
let disX = e.clientX - oDiv.offsetLeft;
let disY = e.clientY - oDiv.offsetTop;
document.onmousemove = function(e){
e = e || window.event;
let l = e.clientX - disX;
let t = e.clientY - disY;
oDiv.style.left = l+'px';
oDiv.style.top = t+'px';
}
document.onmouseup = function(){
document.onmousemove = null;
document.onmouseup = null;
}
}
</script>
</body>
写一个工厂函数用于生成对象
//工厂模式?
function createPerson(name,age,job){
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
console.log(this.name);
}
return o;
}
实现一个mylog函数
//step1 mylog(str) 控制台输出str
const mylog = function(str){
console.log(str)
}
//step2 mylog(str1,str2,str3...) 控制台输出str1 str2 str3
const mylog = (...args)=>{
console.log(...args)
}
//step3 不适用es6的语法,如何实现
var mylog = function(){
console.log(Array.prototype.join.call(arguments,' '))
}
//step4 更简单的方法
var mylog = function(){
console.log.apply(null,arguments)
}
弹出框
一个弹出框,最外面一层遮罩,然后遮罩中间有一个弹出框的div,点击非弹框外的部分,弹框就会关闭。
tips:阻止事件冒泡
手写题
<ul id='ul'>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
let list = document.getElementsByTagName('li');
for(let i=0,len=list.length;i<len;i++){
list[i].index = i;
list[i].onclick=function(){
console.log(this.index);
}
}
</script>
常用的手写
- bind,call,apply
Function.prototype._call = function(context=window){
context.fn = this;
let args = [...arguments].slice(1);
let result = context.fn(...args);
delete context.fn;
return result;
}
Function.prototype._apply = function(context=window){
context.fn = this;
let result;
if(arguments[1]){
result = context.fn(...arguments[1])
}
else{
result = context.fn()
}
delete context.fn;
return result;
}
Function.prototype._bind = function(context){
if(typeof this !== 'function'){
throw Error('not a function');
}
let fn = this;
let args = [...arguments].slice(1);
let bindFn = function(){
let bindArgs = Array.prototype.slice.call(arguments);
// this的实例是bindFn,说明使用了new
return fn.apply(this instanceof bindFn ? this : context,args.concat(bindArgs));
}
// bindFn继承this
function tem(){}
tem.prototype = this.prototype;
bindFn.prototype = new tem();
return bindFn
}
- new
// myNew(fn, args) --> new fn(args)
/*
fn是构造函数
1、创建一个新对象;
2、将构造函数的作用域赋给新对象(因此this就指向了这个新对象);
3、执行构造函数中的代码(为这个新对象添加属性);
4、返回新对象。
*/
function myNew(fn, ...args){
if(typeof fn !== 'function'){
return new Error('not a function')
}
let obj = {};
if(fn.prototype !== null){
obj.__proto__ === fn.prototype
}
let res = fn.apply(obj,args);
if(res !== null && (typeof res === 'object' || typeof res === 'function')){
return res
}
return obj
}
- Object.create
// Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
// var b = Object.create(obj)
function create(obj){
function F(){}
F.prototype = obj
return new F()
}
- instanceOf
/*
一直遍历 left的__proto__,一直遍历到null。
如果到null之前都没有proto等于prototype就返回false
*/
function instanceOf(left,right){
let proto = left.__proto__;
let prototype = right.prototype;
while(true){
if(proto === null) return false;
if(proto === prototype) return true;
proto = proto.__proto__;
}
}
//这种写法更简洁吧
function instanceOf(left,right){
while(left){
if(left.__proto__ === right.prototye) return true;
left = left.__proto__
}
return false
}
- Promise
// todo 手写promise
const PENDING = 1;
const FULFILLED = 2;
const REJECTED = 3;
function MyPromise(fn){
let self = this;
self.val = '';
self.state = PENDING;
self.resolveQueue = [];
self.rejectQueue = [];
function resolve(val){
if(self.state === PENGDING){
setTimeout(()=>{
self.state = FULFILLED;
self.val = val;
self.resolveQueue.forEach(cb=>cb(val))
})
}
}
function reject(err){
if(self.state === REJECTED){
setTimeout(()=>{
self.state = REJECTED;
self.val = err;
self.rejectQueue.forEach(cb=>cb(err))
})
}
}
try{
fn(resolve,reject)
}
catch() {
reject(err)
}
}
MyPromise.prototype.then = function(resolveFn,rejectFn){
resolveFn = typeof resolveFn === 'function' ? resolveFn : (v=>v);
rejectFn = typeof rejectFn === 'function' ? rejectFn : (e=>{throw e});
if(self.state === FULFILLED){
return new MyPromise((resolve,reject)=>{
setTimeout(()=>{
try {
let x = resolveFn(self.val);
if(x instanceof MyPromise){
x.then(resolve)
}
else{
resolve(x)
}
}
catch(e) {
reject(e)
}
})
})
}
if(self.state === REJECTED){
return new MyPromise((resolve,reject)=>{
setTimeout(()=>{
try {
let x = rejectFn(self.val);
if(x instanceof MyPromise){
x.then(reject)
}
else{
resolve(x)
}
}
catch (e){
throw(e)
}
})
})
}
}
- flat
flat = array=>array.reduce((p,c)=>p.concat(Array.isArray(c) ? flat(c) : c),[])
- 节流
function throttle(fn,delay){
let prev = new Date();
return function (){
let now = new Date();
if(now-prev>delay){
fn.apply(this,arguments);
prev = new Date();
}
}
}
- 防抖
function debounce(fn,delay,immediate){
let timer;
return function(){
if(immediate){
fn.apply(this,arguments);
}
if(timer) clearTimeout(timer);
timer = setTimeout(()=>{
fn.apply(this,arguments);
},delay)
}
}
- 继承
function Father(name){
this.name = name;
}
Father.prototype.sayName = function(){
console.log('my name is ',this.name);
}
function Son(name,fatherName){
//step 1
Father.call(this,fatherName);
this.name = name;
}
function create(Father){
//step 2
function F(){};
F.prototype = Father.prototype;
return new F();
}
Son.prototype = create(Father);
Son.prototype.say = function(){
console.log('hello boy');
}
Son.prototype.constructor = Son;
Vue部分
MVVM
M:Model层 数据模型
V:View层 视图层
VM:ViewModel层 是Model和Controller之间的一座桥梁。
双向数据绑定 V和VM之间采用双向数据绑定,View的变动,会自动反映在ViewModel。反之亦然。
各部分之间的通讯都是双向的。 MVVM的设计思想:关注Model的变化,让MVVM框架去自动更新DOM的状态,从而把开发者从操作DOM的繁琐步骤中解脱出来。
单向数据流
- M,VM和V中,数据流从M流向VM,再由VM去修改V。
Vue提倡的是单向数据流,但是v-model是双向数据流,为什么?
- v-model只是一个语法糖,本质还是单向数据流
- v-bind:value和v-on:input
vue.use()
全局注入一个插件
Vue双向绑定实现原理/数据响应系统原理
Vue.js 是采用 Object.defineProperty 的 getter 和 setter,并结合观察者模式来实现数据绑定的。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。getter 做的事情是依赖收集,setter 做的事情是派发更新
Vue中,数组的操作是如何实现数据响应的(push等原型函数以及为什么用$set)
- todo
Vue父组件向子组件通信,子组件向父组件通信,兄弟组件通信
props
$emit
vue 3有哪些新的东西知道吗
- 可以着重聊聊数据响应系统使用Proxy和原来的区别,传送门
- 对Vue确实需要比较深的了解,推荐把这个看完 Vue技术内幕,不过这个里面没有AST的东西
以下代码经过模板渲染后变成了什么样子简单讲一下
<parent :prop1="data1">
<child :prop2="data2"></child>
</parent>
provide/inject能解决什么问题
- todo
computed和props/data的区别,其实现原理是什么
- todo
Vue Router
- 主要有两种模式 hash模式 history模式
- 本质就是监听url的变化,然后匹配路由规则,显示相应的页面,并且无需刷新
- hash模式 点击跳转通过hashchange事件来监听到url的变化 手动刷新通过load事件
- history模式 点击跳转通过pushState 浏览器动作 回退等 popState 刷新或者输入url 会向服务器发送请求 需要后端配合重定向
Vue Router中query和param的区别
- 都可以在两个页面之间进行传值
- query path?id=1 ; param path/1
- param如果不在router中配置的话,页面刷新,param会丢失
Vuex数据周期 如何使用 有什么好处
state – dispatch –> actions – commit –> mutations
vue.nextTick的原理 如何找到dom 依赖收集过程
- nextTick是Vue的一个全局函数,用于处理dom更新操作。Vue里面有一个watcher,用于观察数据的变化,然后更新dom。Vue里面并不是每次数据变化都会触发更新dom,而是将这些操作都缓存在一个队列,在一个事件循环结束之后,刷新队列,统一执行dom操作。
- watch的实现,当某个响应式数据发生变化的时候,它的setter函数会通知闭包中的Dep,Dep则会调用它管理的所有Watch对象。触发Watch对象的update实现。Watch对象并不是立即更新视图,而是被push进了一个队列queue,此时状态处于waiting的状态,这时候继续会有Watch对象被push进这个队列queue,等待下一个tick时,这些Watch对象才会被遍历取出,更新视图。同时,id重复的Watcher不会被多次加入到queue中去,因为在最终渲染时,我们只需要关心数据的最终结果。
- Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。
- Vue是异步执行dom更新的
- Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
- 源码中
- 2.5版本使用宏任务和微任务组合
- 源码中依次判断的逻辑:先看是否可以使用Promise,MutationObserver,setImmediate,setTimeout
virtual-dom diff算法具体实现过程
- diff算法源于snabbdom,复杂度为O(n)
- 只会在同层级比较,不会跨层级比较
- 重点是patch方法
patch方法详解
注释:vnode:新的虚拟节点 oldVnode:旧的虚拟节点
- vnode不存在但是oldVnode存在,需要销毁oldVnode
- oldVnode不存在但是vnode存在,创建新节点
- oldVnode和vnode都存在
- oldVnode和vnode是同一个节点 执行patchVnode方法
- vnode创建真实dom并替换oldVnode.elm
patchVnode方法详解
- oldVnode和vnode完全一致,则不需要做任何事情
- oldVnode和vnode都是静态节点,且具有相同的key,当vnode是克隆节点或是v-once指令控制的节点时,只需要把oldVnode.elm和oldVnode.child都复制到vnode上即可
- vnode不是文本节点或注释节点
- 如果oldVnode和vnode都有子节点,且2方的子节点不完全一致,就执行updateChildren方法
- 只有oldVnode有子节点,那就把这些节点都删除
- 如果只有vnode有子节点,那就创建这些子节点
- 如果oldVnode和vnode都没有子节点,但是oldVnode是文本节点或注释节点,就把vnode.elm的文本设置为空字符串
- vnode是文本节点或注释节点,但是vnode.text != oldVnode.text时,只需要更新vnode.elm的文本内容就可以
updateChildren方法详解
- oldStartIdx>oldEndIdx oldCh已经遍历完了,但是newCh还没有,把newStartIdx-newEndIdx之间的vnode都是新增的,把这些vnode添加到oldCh末尾
- newStartIdx>newEndIdx newCh已经遍历完了,但是oldCh还没有,把oldCh中oldStartIdx-oldEndIdx之间的vnode删除
- oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx
- oldVnode第一个child不存在,oldStart索引右移
- oldVnode最后一个child不存在,oldEnd索引左移
- oldStartVnode和newStartVnode是同一个节点,patchVnode两个节点,索引右移
- oldEndVnode和newEndVnode是同一个节点,patchVnode两个节点,索引左移
- oldStartVnode和newEndVnode是同一个节点,移动oldStartVnode到oldEndVnode节点后面
- oldEndVnode和newStartVnode是同一个节点,移动oldEndVnode到oldStartVnode前面
- oldChildren中寻找跟newStartVnode具有相同key的节点,如果找不到相同key的节点,说明newStartVnode是一个新节点,就创建一个,然后把newStartVnode设置为下一个节点
- 如果找到了相同key的节点,比较两个节点是否属于同一个节点,如果属于同一节点,就patchVnode,否则新创建节点
为什么要使用key?
不设key,newCh和oldCh只会进行头尾两端的相互比较,设key后,除了头尾两端的比较外,还会从用key生成的对象oldKeyToIdx中查找匹配的节点,所以为节点设置key可以更高效的利用dom。
Vue,React,Angular区别
Vue多端框架
- uni-app
axios 拦截器
- axios.interceptors.request.use
- axios.interceptors.response.use
- axios.default.header
Wepack以及项目优化
webpack打包原理
module和chunk的区别
loader和plugin的区别
- loader loaders是在打包构建过程中用来处理源文件的(JSX,Scss,Less),一次处理一个;
- plugin plugins并不直接操作单个文件,它直接对整个构建过程起作用。
webpack 4相比3有什么变化
webpack构建速度优化有哪些方式(这个能扯半个小时)
tree-shaking/scope-hoisting/code-spliting 都有什么作用
webpack热更新的模块
- HotModuleReplacementPlugin
- 该模块是如何做到监听代码修改然后刷新页面的
webpack配置压缩一个js如何配置
- webpack.base.conf.js entry output module loaders
webpack编译后图片名为什么会改变
浏览器及安全
浏览器中支持最大的请求并发数
chrome中是6
阻止冒泡
- stopPropagation
- cancelBubble
常见的浏览器安全问题
- XSS 跨站脚本攻击
XSS 的原理是恶意攻击者往 Web 页面里插入恶意可执行网页脚本代码,当用户浏览该页之时,嵌入其中 Web 里面的脚本代码会被执行,从而可以达到攻击者盗取用户信息或其他侵犯用户安全隐私的目的。
- CSRF 跨站请求伪造攻击
1.浏览器登录信任网站A
2.通过验证,在浏览器中产生cookie
3.用户在没有登出A的情况下,访问危险网站B
4.B要求访问A网站,并发一个请求
5.根据B的要求,浏览器待着cookie访问A
- SQL注入
- 命令行注入
- DDos攻击
原理就是利用大量的请求造成资源过载,导致服务不可用
- 流量劫持
- DNS劫持
- DNS劫持
“你打了一辆车想去商场吃饭,结果你打的车是小作坊派来的,直接给你拉到小作坊去了”
- HTTP劫持
“你打了一辆车想去商场吃饭,结果司机跟你一路给你递小作坊的广告”
chrome跨标签通信
- 获取句柄 postMessage
- localStorage
- cookie
- SharedWorker
跨域
- jsonp
- CORS
- WebSocket
- Nginx代理
- Node中间件代理
http-proxy-middleware
proxy-middleware
输入网址回车之后浏览器做了什么
以chrome为例:
- 输入地址 会从历史记录,书签等地方智能提示补全url。查找缓存。
- NDS解析
- TCP请求
- 浏览器向服务器发送一个 HTTP 请求
- 服务器的永久重定向相应(如果有的话)
- 浏览器跟踪重定向地址
- 服务器处理请求
- 服务器返回一个 HTTP 响应
浏览器显示HTML
浏览器发送请求获取嵌入在 HTML 中的资源(图片,CSS,JS,音频,视频)
进程和线程
- 进程是cpu资源分配的最小单位(是能拥有资源和独立运行的最小单位)
- 线程是cpu调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)
- 进程是一个工厂,拥有独立的资源(独立的一块内存),进程之间相互独立,工厂中有一个或者多个工人(一个进程由一个或者多个线程组成)。
- 线程是工厂中的工人,工人共享空间(同一进程下的各个线程之间共享程序的内存空间)
Chrome是多进程吗,为什么是多进程而不是多线程,包括哪些进程
- 是多进程的。一个网页相当一个进程
包括以下四个进程:
- Browser进程:浏览器的主进程(负责协调、主控),只有一个
负责浏览器界面显示,与用户交互。如前进,后退等 负责各个页面的管理,创建和销毁其他进程 将Renderer进程得到的内存中的Bitmap,绘制到用户界面上 网络资源的管理,下载等
- 第三方插件进程
每种类型的插件对应一个进程,仅当使用该插件时才创建看上面的图片中的“扩展程序”就是了
GPU进程:最多一个,用于3D绘制等
浏览器渲染进程(浏览器内核):重点
Renderer进程,内部是多线程的 页面渲染,脚本执行,事件处理等
浏览器多进程的优势
- 单个tab奔溃不会影响整个浏览器
- 避免第三方插件影响浏览器
- 多进程充分利用多核优势
Chrome 渲染(render)进程包含哪些线程
- GUI渲染线程
负责渲染浏览器界面,解析HTML、CSS 当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行 GUI渲染线程与JS引擎线程是互斥的,因为JS可以操作DOM元素, 从而影响到GUI的渲染结果,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行
- JS引擎线程
JS内核(例如V8引擎),负责处理Javascript脚本程序 JS引擎一直等待着任务队列中任务的到来,然后加以处理 GUI渲染线程与JS引擎线程是互斥的,所以如果JS执行时间过长,页面渲染就不连贯,造成页面渲染加载阻塞
- 事件触发线程
由于JS引擎这个单线程的家伙自己都忙不过来,所以需要浏览器另开一个线程协助它 待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
- 定时触发器线程
setInterval与setTimeout所在线程 JS引擎阻塞状态下计时不准确,所以由浏览器另开线程单独计时 计时完毕后,添加到事件队列中,等待JS引擎空闲后执行 W3C规定,setTimeout中低于4ms的时间间隔算为4ms
- 异步HTTP请求线程
如果请求有回调事件,异步线程就产生状态变更事件,将这个回调再放入事件队列中,等JS引擎空闲后执行
Chrome是如何实现setTimout的
todo
如何监控前端性能
todo
前端性能优化
todo
浏览器缓存机制
- 强制缓存
- 协商缓存
- 先判断是否命中强缓存,如果命中,则不请求服务器,直接从缓存中返回数据;
- 如果未命中,则向服务器发送一个请求,再判断是否命中协商缓存,如果命中,更新缓存,然后缓存中返回数据
- 如果均未命中,则向服务器发送请求,服务器返回数据。
网络相关
Http状态码
- 1** 信息类
- 2** 成功
- 3** 重定向类 301永久重定向 302临时重定向
- 4** 客户端出错
- 5** 服务器出错
- 200成功/204无内容
- 301永久重定向/302临时重定向/304未修改
- 401未授权/403禁止访问/404未找到/405请求方式不对
- 500服务器内部错误/502错误网关/503服务不可用/504服务器超时
Http常见请求方法以及简单描述
get
post
head
options
delete
put
Http header中都有哪些信息
Accept
,Connection
,Content-Type
,Expires
,Server
,Etag
,Last-Modified
,Server
,Date
,Cache-Control
,User-Agent
等。
Http和Https的区别
Http主要有三点不足。
- 通信使用明文,未加密。导致内容可能会被窃听。
- 不验证通讯方的身份。有可能遭遇伪装。
- 无法证明报文的完整性。有可能已遭篡改。
Https的出现也是为了解决上述问题。因此: Http+加密+认证+完整新保护 = Https。 Https是身披SSL外壳的Http。SSL协议在Http和Tcp协议之间。
TCP三次握手过程
- 第一次握手。client发送一个SYN(J)包给server,等待server的ACK回复,进入
SYN-SENT
状态。 - 第二次握手。server接收到SYN(seq=J)包后就返回一个ACK(J+1)包以及一个自己的SYN(K)包,然后等待client的ACK回复,server进入
SYN-RECIVED
状态。 - 第三次握手。client接收到server发回的ACK(J+1)包后,进入
ESTABLISHED
状态。然后根据server发来的SYN(K)包,返回给等待中的server一个ACK(K+1)包。等待中的server收到ACK回复,也把自己的状态设置为ESTABLISHED
。
为什么要三次握手
为了确保通讯双方都有收发信息的能力。
TCP四次挥手
- 第一次挥手。client发送一个FIN(M)包,此时client进入
FIN-WAIT-1
状态,这表明client已经没有数据要发送了。 - 第二次挥手。server收到了client发来的FIN(M)包后,向client发回一个ACK(M+1)包,此时server进入
CLOSE-WAIT
状态,client进入FIN-WAIT-2
状态。 - 第三次挥手。server向client发送FIN(N)包,请求关闭连接,同时server进入
LAST-ACK
状态。 - 第四次挥手。client收到server发送的FIN(N)包,进入
TIME-WAIT
状态。向server发送ACK(N+1)包,server收到client的ACK(N+1)包以后,进入CLOSE
状态;client等待一段时间还没有得到回复后判断server已正式关闭,进入CLOSE
状态。
为什么TCP建立连接是三次而关闭连接要多一次
因为server的ACK和FIN是分开发送了,因此关闭多了一次。 client发送一个FIN包,表示client已经没有数据要发送了。但是此时server可能还会有未发送的数据,因此server也要发送一个FIN包。
Https认证过程
- 浏览器发送一个连接请求给安全服务器。
- 服务器将自己的证书,以及同证书相关的信息发送给客户浏览器。
- 客户浏览器检查服务器送过来的证书是否是由自己信赖的 CA 中心所签发的。如果是,就继续执行协议;如果不是,客户浏览器就给客户一个警告消息:警告客户这个证书不是可以信赖的,询问客户是否需要继续。
- 接着客户浏览器比较证书里的消息,例如域名和公钥,与服务器刚刚发送的相关消息是否一致,如果是一致的,客户浏览器认可这个服务器的合法身份。
- 服务器要求客户发送客户自己的证书。收到后,服务器验证客户的证书,如果没有通过验证,拒绝连接;如果通过验证,服务器获得用户的公钥。
- 客户浏览器告诉服务器自己所能够支持的通讯对称密码方案。
- 服务器从客户发送过来的密码方案中,选择一种加密程度最高的密码方案,用客户的公钥加过密后通知浏览器。
- 浏览器针对这个密码方案,选择一个通话密钥,接着用服务器的公钥加过密后发送给服务器。
- 服务器接收到浏览器送过来的消息,用自己的私钥解密,获得通话密钥。
- 服务器、浏览器接下来的通讯都是用对称密码方案,对称密钥是加过密的。
HTTP2相比HTTP1多了什么
新的二进制格式(Binary Format),HTTP1.x的解析是基于文本。 基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
多路复用(MultiPlexing) 即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。
header压缩 如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
服务端推送(server push) 同SPDY一样,HTTP2.0也具有server push功能。
HTTP2的多路复用和HTTP 1.1的keep-alive有什么区别
HTTP/1.0 一次请求-响应,建立一个连接,用完关闭;每一个请求都要建立一个连接;
HTTP/1.1 Pipeling解决方式为,若干个请求排队串行化单线程处理,后面的请求等待前面请求的返回才能获得执行机会,一旦有某请求超时等,后续请求只能被阻塞,毫无办法,也就是人们常说的线头阻塞;
HTTP/2多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行。
cookie
- expires 过期时间
- domain 域名
- path 路径
- security 安全情况下才传输给服务器 https
- httponly js是否可以操作
- 如何防止js访问cookie,如何限制只能在https中才能访问cookie(set-cookie相关字段)
websocket是如何建立连接的
算法
递归的复杂度
快排的时间复杂度和空间复杂度
冒泡排序
for(let i=0;i<a.length-1;i++){
for(let j=0;j<a.length-i-1;j++){
if(a[j]>a[j+1]){
[a[j],a[j+1]] = [a[j+1],a[j]];
}
}
}
给个数组和n,找到数组里的三个数相加是n,复杂度要求O(n^2*logn)
对数组[2, 3, 3, 2, 5]进行多重相邻去重(第一次去重后结果[2, 2, 5],然后得到[ 5 ]),复杂度要求O(n)
给一个二叉树,每个节点有value,找到是否存在一条路径,从根路径到叶节点的value相加为n
给一个二叉树,找到深度为n的一层,这一层的节点是所有层中最多的一层
二叉树广度优先遍历
给一个二维数组,写一个函数,回字形走法,走过的节点不能再走,走到不能走为止
给一个二维数组,如[[0, 0, 0], [1, 0, 0], [0, 1, 0]],1不能经过0能经过,给一个起点和一个终点,找到一条到终点的路径
反转链表
数组类
/*
一个数组arr,不新建变量,过滤掉arr中大于5的,并输出arr
*/
for(let i=0,len=arr.length;i<len;){
if(arr[i]>5){
arr.splice(i,1);
}
else{
i++;
}
}
其他
项目优化的点
- html标签语义化
- 高效的css
- 图片压缩,尽可能不用图片
- js等代码优化
- 减少http请求
- 打包后的代码压缩
- PWA
- webpack按需加载
- cdn
- 骨架屏
项目中做过比较复杂的东西如何解决
MySQL
鸡兔同笼 70脚 20头
- 20只动物都收起两只脚,这个时候,就剩下了30只脚
- 30只脚全是兔子的,这个时候每只兔子只有2只脚,所以有15个兔子
两个村庄 A村庄一定生两个不管男女,B村庄生出第一个男孩就不生了 这俩村庄的男女比例
推荐几个链接
传送门-「中高级前端面试」JavaScript手写代码无敌秘籍
小米 百度 猿辅导 瓜子 VIPKide 爱奇艺 阿里 滴滴 美团