面试小记


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可以阻止元素被浮动元素覆盖。

传送门-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

XMLHttp​Request

window.onload和document.onload的区别

  • todo

match方法实现,支持?和*通配符

  • todo

ES6模块和CommonJs模块的区别

es6新特性列举几个

let/const箭头函数解构模板字符串PromiseClass

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

    1. 可以遍历数组和对象。可以获取key和value。
    2. 适用于普通对象,并且做了对应的优化。但是不适用于数组,因此速度要慢 10-100 倍。(适用,适合的意思,而不是不能使用的意思。)
    3. 循环会遍历 所有属性,不仅仅是这些数字属性。
  • for of

    1. 可以遍历数组,不可遍历对象。只能获取value
    2. 性能更好
    3. 遍历具有迭代器属性的对象。因此如果某个对象有迭代器属性,也是可以遍历的。
  • for循环就是遍历数组的方法。

如何判断一个数组

  • arr instanceof Array
  • arr.constructor === Array
  • Object.prototype.toString.call(arr)
  • Array.isArray(arr)

如何判断一个对象是否为空对象

  • for in
  • Object.keys()

如何判断一个类数组对象

  • 本质还是一个对象
  • 有一个length属性

异步的处理方式都有哪些

  • 回调函数
  • promise
  • generator
  • async/await

传送门-关于js中的异步

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 LoopNode中的Event Loop

传送门-JS 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劫持

“你打了一辆车想去商场吃饭,结果你打的车是小作坊派来的,直接给你拉到小作坊去了”

  • 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,音频,视频)

传送门-老生常谈-从输入url到页面展示到底发生了什么

进程和线程

  • 进程是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引擎空闲后执行

传送门-浏览器工作原理:从输入URL到页面加载完成

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中都有哪些信息

AcceptConnectionContent-TypeExpiresServerEtagLast-ModifiedServerDateCache-ControlUser-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多个请求可同时在一个连接上并行执行。某个请求任务耗时严重,不会影响到其它连接的正常执行。

  • 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 爱奇艺 阿里 滴滴 美团