@TOChtml
震惊!!! 2019年10月5日,尤小右公开了 Vue 3.0 的源代码。源码地址:vue-next,这次更新的主要内容除了自行查看源码还能够在知乎上进行了解尤小右 3.0 RFC,在这两篇的基础上,接下来我将为你们展现最近学习到3.0的内容解读vue
了解3.0的进步,咱们得先了解2.0的响应式原理,若是已经知道其优点劣势的大佬自行跳过~~react
看过官方文档的同窗都知道Vue 响应式系统的解释: 当你把一个普通的 JavaScript 对象传入 Vue 实例做为
data
选项,Vue 将遍历此对象全部的属性,并使用Object.defineProperty
把这些属性所有转为getter/setter
。Object.defineProperty
是 ES5 中一个没法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的缘由git
下面咱们将大概先实现vue2.0响应github
原理:使用 Object.defineProperty 能够从新定义属性,而且给属性增长 getter 和setter;数组
// 咱们先建立一个对象,而后经过某个方法去监听这个对象,当对象的值改变时,触发操做
let defalutName = ''
let data = {name:''}
// observer监听函数
observer(data)
console.log(data.name);
// expected output: Magic Eno
// 给data里面的name赋值 = "Eno"
data.name = 'Eno'
console.log(data.name); // expected output: Eno
console.log(defalutName); // expected output: Eno
复制代码
observer的效果要求很简单,就是监听data对象,当data里面的属性值改变时,监听到其改变; 下面实现一个简陋的双向数据绑定,即data的name改变时,defaultName也要改变,实现双向数据绑定,即defalutName与data对象的name双向绑定了浏览器
let defalutName = ''
let data = {name:''};
function observer (data) {
//Object.defineProperty直接在对象上定义新属性,或修改对象上的现有属性,而后返回对象。
//不了解的请转MDN文档 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Object.defineProperty(data, 'name', {
get(){
return defalutName
},
set(newValue){
defalutName = newValue
}
});
}
//
observer(data)
console.log(data.name);
// expected output: ''
// 给data里面的name赋值 = "Eno"
data.name = 'Eno'
console.log(data.name); // expected output: Eno
console.log(defalutName); // expected output: Eno
复制代码
上面咱们的observer对象并无对data的全部值进行监听,接下来咱们完善oberver函数以下:bash
function observer(data){
// 判断是否为对象 若是不是则直接返回,Object.defineProperty是对象上的属性
if(typeof data !== 'object' || data == null){
return data;
}
for(let key in data){
defineReactive(data,key,data[key]);
}
}
function defineReactive(data,key,value){
//Object.defineProperty直接在对象上定义新属性,或修改对象上的现有属性,而后返回对象。
//不了解的请转MDN文档 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Object.defineProperty(data, key, {
get(){
console.log('获取了值') // 在此作依赖收集的操做
return value
},
set(newValue){
if(newValue !== value){
console.log('设置了值')
value = newValue
}
}
});
}
let data = {name:'',age:18};
observer(data)
// 给data里面的name赋值 = "Eno"
data.name = 'Eno'
console.log(data.name);
// 设置了值
// 获取了值
// Eno
data.age = 12
console.log(data.age);
// 设置了值
// 获取了值
// 12
复制代码
补充:什么是依赖收集? 咱们都知道,当一个可观测对象的属性被读写时,会触发它的getter/setter方法。若是咱们能够在可观测对象的getter/setter里面,执行监听器里面的update()方法;不就可以让对象主动发出通知了吗?app
// ...
let data = {name:'',age:18};
observer(data)
// 给data里面的name赋值 = "Eno"
// data.name = 'Eno'
// console.log(data.name);
// data.age = 12
// console.log(data.age);
data.gender = '男'
复制代码
输出结果以下: ide
由图可知,并无触发set和get,这个由于,在咱们对data进行监测的时候是没有gender这个属性值的,所以咱们若是想要对新增的属性进行监听的话,须要在赋值后再进行一次监听,即vm.$set的效果;咱们能够建立一个reactiveSet函数以下:
function reactiveSet (data,key,value) {
data[key] = value
observer(data)
}
let data = {name:'',age:18};
observer(data)
// 给data里面的name赋值 = "Eno"
// data.name = 'Eno'
// console.log(data.name);
// data.age = 12
// console.log(data.age);
// 经过reactiveSet添加属性
reactiveSet(data,'gender','男')
console.log(data.gender)
复制代码
执行结果以下:
此时是能够响应的,不过vue并非这样作的,下面能够看vue的源码 vuejs in github 里面是这样判断的,若是这个key目前没有存在于对象中,那么会进行赋值并监听。可是这里省略了ob的判断;
补充:
ob是什么呢?
vue初始化的数据(如data中的数据)在页面初始化的时候都会被监听,而被监听的属性都会被绑定__ob__属性,下图就是判断这个数据有没有被监听的。若是这个数据没有被监听,那么就默认你不想监听这个数据,因此直接赋值并返回
目前,咱们是对data一个简单对象进行监听,思考🤔一下假如是多层对象该如何调整及修改observer呢?
// ...
function defineReactive(data,key,value){
observer(value); // 递归 继续对当前value进行拦截
//Object.defineProperty直接在对象上定义新属性,或修改对象上的现有属性,而后返回对象。
//不了解的请转MDN文档 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Object.defineProperty(data, key, {
get(){
console.log('获取了值') // 在此作依赖收集的操做
return value
},
set(newValue){
if(newValue !== value){
// 对于新增的值也须要监听
observer(newValue)
console.log('设置了值')
value = newValue
}
}
});
}
// ...
复制代码
2019年10月21日更新
假如data里面的对象里面有数组,那么须要对数组进行拦截,若是数组里面是多维数组,还需和5.嵌套对象的作法一致,还须要进行递归监听,observer修改以下:
function observer(data){
// 判断是否为对象 若是不是则直接返回,Object.defineProperty是对象上的属性
if(typeof data !== 'object' || data == null){
return data;
}
if(Array.isArray(data)){ // 若是是数组,则对数据进行遍历并对其value进行递归监听
for(let i = 0; i< data.length ;i++){
observer(data[i]);
}
} else {
for(let key in data){
defineReactive(data,key,data[key]);
}
}
}
// ...
// ...
let data = {name:'',age:18};
observer(data)
// 给data里面的name赋值 = "Eno"
// data.name = 'Eno'
// console.log(data.name);
// data.age = 12
// console.log(data.age);
// reactiveSet(data,'gender','男')
// console.log(data.gender)
reactiveSet(data,'attr',[1,2,3,4,5,100])
console.log(data.attr)
复制代码
从上图能够看出,在reactiveSet的状况下,即便给data设置了不存在的数组,也可以获得监听,接下来尝试对数组进行修改测试;
// ...
let data = {name:'',attr:[1,2,3,4,5,100]};
observer(data)
// 给data里面的name赋值 = "Eno"
// data.name = 'Eno'
// console.log(data.name);
// data.age = 12
// console.log(data.age);
// reactiveSet(data,'gender','男')
// console.log(data.gender)
// console.log(data.attr)
data.attr.push(10000);
data.attr.splice(0,1);
复制代码
究其缘由:因为数组的方法在对数组的增删查改过程当中,vue并没其操做更新视图的操做,故此时是不能响应式的,所以若是须要对此类方法的调用时,经过视图更新,则须要对数组方法
重写
,查看vue/array.js源码可知:
其中:def的源码以下: 即对obj的属性进行了重写或者称之为元素的属性从新定义
接下来咱们写一个简陋版的数组重写:
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(method=>{
arrayMethods[method] = function(){
// 函数劫持 把函数进行重写
// 而内部实际上继续调用原来的方法但在这里咱们能够去调用更新视图的方法
console.log('数组 更新啦...')
arrayProto[method].call(this,...arguments)
}
});
复制代码
function observer(data){
// 判断是否为对象 若是不是则直接返回,Object.defineProperty是对象上的属性
if(typeof data !== 'object' || data == null){
return data;
}
if(Array.isArray(data)){ // 若是是数组,则对数据进行遍历并对其value进行递归监听
// 在这里对数组方法进行重写 即函数劫持
Object.setPrototypeOf(data, arrayMethods);
for(let i = 0; i< data.length ;i++){
observer(data[i]);
}
} else {
for(let key in data){
defineReactive(data,key,data[key]);
}
}
}
复制代码
const arrayProto = Array.prototype
const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(method=>{
arrayMethods[method] = function(){
//函数劫持 把函数进行重写
// 而内部实际上继续调用原来的方法但在这里咱们能够去调用更新视图的方法
console.log('数组 更新啦...')
arrayProto[method].call(this,...arguments)
}
});
function observer(data){
// 判断是否为对象 若是不是则直接返回,Object.defineProperty是对象上的属性
if(typeof data !== 'object' || data == null){
return data;
}
if(Array.isArray(data)){ // 若是是数组,则对数据进行遍历并对其value进行递归监听
// 在这里对数组方法进行重写 即函数劫持
Object.setPrototypeOf(data, arrayMethods);
for(let i = 0; i< data.length ;i++){
observer(data[i]);
}
} else {
for(let key in data){
defineReactive(data,key,data[key]);
}
}
}
function defineReactive(data,key,value){
observer(value); // 递归 继续对当前value进行拦截
//Object.defineProperty直接在对象上定义新属性,或修改对象上的现有属性,而后返回对象。
//不了解的请转MDN文档 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Object.defineProperty(data, key, {
get(){
console.log('获取了值') // 在此作依赖收集的操做
return value
},
set(newValue){
if(newValue !== value){
// 对于新增的值也须要监听
observer(newValue)
console.log('设置了值')
value = newValue
}
}
});
}
function reactiveSet (data,key,value) {
data[key] = value
observer(data)
}
let data = {name:'',attr:[1,2,3,4,5,100]};
observer(data)
// 给data里面的name赋值 = "Eno"
// data.name = 'Eno'
// console.log(data.name);
// data.age = 12
// console.log(data.age);
// reactiveSet(data,'gender','男')
// console.log(data.gender)
// console.log(data.attr)
data.attr.push(10000);
data.attr.splice(0,1);
复制代码
运行结果以下:
看到视图真的更新了,不得不佩服尤大大真的厉害,据说2020年第一季度就要出vue3.0了,接下来要写篇vue3.0的初步学习文章,但愿各位看官支持,不要忘记点赞喔;
文章源码:github wLove-c
总结: 能力有限,暂时先写这么多,接下来有时间会写一篇vue-next的源码实践和理解, 但愿各位看官大人不要忘记点赞哈,写的很差的地方欢迎指正;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="vue.global.js"></script>
<script>
console.log('Vue====',Vue)
const App = {
setup() {
// reactive state
let count = Vue.reactive({value:1}) // 知乎上尤大大推荐的是使用 const count = value(0) 但目前这个版本是没有value的 先用reactive作响应
// computed state
const plusOne = Vue.computed(() => count.value * 2)
// method
const increment = () => {
count.value++
}
// watch
Vue.watch(() => count.value * 2, val => {
console.log(`value * 2 is ${val}`)
})
// lifecycle
Vue.onMounted(() => {
console.log(`mounted`)
})
// expose bindings on render context
return {
count,
plusOne,
increment
}
},
template: `
<div>
<div>count is {{ count.value }}</div>
<span>plusOne is {{ plusOne }}</span>
<button @click="increment">count++</button>
</div>
`,
}
Vue.createApp().mount(App,app)
</script>
</body>
</html>
复制代码