2021年7月27日星期二

手写一个超简单的Vue

基本结构

这里我根据自己的理解模仿了Vue的单文件写法,通过给Vue.createApp传入参数再挂载元素来实现页面与数据的互动。

其中理解不免有错,希望大佬轻喷。

收集数据

这里将Vue.createApp()里的参数叫做options

data可以是一个对象或者函数,在是函数的时候必须ruturn出一个对象,该对象里的数据会被vm直接调用。

可以直接先获取options,然后将里面的data函数执行一次再把结果挂载到实例上,methods等对象也可以直接挂载:(这里忽略了data是对象的情况,只按照是函数来处理)

class Vue{ constructor() {  this.datas = Object.create(null); } static createApp(options){  const vm = new Vue();  vm.datas = options.data?.call(vm);  for (const key in options.methouds) {   vm.methouds[key] = options.methouds[key].bind(vm);  }  return vm; }}

当然这样只是会获得一个Vue实例,上面有输入的数据,这些数据还不会与页面发生互动。

Vue 的响应式数据

Vue的数据双向绑定是通过代理注入来实现的,在vue2中使用Object.defineProperty而到了vue3使用的是ProxyAPI。虽然用的方法不同,但核心思想是一样的:截获数据的改变,然后进行页面更新。

这样就可以试着写出获得代理数据的方法:

class Vue{ constructor() {}  static createApp(options){  const vm = new Vue();  const data = options.data?.call(vm);  for (const key in data) {   vm.datas[key] = vm.ref(data[key]);  }  return vm; }  reactive(data) {  const vm = this; //! 固定VUE实例,不然下面的notify无法使用  return new Proxy(data, {   //todo 修改对象属性后修改Vnode   set(target, p, value) {    target._isref     ? Reflect.set(target, "value", value)     : Reflect.set(target, p, value);        //todo 在这里通知,然后修改页面    dep.notify(vm);        return true;   },  }); } ref(data) {  //? 基本数据类型会被包装为对象再进行代理  if (typeof data != "object") {   data = {    value: data,    _isref: true,    toSting() {     return this.value;    },   };  }  return this.reactive(data); }}

现在如果data中设置的数据发生了改变,那么就会调用dep.notify来改变页面内容。

vm代理datas等数据

因为再模板里是不会写this.datas.xxx来调用数据的,这里也可以使用代理来把datas中的数据放到vm上:

class Vue { constructor() {  //! 因为vm代理了datas 以后在vm上添加新属性会被移动到datas中,所以如果是实例上的属性要像el一样占位  this.el = "document";  this.mountHTML = "mountHTML";  this.datas = Object.create(null);  this.methouds = Object.create(null); } static createApp(options) {  //? 将data代理到vm上  const vm = new Proxy(new Vue(), {   get(target, p) {    if (Reflect.get(target, p)) {     return Reflect.get(target, p);    } else {     return target.datas[p]._isref ? target.datas[p].value : target.datas[p];    }   },   set(target, p, value) {    if (target[p]) {     Reflect.set(target, p, value);    } else if (target.datas[p]?._isref) {     Reflect.set(target.datas[p], "value", value);    } else {     Reflect.set(target.datas, p, value);    }    return true;   },  });  //? onBeforeCreate  options.onBeforCreate?.call(vm);  const data = options.data?.call(vm);  for (const key in data) {   vm.datas[key] = vm.ref(data[key]);  }  for (const key in options.methouds) {   vm.methouds[key] = options.methouds[key].bind(vm);  }  //? onCreated  options.onCreated?.call(vm);  return vm; }}

这样通过createApp获得的Vue实例直接访问并修改收集到的datas里的数据。

挂载

通过Vue.createApp可以获得一个Vue实例,这样只需要调用实例中的mount方法就可以进行挂载了,在挂载后就马上进行数据的渲染。

vm.mount接收一个参数,可以是css选择器的字符串,也可以直接是html节点:

class Vue{ constructor() {} mount(el) {  //todo 初始化  this.init(el);  //todo 渲染数据  render(this);  return this; } init(el) {  this.el = this.getEl(el);  this.mountHTML = this.el.innerHTML; //? 获得挂载时元素的模板 } getEl(el) {  if (!(el instanceof Element)) {   try {    return document.querySelector(el);   } catch {    throw "没有选中挂载元素";   }  } else return el; }}

渲染页面

Vue渲染页面使用了VNode来记录并按照它进行页面的渲染,在每次更新数据时获得数据更新的地方并通过diff算法来比较旧VNode和更新数据后VNode的不同来对页面进行渲染。

这里不做太复杂处理,直接把挂载节点的innerHTML作为模板,通过正则进行捕获并修改,然后渲染到页面上,同时如果有通过@ 或 v-on绑定的事件,则按照情况进行处理:

  • 如果是原生的事件,则直接添加进去;
  • 如果是非原生的事件,则通过on来记录,以后用emit来进行触发。
export default function render(vm) { const regexp =  /(?<tag>(?<=<)[^\/]+?(?=(>|\s)))|\{\{(\s*)(?<data>.+?)(\s*)\}\}|(?<text>(?<=>)\S+?(?=<))|(?<eName>(?<=@|(v-on:))\S+?)(=")(?<event>\S+?(?="))/g; const fragment = document.createDocumentFragment(); let ele = {}; //? 每次匹配到tag就把获得的信息转成标签 for (const result of vm.mountHTML.matchAll(regexp)) {  if (result.groups.tag && ele.tag) {   fragment.appendChild(createEle(vm, ele));   ele = {};  }  Object.assign(ele, JSON.parse(JSON.stringify(result.groups))); } fragment.appendChild(createEle(vm, ele)); //? 最后这里再执行一次把最后的一个元素也渲染 ele = null; //? 清空原来的DOM vm.el.innerHTML = ""; vm.el.appendChild(fragment);}//? 放入原生事件,用字典储存,这里只记录了clickconst OrangeEvents = { click: Symbol() };/** * 根据解析的数据创建放入文档碎片的元素 */function createEle(vm, options) { const { tag, text, data, eName, event } = options; if (tag) {  const ele = document.createElement(tag);  if (data) {   ele.innerText = getByPath(vm, data);  }  if (text) {   ele.innerText = text;  }  if (event) {   //todo 先判断是不是原生事件,是就直接绑定,不然用eventBinder来注册   if (OrangeEvents[eName]) {    ele.addEventListener(eName, vm.methouds[event]);   } else {    eventBinder.off(eName); //? 因为这里render的实现是重新全部渲染,所以要清空对应的事件缓存    eventBinder.on(eName, vm.methouds[event].bind(vm));   }  }  return ele; }}/** * 通过字符串来访问对象中的属性 */function getByPath(......

原文转载:http://www.shaoqun.com/a/900515.html

跨境电商:https://www.ikjzd.com/

巴士物流:https://www.ikjzd.com/w/2329

patpat:https://www.ikjzd.com/w/1079.html

easybuy:https://www.ikjzd.com/w/2162


基本结构这里我根据自己的理解模仿了Vue的单文件写法,通过给Vue.createApp传入参数再挂载元素来实现页面与数据的互动。其中理解不免有错,希望大佬轻喷。收集数据这里将Vue.createApp()里的参数叫做optionsdata可以是一个对象或者函数,在是函数的时候必须ruturn出一个对象,该对象里的数据会被vm直接调用。可以直接先获取options,然后将里面的data函数执行一次再
ola:https://www.ikjzd.com/w/2103
派代:https://www.ikjzd.com/w/2197
游生态园 古村 购美玉山货 吃潮汕小吃 揭阳神跨越自驾路线:http://www.30bags.com/a/222318.html
游石柱"林海雪原"吃美味刨猪汤:http://www.30bags.com/a/416565.html
游四川美景感受美丽新四川 :http://www.30bags.com/a/406891.html
游泰山必看的四大奇觀 - :http://www.30bags.com/a/408716.html
口述被老男人亲下面的感觉 我被老男人的床技征服:http://lady.shaoqun.com/a/248080.html
男人太粗太长弄死了我了 我受不了了好想被狂躁:http://lady.shaoqun.com/m/a/248392.html
女人要明白,男人越爱你,睡觉时这六个"小把戏"就越频繁!:http://lady.shaoqun.com/a/436503.html
HPV阳性=宫颈癌?女性必须了解这些知识:http://lady.shaoqun.com/a/436504.html
一旦一个男孩对你"有感觉",这些反应很可能就会发生:http://lady.shaoqun.com/a/437488.html
孕期可以合住一个房间吗?怀孕期间同房的五个好处中,最后一个好处被大多数人忽略了:http://lady.shaoqun.com/a/437489.html

没有评论:

发表评论