资讯专栏INFORMATION COLUMN

使用ES6新特性实现简单的MVVM(1)--数据驱动

ispring / 1443人阅读

摘要:尝试使用新特性,自己来实现一个及的各种特性。我们可以利用这个特性来实现对数据的监听结果简单的操作我们已经可以对简单的数据操作进行监听虽然还有各种问题,接下来,我们只要在监听到后进行数据操作即可。

尝试使用es6新特性,自己来实现一个mvvm及vue的各种特性。
相关代码放在github,会持续更新,欢迎赏个star。
本篇文章为系列文章的第一篇,会比较容易理解,后续会持续更新后面的记录。
文章首发于本人博客

最简单的watcher

从开始接触Vue开始,我们便对它的“数据响应”赞叹不绝,那么我们首先,来实现一个最简单的watcher,来监听数据,以进行对应的操作,类似后续会涉及的dom操作等。

Proxy

我们都知道,Vue使用Object.defineProperty来进行数据监听,监听obj的get和set方法。在ES6中,Proxy可以拦截某些操作的默认行为,也就是对目标对象的访问进行拦截,过滤和改写。我们可以利用这个特性来实现对数据的监听:

const watcher = (obj, fn) => {
  return new Proxy(obj, {
    get (target, prop, receiver) {
      return Reflect.get(target, prop, receiver)
    },

    set (target, prop, value) {
      const oldValue = Reflect.get(target, prop, receiver)
      const result = Reflect.set(target, prop, value)

      fn(value, oldValue)

      return result
    }
  })
}

结果:

let obj = watcher({ a: 1 }, (val, oldVal) => {
  console.log("old =>> ", oldVal)
  console.log("new =>> ", val)
})

obj.a = 2
// old =>> , 1
// new =>> , 2
简单的Dom操作

我们已经可以对简单的数据操作进行监听(虽然还有各种问题),接下来,我们只要在监听到dom后进行数据操作即可。解析模板什么的我们就先不做了,我们可以继续利用Proxy实现一个dom辅助函数:

const dom = new Proxy({}, {
  get (target, tagName) {
    return (attrs = {}, ...childrens) => {
      // 创建节点
      const elem = document.createElement(tagName)

      // 添加attribute
      attrs.forEach(attr => elem.addAttribute(attr, attrs[attr])

      // 添加子元素
      childrens.forEach(child => {
        const child = typeof child === "string"
          ? document.createTextNode(child) 
          : child

        elem.appendChild(child)
      })

      return elem
    }
  }
})

也就是说,我们为dom的各属性进行监听,当访问对应的节点时,我们创建并且为他添加各种属性等:

dom.div(
  {class: "wrap"}, 
  "helloworld",
  dom.a({
    href: "https://www.360.cn"
  }, "welcome to 360")
)

// 输出
helloworld welcome to 360
拼接基础框架

我们在这里给我们的这个小架子起名为"W",让它可以真正的运行起来。类似Vue的语法,我们需要在进行实例化的时候,watch我们的data,并且更新dom。类似这样:

const vm = new W({
  el: "body",
  data () {
    return {
      msg: "hello world"
    }
  },
  render () {
    return dom.div({
      class: "wrap"
    },
      dom.a({
        href: "http://www.360.cn"
      }, this.msg)
    )
  }
})

因此,我们需要实现这样一个类,来处理我们的参数,并进行实例的初始化,监听,以及渲染控制等。

export default class W {
  constructor (config) {}

  /**
   * observe data
   */
  _initData () {}

  /**
   * 渲染节点
   */
  _renderDom () {}
}
初始化数据

首先,我们进行数据初始化,将数据置为observable,在对其修改的时候进行监听:

import watcher from "./data.js"

class W {
  constructor (config) {
    const { data = () => {} } = config

    this._config = config
    this._initData(data)

    return this._vm
  }
}

_initData (data) {
  this._vm = watcher(Object.assign({}, this, data()), this._renderDom.bind(this))
}

在这里我们需要注意两点:

我们的data参数为一个function
这个原因在vue官方文档已经说过,当我们直接使用对象的时候,不同的实例间会共享同一个对象,导致出现对一个组件进行修改,另一个组件也进行修改的问题。具体可以查看data-必须是函数

我们返回的是this._vm而不是this
我们这里做了两步操作,首先将this与data进行合并,再将整个对象进行监听,并赋值到_vm属性上。

这样,我们通过new W()初始化的实例,则可以访问到我们的data属性及方法,并且具有数据驱动的特性了。

更新DOM

我们已经为watcher的回调添加了dom更新的事件,我们只要在这里执行render函数,并挂载到对应的el上即可:

const { render, el } = this._config
const targetEl = document.querySelector(el)
const renderDom = render()

targetEl.innerHTML = ""
targetEl.appendChild(renderDom)
绑定this

我们会发现,我们在config的render函数中,使用了this.msg来访问data的msg属性,因此我们需要实现在各组件中,通过this可以访问到本实例的特性。我猜你已经想到了,我们可以使用bind,call和apply来实现它:

/**
 * 为所有的函数绑定this
 */
bindVM () {
  const { _config } = this

  for(let key of Object.keys(_config)) {
    const val = _config[key]
    if (typeof(val) === "function") {
      _config[key] = val.bind(this._vm)
    }
  }
}
测试

简单的架子拼接完成,我们来进行测试下我们的成果,我们需要实现两点功能:

可以按照我们的render函数正常挂载,并可访问到data上的数据

通过对实例进行修改,修改会自动更新到节点上

代码:

const vm = new W({
  el: "body",
  data () {
    return {
      msg: "hello world"
    }
  },
  render () {
    return dom.div({
      class: "wrap"
    },
      dom.a({
        href: "http://www.360.cn"
      }, this.msg)
    )
  }
})

// 测试修改vm
setInterval(_ => {
  vm.msg = "hello world =>>>" + new Date()
}, 1000)

结果:

最基本的功能已经实现啦!

结语

本次我们只实现了最最最简单的数据驱动功能,后续还有很多需要进行处理,我们也会对其一一进行梳理和实现,大家可以持续关注下,例如:

数组变动监听

object深度监听

更新队列

render过程中记录仅相关的属性

模板渲染

v-model

...等等

相关代码放在github,会持续更新,欢迎赏个star。

敬请期待!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/84306.html

相关文章

  • 使用ES6特性实现简单MVVM1)--数据驱动

    摘要:尝试使用新特性,自己来实现一个及的各种特性。我们可以利用这个特性来实现对数据的监听结果简单的操作我们已经可以对简单的数据操作进行监听虽然还有各种问题,接下来,我们只要在监听到后进行数据操作即可。 尝试使用es6新特性,自己来实现一个mvvm及vue的各种特性。相关代码放在github,会持续更新,欢迎赏个star。本篇文章为系列文章的第一篇,会比较容易理解,后续会持续更新后面的记录。文...

    cloud 评论0 收藏0
  • Vue面试中,经常会被问到面试题/Vue知识点整理

    摘要:可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。我工作中只用到,对和不怎么熟与的区别相同点都支持指令内置指令和自定义指令都支持过滤器内置过滤器和自定义过滤器都支持双向数据绑定都不支持低端浏览器。 看看面试题,只是为了查漏补缺,看看自己那些方面还不懂。切记不要以为背了面试题,就万事大吉了,最好是理解背后的原理,这样面试的时候才能侃侃而谈。不然,稍微有水平的面试官一看就能看出,是...

    mengbo 评论0 收藏0
  • Regularjs是什么

    摘要:目前已经在大大小小多个线上产品中使用了,也收集了一些有效的建议好了,该看下一个最简单的组件长什么样吧免费领取验证码内容安全短信发送直播点播体验包及云服务器等套餐更多网易技术产品运营经验分享请访问网易云社区。文章来源网易云社区 本文由作者郑海波授权网易云社区发布。 此文摘自regularjs的指南, 目前指南正在全面更新, 把老文档的【接口/语法部分】统一放到了独立的 Reference...

    seal_de 评论0 收藏0
  • 前端发展历程

    摘要:前端的发展历程什么是前端前端针对浏览器的开发,代码在浏览器运行后端针对服务器的开发,代码在服务器运行前端三剑客超文本标记语言是构成世界的基石。 前端的发展历程 什么是前端 前端:针对浏览器的开发,代码在浏览器运行 后端:针对服务器的开发,代码在服务器运行 前端三剑客 HTML CSS JavaScript HTML HTML(超文本标记语言——HyperText Markup ...

    刘明 评论0 收藏0
  • 前端面试题总结——VUE(持续更中)

    摘要:前端面试题总结持续更新中是哪个组件的属性模块的组件。都提供合理的钩子函数,可以让开发者定制化地去处理需求。 前端面试题总结——VUE(持续更新中) 1.active-class是哪个组件的属性? vue-router模块的router-link组件。 2.嵌套路由怎么定义? 在 VueRouter 的参数中使用 children 配置,这样就可以很好的实现路由嵌套。 //引入两个组件 ...

    SimonMa 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<