首先是对data里面的数据进行代理的理解
<script type="text/javascript">
class Vue{
constructor(options){
this.$el = document.querySelector(options.el)
this.$options = options
// 代理options的data数据
this.getprory()
}
getprory(){
for (const key in this.$options.data) {
// 这里理解,this就是new出来的app
// 下面意思就是,给这个this添加key这个属性,后面就是配置
// Object.defineProperty方法就是访问this里面的key或者添加key这个属性
Object.defineProperty(this,key,{
// 可以不可以被重复设计
configurable:false,
// 可以不可以修改定义的值
enumerable:true,
// 在使用this[key]的时候,就会返回this.$options.data[key]
get(){
return this.$options.data[key]
},
// 在使用this[key] = '' 设置值的时候,就会执行下面的办法,就会把val的值,赋值给对应的data里面的key的属性
set(val){
this.$options.data[key] = val
}
})
}
}
}
</script>
<script>
let options = {
el:'#app',
data:{
msg:'我是大傻逼',
username:'大傻逼'
},
methods: {
change(){
this.msg = 'aa'
}
},
}
let app = new Vue(options)
// 上面代码就是实现如:app.msg , 就能拿到msg的值,只要是使用代理的方式
// 主要实现再这里
// 在使用this[key]的时候,就会返回this.$options.data[key]
// get(){
// return this.$options.data[key]
// },
数据劫持
指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。比较典型的是 Object.defineProperty() 和 ES2015 中新增的 Proxy 对象。
以下代码主要说明,在数据进行更新的时候,如何使得绑定数据的地方也进行更新(也算订阅者发布模式)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-model="msg" name="" id="" value="" />
<h1>{{msg}}</h1>
<h1 v-html="msg"></h1>
<button @click="change()">修改</button>
</div>
<script type="text/javascript">
class Vue{
constructor(options){
this.$el = document.querySelector(options.el)
this.$options = options
this.$watchEvent = {}
this.getprory()
this.observe()
this.compile()
}
getprory(){
for (const key in this.$options.data) {
let that = this
Object.defineProperty(this,key,{
// 可以不可以被重复设计
configurable:false,
// 可以不可以修改定义的值
enumerable:true,
get(){
return this.$options.data[key]
},
set(val){
this.$options.data[key] = val
}
})
}
}
// 劫持事件 第一步
observe(){
for (const key in this.$options.data) {
let that = this
let value = this.$options.data[key]
// 在访问this.$options.data里面的key属性的时候,就会来到数据劫持
// 然后对数据进行处理然后再返回
Object.defineProperty(this.$options.data,key,{
// 可以不可以被重复设计
configurable:false,
// 可以不可以修改定义的值
enumerable:true,
// 直接返回
get(){
return value
},
// 对数据进行处理再返回
set(val){
value = val
// 再进行数据处理的时候,执行方法,来使得绑定这个属性的地方也进行更新
if (that.$watchEvent[key]) {
// 通过key把这个属性绑定到 that.$watchEvent里面
that.$watchEvent[key].forEach((item,index)=>{
// 这里的这个item,就是在that.$watchEvent里面key绑定的值,其实就是一个watch(类,在第三步)
// 执行watch里面的方法来更新其他使用key属性的值
item.update()
})
}
}
})
}
}
// 劫持事件第三步
// 第三步就是为了把key和watch绑定在一起
compile(){
// 使用一个[ ] 括号,可以打印出这个this.$el的属性
// console.log([this.$el]);
// 一开始把div#app 的所有节点循环出来
this.$el.childNodes.forEach((node,index)=>{
// 拿到元素节点
if (node.nodeType == 1) {
// 拿到绑定msg(也就是key的值)
if (node.hasAttribute('v-html')) {
// 获取的这个属性的值 ,也就是msg(key)
let vmkey = node.getAttribute('v-html').trim()
// 然后赋值给他本来的节点(这里是为了让数据展示出来)
node.innerHTML = this[vmkey]
// new出一个绑定key的watch
let watcher = new Watch(this,vmkey,node,'innerHTML')
if (this.$watchEvent[vmkey]) {
this.$watchEvent[vmkey].push(watcher)
}else{
this.$watchEvent[vmkey]=[]
this.$watchEvent[vmkey].push(watcher)
}
}
}
// 文本节点
if (node.nodeType == 3) {
}
})
}
}
// 劫持事件 第二步
// 为了拿到dom里面的节点
class Watch{
constructor(vm,key,node,attr,nType){
// 比如上面最外面的div,并且是vue对象
this.vm = vm
// 这个就是绑定的data里面的属性,以作为key值放到 that.$watchEvent 如msg
this.key =key
// div里面的节点 // 如h1
this.node = node
// 绑定上面key属性的标签属性属性 如v-html
this.attr = attr
}
update(){
console.log('进来'+this.vm);
// 当数据改变的时候去进行更新有这个key属性的地方
this.node[this.attr] = this.vm[this.key]
// 如 h1.innnerTTML = vue.msg
}
}
</script>
<script>
let options = {
el:'#app',
data:{
msg:'我是大傻逼',
username:'大傻逼'
},
methods: {
change(){
this.msg = 'aa'
}
},
}
let app = new Vue(options)
</script>
</body>
</html>
数据劫持实现的v-model
// 劫持事件第三步
// 第三步就是为了把key和watch绑定在一起
compile(cNode){
// 使用一个[ ] 括号,可以打印出这个this.$el的属性
// console.log([this.$el]);
// 一开始把div#app 的所有节点循环出来
cNode.childNodes.forEach((node,index)=>{
// 拿到元素节点
if (node.nodeType == 1) {
// 拿到绑定msg(也就是key的值)
if (node.hasAttribute('v-html')) {
// 获取的这个属性的值 ,也就是msg(key)
let vmkey = node.getAttribute('v-html').trim()
// 然后赋值给他本来的节点(这里是为了让数据展示出来)
node.innerHTML = this[vmkey]
// new出一个绑定key的watch
let watcher = new Watch(this,vmkey,node,'innerHTML')
if (this.$watchEvent[vmkey]) {
this.$watchEvent[vmkey].push(watcher)
}else{
this.$watchEvent[vmkey]=[]
this.$watchEvent[vmkey].push(watcher)
}
// 删除原来绑定的{{}}语句
node.removeAttribute('v-html')
}
// 这里就是实现v-model
// 下面是对于v-model的数据绑定
// 先判断有没有v-model属性
if (node.hasAttribute('v-model')) {;
// 获取绑定的值(msg)
let vmkey = node.getAttribute('v-model').trim()
// 看有没有这个msg
if (this.hasOwnProperty(vmkey)) {
node.value = this[vmkey]
// 然后把他放进watch事件对象中,绑定起来
let watcher = new Watch(this,vmkey,node,'value')
if (this.$watchEvent[vmkey]) {
this.$watchEvent[vmkey].push(watcher)
}else{
this.$watchEvent[vmkey]=[]
this.$watchEvent[vmkey].push(watcher)
}
}
// 绑定输入框input输入事件
// 实现改变input框的值来改变所有msg的值
// 实现input的双向绑定
node.addEventListener('input',(event)=>{
this[vmkey] = node.value
})
node.removeAttribute('v-model')
}
// 实现点击事件修改
if (node.hasAttribute('@click')) {
let vmkey = node.getAttribute('@click').trim();
// 绑定click点击事件
node.addEventListener('click',(event)=>{
// 绑定当前methods里面的方法,然后执行
this.eventFn = this.$options.methods[vmkey].bind(this)
this.eventFn()
})
}
// 检测当前节点有没有子节点,有的话继续执行
if (node.childNodes.length>0) {
this.compile(node)
}
}
// 文本节点
if (node.nodeType == 3) {
}
})
}
}
匹配{{}}语法
// 文本节点
if (node.nodeType == 3) {
// 文本节点的内容放在属性textContent里面
// 匹配{{}}
let reg = /\{\{(.*?)\}\}/g;
// 拿到{{msg}}
let text = node.textContent;
node.textContent= text.replace(reg,(match,vmkey)=>{
console.log(match); // {{msg}} 匹配到的内容
console.log(vmkey); // msg .*?匹配到的内容
vmkey = vmkey.trim()
if (this.hasOwnProperty(vmkey)) {
let watcher = new Watch(this,vmkey,node,'textContent')
if (this.$watchEvent[vmkey]) {
this.$watchEvent[vmkey].push(watcher)
}else{
this.$watchEvent[vmkey]=[]
this.$watchEvent[vmkey].push(watcher)
}
}
return this[vmkey]
})
}
生命周期函数的设置
<script type="text/javascript">
class Vue{
constructor(options){
this.$el = document.querySelector(options.el)
// 生命周期函数 beforeCreated
if (typeof options.beforeCreated == 'function') {
options.beforeCreated.bind(this)()
}
this.$options = options
// 生命周期函数 create
if (typeof options.created == 'function') {
options.created.bind(this)()
}
this.$watchEvent = {}
this.getprory()
this.observe()
// 生命周期函数 beforeMountate
if (typeof options.beforeMount == 'function') {
options.beforeMount.bind(this)()
}
this.compile(this.$el)
// 生命周期函数 mounted
if (typeof options.mounted == 'function') {
options.mounted.bind(this)()
}
}
class Watch{
constructor(vm,key,node,attr,nType){
this.vm = vm
this.key =key
// div里面的节点 // 如h1
this.node = node
// 绑定上面key属性的标签属性属性 如v-html
this.attr = attr
}
update(){
// 生命周期函数 beforeUpdata
if (typeof options.beforeUpdata == 'function') {
options.beforeUpdata.bind(this)()
}
// 当数据改变的时候去进行更新有这个key属性的地方
this.node[this.attr] = this.vm[this.key]
// 如 h1.innnerTTML = vue.msg
// 生命周期函数 Updata
if (typeof options.Updata == 'function') {
options.Updata.bind(this)()
}
}
}
</script>
<script>
let options = {
el:'#app',
data:{
msg:'我是大傻逼',
username:'大傻逼'
},
methods: {
change(){
this.msg = 'aa'
}
},
beforeCreated(){
console.log('我还没创建');
},
created(){
console.log('我创建');
},
beforeMount(){
console.log('我还没挂载');
},
mounted(){
console.log('我挂载');
},
beforeUpdata(){
console.log('我还没更新');
},
Updata(){
console.log('我更新');
},
}
let app = new Vue(options)
</script>