reduce()
// reduce是数组的一个api
const arr=[1,2,3,4,5,6,7]
const res=arr.reduce((val,item)=>{return val+item},0)
console.log(res);
const obj={
name:'zs',
info:{
address:{
location:'北京'
}
}
}
const attrs=['info','address','location']
// const location=attrs.reduce((newobj,k)=>{return newobj[k]},obj)
// const location=attrs.reduce((newobj,k)=>newobj[k],obj)
// console.log(location);
const attrStr='info.address.location'
const location=attrStr.split('.').reduce((newobj,k)=>newobj[k],obj)
console.log(location);
发布订阅
- dep()
- 定义一个数组存取订阅消息
- 添加订阅
- 遍历/发布订阅
- watcher()
简单的发布订阅
// 收集依赖/收集订阅
class Deep{
constructor(){
// 存放所有订阅者信息
this.subs=[]
}
// 向subs数组中添加订阅信息
addSub(watcher){
this.subs.push(watcher)
}
// 发布订阅方法
notify(){
}
}
// 订阅者
class Watcher{
// 新建watcher实例的时候传一个cb进来挂载到自己身上
constructor(cb){
this.cb=cb
}
// 触发回调方法
update(){
this.cb()
}
}
const w1=new Watcher(()=>{
console.log('我是第一个订阅者');
})
const w2=new Watcher(()=>{
console.log('我是第二个订阅者');
})
w1.update()
w2.update()
发布订阅
// 发布订阅方法
notify(){
this.subs.forEach(watcher=>watcher.update())
}
当vue监听到数据变化时,会把新数据通过notify通知给订阅者(DOM),订阅者通过update来改变数据,重新渲染DOM节点。
数据劫持
Object.defineProperty的基本属性
const obj={
name:'zs',
age:20,
info:{
a:1,
b:2
}
}
console.log(obj.name);
// 赋值操作
obj.name='ls'
console.log(obj.name);
// 定义属性,监听?
Object.defineProperty(obj,'name',{
enumerable:true, //当前属性允许被循环
configurable:true, //当前属性允许被配置
// 拦截取值操作
get(){
console.log('有人获取了obj.name的值')
return '我不是zs'
},
// 拦截赋值操作
set(newValue){
console.log('我不要你给的值',newValue);
}
})
console.log(obj.name);
obj.name="ls"
何时通知DOM数据发生变化
数据变动之后,在Object.defineProperty的setter中通知watcher数据发生变化
// 拦截赋值操作
set(newValue){
console.log('我不要你给的值',newValue);
// 数据变动之后,在setter中通知watcher数据发生变化
dep.notify()
}
创建一个vue实例/以及写一个vue对象
const vm=new Vue({
el:'#app',
data:{
name:'zs',
age:20,
info:{
a:'a1',
c:'c1'
}
}
})
console.log(vm);
遍历所有对象一次设置getter/setter拦截
// vue.js
class Vue{
constructor(options){
this.$data=options.data
// 进行调用数据劫持的方法
Observe(this.$data)
}
}
//定义一个数据劫持的方法
// obj为data
function Observe(obj){
//拿到对象的属性值形成一个数组
// console.log(Object.keys(obj));
Object.keys(obj).forEach(key=>{
// 需要为当前的key所对应的属性添加getter/setter
// 当前被循环的key所对应的属性值
let value=obj[key]
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){
console.log(`有人获取了${key}的值`);
return value
},
set(newValue){
console.log(`有人修改了${key}的值`);
value=newValue
}
})
})
}
此时data内的对象并未被添加拦截,递归设置
//定义一个数据劫持的方法
// obj为data
function Observe(obj){
// 递归的终止条件
if(!obj||typeof obj!=='object') return
//拿到对象的属性值形成一个数组
// console.log(Object.keys(obj));
Object.keys(obj).forEach(key=>{
// 需要为当前的key所对应的属性添加getter/setter
// 当前被循环的key所对应的属性值
let value=obj[key]
// 把value子节点进行递归
Observe(value)
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){
console.log(`有人获取了${key}的值`);
return value
},
set(newValue){
console.log(`有人修改了${key}的值`);
value=newValue
}
})
})
}
此时重新赋值getter/setter又消失了,因此需要再次调用递归
set(newValue){
console.log(`有人修改了${key}的值`);
value=newValue
Observe(newValue)
}
属性代理
通过vm就能调用data中的数据(属性代理)
// 属性代理
Object.keys(this.$data).forEach(key=>{
Object.defineProperty(this,key,{
enumerable:true,
configurable:true,
get(){
return this.$data[key]
},
set(newValue){
this.$data[key]=newValue
}
})
})
如今已经可以实现监听到数据的变化
单向绑定
在vue构造函数中调用模板函数/
// 调用模板编译的函数
Compile(options.el,this)
// 对HTML结构进行模板编译,数据渲染
// el获取区域,vm实例获取数据
// new Vue实例的时候执行
function Compile(el,vm) {
// 获取到的dom元素直接挂载到vm的$el身上
vm.$el=document.querySelector(el)
// 创建文档碎片,提高DOM操作性能
// 文档碎片:开辟内存存放dom节点,在内存中操作
const fragment=document.createDocumentFragment()
// 放入文档碎片
while(childNode=vm.$el.firstChild){
fragment.appendChild(childNode)
}
// 进行模板编译
// 拿出来
vm.$el.appendChild(fragment)
}
获取根节点
// Compile()
// 进行模板编译
replace(fragment)
// 对dom节点进行编译
function replace(node){
// 正则提取插值表达式{{value}}
const regMustache=/\{\{\s*(\S+)\s*\}\}/
// 证明当前的node节点是文本子节点,进行正则替换
if(node.nodeType===3){
// 文本子节点也是dom,获取值需要调用textContent属性
console.log(node.textContent)
// 终止递归条件
return
}
// 证明不是文本节点,调用递归
node.childNodes.forEach(child=>replace(child))
}
获取根节点(包括对象的根节点)的数据,赋值给代码片的nodeContent
// 对dom节点进行编译
function replace(node){
// 正则提取插值表达式{{value}}
const regMustache=/\{\{\s*(\S+)\s*\}\}/
// 证明当前的node节点是文本子节点,进行正则替换
if(node.nodeType===3){
// 文本子节点也是dom,获取值需要调用textContent属性
// console.log(node.textContent)
const text=node.textContent
// 正则获取代码片段到一个数组
const execres=regMustache.exec(text)
// console.log(execres);
if(execres){
// node.textContent=vm[execres[1]]
// 分割对象(split),且获取最后的值(reduce)
const value=execres[1].split('.').reduce((newObj,k)=>newObj[k],vm)
// 此replace是字符串的替换?
node.textContent=text.replace(regMustache,value)
}
// 终止递归条件
return
}
// 证明不是文本节点,调用递归
node.childNodes.forEach(child=>replace(child))
}
此时只能实现数据的绑定,渲染到页面,不能监听数据的改变,接下来要发布订阅了
发布者告诉watcher数据改变,watcher调用update告诉自己的回调
watcher的回调应该包含数据的更新
node.textContent=text.replace(regMustache,value)
创建发布订阅的类
// 收集watcher订阅者的类
class Dep{
constructor(){
// 所有的watcher都要存放在数组中
this.subs=[]
}
// 加watcher
addSub(watcher){
this.subs.push(watcher)
}
// 通知watcher
notify(){
// 循环每一个watcher调用更新他们的函数
this.subs.forEach(watcher=>watcher.update())
}
}
// watcher订阅者的类
class Watcher{
// cb回调函数中记录着watcher如何更新自己的文本
// vm保存着需要更新自己的数据
// key表示着更新文本内容的属性
constructor(vm,key,cb){
this.vm=vm
this.key=key
this.cb=cb
}
// 让发布者通知你该更新了
update(){
this.cb()
}
}
创建watcher的实例
// 你已经学会更新自己了
// 此replace是字符串的替换?
node.textContent=text.replace(regMustache,value)
// 创建watcher的实例
new Watcher(vm,execres[1],(newValue)=>{
node.textContent=text.replace(regMustache,newValue)
})
存到dep数组中去
// watcher的构造
// 负责把创建的watcher实例存到Dep实例的subs数组中,搞不懂
// 此this为watcher
Dep.target=this
// 整个reduce走完,那么就可以取到新值,从vm取值会触发数据劫持的getter=>getter
key.split('.').reduce((newObj,k)=>newObj[k],vm)
Dep.target=null
// obsever实例
// 创建一个订阅者实例
const dep=new Dep()
// get方法
Dep.target&&dep.addSub(Dep.target)
什么时候调用notify呢,在setter中
// 通知订阅者更新自己的文本
dep.notify()
在update的cb中传一个新值
// 让发布者通知你该更新了
update(){
const value=this.key.split('.').reduce((newObj,k)=>newObj[k],this.vm)
this.cb(value)
}
单向数据绑定实现
全部代码
class Vue{
constructor(options){
// this===vm
this.$data=options.data
// 进行调用数据劫持的方法
Observe(this.$data)
// 属性代理
Object.keys(this.$data).forEach(key=>{
Object.defineProperty(this,key,{
enumerable:true,
configurable:true,
get(){
return this.$data[key]
},
set(newValue){
this.$data[key]=newValue
}
})
})
// 调用模板编译的函数
Compile(options.el,this)
}
}
//定义一个数据劫持的方法
// obj为data
function Observe(obj){
// 递归的终止条件
if(!obj||typeof obj!=='object') return
// 创建一个订阅者实例
const dep=new Dep()
//拿到对象的属性值形成一个数组
// console.log(Object.keys(obj));
Object.keys(obj).forEach(key=>{
// 需要为当前的key所对应的属性添加getter/setter
// 当前被循环的key所对应的属性值
let value=obj[key]
// 把value子节点进行递归
Observe(value)
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){
// 只要执行了下边这一行,那么new的watcher实例就被放入了dep.subs数组中了,666
Dep.target&&dep.addSub(Dep.target)
console.log(`有人获取了${key}的值`);
return value
},
set(newValue){
console.log(`有人修改了${key}的值`);
value=newValue
Observe(newValue)
// 通知订阅者更新自己的文本
dep.notify()
}
})
})
}
// 对HTML结构进行模板编译,数据渲染
// el获取区域,vm实例获取数据
// new Vue实例的时候执行
function Compile(el,vm) {
// 获取到的dom元素直接挂载到vm的$el身上
vm.$el=document.querySelector(el)
// 创建文档碎片,提高DOM操作性能
// 文档碎片:开辟内存存放dom节点,在内存中操作
const fragment=document.createDocumentFragment()
// 放入文档碎片
while(childNode=vm.$el.firstChild){
fragment.appendChild(childNode)
}
// 进行模板编译
replace(fragment)
// 拿出来
vm.$el.appendChild(fragment)
// 对dom节点进行编译
function replace(node){
// 正则提取插值表达式{{value}}
const regMustache=/\{\{\s*(\S+)\s*\}\}/
// 证明当前的node节点是文本子节点,进行正则替换
if(node.nodeType===3){
// 文本子节点也是dom,获取值需要调用textContent属性
// console.log(node.textContent)
const text=node.textContent
// 正则获取代码片段到一个数组
const execres=regMustache.exec(text)
// console.log(execres);
if(execres){
// node.textContent=vm[execres[1]]
// 分割对象(split),且获取最后的值(reduce)
const value=execres[1].split('.').reduce((newObj,k)=>newObj[k],vm)
// 你已经学会更新自己了
// 此replace是字符串的替换?
node.textContent=text.replace(regMustache,value)
// 创建watcher的实例
new Watcher(vm,execres[1],(newValue)=>{
node.textContent=text.replace(regMustache,newValue)
})
}
// 终止递归条件
return
}
// 证明不是文本节点,调用递归
node.childNodes.forEach(child=>replace(child))
}
}
// 收集watcher订阅者的类
class Dep{
constructor(){
// 所有的watcher都要存放在数组中
this.subs=[]
}
// 加watcher
addSub(watcher){
this.subs.push(watcher)
}
// 通知watcher
notify(){
// 循环每一个watcher调用更新他们的函数
this.subs.forEach((watcher)=>watcher.update())
}
}
// watcher订阅者的类
class Watcher{
// cb回调函数中记录着watcher如何更新自己的文本
// vm保存着需要更新自己的数据
// key表示着更新文本内容的属性
constructor(vm,key,cb){
this.vm=vm
this.key=key
this.cb=cb
// 负责把创建的watcher实例存到Dep实例的subs数组中,搞不懂
// 此this为watcher
Dep.target=this
// 整个reduce走完,那么就可以取到新值,从vm取值会触发数据劫持的getter=>getter
key.split('.').reduce((newObj,k)=>newObj[k],vm)
Dep.target=null
}
// 让发布者通知你该更新了
update(){
const value=this.key.split('.').reduce((newObj,k)=>newObj[k],this.vm)
this.cb(value)
}
}
文本框实现单向数据绑定
// Compile
// 判断当前的node节点是否为input输入框
if(node.nodeType===1||node.tagName==='INPUT'){
console.log(node);
// 得到元素的所有节点,将为数组变为真数组
const attrs=Array.from(node.attributes)
const findRes=attrs.find(x=>x.name==='v-model')
if(findRes){
// 获取到v-model的值
const expStr=findRes.value
// 取对象的根节点的值
const value=expStr.split('.').reduce((newObj,k)=>newObj[k],vm)
node.value=value
// 创建watcher的实例
new Watcher(vm,expStr,(newValue)=>{
node.value=newValue
})
}
// console.dir(findRes);
}
文本框实现双向数据绑定
监听文本框输入事件
// 监听文本框的输入事件,拿到文本框的值,更新到vm上
node.addEventListener('input',(e)=>{
// console.log(e.target.value);
// 分割data属性数组
const keyArr=expStr.split('.')
// 从零截到倒数第二个
const obj=keyArr.slice(0,keyArr.length-1).reduce((newObj,k)=>newObj[k],vm)
obj[keyArr[keyArr.length-1]]=e.target.value
})
完结,撒花
全部代码
// vue.js
class Vue{
constructor(options){
// this===vm
this.$data=options.data
// 进行调用数据劫持的方法
Observe(this.$data)
// 属性代理
Object.keys(this.$data).forEach(key=>{
Object.defineProperty(this,key,{
enumerable:true,
configurable:true,
get(){
return this.$data[key]
},
set(newValue){
this.$data[key]=newValue
}
})
})
// 调用模板编译的函数
Compile(options.el,this)
}
}
//定义一个数据劫持的方法
// obj为data
function Observe(obj){
// 递归的终止条件
if(!obj||typeof obj!=='object') return
// 创建一个订阅者实例
const dep=new Dep()
//拿到对象的属性值形成一个数组
// console.log(Object.keys(obj));
Object.keys(obj).forEach(key=>{
// 需要为当前的key所对应的属性添加getter/setter
// 当前被循环的key所对应的属性值
let value=obj[key]
// 把value子节点进行递归
Observe(value)
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){
// 只要执行了下边这一行,那么new的watcher实例就被放入了dep.subs数组中了,666
Dep.target&&dep.addSub(Dep.target)
console.log(`有人获取了${key}的值`);
return value
},
set(newValue){
console.log(`有人修改了${key}的值`);
value=newValue
Observe(newValue)
// 通知订阅者更新自己的文本
dep.notify()
}
})
})
}
// 对HTML结构进行模板编译,数据渲染
// el获取区域,vm实例获取数据
// new Vue实例的时候执行
function Compile(el,vm) {
// 获取到的dom元素直接挂载到vm的$el身上
vm.$el=document.querySelector(el)
// 创建文档碎片,提高DOM操作性能
// 文档碎片:开辟内存存放dom节点,在内存中操作
const fragment=document.createDocumentFragment()
// 放入文档碎片
while(childNode=vm.$el.firstChild){
fragment.appendChild(childNode)
}
// 进行模板编译
replace(fragment)
// 拿出来
vm.$el.appendChild(fragment)
// 对dom节点进行编译
function replace(node){
// 正则提取插值表达式{{value}}
const regMustache=/\{\{\s*(\S+)\s*\}\}/
// 证明当前的node节点是文本子节点,进行正则替换
if(node.nodeType===3){
// 文本子节点也是dom,获取值需要调用textContent属性
// console.log(node.textContent)
const text=node.textContent
// 正则获取代码片段到一个数组
const execres=regMustache.exec(text)
// console.log(execres);
if(execres){
// node.textContent=vm[execres[1]]
// 分割对象(split),且获取最后的值(reduce)
const value=execres[1].split('.').reduce((newObj,k)=>newObj[k],vm)
// 你已经学会更新自己了
// 此replace是字符串的替换?
node.textContent=text.replace(regMustache,value)
// 创建watcher的实例
new Watcher(vm,execres[1],(newValue)=>{
node.textContent=text.replace(regMustache,newValue)
})
}
// 终止递归条件
return
}
// 判断当前的node节点是否为input输入框
if(node.nodeType===1||node.tagName==='INPUT'){
// 得到元素的所有节点,将为数组变为真数组
const attrs=Array.from(node.attributes)
// 获取到v-model的属性值
const findRes=attrs.find(x=>x.name==='v-model')
if(findRes){
// 获取到v-model的值
const expStr=findRes.value
// 取对象的根节点的值
const value=expStr.split('.').reduce((newObj,k)=>newObj[k],vm)
node.value=value
// 创建watcher的实例
new Watcher(vm,expStr,(newValue)=>{
node.value=newValue
})
// 监听文本框的输入事件,拿到文本框的值,更新到vm上
node.addEventListener('input',(e)=>{
// console.log(e.target.value);
// 分割data属性数组
const keyArr=expStr.split('.')
// 从零截到倒数第二个
const obj=keyArr.slice(0,keyArr.length-1).reduce((newObj,k)=>newObj[k],vm)
obj[keyArr[keyArr.length-1]]=e.target.value
})
}
// console.dir(findRes);
}
// 证明不是文本节点,调用递归
node.childNodes.forEach(child=>replace(child))
}
}
// 收集watcher订阅者的类
class Dep{
constructor(){
// 所有的watcher都要存放在数组中
this.subs=[]
}
// 加watcher
addSub(watcher){
this.subs.push(watcher)
}
// 通知watcher
notify(){
// 循环每一个watcher调用更新他们的函数
this.subs.forEach((watcher)=>watcher.update())
}
}
// watcher订阅者的类
class Watcher{
// cb回调函数中记录着watcher如何更新自己的文本
// vm保存着需要更新自己的数据
// key表示着更新文本内容的属性
constructor(vm,key,cb){
this.vm=vm
this.key=key
this.cb=cb
// 负责把创建的watcher实例存到Dep实例的subs数组中,搞不懂
// 此this为watcher
Dep.target=this
// 整个reduce走完,那么就可以取到新值,从vm取值会触发数据劫持的getter=>getter
key.split('.').reduce((newObj,k)=>newObj[k],vm)
Dep.target=null
}
// 让发布者通知你该更新了
update(){
const value=this.key.split('.').reduce((newObj,k)=>newObj[k],this.vm)
this.cb(value)
}
}
//1.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h3>姓名是:{{name}}</h3>
<h3>年龄是:{{age}}</h3>
<h3>info.a是:{{info.a}}</h3>
<div>name的值是<input type="text" v-model="name"></div>
<div>info.a的值是<input type="text" v-model="info.a"></div>
</div>
<script src="./vue.js"></script>
<script>
const vm=new Vue({
el:'#app',
data:{
name:'zs',
age:20,
info:{
a:'a1',
c:'c1'
}
}
})
console.log(vm);
</script>
</body>
</html>