vue

207 阅读11分钟

vue的核心

  • 组件化
  • 数据驱动

虚拟dom

  • 本质是一个js对象
  • 2.0才有
虚拟DOM原理

虚拟DOM实现的原理主要包含三部分:

  • 使用JavaScript对象模拟真实的DOM,对真实的DOM进行抽象;
  • diff算法计算两棵虚拟DOM树的差异;
  • patch算法将两个虚拟DOM对象差异应用到真正的DOM上。 或者这样说:
  • 用JS对象模拟DOM(虚拟DOM)
  • 把此虚拟DOM转成真实DOM并插入页面中(render)
  • 如果有事件发生修改了虚拟DOM,比较两棵虚拟DOM树的差异,得到差异对象(diff)
  • 把差异对象应用到真正的DOM树上(patch) 优点:
  • 保证性能下限;
  • 无需手动操作DOM;
  • 跨平台 缺点:
  • 尺寸: 更多的功能意味着更多的代码;
  • 内存: 虚拟DOM需要在内存中的维护一份DOM的副本。在DOM更新速度和使用内存空间之间取得平衡;
  • 不是适合所有情况:如果虚拟DOM大量更改,这是合适的。但是少量的,频繁的更新的话,虚拟DOM将会花费更多的时间处理计算的工作。所以,如果一个DOM节点相对较少页面,用虚拟DOM,它实际上有可能会更慢 如何设计一个虚拟DOM:
  • 遍历newVdom的节点,找到他在oldVdom的位置,找到就移动对应的dom元素,没找到就说明是新节点,则创建一个节点插入。遍历完后如果oldVdom中还有没有处理过的节点,说明这些节点在newVdom中被删除了,删除他们即可。

虚拟Dom做了什么

  • 将真实的dom转化为虚拟dom
  • 更新的时候做对比

虚拟dom如何提升vue的渲染效率

  • 局部更新(节点数据)
  • 将直接操作dom的地方拿到两个js对象中做比较,找出差异项去更新

虚拟dom的diff算法中的patch方法

  • 初始化: patch(container, vnode)
  • 更新: update(vnode, newVode)
const createElement = vnode => {
  let tag = vnode.tag;
  let attrs = vnode.attrs || {};
  let childrens = vnode.children || [];

  if (!tag) {
    return null;
  }

  let elem = document.createElement(tag);

  for (let attrName in attrs) {
    if (attrs.hasOwnProperty(attrName)) {
      elem.setAttribute(attrName, attrs[attrName]);
    }
  }

  childrens.forEach(item => {
    elem.appendChild(!item.children ? item : createElement(item));
  });
}

const updateElement = (vnode, newVnode) => {
  let children = vnode.children || [];
  let newChildren = newVnode.children || [];
  children.forEach((childrenVnode, index) => {
    let newChildrenVnode = newChildren[index];
    if (childrenVnode === newChildrenVnode) {
      updateElement(childrenVnode, newChildrenVnode);
    } else {
      // 替换
      // replace(childrenVnode, newChildrenVnode);
    }
  })
}
proxy应用
  • 通过拦截set,可以做数据的校验;
  • 通过拦截apply,可以统计函数调用次数、实现普通函数和构造函数的简单处理;
  • 通过拦截set、get,可以实现简单断言;
  • 通过拦截get,可以实现自定义报错信息、实现隐藏属性;
// 数据校验
let handler = {
    set: function(target, prop, value){
        if(prop==='age'){
            if(value > 200){
                throw new RangeError('超过200岁了!')
            }
        }
    }
}
let person = new Proxy({},handler)
person.age = 300 //超过200岁了!
// 统计函数调用次数
function orginFunction () {}
let handler = {
    apply: function(target, thisObj, argumentsList){
        console.log('xxx')
        return target.apply(thisObj, argumentsList)
    }
}
let proxyFunction = new Proxy(orginFunction, handler)

模板编译到DOM整个流程

Vue模板编译原理(生成渲染函数):

  • 第一步解析器:将模板字符串转换成AST对象;
  • 第二步优化器:对AST进行静态节点标记,主要用来做虚拟DOM的渲染优化;
  • 第三步代码生成器:使用AST生成render函数代码字符串;

渲染过程:

  • 调用编译comlile函数,生成render函数字符串;
  • 通过调用new Watcher()函数,监听数据的变化,当数据发生变化时,然后更新render函数生成vnode;
  • 最后调用patch()方法,对比新旧节点vnode对象,通过DOM的diff算法,将vnode生成真正的DOM,去更新视图;
什么是AST
  • AST是对源代码的抽象语法结构的树状表现形式,在不同的场景下,会有不同的解析器将源码解析成抽象语法树。
vuex的原理

核心原理:

  • vuex本质是一个对象;
  • vuex对象有两个属性,一个是install方法,一个是store类;
  • install方法的作用是将同一个store实例挂载到所有组件上,用Vue(vuex)方法挂载,其实就是调用了Vue.mixin,在所有组件的beforeCreate生命周期注入了设置this.$store这样一个对象;
  • store这个类拥有commit,dispatch这些方法,store类里将用户传入的state包装成data,作为new Vue的参数,从而实现了state的响应式; 分析:
  • 本质上vuex内部其实是new Vue实现,vuex中的state放到vue的data中的$$state 中,getters是通过new Vue的computed实现,getters的取值本质上是将所有的方法放到computed中,getters取值的时候取的就是computed
Vue的双向数据绑定原理

vue采用数据劫持结合发布者-订阅者的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:

  • 需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加setter和getter。这样的话给这个对象的某个值赋值,就会触发setter,就能监听到数据变化。
  • compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图;
  • Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
    a、在自身实例化时往属性订阅器(dep)里面添加自己;
    b、自身必须有一个update()方法;
    c、待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调;
  • MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化->视图更新;视图交互变化(input)->数据model变更的双向数据绑定效果。 简单的理解有四个核心:
  • 一个监听器observer,用来劫持并监听所有属性,如果属性发生变化,就通知订阅者;
  • 一个订阅器Dep,用来收集订阅者。,对监听器Observer和订阅者Watcher进行统一管理;
  • 一个订阅者Watcher,可以收到属性的变化通知并执行相应的方法,从而更新视图;
  • 一个解析器Compile,可以解析每个节点的相关指令,对模板数据和订阅器进行初始化。
理解xss、csrf、ddos原理及避免方式
  • XSS(Cross-Site Scripting)跨站脚本攻击: 是一种代码注入攻击。攻击者在目标网站上注入恶意的代码,当被攻击者登录网站时就会执行这些恶意代码,这些脚本可以读取cookie、session、tokeb,或者其他敏感的网站信息,对用户进行钓鱼欺诈,甚至发起蠕虫攻击等;

  • CSRF(Cross-Site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击者网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

  • XSS避免方式:
    1、url参数使用encodeURIComponent方法转义;
    2、尽量不要用InnerHtml插入HTML内容;
    3、使用特殊符号、标签转义符号。

  • CSRF避免方式:
    1、添加验证码;
    2、使用token:服务端给用户生成一个token,加密后传递给用户;用户在提交申请时需要携带这个token服务端验证token是否正确。

  • DDoS又叫分布式拒绝服务,全称Distributed Denial of Service,其原理就是利用大量的请求造成资源过载,导致服务不可用。

  • DDoS避免方式:
    1、限制单IP请求频率;
    2、防火墙等防护设置禁止ICMP包等;
    3、检查特权端口的开放。

VUE2跟VUE3有什么不一样

VUE3.0的目标是让vue的核心变得更小、更快、更强大;因此增加了一些特性:

  • 监测机制的改变:vue3将带来基于代理Proxy的observer的实现,提供了全语言覆盖式的反应性跟踪。这消除了vue2当中基于Object.defineProperty的实现所带来的的很多限制。

  • 生命周期的不同:如果要想在页面中使用生命周期函数,以往vue2的操作是直接在页面中写入生命周期,而vue3是需要去引用的,这就是为什么3能够将代码压缩到更低的原因;

  • 默认项目目录结构的不同:vue3移除了配置文件目录,config 和 build 文件夹,移除了 static 文件夹,新增 public 文件夹,并且 index.html 移动到 public 中,在 src 文件夹中新增了 views 文件夹,用于分类视图组件和公共组件;

  • 模板:模板方便没有多大的变更,只改变了作用域和插槽,vue2的机制导致作用域插槽变了,父组件会重新渲染,而vue3把作用域插槽改成函数的方式,这样只影响了子组件的渲染,提升了渲染的性能;

  • 对象式的组件声名方式:vue2的组件是通过声名的方式传入一系列的option,和TypeScript的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。vue3修改了组件的声明方式,改成了类式的写法,这样使得和TypeScript的结合变得很容易;

  • 此外,vue的源码也改用了TypeScript来写。其实当代码的功能复杂之后,必须有一个静态的类型系统来做一些辅助管理。

  • 其他方面的更改:
    a、支持自定义渲染器,从而使得weex可以通过自定义渲染器方式来扩展,而不是直接fork源码来改动方式;
    b、支持Fragment(多个根节点)和Protal(在dom其他部分渲染组件内容)组件,针对一些特殊的场景做了处理;
    c、基于treeshaking优化,提供了更多的内置功能;

Proxy与Object.defineProperty的优劣对比

Proxy的优势如下:

  • Proxy可以直接监听对象而非属性;
  • Proxy可以直接监听数组的变化;
  • Proxy有多大13中拦截方式,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的;
  • Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能对象属性直接修改; Object.defineProperty的优势:
  • 兼容性好,支持IE9,而Proxy存在浏览器兼容问题,而且无法用polyfil磨平;
v-model的原理

在vue项目中主要使用v-model指令在表单input、textarea、select等元素上创建双向数据绑定,其实v-model本质上不过是语法糖[便捷写法],v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件:

  • text和textarea元素使用value属性和input事件;
  • checkbox和radio使用checked属性和change事件;
  • select字段将value作为prop并将传个作为事件。
computed与watch
  • watch:属性监听,更多的是观察的作用,类似于某些数据的监听回调,每当监听的数据发生改变时都会执行回调进行后续操作;
  • computed:计算属性,依赖其他属性值,并且computed的值有缓存,只有它依赖的属性值发生改变,下一次获取computed的值才会重新计算computed的值; 适用场景:
  • 当我们需要进行数值计算,并且依赖于其他数据时,应该使用computed,因为可以利用computed的缓存属性,避免每次获取值时,都要重新计算;
  • 当我们需要在数据变化执行异步操作或者开销较大的时,应该使用watch,使用watch选项允许我们执行异步操作,限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。
  • computed: 当一个属性受多个属性影响时使用,例: 购物车商品结算功能
  • watch:当一条数据影响多条数据的时候使用,例: 搜索数据
    watch 和 watchEffect有什么差异
  • 1、watch需要依赖,watchEffect 不需要依赖,且会自动跟踪依赖
  • 2、watch不会立即执行,watchEffect会立即执行,在首次加载
keep-alive
  • keep-alive是vue内置的一个组件,可以使被包含的组件保留状态,避免重新渲染;
  • 一般结合路由和动态组件使用,用于缓存组件;
  • 提供include和exclude属性,两者都支持字符串或正则表达式,include表示只有名称匹配的组件会被缓存,exclude表示任何名称匹配的组件都不会被缓存,其中exclude的优先级比include高;
  • 对应两个钩子函数activated和deactivated,当组件被激活时,触发钩子函数activated,当组件被移除时,触发钩子函数deactivated。