经过如下demo,深刻理解mvvm的原理,实现数据劫持,数据双向绑定,数据驱动页面,数据双向绑定,计算属性computed! 注意:为了方便理解,我会在每一个函数内部把执行顺序和执行思路用文字注释,望知晓...
HTMLjavascript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vm的demo</title>
</head>
<body>
<div id="app">
<div>a的值是:{{a.a}}</div>
<p>b的值是:{{b}}</p>
<input type="text" v-model="b">
<p>computed: {{hello}}</p>
</div>
<script src="./lvm2.js"></script>
<script> var lvm = new Lvm({ el: '#app', data: { a: {a:3}, b: '2', c: 10 }, computed: { hello (){ console.log('computed') return this.c + this.b } } }) </script>
</body>
</html>
新建js文件 html
//新建一个vm对象
function Lvm (opt = {}) {}
1.对象初始化java
2.数据劫持node
3.this代理data对象 web
function Lvm (opt = {}) {
//1.拿到opt对象,保存到this下边
this.$options = opt;
var data = this._data = this.$options.data;
//2.进行数据劫持
createObj (data)
//3.this代理this._data对象
for (let key in data) {
Object.defineProperty(this, key, {
enumerable: true,
get: () => this._data[key],
set: (newVal) =>this._data[key] = newVal
})
}
}
/** * @desc 数据劫持方法 * 为了方便递归调用Objserver方法封装,返回值是Objserver的一个实例 */
function createObj (data) {
if (typeof data !== 'object') return;
return new Objserver(data)
}
/** * @desc 建立对象函数 * @param {object} data 建立对象时传入的data对象 * 1.遍历data对象,拿到左右的对象名和属性值 * 2.使用Object.defineProperty建立对象 * 3.加入遍历出来的val是一个对象,调用本身,实现递归调用 * Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。 */
function Objserver (data) {
for(let key in data) {
let val = data[key];
createObj(val)
//使用Object.defineProperty 进行数据劫持
Object.defineProperty(data, key, {
enumerable: true,
get: function () {
return val
},
set: function (newVal) {
//若是用户输入的新值和原先的值彻底相同,则不进行操做直接返回
if (newVal === val) return;
val = newVal
//跟新属性值之后从新监听数据
createObj(val)
}
})
}
}
function Lvm (opt = {}) {
//1.拿到opt对象,保存到this下边
this.$options = opt;
var data = this._data = this.$options.data;
//2.进行数据劫持
createObj (data)
//3.this代理this._data对象
for (let key in data) {
Object.defineProperty(this, key, {
enumerable: true,
get: () => this._data[key],
set: newVal =>this._data[key] = newVal
})
}
//4.编译页面
new Compile(this.$options.el, this);
}
/** * @desc 数据劫持方法 */
function createObj (data) {
if (typeof data !== 'object') return;
return new Objserver(data)
}
/** * @desc 建立对象函数 * @param {object} data 建立对象时传入的data对象 * 1.遍历data对象,拿到左右的对象名和属性值 * 2.使用Object.defineProperty建立对象 * 3.加入遍历出来的val是一个对象,调用本身,实现递归调用 * Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。 */
function Objserver (data) {
for(let key in data) {
let val = data[key];
createObj(val)
//使用Object.defineProperty 进行数据劫持
Object.defineProperty(data, key, {
enumerable: true,
get: function () {
return val
},
set: function (newVal) {
//若是用户输入的新值和原先的值彻底相同,则不进行操做直接返回
if (newVal === val) return;
val = newVal
//跟新属性值之后从新监听数据
createObj(val)
}
})
}
}
/** * @desc 编译模板 * @param {string} el 包裹元素选择器 * @param {object} vm 咱们的lvm实例对象 * * 1.拿到咱们初始化的el元素 * 2.新建一个文档随便,用来缓存dom * 3.遍历el元素的全部子元素节点,添加到文档碎片 * 4.进行表达式里的值替换 * 5.添加到dom,更新页面 */
function Compile (el, vm) {
vm.$el = document.querySelector(el);
var frag = document.createDocumentFragment(), node = null;
//把app的dom移动到内存中进行操做
while(node = vm.$el.firstChild) {
frag.appendChild(node)
}
//替换变量
replice (frag)
/** * @descrtion 替换内容 * @param {dom} node html dom标签 * @return {null} 没有返回值 * * 1.传入全部的dom的元素节点 * 2.使用Array.form转换为数组之后遍历处理 * 3.新建reg对象,匹配和捕获表达式里的 * 4.拿到每个节点的文本节点(若是是元素,则递归调用本身) * 5.经过nodeType判断是否是文本节点,而且使用正则对象验证是否包含大括号表达式 * 6.若是有大括号表达式,则进行替换赋值操做 * 7.数据监听(接下来实现) * 8.数据双向绑定(稍后实现) * 9.添加到dom */
function replice (node) {
// 把node类数组转换为数组遍历
Array.from(node.childNodes).forEach(item => {
var reg = /\{\{(.*)\}\}/,
text = item.textContent;//拿到文本节点
if(item.nodeType == 3 && reg.test(text)) {
reg.exec(text)
var key, arr = RegExp.$1.split('.'), //若是拿到的是对象的深层属性(a.a.b),在下边进行遍历
val = vm;//这里设置为vm => this 对象之后就能够直接访问获得数据
arr.forEach(key => {
//获得数据 遍历拿到对象属性的属性,主要操做多层数据
val = val[key]
})
//替换赋值操做
item.textContent = text.replace(/\{\{(.*)\}\}/, val)
val = null;
}
//若是node不是文本节点,继续循环
if (item.childNodes) {
replice (item)
}
})
}
//把操做过的数据添加到dom
vm.$el.appendChild(frag)
frag = null
}
function Lvm (opt = {}) {
//1.拿到opt对象,保存到this下边
this.$options = opt;
var data = this._data = this.$options.data;
//2.进行数据劫持
createObj (data)
//3.this代理this._data对象
for (let key in data) {
Object.defineProperty(this, key, {
enumerable: true,
get: () => this._data[key],
set: newVal =>this._data[key] = newVal
})
}
//4.编译页面
new Compile(this.$options.el, this);
}
/** * @desc 数据劫持方法 */
function createObj (data) {
if (typeof data !== 'object') return;
return new Objserver(data)
}
/** * @desc 建立对象函数 * @param {object} data 建立对象时传入的data对象 * 1.遍历data对象,拿到左右的对象名和属性值 * 2.使用Object.defineProperty建立对象 * 3.加入遍历出来的val是一个对象,调用本身,实现递归调用 * 4.新建一个事件池dep * Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。 */
function Objserver (data) {
let dep = new Dep();
for(let key in data) {
let val = data[key];
createObj(val)
//使用Object.defineProperty 进行数据劫持
Object.defineProperty(data, key, {
enumerable: true,
get: function () {
//若是存在target属性的话添加到事件池dep
Dep.target && dep.addSub(Dep.target)
return val
},
set: function (newVal) {
//若是用户输入的新值和原先的值彻底相同,则不进行操做直接返回
if (newVal === val) return;
val = newVal
//跟新属性值之后从新监听数据
createObj(val)
//调用事件池通知方法 提醒更新时视图
dep.notify()
}
})
}
}
/** * @desc 编译模板 * @param {string} el 包裹元素选择器 * @param {object} vm 咱们的lvm实例对象 * * 1.拿到咱们初始化的el元素 * 2.新建一个文档随便,用来缓存dom * 3.遍历el元素的全部子元素节点,添加到文档碎片 * 4.进行表达式里的值替换 * 5.添加到dom,更新页面 */
function Compile (el, vm) {
vm.$el = document.querySelector(el);
var frag = document.createDocumentFragment(), node = null;
//把app的dom移动到内存中进行操做
while(node = vm.$el.firstChild) {
frag.appendChild(node)
}
//替换变量
replice (frag)
/** * @descrtion 替换内容 * @param {dom} node html dom标签 * @return {null} 没有返回值 * * 1.传入全部的dom的元素节点 * 2.使用Array.form转换为数组之后遍历处理 * 3.新建reg对象,匹配和捕获表达式里的 * 4.拿到每个节点的文本节点(若是是元素,则递归调用本身) * 5.经过nodeType判断是否是文本节点,而且使用正则对象验证是否包含大括号表达式 * 6.若是有大括号表达式,则进行替换赋值操做 * 7.数据监听(接下来实现) * 8.数据双向绑定(稍后实现) * 9.添加到dom */
function replice (node) {
// 把node类数组转换为数组遍历
Array.from(node.childNodes).forEach(item => {
var reg = /\{\{(.*)\}\}/,
text = item.textContent;//拿到文本节点
if(item.nodeType == 3 && reg.test(text)) {
reg.exec(text)
var key, arr = RegExp.$1.split('.'), //若是拿到的是对象的深层属性(a.a.b),在下边进行遍历
val = vm;//这里设置为vm => this 对象之后就能够直接访问获得数据
arr.forEach(key => {
//获得数据 遍历拿到对象属性的属性,主要操做多层数据
val = val[key]
})
//建立监听小函数
new Wetcher(vm, arr, function(newVal){
item.textContent = text.replace(/\{\{(.*)\}\}/, newVal)
})
//替换赋值操做
item.textContent = text.replace(/\{\{(.*)\}\}/, val)
val = null;
}
//若是node不是文本节点,继续循环
if (item.childNodes) {
replice (item)
}
})
}
//把操做过的数据添加到dom
vm.$el.appendChild(frag)
frag = null
}
/** * 新建一个dep 实现模拟事件池 * subs 保存全部须要监听的对象 * addSub 将监听事件添加到事件池 * notify当数据发生变化的时候调用,通知更新数据 * notify触发的时候,遍历执行subs里的全部事件 */
function Dep () {
this.subs = [];
}
/** * @desc 事件池添加方法 * @param {Function} fn 添加事件到事件池 */
Dep.prototype.addSub = function (fn) {
this.subs.push(fn)
}
/** * @desc 数据更新时刷新页面 * @return {[type]} [description] */
Dep.prototype.notify = function () {
this.subs.forEach(fn => {
fn.update()
})
}
/** * @desc 监听小函数 每一个须要监听的对象的实例对象 * @param {object} vm 当前this的实例 * @param {string} exp 监听的属性 a|a.a * @param {Function} fn 数据更改之后执行的回调函数 * * 1.咱们在建立一个wecher监听对象的实例,传入上述的参数 * 2.为了方便加入事件池,在初始化函数的过程当中给Dep一个target属性, * 而后在触发属性的get方法,在哪里判断是否存在target属性,存在就加入,不存在则不操做 * 3.在操做完成之后Dep.target属性制空 */
function Wetcher (vm, exp, fn){
this.fn = fn;
this.vm = vm;
this.exp = exp;
Dep.target = this;
let val = this.vm;
let arr = this.exp;
//获取值 会调用get方法
arr.forEach(k => {
val = val[k]
})
Dep.target = null;
}
/** * @description 监听小函数在数据变动之后调用 * @return {[type]} [description] * 1.在这里咱们拿到更新后的数据 * 2.调用调用回调函数fn,并将新值传入 */
Wetcher.prototype.update = function () {
let val = this.vm
let arr = this.exp
//获取值 会调用get方法
arr.forEach(k => {
val = val[k]
})
//执行建立实例时的回调函数,而且传入新值替换
this.fn(val)
}
function Lvm (opt = {}) {
//1.拿到opt对象,保存到this下边
this.$options = opt;
var data = this._data = this.$options.data;
//2.进行数据劫持
createObj (data)
//3.this代理this._data对象
for (let key in data) {
Object.defineProperty(this, key, {
enumerable: true,
get: () => this._data[key],
set: newVal =>this._data[key] = newVal
})
}
//4.编译页面
new Compile(this.$options.el, this);
}
/** * @desc 数据劫持方法 */
function createObj (data) {
if (typeof data !== 'object') return;
return new Objserver(data)
}
/** * @desc 建立对象函数 * @param {object} data 建立对象时传入的data对象 * 1.遍历data对象,拿到左右的对象名和属性值 * 2.使用Object.defineProperty建立对象 * 3.加入遍历出来的val是一个对象,调用本身,实现递归调用 * 4.新建一个事件池dep * Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。 */
function Objserver (data) {
let dep = new Dep();
for(let key in data) {
let val = data[key];
createObj(val)
//使用Object.defineProperty 进行数据劫持
Object.defineProperty(data, key, {
enumerable: true,
get: function () {
//若是存在target属性的话添加到事件池dep
Dep.target && dep.addSub(Dep.target)
return val
},
set: function (newVal) {
//若是用户输入的新值和原先的值彻底相同,则不进行操做直接返回
if (newVal === val) return;
val = newVal
//跟新属性值之后从新监听数据
createObj(val)
//调用事件池通知方法 提醒更新时视图
dep.notify()
}
})
}
}
/** * @desc 编译模板 * @param {string} el 包裹元素选择器 * @param {object} vm 咱们的lvm实例对象 * * 1.拿到咱们初始化的el元素 * 2.新建一个文档随便,用来缓存dom * 3.遍历el元素的全部子元素节点,添加到文档碎片 * 4.进行表达式里的值替换 * 5.添加到dom,更新页面 */
function Compile (el, vm) {
vm.$el = document.querySelector(el);
var frag = document.createDocumentFragment(), node = null;
//把app的dom移动到内存中进行操做
while(node = vm.$el.firstChild) {
frag.appendChild(node)
}
//替换变量
replice (frag)
/** * @descrtion 替换内容 * @param {dom} node html dom标签 * @return {null} 没有返回值 * * 1.传入全部的dom的元素节点 * 2.使用Array.form转换为数组之后遍历处理 * 3.新建reg对象,匹配和捕获表达式里的 * 4.拿到每个节点的文本节点(若是是元素,则递归调用本身) * 5.经过nodeType判断是否是文本节点,而且使用正则对象验证是否包含大括号表达式 * 6.若是有大括号表达式,则进行替换赋值操做 * 7.数据监听(接下来实现) * 8.数据双向绑定(稍后实现) * 9.添加到dom */
function replice (node) {
// 把node类数组转换为数组遍历
Array.from(node.childNodes).forEach(item => {
var reg = /\{\{(.*)\}\}/,
text = item.textContent;//拿到文本节点
if(item.nodeType == 3 && reg.test(text)) {
reg.exec(text)
var key, arr = RegExp.$1.split('.'), //若是拿到的是对象的深层属性(a.a.b),在下边进行遍历
val = vm;//这里设置为vm => this 对象之后就能够直接访问获得数据
arr.forEach(key => {
//获得数据 遍历拿到对象属性的属性,主要操做多层数据
val = val[key]
})
//建立监听小函数
new Wetcher(vm, arr, function(newVal){
item.textContent = text.replace(/\{\{(.*)\}\}/, newVal)
})
//替换赋值操做
item.textContent = text.replace(/\{\{(.*)\}\}/, val)
val = null;
}
//实现数据双向绑定
//思路:
//首先判断若是是元素,是否存在v-model属性,(拿到全部的attribute,而后遍历判断)
//若是不存在就不操做,若是存在(v-model)属性则进行以下操做
//拿到属性,遍历对象拿到val,赋值给元素
//给input元素绑定事件,在数据变化的时候那致使,而后从新赋值
//由于这里赋值若是说嵌套层级较多的时候须要进行特殊处理,我偷懒,没有作,只作了一级的,
if (item.nodeType == 1) {
let attrs = item.attributes;
let reg = /v\-model/;
//循环遍历属性 看有没有v-model
Array.from(attrs).forEach(attr => {
let name = attr.name;
let exp = attr.value.split('.');
if (reg.test(name)) {
let val = vm;
exp.forEach(k => {
val = val[k]
})
item.value = val;
//监听数据变化 更新input的值
new Wetcher(vm, exp, function(newVal){
item.value = newVal
})
item.addEventListener('input', function (e) {
let val = e.target.value;
//尚未想到多层级的数据怎么设置,后期学了再补
vm[exp] = val
})
}
})
}
//若是node不是文本节点,继续循环
if (item.childNodes) {
replice (item)
}
})
}
//把操做过的数据添加到dom
vm.$el.appendChild(frag)
frag = null
}
/** * 新建一个dep 实现模拟事件池 * subs 保存全部须要监听的对象 * addSub 将监听事件添加到事件池 * notify当数据发生变化的时候调用,通知更新数据 * notify触发的时候,遍历执行subs里的全部事件 */
function Dep () {
this.subs = [];
}
/** * @desc 事件池添加方法 * @param {Function} fn 添加事件到事件池 */
Dep.prototype.addSub = function (fn) {
this.subs.push(fn)
}
/** * @desc 数据更新时刷新页面 * @return {[type]} [description] */
Dep.prototype.notify = function () {
this.subs.forEach(fn => {
fn.update()
})
}
/** * @desc 监听小函数 每一个须要监听的对象的实例对象 * @param {object} vm 当前this的实例 * @param {string} exp 监听的属性 a|a.a * @param {Function} fn 数据更改之后执行的回调函数 * * 1.咱们在建立一个wecher监听对象的实例,传入上述的参数 * 2.为了方便加入事件池,在初始化函数的过程当中给Dep一个target属性, * 而后在触发属性的get方法,在哪里判断是否存在target属性,存在就加入,不存在则不操做 * 3.在操做完成之后Dep.target属性制空 */
function Wetcher (vm, exp, fn){
this.fn = fn;
this.vm = vm;
this.exp = exp;
Dep.target = this;
let val = this.vm;
let arr = this.exp;
//获取值 会调用get方法
arr.forEach(k => {
val = val[k]
})
Dep.target = null;
}
/** * @description 监听小函数在数据变动之后调用 * @return {[type]} [description] * 1.在这里咱们拿到更新后的数据 * 2.调用调用回调函数fn,并将新值传入 */
Wetcher.prototype.update = function () {
let val = this.vm
let arr = this.exp
//获取值 会调用get方法
arr.forEach(k => {
val = val[k]
})
//执行建立实例时的回调函数,而且传入新值替换
this.fn(val)
}
function Lvm (opt = {}) {
//1.拿到opt对象,保存到this下边
this.$options = opt;
var data = this._data = this.$options.data;
//2.进行数据劫持
createObj (data)
//3.this代理this._data对象
for (let key in data) {
Object.defineProperty(this, key, {
enumerable: true,
get: () => this._data[key],
set: newVal =>this._data[key] = newVal
})
}
//计算属性初始化 必须在compile以前调用 不然无效
initComputed.call(this)
//5.编译页面
new Compile(this.$options.el, this);
}
/** * @desc 数据劫持方法 */
function createObj (data) {
if (typeof data !== 'object') return;
return new Objserver(data)
}
/** * @desc 建立对象函数 * @param {object} data 建立对象时传入的data对象 * 1.遍历data对象,拿到左右的对象名和属性值 * 2.使用Object.defineProperty建立对象 * 3.加入遍历出来的val是一个对象,调用本身,实现递归调用 * 4.新建一个事件池dep * Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。 */
function Objserver (data) {
let dep = new Dep();
for(let key in data) {
let val = data[key];
createObj(val)
//使用Object.defineProperty 进行数据劫持
Object.defineProperty(data, key, {
enumerable: true,
get: function () {
//若是存在target属性的话添加到事件池dep
Dep.target && dep.addSub(Dep.target)
return val
},
set: function (newVal) {
//若是用户输入的新值和原先的值彻底相同,则不进行操做直接返回
if (newVal === val) return;
val = newVal
//跟新属性值之后从新监听数据
createObj(val)
//调用事件池通知方法 提醒更新时视图
dep.notify()
}
})
}
}
/** * @desc 编译模板 * @param {string} el 包裹元素选择器 * @param {object} vm 咱们的lvm实例对象 * * 1.拿到咱们初始化的el元素 * 2.新建一个文档随便,用来缓存dom * 3.遍历el元素的全部子元素节点,添加到文档碎片 * 4.进行表达式里的值替换 * 5.添加到dom,更新页面 */
function Compile (el, vm) {
vm.$el = document.querySelector(el);
var frag = document.createDocumentFragment(), node = null;
//把app的dom移动到内存中进行操做
while(node = vm.$el.firstChild) {
frag.appendChild(node)
}
//替换变量
replice (frag)
/** * @descrtion 替换内容 * @param {dom} node html dom标签 * @return {null} 没有返回值 * * 1.传入全部的dom的元素节点 * 2.使用Array.form转换为数组之后遍历处理 * 3.新建reg对象,匹配和捕获表达式里的 * 4.拿到每个节点的文本节点(若是是元素,则递归调用本身) * 5.经过nodeType判断是否是文本节点,而且使用正则对象验证是否包含大括号表达式 * 6.若是有大括号表达式,则进行替换赋值操做 * 7.数据监听(接下来实现) * 8.数据双向绑定(稍后实现) * 9.添加到dom */
function replice (node) {
// 把node类数组转换为数组遍历
Array.from(node.childNodes).forEach(item => {
var reg = /\{\{(.*)\}\}/,
text = item.textContent;//拿到文本节点
if(item.nodeType == 3 && reg.test(text)) {
reg.exec(text)
var key, arr = RegExp.$1.split('.'), //若是拿到的是对象的深层属性(a.a.b),在下边进行遍历
val = vm;//这里设置为vm => this 对象之后就能够直接访问获得数据
arr.forEach(key => {
//获得数据 遍历拿到对象属性的属性,主要操做多层数据
val = val[key]
})
//建立监听小函数
new Wetcher(vm, arr, function(newVal){
item.textContent = text.replace(/\{\{(.*)\}\}/, newVal)
})
//替换赋值操做
item.textContent = text.replace(/\{\{(.*)\}\}/, val)
val = null;
}
//实现数据双向绑定
//思路:
//首先判断若是是元素,是否存在v-model属性,(拿到全部的attribute,而后遍历判断)
//若是不存在就不操做,若是存在(v-model)属性则进行以下操做
//拿到属性,遍历对象拿到val,赋值给元素
//给input元素绑定事件,在数据变化的时候那致使,而后从新赋值
//由于这里赋值若是说嵌套层级较多的时候须要进行特殊处理,我偷懒,没有作,只作了一级的,
if (item.nodeType == 1) {
let attrs = item.attributes;
let reg = /v\-model/;
//循环遍历属性 看有没有v-model
Array.from(attrs).forEach(attr => {
let name = attr.name;
let exp = attr.value.split('.');
if (reg.test(name)) {
let val = vm;
exp.forEach(k => {
val = val[k]
})
item.value = val;
//监听数据变化 更新input的值
new Wetcher(vm, exp, function(newVal){
item.value = newVal
})
item.addEventListener('input', function (e) {
let val = e.target.value;
//尚未想到多层级的数据怎么设置,后期学了再补
vm[exp] = val
})
}
})
}
//若是node不是文本节点,继续循环
if (item.childNodes) {
replice (item)
}
})
}
//把操做过的数据添加到dom
vm.$el.appendChild(frag)
frag = null
}
/** * 新建一个dep 实现模拟事件池 * subs 保存全部须要监听的对象 * addSub 将监听事件添加到事件池 * notify当数据发生变化的时候调用,通知更新数据 * notify触发的时候,遍历执行subs里的全部事件 */
function Dep () {
this.subs = [];
}
/** * @desc 事件池添加方法 * @param {Function} fn 添加事件到事件池 */
Dep.prototype.addSub = function (fn) {
this.subs.push(fn)
}
/** * @desc 数据更新时刷新页面 * @return {[type]} [description] */
Dep.prototype.notify = function () {
this.subs.forEach(fn => {
fn.update()
})
}
/** * @desc 监听小函数 每一个须要监听的对象的实例对象 * @param {object} vm 当前this的实例 * @param {string} exp 监听的属性 a|a.a * @param {Function} fn 数据更改之后执行的回调函数 * * 1.咱们在建立一个wecher监听对象的实例,传入上述的参数 * 2.为了方便加入事件池,在初始化函数的过程当中给Dep一个target属性, * 而后在触发属性的get方法,在哪里判断是否存在target属性,存在就加入,不存在则不操做 * 3.在操做完成之后Dep.target属性制空 */
function Wetcher (vm, exp, fn){
this.fn = fn;
this.vm = vm;
this.exp = exp;
Dep.target = this;
let val = this.vm;
let arr = this.exp;
//获取值 会调用get方法
arr.forEach(k => {
val = val[k]
})
Dep.target = null;
}
/** * @description 监听小函数在数据变动之后调用 * @return {[type]} [description] * 1.在这里咱们拿到更新后的数据 * 2.调用调用回调函数fn,并将新值传入 */
Wetcher.prototype.update = function () {
let val = this.vm
let arr = this.exp
//获取值 会调用get方法
arr.forEach(k => {
val = val[k]
})
//执行建立实例时的回调函数,而且传入新值替换
this.fn(val)
}
/** * @description 计算属性函数 * @return {} [description] */
function initComputed () {
let vm = this
let computed = vm.$options.computed
//拿到全部的computed属性,进行遍历操做
//Object.keys(computed) => 返回computed对象的全部属性的name的数组
//判断若是val是一个函数,则直接返回这个函数,若是是一个变量,则调用变量的get方法
Object.keys(computed).forEach(key => {
//创建数据劫持
Object.defineProperty(vm, key, {
get: typeof computed[key] === 'function' ? computed[key]
: computed[key].get,
set: function() {}
})
})
}