阅读建议
原理想去理解折腾,挺费时间的,不想知道原理。
直接跳过原理看使用方法,有具体的demo的case可以直接使用html跑。
原理
1、文档背景
根据官方文档以及图例做的翻译:
-
Data:数据
-
getter 获得者
-
touch 触摸
-
setter 安排者
-
Collect as Dependency 收集的依赖
-
Notify 通报
-
watcher观察者
-
Trigger re-render : 触发更新渲染
-
Component Render Function 渲染组件
-
Virtual Dom Tree 虚拟组件
大概的意思是组件通过Render函数,使用render方法,渲染到这个叫虚拟DOM的东东,虚拟DOM执行完毕之后,我们就可以愉快的去操作DOM,那么可以tonch一下,在对应的节点可以触发相应的事件,底层是用到了 Object.defineProperty()的getter 和 setter方法进行读和写的操作(这些 getter/setter 对用户来说是不可见的),watch->getter->Collect as Dependency ,dep.notify()->通知观察者,, dep 通过 notify 遍历了 dep.subs 通知每个 watcher 更新,执行触发更新渲染
抄自官方文档图例,注释一图定乾坤,如下方图示,虽然我也看不怎么懂。
民间大神解读:
对于文章手写Vue2.0源码(一)-响应式数据原理|技术点评的总结
标记: ★->难点
关键api
Object.defineProperty 参数
被传递给函数的对象。
2、数据初化
- 关键函数
initMixin()全局Vue挂载_init(options),初始化Vue示例的函数initState()初始化状态 注意这里的顺序 比如我经常面试会问到 是否能在data里面直接使用prop的值,这里初始化的顺序依次是: prop>methods>data>computed>watchinitData()里面的 observe 是响应式数据核心 所以另建 observer 文件夹来专注响应式逻辑,配合所定义的proxy()下Object.defineProperty的get和set(newValue)
3、对象的数据劫持
- 关键函数
walk()对象上的所有属性依次进行观测- ★
defineReactive()Object.defineProperty数据劫持核心 兼容性在ie9以及以上 关键代码:
function defineReactive(data, key, value) { observe(value); // 递归关键 // --如果value还是一个对象会继续走一遍odefineReactive 层层遍历一直到value不是对象才停止 // 思考?如果Vue数据嵌套层级过深 >>性能会受影响 Object.defineProperty(data, key, { get() { console.log("获取值"); return value; }, set(newValue) { if (newValue === value) return; console.log("设置值"); value = newValue; }, }); }observe(value)如果传过来的是对象或者数组 进行属性劫持
4、数组的观测
- 关键思路
- Object.defineProperty 下有一个属性enumerable: false 表示不可枚举型,数组
- 重写数组方法
5、响应式数据的思维导图
具体的使用的方式
-
对象
Vue.set(vm.someObject, 'b', 2)this.$set(this.someObject,'b',2)this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
-
数组重写的方法,可以实现相应式数据
Object
// 具体的细节看下注释
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#demo p{
margin: 30px;
text-align: center;
}
</style>
</head>
<body>
<div id="demo">
<p>我是vm.a : {{a}}</p>
<p>我是vm.someObject.b:{{someObject.a === undefined ? '我是someObject.a':someObject.a}}</p>
<p>我是vm.someObject.a:{{someObject.b === undefined ? '我是someObject.b':someObject.b}}</p>
<p>我是vm.b:{{b === undefined?'我是b':b}}</p>
</div>
<div id="demo2">
{{items[0]}}
</div>
</body>
<script src="vue.js"></script>
// 不会找vue.js在哪? 直接官网 https://cn.vuejs.org/js/vue.js 作为src引入也可
<script>
var vm = new Vue({
el: '#demo',
data: {
someObject: {}
},
mounted() {
/*
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。
但是,可以使用 Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式 property。例如,对于:
*/
this.$set(this.someObject, 'b', 5) //视图层会返回5
/*
有时你可能需要为已有对象赋值多个新 property,
比如使用 Object.assign() 或 _.extend()。但是,这样添加到对象上的新 property 不会触发更新。
在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
*/
this.someObject = Object.assign({}, this.someObject, {
a: 1,
b: 2
})
// this.$set(this.someObject, "b", 2)
}
})
vm.a //a是响应式的
vm.b = 2 //这里没有定义会报一个错误
console.log(vm)
console.log(vm.b)
// 上面难的
</script>
截图:
- 1)、vm.a 是响应式的
-
2)、vm.b 未定义会报错
这时候就需要用到我们的无脑翻译:
属性或方法“b”没有在实例上定义,但在渲染期间被引用。 通过初始化该属性,确保该属性是被动的,无论是在data选项中,还是对于基于类的组件。
这句话什么意思呢,就是当我们渲染数据的时候,如果data没有定义该属性,那么不会渲染到对应的视图,但是vm.b 其实是有修改的,可以理解成,这个数据只是只读的,但是不能写到我们的渲染层
这时候就会让我们跳转到以下链接
看了下是实用了proxy,emm,大概率是vue3的东东(未完待续)
- 3)、Object在vue中如何做到响应式?
使用以下的方法:
$set、Object.assign
// this.$set(this.someObject, 'b', 5)
// this.someObject = Object.assign({}, this.someObject, {
// a: 1,
// b: 2
// })
控制台输入vm.$set(vm.someObject, 'b', 5),现在视图层有了数据,使用这两句代码可以操作obj,后续要可以做到响应式了,如果未加,我们一开始就使用this.someObject.a=1这样子去操作obj数据是不会响应式的。
Array
对于Array不熟悉的话,参考下菜鸟教程案例每个demo敲敲就理解了。 菜鸟教程:
乱入:有感而发,和主题无关
菜鸟看着更新的也挺新的,新的数组方法都有引入。而且阅读的网站布局及样式风格比较符合我们的阅读感受,映入眼帘一片绿。看着太舒服了,再看了下掘金以蓝白为主色调的风格也是挺舒服的,阅读起来是比mdn舒服一些(强烈吐槽mdn左侧的导航栏,为什么不做一个移入的时候出现导航,而是左边出来一个大大的导航条,难受啊,light theme 亮瞎了我的狗眼(难怪我平时读不进去mdn,
emm,然后发现了MDN开启暗黑模式才是最适合阅读的,emm,突然觉得它又香了,对眼睛伤害最小就是黑白颜色,但是不是亮色,亮色对眼睛伤害太大了,纯黑白,那种卓别林时代默剧的颜色demo就给人以很舒服的感觉)
贴下用户讨论:来自某乎用户的吐槽
mdn滚动条:(吐槽截图)
色彩相关方面的可以看我写的另一篇文章使用电脑/手机,怎么样愉快的保护眼睛
阅读到这,兄弟们,别怪我跑题,突然有感而发,回到正题。
相关api的学习资料
菜鸟教程如何阅读Array相关api的,我觉得看下下面这几个应该就足够了,如果涉及浏览器兼容,建议到MDN去看看
定义和用法、语法、参数、返回值,比如
JavaScript concat() 方法:
具体案例
可分为三个部分:
- a. 使用以下方法操作数组, 可以检测变动 push() pop() shift() unshift() splice() sort() reverse()
- b. filter(), concat() 和 slice() ,map(),新数组替换旧数组
- c. 不能检测以下变动的数组 vm.items[indexOfItem] = newValue 解决 (1)Vue.set(example1.items, indexOfItem, newValue) (2)splice
<div id="box">
<!-- input change事件区别 -->
<input type="text" @input="handleInput" v-model="mytext" />
<ul>
<li v-for="data in datalist" :key="data">
{{data}}
</li>
</ul>
</div>
<script src="lib/vue.js"></script>
<script>
var vm = new Vue({
el: "#box",
data: {
mytext: "",
datalist: ["aaa", "add", "bbb", "bbc", "ccc", "ddd", "eee", "ade"], // 要改的
originList: ["aaa", "add", "bbb", "bbc", "ccc", "ddd", "eee", "ade"] // 原始数据
},
methods: {
handleInput() {
/*
a. 使用以下方法操作数组,
可以检测变动 push() pop() shift() unshift() splice() sort() reverse()
*/
// this.datalist.push("向后推送数据")
// this.datalist.pop();
// this.datalist.shift();
// this.datalist.unshift('向前插入数据');
// this.datalist.splice(0,0,'向第一个元素的删除0个 查润我我我我我'); //删除0个 插入
// this.datalist.sort()
// this.datalist.reverse() //reverse() 方法用于颠倒数组中元素的顺序。
/*
b. filter(), concat() 和 slice() ,map(),新数组替换旧数组
*/
// setTimeout(()=>{
// this.datalist = this.originList.filter(item=> item.includes(this.mytext) )
// },2000)
// 遍历1 - 5
function calculateItem(mytext){
var arr = ['1','2','3','4','5']
for(let i of arr){
if(i === mytext){
return true
}
}
}
//输入1 - 5 后返回原数组
if(calculateItem(this.mytext) ){
this.datalist = this.originList
}else{//否则执行数组的方法
// this.datalist = this.originList.concat([1,2,3])
// this.datalist = this.originList.slice(1,3)
this.datalist = this.originList.map(item=>{
return item = item +'a'
})
}
/*c. 不能检测以下变动的数组
vm.items[indexOfItem] = newValue
*解决*
(1)Vue.set(example1.items, indexOfItem, newValue)
(2)splice
*/
//(1)
this.$set(this.datalist,4,'我是改变的数据')
// 控制台可以输入
vm.$set(vm.datalist,4,'我是改变的数据')
// (2)
// this.datalist.splice(0,0,'向第一个元素的删除0个 查润我我我我我')
// console.log(newlist)
}
},
mounted(){
console.log('mounted',this)
}
})
var arr = ["aaa", "add", "bbb", "bbc", "ccc", "ddd", "eee", "ade"]
var newlist = arr.filter(item => item.includes("a"))
console.log(newlist, arr)
</script>
以上可以把数组相关的数组方法解开后,自行debug,可以chrome调试器下的console 配合 vscode(或者读者的其他的编译器进行其他的操作)
截图 vm.$set修改
Array 总结
| 数组方法名 Or vue实例方法 | 定义和用法 | 语法 | 返回值 | Vue中是否可以响应式 | ||
|---|---|---|---|---|---|---|
| push() | 数组的末尾添加一个或多个元素,并返回新的长度 | array.push(item1, item2, ..., itemX) ) | Number | 可以 | ||
| pop() | 删除数组的最后一个元素并返回删除的元素 | array.pop() | 返回删除的元素(数组元素可以是一个字符串,数字,数组,布尔,或者其他对象类型。) | 可以 | ||
| shift() | 数组的第一个元素从其中删除,并返回第一个元素的值 | array.shift() | 数组原来的第一个元素的值( 数组元素可以是一个字符串,数字,数组,布尔,或者其他对象类型。) | 可以 | ||
| unshift() | 数组的开头添加一个或更多元素,并返回新的长度。 | array.unshift(item1,item2, ..., itemX) | Number 数组新长度 | 可以 | ||
| unshift() | 数组的开头添加一个或更多元素,并返回新的长度。 | array.splice(index,howmany,item1,.....,itemX) | Array(如果从 arrayObject 中删除了元素,则返回的是含有被删除的元素的数组) | 可以 | ||
| sort() | 数组的元素进行排序 | array.sort(sortfunction) | Array(对数组的引用。请注意,数组在原数组上进行排序,不生成副本) | 可以 | ||
| reverse() | 颠倒数组中元素的顺序 | array.reverse() | Array(颠倒顺序后的数组) | 可以 | ||
| filter() | 创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素 | array.filter(function(currentValue,index,arr), thisValue) | Array,包含了符合条件的所有元素。如果没有符合条件的元素则返回空数组 | 不可以,要添加多一个备份的数组 | ||
| concat() | 连接两个或多个数组 | array1.concat(array2,array3,...,arrayX) | Array对象,返回一个新的数组。该数组是通过把所有 arrayX 参数添加到 arrayObject 中生成的。如果要进行 concat() 操作的参数是数组,那么添加的是数组中的元素,而不是数组。 | 不可以,要添加多一个备份的数组 | ||
| slice() | slice() 方法可从已有的数组中返回选定的元素。slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部 | array.slice(start, end) | Array,返回一个新的数组,包含从 start(包括该元素) 到 end (不包括该元素)的 arrayObject 中的元素。 | 不可以,要添加多一个备份的数组 | ||
| map() | map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。map() 方法按照原始数组元素顺序依次处理元素 | array.map(function(currentValue,index,arr), thisValue) | Array,数组中的元素为原始数组元素调用函数处理后的值。 | 不可以,要添加多一个备份的数组 | ||
| Vue.set | 向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 this.myObject.newProperty = 'hi') | vm.$set( target, propertyName/index, value | 设置的值 | 可以 |