vue

vue 部分

vue 系列 https://juejin.cn/post/6961222829979697165?utm_source=gold_browser_extension (opens new window)

# 1. mvc 和 mvvm

现代前端框架中,vue是mvvm,react是v,angular是mvc

MVC:Model-view-contorl 一句话描述就是 Controller 负责将 Model 的数据用 View 显示出来,换句话说就是在 Controller 里面把 Model 的数据赋值给 View。
MVVM:是Model-View-ViewModel的简写
通过操纵 M(模型层) 层 ,通过 VM 层(数据劫持 和 虚拟DOM机制来实现)来实现 V(视图层) 层会自动跟着变化

MVVM设计模式的优点
1. 低耦合。视图(View)可以独立于Model变化和修改
2. 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。,可以组件式开发
3. 自动更新DOM,减少直接对DOM的操作


MVVM设计模式的缺点
1.  数据绑定也使得bug很难被调试。比如你看到页面异常了,有可能是你的View的代码有bug,也可能是你的model的代码有问题。数据绑定使得一个位置的Bug被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。
2.  数据双向绑定不利于代码重用。客户端开发最常用的是View,但是数据双向绑定技术,让你在一个View都绑定了一个model,不同的模块model都不同。那就不能简单重用view了
3.  一个大的模块中model也会很大,虽然使用方便了也很容易保证数据的一致性,但是长期持有,不释放内存就造成话费更多的内存。

# 2. 生命周期的理解

  • vue 生命周期总共分为 8 个阶段: 创建前/后,载入前/后,更新前/后, 销毁前/后。

  • beforeCreate (创建前)vue 实例的挂载元素$el 和数据对象 data 都是 undefined, 还未初始化

  • created (创建后) 完成了 data 数据初始化, el 还未初始化

  • beforeMount (载入前) vue 实例的$el 和 data 都初始化了, 相关的 render 函数首次被调用。实例已完成以下的配置:编译模板,把 data 里面的数据和模板生成 html。注意此时还没有挂载 html 到页面上。

  • mounted (载入后) 在 el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的 html 内容替换 el 属性指向的 DOM 对象。完成模板中的 html 渲染到 html 页面中。此过程中进行 ajax 交互

  • beforeUpdate (更新前) 在数据更新之前调用,发生在虚拟 DOM 重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。

  • updated (更新后) 在由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。调用时,组件 DOM 已经更新,所以可以执行依赖于 DOM 的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

  • beforeDestroy (销毁前) 在实例销毁之前调用。实例仍然完全可用。 destroyed (销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

  • activated keep-alive 专属,组件被激活时调用

  • deactivated keep-alive 专属,组件被退出时调用

异步请求在哪一步发起?

可以在钩子函数 created、beforeMount、mounted 中进行异步请求,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。 如果异步请求不需要依赖 Dom 推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:

  • 能更快获取到服务端数据,减少页面 loading 时间;
  • ssr 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;

# 3. data 为什么是一个函数

组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果

# 4. 双向数据绑定原理是什么 响应式原理又是啥 还有 v-model 原理

双向绑定:

  • 输入框内容变化时,Data 中的数据同步变化。即 View => Data 的变化。
  • Data 中的数据变化时,文本节点的内容同步变化。即 Data => View 的变化。

响应式原理:

  • 就是指的是 Data => View 的变化原理

v-model 原理:

  • 就是指的是 View => Data

vue 是采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调。

  1. View => Data 其实就是给 input 事件绑定 oninput 事件,改变 data 里的属性的值,再触发 Data => view 去更新视图。就是 v-model 的原理

  2. Data => view 通过 Object.defineProperty()去劫持每一个变量的 get,set。 在 VUE 里的话,变量一变化,set 发通知给 watcher,watcher 告知虚拟 DOM 树,叫它该比较了,我这有值变了,于是生成新的 dom 树进行一个比较,然后逐级分类比较,更新页面

    实际上就是在劫持到 set,在里面进行 DOM 更新

# 5. diff 算法

参考资料 (opens new window)

详见vue源码解析

# 6. v-for 里的 key

详见vue源码解析

# 7. vue-router 钩子函数

1、全局路由守卫:是指路由实例上直接操作的钩子函数,特点是所有路由配置的组件都会触发,直白点就是触发路由就会触发这些钩子函数

  • beforeEach(to,from, next)

  • beforeResolve(to,from, next)

  • afterEach(to,from)

2、路由独享守卫: 是指在单个路由配置的时候也可以设置的钩子函数

  • beforeEnter(to,from, next)

3、组件守卫:是指写在组件内执行的钩子函数,类似于组件内的生命周期,相当于为配置路由的组件添加的生命周期钩子函数。

  • beforeRouteEnter(to,from, next)

  • beforeRouteUpdate(to,from, next)

  • beforeRouteLeave(to,from, next)

顺序:

  • router.beforeEach
  • beforeEnter
  • beforeRouteEnter
  • router.beforeResolve
  • router.afterEach

# 8. react 和 vue 的对比

  1. 共同点:

    • 都使用虚拟 dom
    • 提倡组件化
    • 注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库。
  2. 区别:

    • vue 采用 HTML 和 JS,CSS 分离的写法,更加直观。react 使用 JSX 语法
    • vue 双向绑定,react 是单向数据流
  3. vue 优势

    • 简单的语法及项目创建,易于上手
    • 更快的渲染速度和更小的体积
    • 中国框架,文档更加完善
  4. React 的优势

    • 更适用于大型应用和更好的可测试性
    • 同时适用于 Web 端和原生 App
    • 扩展性更强
    • 支持 TS
  5. 总结

  • 当 state 特别多的时候,Watcher 会很多,会导致卡顿,所以大型应用(状态特别多的)一般用 React,更加可控。可对于易用性来说,VUE 是更容易上手的,对于项目来说新人更容易接手。

# 9. 父子组件的生命周期

父 beforeCreate->父 created->父 beforeMount->子 beforeCreate->子 created->子 beforeMount->子 mounted->父 mounted 总结:从外到内,再从内到外

# 10. 组件间通讯

  1. props 和$emit 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的

  2. $parent,$children 获取当前组件的父组件和当前组件的子组件

  3. $attrs 和$listeners A->B->C。Vue 2.4 开始提供了$attrs 和$listeners 来解决这个问题

  4. 父组件中通过 provide 来提供变量,然后在子组件中通过 inject 来注入变量。(官方不推荐在实际业务中使用,但是写组件库时很常用)

  5. $refs 获取组件实例

  6. envetBus 兄弟组件数据传递 这种情况下可以使用事件总线的方式.Vue.prototype.$eventBus = new Vue();vue可以做事件总线的载体

# 11. keep-alive

  1. 多了 2 个生命周期钩子

    activated 当 keep-alive 包含的组件再次渲染的时候触发 deactivated 当 keep-alive 包含的组件销毁的时候触发

  2. 有 3 个属性:include(包含的组件缓存生效) 与 exclude(排除的组件不缓存,优先级大于 include) ,max 缓存组件的最大值(类型为字符或者数字,可以控制缓存组件的个数)

  3. 部分组件或者路由缓存

    <keep-alive include="a">
      <router-view></router-view>
    </keep-alive>
    

    或者

    //在路由中
    {
     path: '/',
     name: 'home',
     component: Home,
     meta: {
       keepAlive: true // 需要被缓存
     }
    }
    //使用时
    <keep-alive>
      <router-view v-if="$route.meta.keepAlive">
        <!-- 这里是会被缓存的视图组件,比如 Home! -->
      </router-view>
    </keep-alive>
     <router-view v-if="!$route.meta.keepAlive">
       <!-- 这里是不会被缓存的视图组件,比如 Profile! -->
     </router-view>
    

# 12. 自定义指令

https://blog.csdn.net/smlljet/article/details/91874728 (opens new window)

https://zhuanlan.zhihu.com/p/64362889 (opens new window)

  1. 注册方法: 全局注册:

    //main.js里
    Vue.directive('focus', {
      inserted: function(el) {
        el.focus()
      }
    })
    

    局部注册

    <template>
      <div class="hello">
        <div v-test="name"></div>
      </div>
    </template>
    <script>
    export default {
      data () {
        return {
        name:'我是名字',
        }
      },
      directives:{
        test:{
          inserted: function (el,binding) {// 指令的定义
            / /el为绑定元素,可以对其进行dom操作
            console.log(binding) //一个对象,包含很多属性属性
          },
          bind: function (el, binding, vnode) {
            el.innerHTML =binding.value
          }
        }
      },
    }
    </script>
    
  2. 钩子函数和参数

钩子函数

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

  • update:所在组件的 VNode 更新时调用。

  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

  • unbind:只调用一次,指令与元素解绑时调用。

参数

  • el:指令所绑定的元素,可以用来直接操作 DOM 。
  • binding:一个对象,包含以下属性:
  • name:指令名,不包括 v- 前缀。
  • value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2。
  • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
  • expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
  • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
  • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
  • vnode: Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用
  1. 实例:拖拽自定义指令
Vue.directive('dialogDrag', {
    bind(el) {
        const clientType = /Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent) ? 'mobile' : 'pc';

        console.log('clientType', clientType);
        const dialogHeaderEl = el.querySelector('.module_bg_title');
        const dragDom = el;
        dialogHeaderEl.style.cursor = 'move';
        // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
        const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
        dialogHeaderEl[clientType === 'mobile' ? 'ontouchstart' : 'onmousedown'] = (e) => {
            // console.log('屏幕高度', document.documentElement.clientHeight);
            // console.log('弹窗高度', dragDom.offsetHeight);
            // 鼠标按下,获取鼠标在盒子内的坐标
            const disX = (clientType === 'mobile' ? e.touches[0].clientX : e.clientX) - dialogHeaderEl.offsetLeft;
            const disY = (clientType === 'mobile' ? e.touches[0].clientY : e.clientY) - dialogHeaderEl.offsetTop;
            // 获取到的值带px 正则匹配替换
            let styL;
            let styT;
            // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
            if (sty.left.includes('%')) {
                styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
                styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
            } else {
                styL = +sty.left.replace(/\px/g, '');
                styT = +sty.top.replace(/\px/g, '');
            }
            // 鼠标移动的时候,把鼠标在页面中的坐标,减去鼠标在盒子内的坐标就是模态框的left和top值
            document[clientType === 'mobile' ? 'ontouchmove' : 'onmousemove'] = function (e) {
                // 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离)
                const l = (clientType === 'mobile' ? e.touches[0].clientX : e.clientX) - disX;
                const t = (clientType === 'mobile' ? e.touches[0].clientY : e.clientY) - disY;
                const finallyL = l + styL;
                const finallyT = t + styT;

                // 边界值判定 注意clientWidth scrollWidth区别 要减去之前的top left值
                // const limitL = (document.documentElement.clientWidth - dragDom.clientWidth) / 2;
                // if (finallyL < -limitL) {
                //     finallyL = -limitL;
                // } else if (finallyL > limitL) {
                //     finallyL = limitL;
                // }
                // const limitT = document.documentElement.clientHeight * 15 / 100;
                // const limitB = document.documentElement.clientHeight - dragDom.clientHeight - limitT;
                // if (finallyT < -limitT) {
                //     finallyT = -limitT;
                // } else if (finallyT > limitB) {
                //     finallyT = limitB;
                // }
                // 移动当前元素
                dragDom.style.left = `${finallyL}px`;
                dragDom.style.top = `${finallyT}px`;
            };
            document[clientType === 'mobile' ? 'ontouchend' : 'onmouseup'] = function (e) {
                document[clientType === 'mobile' ? 'ontouchmove' : 'onmousemove'] = null;
                document[clientType === 'mobile' ? 'ontouchstart' : 'onmousedown'] = null;
            };
        };
    },
});

# 13. 修饰符有哪些

  1. 事件修饰符

    • stop 阻止事件继续传播
    • native 元素原生事件。
    • prevent 阻止标签默认行为
    • capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
    • self 只当在 event.target 是当前元素自身时触发处理函数
    • once 事件将只会触发一次
    • passive 告诉浏览器你不想阻止事件的默认行为
  2. v-model 的修饰符

    • .lazy 通过这个修饰符,转变为在 change 事件再同步

    • .number 自动将用户的输入值转化为数值类型

    • .trim 自动过滤用户输入的首尾空格

  3. 键盘事件的修饰符

    • .enter
    • .tab
    • .delete (捕获“删除”和“退格”键)
    • .esc
    • .space
    • .up
    • .down
    • .left
    • .right

# 14. 函数式组件使用场景和原理

????

<script>
// 引入的文件里会有多个vue页面,要看情况渲染
import CompPropsConfig from '@/components/iTemplate/config.js'

export default {
  components: {
    propsPanel: {
      props: {
        name: {
          type: String,
          required: true
        }
      },
      render(h) {
        let _module = CompPropsConfig[this.name] //确定取哪个VUE
        return h(_module, {
          props: {
            name: {
              type: String
            }
          }
        })
      }
    }
  },
  data() {
    return {}
  }
}
</script>

# 15. vue-router 中常用的路由模式实现原理吗

  1. 传统的页面,所有的网页地址,都是请求到后端,由后端返回相应的 HTML 页面回来的。没有前端路由这一说
  2. 现在很多单页面应用,只有在初次加载或者刷新时,才会加载 index.html。页面跳转的时候,是不会进行请求的
  3. 哈希模式的时候,有一个#号的锚点,在#后面的地址,实际上是不会传递给后台的。所以是前端路由。在前端路由变化的时候,前端利用 Object.onhashchange() 监听hash的变化,并且加载页面。而这一切,后台并不知情。始终是 index.html
  4. histtory 模式的时候,利用 pushState、replaceState 可以记录修改 URL 历史记录,从而可以直接进行页面跳转而不触发向后台发送请求。但是刷新的时候会再次请求当前 URL 地址。后台如果不做处理的话,会找不到当前的请求地址从而 404.后台需要将不存在的请求重定向到 index.html

# 16.混入

  1. 全局混入

    import Mixins from '@/mixins/Index'
    // 在main.js里混入
    Vue.mixin(Mixins)
    
  2. 局部混入

    // 在组件内
    mixins: [mixin],
    
  3. 数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。

  4. 同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。

  5. 值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

# 17.插槽

  1. 普通插槽 就是一个

      <!-- 父 -->
      <child>
        <p>里面的东西会插入到子组件</p>
      </child>
      <!-- 子 -->
      <slot> 父组件不传东西的时候,会显示这个默认值,父组件加了插槽,就覆盖这个 </slot>
    
    
  2. 具名插槽 跟上面一样,就是带了个name

     <!-- 父 -->
      <child>
          <templete v-slot="mingzi">
            <p>里面的东西会插入到子组件</p>
          </templete>
      </child>
     
     <!-- 子 -->
     <slot name="mingzi"> 父组件不传东西的时候,会显示这个默认值,父组件加了插槽,就覆盖这个 </slot>
    
    
  3. 作用域插槽 父组件能使用自组件的数据

http部分

http部分

http 部分

项目优化部分

项目优化部分

项目优化 部分