目录 § ⇧
- 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
所属的类是Vue
vm.__proto__ === Vue.prototype
- 举例
Vue
:#202
; 自身属性:(*3?)
;Vue.prototype
:#419
;Vue.__proto__
:(*6?)
vm
:#101
; 自身属性:(*2?)
;vm.__proto__
:#419
Vue.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
)data
props
属性本意是propsData
propsValue
单元测试时用,vue3.x
废除computed
计算后的methods
方法- “方法”(面向对象编程,依附于对象
obj.fn()
) - “函数”更偏向数学概念,函数式编程,单独出现
fn()
- “方法”(面向对象编程,依附于对象
watch
emits
DOM
容器/挂载点el
template
模板内容;与render
互斥render
与template
互斥;不手写,加载器生成renderError
- 生命周期钩子
beforeCreated
created
beforeMount
mounted
beforeUpdate
updated
activated
deactivated
beforeUnmount
()beforeDestroy
unmounted
()destroyed
errorCaptured
renderTracked
renderTriggered
- 资源
directives
指令components
组件(建议用filters
computed
代替实现) 过滤器;表面上有用,实际上没用
- 组合
mixins
混入extends
扩展provide
提供inject
注入setup
parent
- 其他:先不看
属性分阶段
- 红色属性:好学、必学入门属性
- 橙色属性:高级属性、单独举例说明
- 绿色属性:可自学属性
- 紫红属性:特殊
- 灰色属性:新版中移除,注意学代替用法
- 烟白属性:先不看
2. 入门属性 § ⇧
el
挂载点template
data
内部数据methods
方法components
组件- 四个钩子
props
外部数据
el
挂载点
el: "#id"
- 可用
.$mount('#id')
代替 - 替换
HTML
的#id
中的节点 - 慢速网络(
slow 3G
)下,可能可以看见原来写在页面上的节点内容
template
- 语法
v-if
v-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
实例对象vm
V.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
更(destroyed
unmounted
vue3.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
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名
- 河 掘 思 知 简