Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

面试记录 #45

Open
L-small opened this issue Jun 14, 2020 · 0 comments
Open

面试记录 #45

L-small opened this issue Jun 14, 2020 · 0 comments

Comments

@L-small
Copy link
Owner

L-small commented Jun 14, 2020

HTML

CSS

JS

Vue

小程序

浏览器和网络

webpack

后端

算法

计算机基础

设计模式

调试

mysql

其他

技术外

答案

HTML

doctype不加会怎么样

是指示 web 浏览器关于页面使用哪个 HTML 版本进行编写的指令。不加会用怪异模式解析,浏览器根据自己的方式解析,展示不同的样式。

script加defer和async区别

相同点:都会异步去加载JS,JS下载时不干扰DOM解析
区别:

  • defer在DOM解析完后,DOMContentLoaded之前执行,并且按照JS顺序执行
  • async下载完后会立即执行,会干扰DOM解析,不能保证JS顺序

HTML解析流程

收到请求 -> 字节流解码 -> 输入流预处理(可能会有document.write写入) -> token化 -> DOM树

标签分类

内容分区
文本内容
图片和多媒体
表单
表格

块元素和行内元素

article aside div p
a b i button input img

preload和prefetch

preload优先级更高,解析到这行代码就去加载,但不执行,真正用的时候再执行,页面关闭停止请求资源
prefetch未来可能用到,闲时加载资源,页面关闭依然请求

disabled和readonly区别

disabled作用于所有表单元素,禁止元素一切行为,表单提交时不会传递数据
readonly作用于input textarea输入元素,可以获取焦点,只是设置为只读,表单提交可以传递数据

CSS

盒模型

标准盒模型:width = content

怪异盒模型:width = content + padding + border

width: 100px;
padding: 10px;
border: 1px;
margin: 10px;

标准盒模型占位:100px + 20px + 2px + 20px = 142px
怪异盒模型占位:100px + 20px = 120px

box-sizing属性

  • content-box:标准盒模型
  • border-box:怪异盒模型

position属性和作用

  • static:元素在文档流中的当前的布局位置。此时top等无效
  • relative:元素先放置在未添加定位时的位置,在不改变页面布局下调整元素位置。对table-xx元素无效
  • absolute:元素移出文档流,并不为元素预留空间,指定元素相对于最近的非static定位祖先元素偏移,且不会和其他边距合并
  • fixed:移出文档流,不为元素预留空间,通过指定元素相对于视口的位置来指定元素位置。创建新的层叠上下文。当元素祖先的transform的perspective 或 filter 属性非 none 时,容器由视口改为该祖先
  • sticky:根据文档流进行定位。相对于它最近的滚动祖先和containing block。偏移不影响其他元素的位置。创建新的层叠上下文。overflow 是 hidden, scroll, auto, 或 overlay时阻止sticky行为。

垂直水平居中

1、绝对定位已知宽度、绝对定位+margin反向偏移

<div class="wrap">
    <div class="example2"></div>
</div>
.wrap {
  position: relative;
  width: 300px;
  height: 300px;
  background: blue;
}
.example2 {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100px;
  height: 100px;
  margin: -50px 0 0 -50px;
  background: red;
}

2、绝对定位宽度未知、transform
上面的方法把margin换成transform: translate(-50%, -50%)
3、flex布局

.wrap {
  position: flex;
  width: 300px;
  height: 300px;
  background: blue;
  just-content: center;
  align-item: center
}
.example2 {
  width: 100px;
  height: 100px;
  background: red;
}

4、绝对定位,margin: auto,top等值设为相同

.wrap {
  position: relative;
  width: 300px;
  height: 300px;
  background: blue;
}
.example2 {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  width: 100px;
  height: 100px;
  margin: auto;
  background-color: red;
}

5、子元素相对定位,margin和transform来实现

.wrap {
  position: relative;
  width: 300px;
  height: 300px;
  background: blue;
}
.example2 {
  position: relative;
  top: 50%;
  width: 100px;
  height: 100px;
  margin: 0 auto;
  transform: translateY(-50%);
  background-color: red;
}

水平居中

1、text-align: center
2、定宽+margin

width: 200px;
margin: 0 auto;

3、table+margin

display: table;
margin: 0 auto;

4、子元素设置inline-block

.parent {
  text-align: center
}
.child {
  display: inline-block
}

5、flex

display: flex;
justify-content: center;

6、绝对定位+负margin
7、绝对定位+transform

三栏布局

1、float+margin。缺点主要内容不能最先加载

<div class="container">
    <div class="left"></div>
    <div class="right"></div>
    <div class="main"></div>
</div>

.left {
  float: left;
  width: 100px;
}
.right {
  float: right;
  width: 100px;
}
.main {
  margin: 0px 100px;
}

2、BFC(BFC区域不会和浮动元素重叠)。缺点:主要内容不能最先加载

.left {
  float: left;
  width: 100px;
}
.right: {
  float: right;
  width: 100px;
}
.main {
  overflow: hidden;
}

3、圣杯。主体内容优先加载

<div class="container">
    <div class="main"></div>
    <div class="left"></div>
    <div class="right"></div>
</div>

.container {
  margin: 0 100px;
}
.main {
  float: left;
  width: 100%;
}
.left {
  float: left;
  width: 100px;
  margin-left: -100%;
  position: relative;
  left: -100px;
}
.right {
  float: left;
  width: 100px;
  margin-left: -100px;
  position: relative;
  right: -100px;
}

4、双飞翼。主体内容可以优先加载

<div class="content">
    <div class="main"></div>
</div>
<div class="left"></div>
<div class="right"></div>

.content {
  float: left;
  width: 100%;
}
.main {
  margin: 0 100px;
}
.left {
  float: left;
  width: 100px;
  margin-left: -100%;
}
.right {
  float: right;
  width: 100px;
  margin-left: -100px;
}

5、flex布局

<div class="container">
    <div class="main"></div>
    <div class="left"></div>
    <div class="right"></div>
</div>

.container {
  display: flex;
}
.main {
  flex-grow: 1;
}
.left {
  order: -1;
  flex: 0 1 100px;
}
.right {
  flex: 0 1 100px;
}

6、table布局

<div class="container">
    <div class="left"></div>
    <div class="main"></div>
    <div class="right"></div>
</div>

.container {
  display: table;
}
.left, .main, .right {
  display: table-cell;
}
.left, .right {
  width: 100px;
}

7、绝对定位

<div class="container">
    <div class="main"></div>
    <div class="left"></div>
    <div class="right"></div>
</div>

.container {
  position: relative;
}
.main {
  margin: 0 100px;
}
.left {
  position: absolute;
  left: 0px;
  top: 0px;
  width: 100px;
}
.right {
  position: absolute;
  right: 0px;
  top: 0px;
  width: 100px;
}

两栏布局

基本同上

flex属性

flex:flex-grow flex-shrink flex-basis

  • flex-grow 项目放大的比例。默认为0,存在剩余也不放大
  • flex-shrink 项目缩小的比例。默认为1,不足则收缩
  • flex-basis 项目占据主轴的空间。默认为auto,即项目本来的大小

清除浮动

1、浮动元素后加clear:both
2、加overflow:hidden触发BFC清除浮动
3、浮动元素容器after伪元素添加clear:both

实现十字窗

1、grid布局
2、flex布局
3、inline-block

BFC、IFC

BFC:块级格式化上下文
触发:

  • 根元素
  • float不会none
  • position: 不为absolute和fixed
  • display: inline-block、table-cell、table-caption
  • overflow: 不为visible

特点:
*

rem

rem,相对单位,相对的只是HTML根元素font-size的值

假设设计稿是宽750px来做的,书写css方便计算考虑,根节点的font-size假定为100px,得出设备宽为7.5rem。设计稿中标注的任何px数值都可以换算成px/100的rem值。

就是说,每一个设备的宽度都定为7.5个rem,然后宽度非750px的设备里,就需要用JS对font-size做动态计算。

计算rem的单位的方式可通过以下方式:
document.documentElement.style.fontSize = document.documentElement.clientWidth*(window.devicePixelRatio||1) / 7.5 + 'px';
需要考虑到dpr,即一倍屏两倍屏的问题。

rem em px的区别

rem基于根元素字体大小展现,可以html font-size设置为62.5%
em基于父元素
vw浏览器可视窗口宽度百分比
vh浏览器可视窗口高度百分比

bem的优缺点

BlockElementModifier其实是块(block)、元素(element)、修饰符(modifier)。
这三个部分使用__ 与--连接
优点:更具有语义化、方便多人协作开发、减少了class层级提高了性能(最好别超过3层)

css选择符解析顺序

如果从左往右会进行大量的共用样式重复匹配(遍历到最后才知道是不是匹配到),反过来减少了公用样式的匹配。从右往左匹配的全部是DOM节点的父元素,从左往右则是匹配的DOM节点的子元素,这样避免了HTML和CSS还没下载完需要等待的情况。

IFC:内联格式化上下文

css实现环绕布局(京东自营标签和标题)

float布局
image设置 align='right'或者其他

css移动端布局和兼容性注意问题

css flex align-item justify-content代表什么 direction代表什么

align-items属性定义项目在交叉轴上如何对齐。
justify-content属性定义了项目在主轴上的对齐方式。
flex-direction属性决定主轴的方向(即项目的排列方向)。

css pre-fetch pre-load区别

prefetch 闲时加载资源,不一定加载
preload 一定需要这些资源,一定加载

覆盖框架中的样式

引入样式表位置盖过默认的
class优先级
vue scope+:deep深度选择器
react :global(xxxxx)不会被应用到CSS Module react是CSS Module

换肤

link rel="alternative stylesheet" 替换样式文件,默认是备选,所以不显示,通过disabled来切换
sass变量换肤
修改class名

三个div实现顶部居中

响应式布局

rem
媒体查询

JS

this代表什么

当前执行上下文的一个属性,根据不同的执行上下文,this代表不同的值。

闭包

创建它的上下文已经毁了,但是它依然存在

JS继承

基于原型链继承

webworker通信

postMessage和onMessage

深拷贝浅拷贝

深拷贝:拷贝到具体的基本类型,彻底改变引用地址
浅拷贝:只是引用改变

for in for of forEach区别

for in 数组索引,对象的可枚举属性
for of 数组元素值,只用于可迭代对象
forEach 不能break或者return 空数据跳过回调函数

Object.prototype.toString和Object.prototype.toString.call区别

前者是调用对象的方法
后者是判断对象
this的指向不同

let const var 区别

暂时性死区
块级作用域
const let 不会绑定到window上
变量提升
const 只是只读引用

正常对象的toString和Object.prototype.toString.call不同

主要在于大部分都是基于对象原型做的修改,所以可以判断类型,而正常toString只是当前方法的

require和import的区别

require是运行时加载、值的拷贝
import是静态编译、值的引用

词法作用域

是根据定义时生成的作用域,创建作用域链时会有一个outer指向他的上一级,这个上一级是根据定义时生成的

原型链

__proto__指向原型,形成的原型链,访问对象属性的时候会沿着原型链往上查找

处理0.1+0.2 !== 0.3的问题

浮点运算精度问题导致的
1、转成整数然后运算完再回去
2、使用toFixed运算
3、转成字符串大数相加计算

reduce的实现

Array.prototype.reduce = function(fn, defaultVal) {
    let acc, i;
    if (defaultVal === undefined) {
        acc = this[0]
        i = 1;
    } else {
        acc = defaultVal;
        i = 0;
    }
    for (; i < this.length; i++) {
        acc = fn(acc, this[i], i, this);
    }
    return acc;
};

事件循环

主线程 -> 所有微任务 -> 一个宏任务 -> 渲染(不一定每次)

  • 宏任务
    setTimeout、setInterval、setImmediate、MessageChannel

优先级:主任务 > setImmediate > MessageChannel > setTimeout/setInterval

  • 微任务
    Promise().then()、process.nextTick、MutationObserver、async中await后的代码

优先级:process.nextTick > Promise.then > MutationObserver

防抖和节流

function debounce(fn, wait) {
  let timeout;
  return function() {
    const args = [...arguments];
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      fn.apply(this, args);
    });
  }
}

function throttle(fn, wait) {
    let timeout;
    return function() {
        let args = [...arguments];
        if (!timeout) {
            timeout = setTimeout(() => {
                fn.apply(this, args);
                clearTimeout(timeout);
            }, wait)
        }
    }
}

应用场景:

  • 防抖:联想表单输入时用等
  • 节流:滚动,秒杀等

EventEmitter

class EventEmitter {
  constructor() {
    this.events = Object.create(null);
  }
  on(key, fn) {
    if (!this.events[key]) {
      this.events[key] = [];
    }
    this.events[key].push(fn);
  }
  off(key, fn) {
    if (!this.events[key]) {
      return;
    }
    if (!fn) {
      this.events[key] = null;
    }
    this.events[key] = this.events[key].filter(item => item != fn);
  }
  once(key, fn) {
    const func = (args) => {
      this.off(key, fn)
      fn.apply(this, args)
    }
    this.on(key, func)
  }
  emit(key, ...args) {
    if (!this.events[key]) {
      return;
    }
    this.events[key].forEach((item) => {
      item.apply(this, args);
    })
  }
}

控制异步请求并发

主要通过队列来进行请求控制

function multiRequest(urls, maxNum) {
  const len = urls.length;
  let count = 0;
  while(count < maxNum) {
    next();
  }
  function next() {
    count++
    if (count > len) return;
    setTimeout(() => {
      if (count < len) {
        next()
      }
    }, 2000)
  }
}

作用域

JS是词法作用域,在函数定义时确定的。

执行上下文包含了变量环境和词法环境。通过变量环境实现了函数作用域,通过词法环境实现了块级作用域。

var a = 10;
function b() {
  console.log(a)
}
function c() {
  var a = 5;
  b();
}
c()

//  10

let const var function

let、const:创建提升,虽然在内存中,但是不能访问是因为V8虚拟机做了限制。
var:创建和初始化被提升。
function:创建、初始化、赋值提升。

let 和 const在全局作用域中声明是不会挂载到window上的。由[[script]]包裹

匿名自执行函数

var a = 1;
function b() {
  (function (){
    a = 2;
  })()
}

b()  // a = 2

bind、call、apply

Function.prototype.bind = function(context) {
  var args = [...arguments].slice(1);
  var _this = this;

  var fBound = function() {
    var _args = [...arguments];
    return _this.apply(this instanceof fBound ? this : context, [...args, ..._args])
  }
  fBount.prototype = Object.create(this.prototype);
  return fBound
}

Function.prototype.apply = function(context) {
  const args = arguments[1];
  context['_fn'] = this;
  const result = context._fn(...args);
  delete context['_fn'];
  return result;
}


Function.prototype.call = function(context) {
  const args = [...arguments].slice(1);
  context['_fn'] = this;
  const result = context._fn(...args);
  delete context['_fn'];
  return result;
} 

函数柯里化

function curry(fn, arg) {
  const length = fn.length;
  const args = arg || [];

  return function() {
    const _args = [...arguments];

    for(let i = 0; i < _args.length; i++) {
      args.push(_args[i])
    }
    if (args.length === length) {
      return fn.apply(this, args)
    } else {
      return curry(fn, args)
    }
  }
}

数组扁平化

主要方法:

  • 递归
  • toString()、join(',') 会改变成字符串
function flat(arr) {
  return arr.reduce((acc, item) => acc.concat(Array.isArray(item) ? flat(item) : item), [])
}

数组去重

主要方法:

  • 循环,借助api。for+indexOf、for+ includes
  • 循环,借助唯一性。for+object、for+ hasOwnProperty
  • 借助api。[...new Set(arr)]
  • sort排序后看相邻是否相同
function unique(arr) {
    const obj = {};
    return array.filter(function(item, index, array){
        console.log(typeof item + JSON.stringify(item))
        return obj.hasOwnProperty(typeof item + JSON.stringify(item)) ? false : (obj[typeof item + JSON.stringify(item)] = true)
    })
}

类型判断

  • instanceof
  • typeof
  • constructor
  • Object.prototype.toString.call()

instanceof

不断查找__proto__是否等于当前的prototype

function instanceof(left, right) {
  left = left.__proto__
  while(left) {
    if (left === right.prototype) {
      return true
    }
    left = left.__proto__
  }
  return false;
}

原型链

只有对象这个结构有继承。每个实例对象都有一个私有属性(proto)执向它的构造函数的原型对象(prototype),该原型对象也有自己的原型对象,层层向上直到原型对象为null。
image

this的指向

谁调用指向谁。new时指向实例。

继承

1、原型链继承

function Parent() {}
function Child() {}
Child.prototype = new Parent();
Child.prototype.constructor = Child;

缺点:
2、借用构造函数继承

function Parent() {}
function Child(name) {
  Parent.call(this, name)
}

缺点 父类型原型上的属性和方法不能继承。每次都调用构造函数创建新的方法和属性
3、组合继承

function Parent() {}
function Child(name) {
  Parent.call(this, name)
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;

缺点:调用两次构造函数,在prototype上添加了不必要的属性
4、寄生组合继承

function Parent() {}
function Child(name) {
  Parent.call(this, name)
}
Child.prototype = Object.create(Parent.prototype, {
  constructor: {
    value: Child
  }
})

优点:减少一次构造函数的调用,减少在原型上添加不必要的属性
5、class

  • 通过extends实现继承
  • 子类没有自己的this对象必须从父类继承this,然后对其加工。所以要先调用super。
  • 有两条继承链Child.proto === Parent、Child.prototype.proto === Parent.prototype

new

1、创建新对象
2、将构造函数的作用域赋值给这个对象
3、执行构造函数
4、返回新对象

function new() {
  const args = [...arguments];
  const _constructor = args.shift();
  const obj = Object.create(_constructor.prototype)
  const result = _constructor.apply(obj, args)
  return typeof result === 'object' ? result : obj
}

深浅拷贝

深拷贝:
1、JSON.stringify()。循环引用、函数不能拷贝
2、

function deepCopy(target, source) {
  for(let key in source) {
    if (Object.prototype.toString.call(source[key]) === '[object Object]' || Array.isArray(source[key])) {
      if (Object.prototype.toString.call(source[key]) === '[object Object]' && Object.prototype.toString.call(target[key]) !== '[object Object]') {
        target[key] = {}
      }
      if (Array.isArray(source[key]) && !Array.isArray(target[key])) {
        target[key] = []
      }
      deepCopy(target[key], source[key])
    } else if (source[key] !== undefined) {
      target[key] = source[key]
    }
  }
}

浅拷贝:
Object.assign
...扩展运算符

基本类型、基本包装类型、引用类型

基本类型:undefined、null、string、number、boolean、symbol
引用类型:object
基本包装类型:number、string、boolean(通过new来创建)

1 === Number(1)
typeof Number(1) === number

1.toString

1.也是数字

所以1..toString才可以

运算符优先级

() > . > [...]需计算的成员 > new ...(...)带参数 > 函数调用 > new ...()不带参数 > ! ~ + - ++前置 --前置 typeof(这一层从右到左)

长列表优化

虚拟列表,transform滑动页面。因为单独的层,走合成线程更快
外面一层监听滚动
里面分两层
一层是占位(计算每项高度*数量)
一层是窗口展示多少个DOM,然后用start,end记录进入视口的索引,start,end通过外层监听滚动去计算出来
https://segmentfault.com/a/1190000041812905
https://juejin.cn/post/6844903982742110216#heading-4

箭头函数和普通函数的区别

箭头函数:

  • 没有arguments
  • 没有this,根据自己作用域链的上一次继承this。
  • 继承来的this永远不变,call、apply、bind都无法改变
  • 不能作为构造函数
  • 没有原型prototype
  • 不能作为Generator,不能使用yeild

JSONP原理

利用script标签的src属性可以跨域,又由于src属性的限制,只能是get请求
客户端将参数拼到url上、并传一个函数名callback。后端将结果callback(JSON.stringify(data))返回

function jsonp(src, callbackName, callback) {
  const element = document.querySelector('script');
  const container = document.querySelector('body');
  element.setAttribute("type","text/javascript");
  element.src = src;
  document.body.appendChild(element);
  window[callbackName] = function(res) {
    callback && callback(res);
    container.removeChild(element)
    delete window[callbackName];
  }
}

完整实现

Promise的实现原理

观察者模式,then中收集回调,然后状态改变后逐一触发

Promise的方法

  • all
  • race
  • resolve
  • reject

Promise解决回调地狱

new Promise((resolve, reject) => {
  fetch('').then((res) => {
    resolve(res)
  }).catch((e) => {
    reject(e)
  })
}).then((res) => {
  return fetch('').then(res => res)
}).then((res) => {
  return fetch('').then(res => res)
}).catch((e) => {
  console.log(e)
})

JS为什么单线程

因为异步的操作主要还是操作DOM。设计成单线程让语言更简单。

TS

好处:增加代码可读性和可维护性

  • 类型约束
  • 接口
  • 类型检查

Map的实现

map和object的区别:

  • 键的类型:map可以任意类型、object只能字符串或者symbol
  • 键的顺序:map中的key是有序的、object的键是无序的
  • size:map键值个数可通过size获取、object只能手动计算
  • 迭代:map可直接被迭代、object需要通过其他方法

简单实现:(底层是邻接链表实现)

class Map {
  constructor() {
    this.init();
    this.len = 8;
    this.bucket = [];
  }
  init() {
    for(let i = 0; i < this.len; i++) {
      this.bucket[i] = { next: null }
    }
  }
  makeHash(key) {
    let newHash = 0;
    let keyStr = '';
    if (typeof key !== 'string') {
      keyStr = (key).toString();   // undefined、null没有toString()
    }
    for(let i = 0; i < key.length; i++) {
      newHash += key[i].charCodeAt();
    }
    return newHash
  }
  getList(key) {
    let hash = this.makeHash(key);
    return this.bucket[hash % this.len];
  }
  set(key, val) {
    let list = this.getList();
    let nodeNext = list;

    while(nodeNext.next) {
      if (nodeNext.key === key) {
        nodeNext.val = val;
        return;
      } else {
        nodeNext = nodeNext.next;
      }
    }
    nodeNext.next = {
      key,
      val,
      next: null
    }
    return nodeNext.next
  }
  get(key) {
    let list = this.getList();
    let nodeNext = list;

    while(nodeNext.next) {
      if (nodeNext.key === key) {
        return nodeNext.val;
      } else {
        nodeNext = nodeNext.next;
      }
    }
    return;
  }
  has(key) {
    let list = this.getList();
    let nodeNext = list;

    while(nodeNext.next) {
      if (nodeNext.key === key) {
        return true
      } else {
        nodeNext = nodeNext.next;
      }
    }
    return false;
  }
  delete(key) {
    let list = this.getList();
    let nodeNext = list;

    hile(nodeNext.next) {
      if (nodeNext.next.key === key) {
        nodeNext.next = nodeNext.next.next
        return true
      } else {
        nodeNext = nodeNext.next;
      }
    }
    return false;
  }
  clear() {
    this.init();
  }
}

V8引擎sort的实现

TimSort排序 小于10个则是插入排序 大于则是快排

数组的底层实现

继承自对象,分快慢数组
快数组底层是数组
慢数组底层是哈希表

快数组转换成慢数组
新容量 >= 3 * 扩容后的容量 * 2 则转变成慢数组或者加入的index > 1024 前面有1025个空值
慢数组转换成快数组
V8的启发式算法那检查空间占用量(慢数组不再比快数组节省50%空间时),转换为快数组

快数组是空间换时间
慢数组是时间换空间

对象的底层实现

属性不超过128个时,用Search Cache
属性为较连续数字,则是数组。
其他情况用哈希表
image

JS类图
image

https://zhuanlan.zhihu.com/p/26169639

A(), B(), C() var c = new C(),实现c访问A、B的方法

TS和JS区别

TS是JS的超集,集合了一些JS语法糖
执行环境层面:浏览器和Node可以直接运行JS,TS不可以
时序层面:TS执行前会转义成JS,然后解释执行

TS装饰器

Vue

Vue优势、为什么用Vue

vue门槛低,文档清晰。

生命周期

  • new Vue()
  • beforeCreate:初始化Render方法等
  • created:初始化props、methods、computed、data等
  • beforeMount:判断el的挂载方式、判断是否有template,双向绑定
  • mounted:将模板挂载到DOM上
  • beforeUpdate:更新数据后
  • update:触发update
  • beforeDestory:$destory执行
  • destoryed:删除各类工作

双向绑定

通过数据劫持和发布订阅者模式实现。
通过Object.defineProperty给数据添加getter(依赖收集)和setter(派发更新)实现数据劫持。
通过get进行依赖收集

收集过程:当实例化渲染一个watcher的时候,首先进入watcher的构造函数逻辑,执行get方法,get函数中把Dep.target赋值为当前渲染watcher并压入栈。执行渲染vm._render方法,生成渲染VNode时,对vm上的数据进行访问,此时触发数据对象的getter方法(执行Dep.target.addDep(this)方法)将watcher订阅到这个数据持有的dep的subs中,然后递归遍历添加所有的子项getter

通过set观察者更新视图

派发过程:当我们组件中对响应的数据做了修改,就会触发setter的逻辑,最后调用dep.notify方法(遍历subs里的依赖),调用每一个wathcer的update方法,update方法中有个queueWatcher的方法引入了队列的概念(是派发更新时优化的一个点,并不会每次数据变化都触发watcher回调,而是把这些watcher添加到一个队列中,然后在nextTick后执行watcher的run函数)。

队列排序(queueWatcher中有个根据id的sort排序)的保证:
1、组件更新由父到子,父组件创建早于子组件,所以watcher创建也是
2、用户自定义的watcher早于渲染watcher执行,用户自定义的watcher早于渲染watcher创建
3、父组件watcher期间被销毁,对应的watcher都会被跳过,所以父组件watcher先执行

run函数解析:通过this.get()得到当前的值,然后做判断,满足新旧值不等,新值是对象类型,deep模式任何一个条件时,执行watcher回调(回调执行时会把新旧值当参数传入,也是自定义watcher时新旧值的来源)。对于渲染watcher执行this.get()时,会执行getter方法,触发组件重新执行patch重新渲染。

Object.defineProperty缺点

1、无法监听数组变化
2、只能劫持对象的属性,如果属性还是对象,则需要深度遍历去劫持

vue3为啥用proxy
1、Proxy可以监听数组变化
2、proxy监听对象而非属性,在访问目标对象前架设了一层拦截,所以可以对外界的访问进行过滤和该写。可以劫持整个对象并返回新的对象

proxy缺点:兼容性问题,无法用polyfill实现

哪些监控不了,如何解决

不能检测数组和对象的变化
对象:无法检测属性的添加和移除,可以通过Vue.set或vm.$set来添加响应式属性
数组:无法检测通过下标直接设置数组项、修改数组长度时。通过vm.$set或者是splice数组方法

数组如何监控

重写数组的方法

3.0有什么

  • Composition API
  • Better TypeScript support:更优秀的Ts支持
  • 重写了虚拟DOM的实现
  • Tree shaking support用不到的指令功能打包的时候去掉
  • TS重构

vue和react区别

  • react采用高阶函数(react组件是一个纯粹函数所以比较容易,觉得mixin对组件侵入性太强)vue是mixin
  • react通信倾向于回调函数、vue倾向于通过事件
  • react通过JSX、vue通过Templates
  • react写法函数式、vue则是声明式

AST

抽象语法树,本质就是一个JS对象
先将模板语法变成AST,然后解析成HTML结构
image

虚拟DOM

由于DOM元素属性很多,性能有问题。所以虚拟DOM就是用JS对象去描述一个DOM节点,核心就是标签名、数据、子节点、键值等

模板编译

  • 创建AST parse
    • 通过正则将template模板进行字符串解析,得到指令、class、style等,生成AST(元素节点共3种,Type1普通元素,2是表达式,3是纯文本)。
    • 取<位置对应不同的方法通过栈来匹配(非一元标签才放入栈中)
    • 循环匹配,不断地裁剪后移(前面匹配过的就裁剪掉)
  • 优化AST
    • 深度遍历AST,标记静态节点,防止重复渲染
  • 生成代码
    • 生成with(this) {return code},然后通过compileToFunctions中的(createFunction)将render字符串通过new Function转换成可执行代码。

v-key作用]

vue识别节点的一个机制,虚拟DOM的VNode节点的唯一id,是diff的一种优化策略(判断是否是相同节点时需要,不添加则增多了比较次数),可根据key更准确、更快地找到VNode节点。
比较相同节点时先用的key值。
暴力破解时也是使用的key值匹配。

不用下标作为index原因,数组变化会导致下标变化,不稳定性导致性能浪费,无法关联上次的数据

diff算法

同层树节点的比较
特点:
只在同层级比较,不会跨层级比较
在diff比较的过程中,循环从两边向中间比较
策略:深度优先、同层比较

过程:
新节点存在,旧节点不存在——新增
新节点不存在,旧节点存在——删除
新旧节点都存在——判断是否是相同节点
通过patchVNode进行对比:
判断新旧虚拟DOM是否全等,是则结束
更新节点的属性
判断新节点是不是文本节点,是则更新文本,否则继续比较,
判断新旧节点是否有孩子节点:
两个都有孩子节点——updateChildren比较孩子节点
新的有,旧的没有——新增
新的没有,旧的有——删除
新旧都没有,判断文本节点是否有内容,有则清空
新旧节点都不存在——初始化页面,createElm

updateChildren时
新旧两个数组,声明四个指针,分别指向两个数组的首尾
两个开始指针比较,相同则往后移一个
不相同则两个结尾指针比较,相同前移
不相同则旧开和新尾对比,相同则各自移动
不相同则新开和旧尾对比,相同则各自移动
如果都不行,则遍历旧节点寻找(将数组转成对象,然后通过键值对key查找),找到则设为undefined。后续遇到undefined则跳过,当新旧各自出现指针重叠的时候,则是处理另一方多余节点的时候
image

data为什么是函数

(new时,对象的话共用内存地址)防止多个组件实例之间共用一个data,产生数据污染。采用函数,在initData时会作为工厂函数返回全新的data对象

nextTick的实现

Promise.then mutationObserver setImmediate setTimeout

为什么异步渲染

如果没有nextTick机制,每次数据更新都触发视图更新。有nextTick机制,只需要更新一次。性能更好

nextTick为什么可以拿到DOM

dom更新是实时的,只是操作DOM比较昂贵,所以一次性渲染DOM

AST上都有啥

computed和watcher的区别

v-if和v-show区别

v-if
将DOM元素删除或添加。
会触发子组件的生命周期
相应的事件监听等会销毁或新建(Watcher的newDepIds和depIds)
v-show:
display:none,DOM元素依然存在。
简单的CSS切换,不触发生命周期

slot和scope-slot,实现原理

slot:父组件编译和渲染阶段生成vnodes(作用域是父组件实例的)子组件渲染直接拿渲染好的vnodes
scpoe-slot:父组件渲染和编译的时候并不直接生成vnodes,而是再vnode的data中保留一个scopedSlot对象,存储不同名称的插槽以及他们的渲染函数,在编译和渲染子组件的时候才执行渲染函数生成vnodes

组件通信方式

props:父->子
provide-inject:祖->孙
vuex
eventBus:兄弟
$parent $root:兄弟
attrs或者listeners:祖->孙
$emit:子->父
ref:父->子

vuex的原理,怎么实现每个组件实例都有Store

vuex数据流

action和mutation区别

provide和inject是响应式的么

不是

eventBus实现

封装组件应该注意什么

router的实现原理

hash模式监听hashChange
hisitory模式监听popState事件,只有浏览器动作的时候后才会触发popState,pushState和popState并不会触发

router的导航守卫

beforeRouterLeave->beforeEach->beforeEnter->beforeRouteEnter->beforeResolve->afterEach->进入组件声明生命周期beforeCreate->created->beforeMount->mounted->beforeRouteEnter

加v-key和不加区别,哪个效率高

不一定,如果是不依赖于子组件状态或临时DOM状态的列表渲染 不加是高效的,可以直接复用

如何写全局组件

如何实现的异步加载

Vue的设计思想、React的设计思想

keep-alive缓存的是整个组件么

是,键名是key 键值为组件, 设置abstract为true
在keep-alive渲染函数中判断vnode是否有缓存,有则取出,没有则缓存
超过设定的缓存长度,则进行LRU清理(最近用的留下,远的删除)

keep-alive如果a->b然后a->c此时不想缓存a了

vue-router的meta是做什么的

动态设置html title标签内容和是否keepalive缓存

nextTick的应用场景

修改数据后,立刻得到更新后的DOM结构

mixin和extend的区别

mixin调用顺序:父beforeCreate->父created->父beforeMounted->子beforeCreate->子created->子beforeMount->子mounted->父mounted
更新过程:父beforeUpdate-->子beforeUpdate-->子updated-->父updated;
销毁过程:父beforeDestroy-->子beforeDestroy-->子destroyed-->父destroyed;

mixin是对Vue类的options进行混入,所有的vue实例对象都具备混入进来的配置行为
extend是产生一个继承自vue类的子类,只会影响这个子类的实例对象,不会对vue类本身以及vue类的实例对象产生影响

template是如何转成AST

转换的那个插件是怎么实现的

###浏览器

请求方法,get和post区别

请求方法:get、post、put、delete、options、trace等

为什么要四次挥手

为了确保接收到了信号。第四次客户端回复ACK是为了防止由于网络的因素导致服务端没有接收到确认信号而不能关闭。服务端没收到会重新发送FIN信号。

为什么四次挥手客户端要等待

防止产生混乱,因为当服务端发送FIN到客户端,可能还伴随其他的信息,如果客户端关闭的话,客户端的端口被新的应用占用,其他应用接收到这些数据,可能造成数据混乱

get和post区别:
get:产生一个TCP数据包。把header和data一起发送出去
post:产生两个TCP数据包。先发送header,服务器响应100 continue。浏览器再发送data服务器响应200

Node

Node require

1、是否是原生模块,内存中有则直接加载, 没有则加载模块并缓存模块
2、是否是文件模块,根据路径查找文件模块并缓存模块
3、自定义模块,逐级找node_modules。通过package.json的main字段找入口。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant