目录 § ⇧
- 0. 内存图 §
- 1.
options里有什么 § - 2. 入门属性 §
- 3.
Vue对data做了什么 § - 4. 数据响应式 §
- 5.
Vue2.x的bug§ - 6.
Vue.set和this.$set§ - 7.
data中的数组 § - 8.
ES6手写数组新push方法 V.S.ES5写法 § - 9. 总结 §
- 10. 面试题 §
[toc]
搞清构造选项,Vue 实例
0. 内存图 § ⇧
作者习惯将
Vue实例命名为vm,const vm = new Vue(options)
vm对象封装了对视图的(视图层)所有操作,包括- 数据读写
- 事件绑定
DOM更新- 不包括网络层的
Ajax
vm的构造函数是Vue,按照ES6,vm所属的类是Vuevm.__proto__ === Vue.prototype- 举例
Vue:#202; 自身属性:(*3?);Vue.prototype:#419;Vue.__proto__:(*6?) vm:#101; 自身属性:(*2?);vm.__proto__:#419Vue.prototype:#419; 自身属性:(*4?);Vue.prototype.__proto__:(*5?);- 注意点
(*1)、(*2)、(*3)、(*4)、(*5)、(*6)分别是什么 (*1)初始化传入的参数options是什么(*2)实例vm有哪些自身属性(*3)Vue函数有哪些自身属性(*4)Vue.prototype有哪些自身属性(*5)Vue.prototype.__proto__的指向(*6):Vue.__proto__ === Function.prototype基本原则:任何函数的__proto__都指向Function.prototype构造函数
options:(*1?)是new Vue()的参数,一般称之为选项或构造选项
1. options里有什么 § ⇧
文档
- 英文文档搜
options - 中文文档搜
选项
options的五类属性
对比
Vue3.x,加粗斜体代表新增,删除线代表移除,斜体代表有改动
- 数据(
Data)dataprops属性本意是propsDatapropsValue单元测试时用,vue3.x废除computed计算后的methods方法- “方法”(面向对象编程,依附于对象
obj.fn()) - “函数”更偏向数学概念,函数式编程,单独出现
fn()
- “方法”(面向对象编程,依附于对象
watchemits
DOM容器/挂载点eltemplate模板内容;与render互斥render与template互斥;不手写,加载器生成renderError
- 生命周期钩子
beforeCreatedcreatedbeforeMountmountedbeforeUpdateupdatedactivateddeactivatedbeforeUnmount()beforeDestroyunmounted()destroyederrorCapturedrenderTrackedrenderTriggered
- 资源
directives指令components组件(建议用filterscomputed代替实现) 过滤器;表面上有用,实际上没用
- 组合
mixins混入extends扩展provide提供inject注入setupparent
- 其他:先不看
属性分阶段
- 红色属性:好学、必学入门属性
- 橙色属性:高级属性、单独举例说明
- 绿色属性:可自学属性
- 紫红属性:特殊
- 灰色属性:新版中移除,注意学代替用法
- 烟白属性:先不看
2. 入门属性 § ⇧
el挂载点templatedata内部数据methods方法components组件- 四个钩子
props外部数据
el 挂载点
el: "#id"- 可用
.$mount('#id')代替 - 替换
HTML的#id中的节点 - 慢速网络(
slow 3G)下,可能可以看见原来写在页面上的节点内容
template
- 语法
v-ifv-for
data 内部数据
- 支持对象和函数,优先用函数(返回值)
- 初始值 存储记录
- 数据响应式中 data 有 bug
|
|
import Demo from './Demo.vue'- 把组件传给
new Vue({render: h => h(Demo)) - 引入的组件本质上就是一个对象
为什么推荐用函数声明
data() {...},而不是用对象的形式
- 文档中说 限制:组件的定义只接受
function,而没说明原因 - 组件是可复用的 Vue 实例
- 两个组件复用
new Vue({render: h => h(X,[h(Demo), h(Demo)])) - 如果是使用对象,创建两个相同组件,引用的
data就是指向同一个内存地址,对象在这个组件的所有实例中共享 - 有时组件需要复用,必须使得每个组件都有一份
data的拷贝,防止不同组件修改数据时被相互覆盖,用函数返回一个data对象的独立的拷贝 - 文档的说明:一个组件的
data选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝 - 为了组件间不互相影响数据(如果 Vue 没有这条规则,点击一个按钮就可能会影响到其它所有实例)
- 注意同一个组件中重复定义的数据指向同一个内存地址
官方示例
|
|
|
|
demo
methods 方法
- 事件处理函数或普通函数(可在模板里调用)
- 绑定的方法必须写到
methods属性中,否则会报错function is not defined - 可以代替
filters,举例 显示数组中偶数 - 作为普通函数写到模板中时,必须有函数返回值(
{{filterEven(array)}})
|
|
- 主动在模板中调用
{{filterOdd()}}的缺点是每次渲染就会调用一次 - 进行其他页面操作也会重新调用
- 用
computed解决
components组件
什么是组件
- 抽象概念,可以与其他部分组合使用
- 模块化
Vue实例对象vmV.S.Vue组件components
new Vue创建的是实例Vue.component创建的是组件import xxx From '*.vue'引入的是组件
components组件的三种引入方式 组件注册
import方式- 全局方式
- 混合方式
import方式,模块化引入外部单文件组件
Vue组件,注意大小写- 三种
Vue使用方式的最后一种:单文件组件Demo.vue - 通过
import Demo from './Demo.vue'引入 - 在
Vue实例中使用另一个组件 - 命名组件
components: {Xmas: Demo} - 写到实例
vm的template中 - 在
template中的形式为<componentName/>
|
|
JS方式 全局声明 全局注册 ⇧
- 写在全局
Vue的属性Vue.component('componentName', options)中,options是一个对象 - 在
template中的形式为<componentName/> - 第二个参数传入一个对象,此对象的和实例中传入
new Vue({})的第一个参数是类似的,可以接受的属性一样 - 可以理解为将一个复杂组件取一个名字(第一个参数),组件对象放到
Vue.component()第二个参数中 - 即
options的写法是一样的 - 可以简单理解为给实例取一个名字
- 不需要额外引入,可被所有其他组件引入
|
|
全局注册的组件在注册之后可以用在任何新创建的
Vue根实例 (new Vue) 的模板中。比如
|
|
|
|
在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用
- 全局注册的行为必须在根 Vue 实例 (通过 new Vue) 创建之前发生
全局注册的缺点
混合方式(结合以上两种方式)
- 在实例中的
components属性中,直接定义
|
|
组件可以理解为实例中的实例
- 组件
options的写法是一样的 - 优先使用第一种
import方式 ,优点是模块化:将独立的功能放到单独的文件中 - 取名可以相同:
components: {Demo: Demo} - 取名相同可以简化(
ES6):components: {Demo} - 可以导入多个
components: {Demo, App}分别导入Demo和App template中用相同的名字<Demo/><App/><Demo/>
|
|
组件名取名规范 Vue 风格指南
- 在单文件组件、字符串模板和 JSX 中使用自闭合组件
- 组件名就是
Vue.component的第一个参数 - 为什么文件名要小写? by 阮一峰
- 避免重名冲突:直接在
DOM中使用一个组件 的时候,推荐遵循 W3C 规范中的自定义组件名 (字母全小写且必须包含一个连字符)。这会帮助你避免和当前以及未来的 HTML 元素相冲突 - 字符串模板或单文件组件的时候, 组件名大写,避免在
template中分不清<Button/>和<button><button/> - 其他文件名小写
- 命名中明确包含用途与归属
SearchButton
全局注册 V.S. 局部注册
四个钩子 ⇧
created创mounted挂updated更(destroyedunmountedvue3.x) 消- “钩子”:即切入点
created 创 ⇧
- 用
debugger证明组件或实例出现在内存中,而未出现在页面中
|
|
mounted 挂
- 用
debugger证明组件或实例出现在内存中,并且也出现在页面中
|
|
updated 更
- 能保证数据更新才触发当前的事件绑定的函数
|
|
destroyed(unmounted vue3.x) 消
destroyed- 需要一个子组件
destroy.vue - 从页面中消失就是
destroy - 每次消除后重新创建的都是新的,
+1后消除在创建,数据归零
Destroy.vue
|
|
怎么使用
destroy的?
- 用最简单的例子说明
在
main.js中引入
|
|
- 从页面中消失就是
destroyed v-if监听数据变化- 取反
this.isVisible = !this.isVisible
props 外部数据
- 也称属性,是由组件外部传入值的(对比内部数据
data,内部自己传值) - 从外部接受一个
message,并自动绑定到this - 在
template中,可写{{this.message}},也可以省略 this ,写成:{{message}}
声明传入内容的类型
- 传字符串:
<Props message="props"/> - 传变量:之前加冒号
:message="n"传入this.n数据,优先在data里查找变量 - 传方法:
:fn="add"传入this.add函数
区别 传字符串 V.S. 传数字(其他数据类型)
- 在
message前加冒号,声明传入的外部数据是变量,可包含数字或其他数据类型<Props :message="0"/>、<Props :message="true"/>、<Props :message=" '0' "/> - 不加冒号,就只是传字符串
将内部数据
data放到组件中的外部数据message使用
<Props :message="n"/>:message=""双引号里的是 JS 代码:message="n"传入的变量n,优先在data里查找
传递参数,调用方法
<Props :fn="add"/>,methods: {add() {...}},<button @click="fn">call fn</button>- 按钮被点击时,调用
fn fn从外部传入的一个方法props: ['message', 'fn']fn取名符合规范即可- 在外部的实例中,组件
Props的属性:fn"add"调用了实例的methods: {add() {...}}中的方法 - 可以认为是
main.js实例的方法传给了组件Props - 组件
Props里的@click="fn"调用add方法
同步更新内外数据
<div>{{n}}<Props :fn="add" :message="n"/></div>
main.js
|
|
Props.demo.vue
|
|
3. Vue对data做了什么 § ⇧
data实验
在实例外面变更
data(而非在methods绑定变更的函数 绑到对应节点的事件上)
- 示例代码 1:
myData变化 - 一开始
{n: 0},传给new Vue之后立即变成{n: (...)}
|
|
{n: (...)}是什么,为什么表现和{n: 0}一致
了解ES6的计算属性 getter和setter
第一次打印的和第二次的区别,在后台展开查看用
setter设置属性的对象
- 调用
obj1.姓名()要加括号,调用obj2.姓名不用加括号 obj2.姓名是属性,只不过使用函数形式定义的- 计算属性的
setter可以用来设置之前的原始属性
|
|
- 可以看到属性
{姓名: (...)} - 并不存在一个名为
n的属性,而是有一个get n和set n来模拟对n的读写操作
为什么要使用
getter和setter模拟{n: (...)}
Object.defineProperty
- 直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
- 应当直接在 Object 构造器对象上调用此方法,而不是在任意一个 Object 类型的实例上调用
- 语法
Object.defineProperty(obj, prop, descriptor) - 示例代码 3
|
|
_xxx是什么
代理和监听
需求一:用
Object.defineProperty定义 属性n
|
|
n的值就是0,而不是value: 0- 属性的属性
需求二:属性
n不能小于 0,即data2.n = -1应该无效,但data2.n = 1有效
- 用点运算符不能实现
- 用
setter
|
|
this适用于任何命名(data2),表示当前对象setter按条件筛选传入的属性值Object.defineProperty可以在声明完了之后添加getter和setter计算属性- 即在定义完一个对象之后,添加新的计算属性
- 注意
Object.defineProperty定义的属性是“不存在的” Object.defineProperty的get() {}中不能返回命名的属性(xxx)本身,会造成死循环- 可以实现按条件筛选传入的属性值
- 理解为属性的属性
需求三:使用代理
proxy
|
|
- 参数为匿名的对象
{data: {n: 0}}包裹的数据data: {n: 0} - 读取
proxyObj的属性,就返回data的属性 - 设置
proxyObj的属性,就写入data的属性 proxyObj就是代理data3暴露给外部访问的代理对象- 无法擅自更改属性(
n)
需求四:绕过代理
proxy
|
|
需求五:就算用户擅自修改 myData,也要拦截他
- 限制绕过代理
proxy - 用
value存储原始的data.n delete data.n删除了原始数据,使得无法修改delete data.n可以不写,Object.defineProperty(data, 'n', {...})声明新的虚拟对象会覆盖原来的
|
|
- 监听的逻辑
- 代理的逻辑
- 防止绕过条件限制修改数据
注意
- 研究方法比知识本身更重要(
console.log) - 不读源码,也可以了解真相
- 初级读源码自杀
data小结
Object.defineProperty
- 可以给对象添加属性
value - 可以给对象添加
getter/setter getter/setter用于对属性的读写进行监控
代理(设计模式)
- 对
myData对象的属性读写,全权由另一个对象vm负责 - 那么
vm就是myData的代理 - 比如
myData.n不用,偏要用vm.n(this.n)来操作myData.n
vm = new Vue({data: myData})
- 1.会让
vm成为myData的代理proxy - 2.会让
myData的所有属性进行监控 - 为什么需要监控,为了防止
myData的属性变了,vm未知 Vue可以监听到任何对myData的读写- 监控的目的是,
vm已知,属性变了就可以调用render(data) - 自动更新
UI = render(data) - 通过
this访问vm
示意
- 将数据用匿名的对象
{data:{n: 0}}传参给new Vue() - 经过
new Vue()的改造加监听,暂存let value = 0,变成{get n() {return value}, set n(v) {value = v}},实现覆盖数据原来的属性 - 并且作为原来数据的代理,将返回值
{get n() {data.n}, set n(v) {data.n = v}}赋值给变量vm - 没有删除对象,只是覆盖了对象的属性,没有改变原来的引用,即没有断开与对象的关联
如果
data有多个属性n、、就会有get n/get m/get k等
- 对所有属性循环进行监听和代理(闭包隔开作用域)
4. 数据响应式 § ⇧
什么是响应式
- 对外界刺激的反应(抽象)
Vue的data是响应式
const vm = new Vue({data: {n: 0}})- 如果修改(更新)数据
vm.n的值,那么UI中的n就会响应用户操作 Vue2通过Object.defineProperty来实现数据响应式(ES5+)
响应式网页
- 如果改变窗口大小,网页内容会做出响应,即响应式网页
- 比如 示例响应式网站 smashingMagazine
- 但是注意,一般用户不会拖动网页大小,除非
iPad分屏 - 用响应式浏览器测试 responsively
- 区别窗口响应式与媒体查询适配设备
5. Vue2.x的bug § ⇧
Object.defineProperty的问题
Object.defineProperty(obj, 'n', {...})- 必须要有一个
key(即n),才能监听和代理obj.n
没有
data.n会发生
- 示例一:
Vue会给出一个警告
|
|
- 示例二:
Vue无警告 Vue只会检查第一层属性是否被定义Vue不会深入检查下一层- 只有属性
a,却要显示属性b Vue并没有监听属性b,只监听了a,即对b的操作都无效- 控制台无报错,
Vue无警告
|
|
- 此时如果点击
set b,视图中不会显示1 - 因为
Vue没法监听一开始不存在的obj.b
解决办法
- 将
key都声明好,后面不用再加属性b: undefined,即在传入的参数中预留会用到的属性 - 使用
Vue.set或者this.$set(防止重名)
|
|
6. Vue.set和this.$set § ⇧
作用
- 新增
key,在实例化后增加新属性,不用事先写在data里 - 自动创建代理和监听(如果没有创建过)
- 触发
UI更新(副作用,并不会立刻更新,异步更新) Vue.set(this.obj, 'b', 1)将this.obj声明传给Object.defineProperty- 之后就会响应对数据
b的操作,更新 UI
语法
|
|
举例
this.$set(this.object, 'm', 100)
小结
Vue只会监听实例化之前传进来的属性和实例化之后用Vue.set或this.$set设置的数据- 在实例化后如果直接在对象上增加新属性,
Vue不会将新属性转化为getter/setter,因为没有setter,所以在新属性的数据发生改变时,就不会通知watcher重新渲染视图 - 在传入的参数中预留会用到的属性,这样就可以对属性进行监听
- 或者是调用
Vue提供的方法来添加新属性 - 添加多个属性,使用
Object.assign()或_.extend()也不会让新属性被监听,在这种情况下,用原对象与要混合进去的对象的属性一起创建一个新的对象
|
|
7. data中的数组怎么破 § ⇧
data: {n: 0, obj: {}, array: []}
- 没法提前声明所有
key(‘0’, ‘1’, ‘2’…) - 示例 1: 数组的长度可以一直增加,长度无法预测,下标就是
key - 本质上
array: {0: 'a', 1: 'b', 2: 'c', length: ...} - 无法提前把数组的
key都声明出来 Vue也不能检测数组新增的下标
|
|
语法
|
|
- 如何不用每次改数组都要用
Vue.set或者this.$set this.$set作用于数组时,并不会自动添加监听和代理,所以不会更新 UI
尤雨溪的做法
- 篡改数组的栈方法
API,文档中「变更方法」章节 - 7 个重新封装的
API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()添加到数组新的一层原型链上,覆盖原来同名的方法 - 例如:新的
push会做 1. 调用以前的push操作;2. 通知Vue添加监听和代理,帮你this.$set(...) - 这 7 个
API都会被Vue篡改,调用后会更新UI
|
|
改写数组的原型方法:
ES6写法 V.S.ES5写法
8. ES6改写数组原型方法 V.S. ES5写法 § ⇧
- 增加一层原型链
|
|
9. 总结 § ⇧
对象中新增的
key
Vue没有办法事先监听和代理- 使用
set来新增key,创建监听和代理,更新UI - 最好提前把属性都写出来,不要新增
key - 但数组做不到「不新增 key」
数组中新增的
key
- 不可以用
set来新增key,创建监听和代理,更新UI this.$set作用于数组时,并不会自动添加监听和代理- 不过尤雨溪篡改了 7 个数组 API 方便对数组进行增删
- 改查不需要,改即改原来的,原来本身就有监听
- 删除多余的监听器,节省内存
- 使用 Vue 提供的数组变异 API 时,会自动添加监听和代理,并异步更新
UI - 结论:数组新增
key最好通过 7 个API - 深入响应式原理-检测变化的注意事项
Vue3.x的解决方案
10. 面试题 § ⇧
- 答案解析文章
- 异步更新 UI
对 Vue 数据响应式的理解
- 数据响应式就是,将 Model 绑定到 View,当更新 Model 时,View 会自动更新
- 数据响应式强调数据驱动 DOM 生成,而不是直接操作 DOM
- 即所谓的数据驱动,指视图是由数据驱动生成的,对视图的修改,不会直接操作 DOM,而是通过修改数据
- Vue 的响应式原理依赖于
Object.defineProperty中的setter和getter - Vue 通过设定对象属性
setter/getter方法来监听并代理数据的变化 - 将一个 JS 对象作为参数传入到 Vue 中时,Vue 会遍历该对象的所有属性,使用
Object.defineProperty把这些属性全部转为getter/setter,实现监听,并且会返回一个组件实例作为该对象的代理 - 当操作组件实例的数据时,就会同时修改传入的 JavaScript 对象的数据,而且 vue 同时会监听这个 JS 对象,在 JS 对象的数据发生修改时,同时也会更新组件实例的数据
- 每个组件实例都对应一个
watcher实例,watcher实例会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的setter触发时,会通知watcher,从而使它关联的组件重新渲染
理解了
data才能继续学习computed和watch
参考文章
相关文章
- 无
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名
- 河 掘 思 知 简