let CompileUtil = {getValue(vm, value){// 切割 time.h ==> time hreturn value.split('.').reduce((data, currentKey) => {// 第一次执行:data = $data, currentKey = time// 第二次执行:data = time, currentKey = h// 使用trim()的原因:插值{{}}内的内容 无空格{{name}} 如果有空格{{  name  }},有空格的时候会把空格包含在内容,就获取不到对应的数据return data[currentKey.trim()] }, vm.$data)},getContent(vm, value){let reg = /\{\{(.+?)\}\}/gilet val = value.replace(reg, (...args)=>{console.log('args', args);return this.getValue(vm, args[1])})// console.log('{{val}}', val);return val},setValue(vm, attr, newValue){attr.split('.').reduce((data, currentAttr,index, arr)=>{if(index === arr.length - 1){data[currentAttr] = newValue}return data[currentAttr]},vm.$data)},model: function(node, value, vm){// 第二步:在第一步渲染的时候,就给所有的属性添加观察者new Watcher(vm, value, (newValue, oldValue)=>{// 更新数据node.value = newValue})// v-model = "time.h" vm.$data[time.h]无法识别,vm.$data[time] time[h]let val = this.getValue(vm, value)node.value = val// 上面是通过数据修改界面// 界面驱动数据更新/**** v-model*      input 输入事件*/node.addEventListener('input', (e)=>{let newValue = e.target.valuethis.setValue(vm, value, newValue)})},html: function(node, value, vm){new Watcher(vm, value, (newValue, oldValue)=>{// 更新数据node.innerHTML = newValue})let val = this.getValue(vm, value)node.innerHTML = val},text: function(node, value, vm){new Watcher(vm, value, (newValue, oldValue)=>{// 更新数据node.innerText = newValue})let val = this.getValue(vm, value)node.innerText = val},content: function(node, value, vm){// value {{name}} --- name  --- $data[name]// let val = this.getContent(vm, value)let reg = /\{\{(.+?)\}\}/gilet val = value.replace(reg, (...args)=>{ // 这一层使用replace是因为value获取到的时候{{xxx}},需要将xxx去进行监听// 内层是为了保证数据完整性new Watcher(vm, args[1], (newValue, oldValue)=>{// console.log('{{{{{{', value);// 更新数据node.textContent = this.getContent(vm, value) // 如果监听到数据发生变化,回调函数执行,使用getContent的原因是:当有多个{{}},改变其中一个{{}}直接赋值将会覆盖其他的{{}}})// 上面是watcher进行监听所有的属性return this.getValue(vm, args[1])})node.textContent = val},on: function(node, value, vm, type){node.addEventListener(type, (e)=>{vm.$methods[value].call(vm, e) // 通过call改变this})}
}
class Sue {constructor(options){// 1.保存创建时候传递的数据if(this.isElement(options.el)){this.$el = options.el;}else{this.$el = document.querySelector(options.el)}this.$data = options.datathis.proxyData()this.$methods = options.methodsthis.$computed = options.computed/*** 将computed中的方法添加到$data中,* 只有这样将来在渲染的时候才能从$data中获取到computed中定义的计算属性*/this.computed2data()// 2.根据指定的区域和数据去编译渲染界面if(this.$el){ // this.$el 存在才去编译渲染// 第一步:给外界传入的所有数据都添加get/set方法,这样就可以监听数据的变化了new Observer(this.$data)new Compiler(this)}}computed2data(){for(let key in this.$computed){Object.defineProperty(this.$data, key, {get:()=>{return this.$computed[key].call(this)}})}}/*** 数据代理:数据拦截*     this.xxx 数据保存在vue实例上才可以获取*     根据 Object.defineProperty在vue实例上增加属性*/proxyData(){for(let key in this.$data){Object.defineProperty(this, key, {get:()=>{return this.$data[key]}})}}// 判断是否是一个元素isElement(node){return node.nodeType === 1;}
}
class Compiler {constructor(vm){this.vm = vm;// 1.将网页上的元素放到内存中let fragment = this.node2fragment(this.vm.$el);// 2.利用指定的数据编译内存中的元素this.buildTemplate(fragment)// 3.将编译好的内容重新渲染到网页上this.vm.$el.appendChild(fragment)   }node2fragment(app){// 1.创建一个空的文档碎片对象let fragment = document.createDocumentFragment();// 2.编译循环获取每一个元素let node = app.firstChild;while(node){// 注意点: 只要将元素添加到了文档碎片对象中, 那么这个元素就会自动从网页上消失fragment.appendChild(node)node = app.firstChild}// 3.返回储存了所有元素的文档碎片对象return fragment}buildTemplate(fragment){// console.log('childNodes', fragment.childNodes); // 伪数组let nodeList = [...fragment.childNodes] // 伪数组转为数组nodeList.forEach(node=>{// 需要判断当前遍历到的节点是一个元素还是一个文本// 如果是一个元素, 我们需要判断有没有v-model属性// 如果是一个文本, 我们需要判断有没有{{}}的内容if(this.vm.isElement(node)){// 是一个元素this.buildElement(node)// 处理子元素(处理后代)this.buildTemplate(node)}else{// 不是一个元素this.buildText(node)}})}// 专门处理元素的方法buildElement(node){let attrs = [...node.attributes] // attributes:节点的属性集合,node.attributes 伪数组 // console.log('attrs', attrs);attrs.forEach(attr => {/*** v-model='xxx'  name=v-model  value = xxx* v-on:click='myFn' name=v-on:click  value = myFn*/let {name,value} = attr;// console.log(name, value);// v-开头是vue的指令if(name.startsWith('v-')){// let [, directive] = name.split('-')// console.log('Vue指令');let [directiveName, directiveType] = name.split(':') // ['v-on','click']let [, directive] = directiveName.split('-')CompileUtil[directive](node, value, this.vm, directiveType)}})}// 专门用来处理文本的buildText(node){let content = node.textContent; // textContent 属性表示一个节点及其后代的文本内容// 匹配插值{{ }}let reg = /\{\{.+?\}\}/giif(reg.test(content)){// console.log('是{{}}的文本', content);CompileUtil['content'](node, content, this.vm)}}
}
// 监听数据变化
class Observer {constructor(data){this.observer(data)}observer(obj){if(obj && typeof obj === 'object'){for(let key in obj){this.defineReactive(obj, key, obj[key])}}}defineReactive(obj, attr, value){this.observer(value)// 第三步:将当前属性的所有观察者对象都放到当前属性的发布订阅对象中管理起来let dep = new Dep(); // 创建了属于当前属性的发布订阅对象Object.defineProperty(obj, attr, {get(){Dep.target && dep.addSub(Dep.target)// console.log('Dep.target', Dep.target);return value},set:(newValue)=>{if(value !== newValue){console.log('监听到数据变化newValue', newValue);// 如果给属性赋值的新值又是一个对象, 那么也需要给这个对象的所有属性添加get/set方法this.observer(newValue)value = newValuedep.notify()}}})}
}
// 数据变化之后更新UI界面,可以使用发布订阅模式来实现
// 先定义一个观察者类,再定义一个发布订阅类,然后再通过发布订阅的类来管理观察者类
// 发布订阅类
class Dep {constructor() {// 这个数组就是专门用于管理某个属性所有的观察者对象的this.subs = []}// 订阅观察的方法(把观察者添加到数组中)addSub(watcher){this.subs.push(watcher)}// 发布订阅的方法(执行数组中所有观察者的更新方法)notify() {this.subs.forEach(watcher => watcher.update())}
}
// 观察者类
class Watcher {/**** vm vue实例* attr 观察的属性* cb 回调函数*/constructor(vm, attr, cb){this.vm = vmthis.attr = attrthis.cb = cb// 在创建观察者对象的时候就去获取当前的旧值this.oldValue = this.getOldValue()}getOldValue() {Dep.target = this;let oldValue = CompileUtil.getValue(this.vm, this.attr)Dep.target = null;return oldValue}// 定义一个更新的方法,用于判断新值和旧值是否相同update(){let newValue = CompileUtil.getValue(this.vm, this.attr)if(this.oldValue !== newValue){this.cb(newValue, this.oldValue)}}
}
<!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><script src="./js/Sue.js"></script>
</head><body><div id="app"><input type="text" v-model="name"><input type="text" v-model="time.h"><input type="text" v-model="time.m"><input type="text" v-model="name"><div v-html="html"></div><div v-text="text"></div><p>{{ time.h }} --- {{time.m}}</p><div v-model="name" class="ss">123</div><a href="">123</a><div v-on:click="myFn">点击</div><div>{{getName}}</div></div><script>/*1.要想使用Vue必须先创建Vue的实例, 创建Vue的实例通过new来创建, 所以说明Vue是一个类所以我们要想使用自己的Vue,就必须定义一个名称叫做Vue的类2.只要创建好了Vue的实例, Vue就会根据指定的区域和数据, 去编译渲染这个区域所以我们需要在自己编写的Vue实例中拿到数据和控制区域, 去编译渲染这个区域注意点: 创建Vue实例的时候指定的控制区域可以是一个ID名称, 也可以是一个Dom元素注意点: Vue实例会将传递的控制区域和数据都绑定到创建出来的实例对象上$el/$data* */let vue = new Sue({el: "#app",data: {name: 'sss',time: {h: '小时',m: "秒",},age: 18,html: `<div>我是标签div</div>`,text: `<span>我是标签span</span>`,},methods: {myFn() {alert('myFn被执行了')console.log('this', this);console.log('this.xxx', this.time.h);/*** this.xxx 数据保存在vue实例上才可以获取*/}},computed: {getName() {return this.name + '666';}}})</script>
</body></html>

学习记录❥(^_-)

手写vue---部分实现相关推荐

  1. vue filter对象_学习vue源码(3) 手写Vue.directive、Vue.filter、Vue.component方法

    一.Vue.directive Vue.directive(id,[definition]); 1)参数 { string } id{ Function | Object } [ definition ...

  2. Vue 源码之手写Vue Router

    Vue 源码之手写Vue Router 源码地址:https://github.com/CONOR007/Handwritten-routing 一.Vue Router的两种模式 hash模式实现原 ...

  3. vue 多点触控手势_手写 Vue 手势组件__Vue.js

    前言 最近需要使用手指捏合扩大的手势操作,找了几个组件,要么对 Vue 适配不好,要么量级太大,决定自己手写手势操作. 项目与效果预览 思路 直接在 DOM 上绑定 touchstart .touch ...

  4. (精华2020年5月17日更新) vue实战篇 手写vue底层源码

    MYvue.js 主要作用监听属性变化 class MYvue {constructor(options) {this.$options = options;this.$data = options. ...

  5. html手写vue多级选择框,vue + html 编写仿element select 多选组件

    现在做vue项目主要用的ui框架差不多都是elementui,但是每个项目设计的不同难免和element组件产生差异甚至是大不相同,有的时候差异少比如页面样式不太相同,功能使用完全一样的话,这样改改样 ...

  6. 手写Vue 的双向数据绑定

    在目前的前端面试中,vue的双向数据绑定已经成为了一个非常容易考到的点,即使不能当场写出来,至少也要能说出原理. 本篇文章中我将会仿照vue写一个双向数据绑定的实例,名字就叫myVue吧.结合注释,希 ...

  7. 手写vue的v-if和v-show

    文件分为html文件和js文件下面贴出代码块: html文件: <!DOCTYPE html> <html lang="en">Ï <head> ...

  8. html手写vue多级选择框,vue多级多选菜单组件开发

    本文实例为大家分享了vue多级多选菜单组件的制作方法,供大家参考,具体内容如下 要开发一个这样的多级多选菜单组件,功能是: 点击父标题栏可以打开与折叠子列表 点击父标题栏的勾选图标可以全选或取消子列表 ...

  9. 从零开始手写vue项目的webpack基础配置

    一.创建目录结构 执行yarn init, 生成package.json文件; 1.写入文件 建立目录结构可参考vue项目目录结构: 首先建立一个src文件夹,其中包含index.html,App.v ...

  10. Vue源码分析-手写Vue(简易版)

    1.Vue双向绑定/MVVM响应式原理/v-model的原理 vue.js通过数据劫持结合发布订阅者模式,通过Object.defineProperty来劫持各个属性的setter,getter,在数 ...

最新文章

  1. python中string.digits_python学习笔记五:字符串方法
  2. python2 x与python3 x_python2.x 与 python3.x的不同
  3. 使用Python控制1602液晶屏实时显示时间(附PyCharm远程调试)
  4. maven报错: 错误的类文件:… 类文件具有错误的版本 52.0,应为 54.0
  5. 38行代码AC——L1-025 正整数A+B (15分)(~解题报告~)
  6. vue1.0和vue2.0生命周期----整理一
  7. Extjs不错的博客
  8. [Hnoi2013]消毒
  9. 基于JAVA+SpringBoot+Mybatis+MYSQL的高铁售票系统
  10. python将十进制转为二进制_如何用Python将十进制数字转为二进制,以及将二进制转为十六进制?...
  11. mysql wait_timeout=_Mysql的wait_timeout解决_MySQL
  12. s5p4418安卓系统适配fpc8563芯片遇到的问题及解决
  13. 性能工具之Jmeter压测Hprose RPC服务
  14. 武汉理工大学华夏学院计算机类,2015年武汉理工大学华夏学院招生专业代码
  15. Xshell6、Xftp6要继续使用此程序,您必须应用最新的更新或使用新版本
  16. 北京大学人工智能研究院落户武汉,致力于打造一流人工智能研发机构
  17. 打破边界,边缘计算有何应用场景?
  18. 一个屌丝程序员的青春(三四六)
  19. adas记录仪app_路影行车记录仪app
  20. Spring和SpringMVC架构

热门文章

  1. 外边距合并(HTML、CSS)
  2. 一个成型的awt所必须的frame组件
  3. Javascript 清空input type=file 的值方法
  4. Media Session API 为当前正在播放的视频,音频,提供元数据来自定义媒体通知
  5. 微信小程序 自定义组件(stepper)
  6. Java大数据-Week2-Day4-IDEA安装
  7. zabbix------监控小技巧
  8. Cocos2d-x编程中的runOnUiThread方法和runOnGLThread方法剖析
  9. 【算法导论学习-29】动态规划经典问题02:最长公共子序列问题(Longest common subsequence,LCS)...
  10. 上计算机课玩游戏检讨400字,上网课玩游戏检讨书