Home Web Front-end JS Tutorial How the MVVM framework resolves two-way binding

How the MVVM framework resolves two-way binding

Jun 09, 2018 pm 04:10 PM
mvvm Two-way binding frame

This article mainly introduces the two-way binding of MVVM framework analysis. Now I will share it with you and give you a reference.

MVVM Framework

An obvious front-end development trend in recent years is the migration of architecture from the traditional MVC model to the MVVM model. Under traditional MVC, the entire page will be refreshed after data interaction between the current front-end and back-end, resulting in a poor user experience. Therefore, we communicate with the gateway REST API through Ajax and refresh a certain section of the page asynchronously to optimize and enhance the experience.

Basic concepts of MVVM framework

In the MVVM framework, View (view) and Model (data) cannot communicate directly Yes, there is ViewModel as an intermediary between them, which acts as an observer. When the user operates the View, the ViewModel senses the change, and then notifies the Model of the corresponding change; conversely, when the Model (data) changes, the ViewModel can also sense the change, causing the View to update accordingly. This back and forth process is what we know as two-way binding.

Application scenarios of the MVVM framework

The benefits of the MVVM framework are obvious: when the front-end operates on data, the data can be persisted through Ajax requests, and only needs to be changed The part of the data content in the dom that needs to be changed without having to refresh the entire page. Especially on mobile, refreshing the page is too expensive. Although some resources will be cached, the DOM, CSS, and JS of the page will be re-parsed by the browser, so mobile pages are usually made into SPA single-page applications. On this basis, many MVVM frameworks were born, such as React.js, Vue.js, Angular.js, etc.

Simple implementation of MVVM framework

#Simulates Vue’s two-way binding flow and implements a simple MVVM framework. You can see the dotted line from the above figure In the square is the ViewModel intermediary layer mentioned earlier, which plays the role of an observer. In addition, you can find that the View to Model in the two-way binding flow is actually implemented through the event listening function of the input. If you switch to React (one-way binding flow), it will be handed over to the state management tool (such as Redux) at this step. accomplish. In addition, the Model to View in the two-way binding flow is actually implemented in the same way by each MVVM framework. The core method used is Object.defineProperty(). Through this method, data can be hijacked and when the data changes, it can be captured. Change accordingly for subsequent processing.

The implementation of Mvvm (entry file)

Generally the Mvvm framework is called like this

1

2

3

4

5

6

7

const vm = new Mvvm({

      el: '#app',

      data: {

       title: 'mvvm title',

       name: 'mvvm name'

      },

     })

Copy after login

But like this If you want to get the title attribute, you need to get it in the form of vm.data.title. In order to let vm.title get the title attribute, add a proxy method to the prototype of Mvvm. The code is as follows:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

function Mvvm (options) {

 this.data = options.data

 const self = this

 Object.keys(this.data).forEach(key =>

  self.proxyKeys(key)

 )

}

Mvvm.prototype = {

 proxyKeys: function(key) {

  const self = this

  Object.defineProperty(this, key, {

   get: function () { // 这里的 get 和 set 实现了 vm.data.title 和 vm.title 的值同步

    return self.data[key]

   },

   set: function (newValue) {

    self.data[key] = newValue

   }

  })

 }

}

Copy after login

After implementing the proxy method, step into the implementation of the main process

1

2

3

4

5

6

function Mvvm (options) {

 this.data = options.data

 // ...

 observe(this.data)

 new Compile(options.el, this)

}

Copy after login

Observer (observer) implementation

The observer’s responsibility is to monitor the Model(JS object ), the core part is the use of the get and set methods of Object.defineProperty(). When the value of the Model (JS object) is to be obtained, the get method will be automatically called; when the value of the Model (JS object) is changed , the set method will be automatically called; thus hijacking the data is achieved. The code is as follows.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

let data = {

 number: 0

}

observe(data)

data.number = 1 // 值发生变化

function observe(data) {

 if (!data || typeof(data) !== 'object') {

  return

 }

 const self = this

 Object.keys(data).forEach(key =>

  self.defineReactive(data, key, data[key])

 )

}

function defineReactive(data, key, value) {

 observe(value) // 遍历嵌套对象

 Object.defineProperty(data, key, {

  get: function() {

   return value

  },

  set: function(newValue) {

   if (value !== newValue) {

    console.log('值发生变化', 'newValue:' + newValue + ' ' + 'oldValue:' + value)

    value = newValue

   }

  }

 })

}

Copy after login

Run the code and you can see that the console output value changes newValue:1 oldValue:0. This completes the logic of the observer.

The relationship between Dep (subscriber array) and watcher (subscriber)

After observing changes, we always need to notify specific groups of people and let them make decisions Deal with it accordingly. In order to understand it more easily, we can think of subscription as subscribing to a WeChat official account. When the content of the WeChat official account is updated, it will push (update) the content to the people who subscribe to it.

There are thousands of people who subscribe to the same WeChat official account, so the first thing that comes to mind is to use new Array() to store these people (html nodes) Bar. So we have the following code:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

// observer.js

function Dep() {

 this.subs = [] // 存放订阅者

}

Dep.prototype = {

 addSub: function(sub) { // 添加订阅者

  this.subs.push(sub)

 },

 notify: function() { // 通知订阅者更新

  this.subs.forEach(function(sub) {

   sub.update()

  })

 }

}

function observe(data) {...}

function defineReactive(data, key, value) {

 var dep = new Dep()

 observe(value) // 遍历嵌套对象

 Object.defineProperty(data, key, {

  get: function() {

   if (Dep.target) { // 往订阅器添加订阅者

    dep.addSub(Dep.target)

   }

   return value

  },

  set: function(newValue) {

   if (value !== newValue) {

    console.log('值发生变化', 'newValue:' + newValue + ' ' + 'oldValue:' + value)

    value = newValue

    dep.notify()

   }

  }

 })

}

Copy after login

At first glance, the code is relatively smooth, but it may get stuck in Dep.target and sub.update, so we naturally turn our attention to watcher,

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

// watcher.js

function Watcher(vm, exp, cb) {

 this.vm = vm

 this.exp = exp

 this.cb = cb

 this.value = this.get()

}

Watcher.prototype = {

 update: function() {

  this.run()

 },

 run: function() {

  // ...

  if (value !== oldVal) {

   this.cb.call(this.vm, value) // 触发 compile 中的回调

  }

 },

 get: function() {

  Dep.target = this // 缓存自己

  const value = this.vm.data[this.exp] // 强制执行监听器里的 get 函数

  Dep.target = null // 释放自己

  return value

 }

}

Copy after login

从代码中可以看到当构造 Watcher 实例时,会调用 get() 方法,接着重点关注 const value = this.vm.data[this.exp] 这句,前面说了当要获取 Model(JS 对象) 的值时,会自动调用 Object.defineProperty 的 get 方法,也就是当执行完这句的时候,Dep.target 的值传进了 observer.js 中的 Object.defineProperty 的 get 方法中。同时也一目了然地在 Watcher.prototype 中发现了 update 方法,其作用即触发 compile 中绑定的回调来更新界面。至此解释了 Observer 中 Dep.target 和 sub.update 的由来。

来归纳下 Watcher 的作用,其充当了 observer 和 compile 的桥梁。

1 在自身实例化的过程中,往订阅器(dep) 中添加自己

2 当 model 发生变动,dep.notify() 通知时,其能调用自身的 update 函数,并触发 compile 绑定的回调函数实现视图更新

最后再来看下生成 Watcher 实例的 compile.js 文件。

compile(编译) 的实现

首先遍历解析的过程有多次操作 dom 节点,为提高性能和效率,会先将跟节点 el 转换成 fragment(文档碎片) 进行解析编译,解析完成,再将 fragment 添加回原来的真实 dom 节点中。代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

function Compile(el, vm) {

 this.vm = vm

 this.el = document.querySelector(el)

 this.fragment = null

 this.init()

}

Compile.prototype = {

 init: function() {

  if (this.el) {

   this.fragment = this.nodeToFragment(this.el) // 将节点转为 fragment 文档碎片

   this.compileElement(this.fragment) // 对 fragment 进行编译解析

   this.el.appendChild(this.fragment)

  }

 },

 nodeToFragment: function(el) {

  const fragment = document.createDocumentFragment()

  let child = el.firstChild // △ 第一个 firstChild 是 text

  while(child) {

   fragment.appendChild(child)

   child = el.firstChild

  }

  return fragment

 },

 compileElement: function(el) {...},

}

Copy after login

这个简单的 mvvm 框架在对 fragment 编译解析的过程中对 {{}} 文本元素、v-on:click 事件指令、v-model 指令三种类型进行了相应的处理。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

Compile.prototype = {

 init: function() {

  if (this.el) {

   this.fragment = this.nodeToFragment(this.el) // 将节点转为 fragment 文档碎片

   this.compileElement(this.fragment) // 对 fragment 进行编译解析

   this.el.appendChild(this.fragment)

  }

 },

 nodeToFragment: function(el) {...},

 compileElement: function(el) {...},

 compileText: function (node, exp) { // 对文本类型进行处理,将 {{abc}} 替换掉

  const self = this

  const initText = this.vm[exp]

  this.updateText(node, initText) // 初始化

  new Watcher(this.vm, exp, function(value) { // 实例化订阅者

   self.updateText(node, value)

  })

 },

 compileEvent: function (node, vm, exp, dir) { // 对事件指令进行处理

  const eventType = dir.split(':')[1]

  const cb = vm.methods && vm.methods[exp]

  if (eventType && cb) {

   node.addEventListener(eventType, cb.bind(vm), false)

  }

 },

 compileModel: function (node, vm, exp) { // 对 v-model 进行处理

  let val = vm[exp]

  const self = this

  this.modelUpdater(node, val)

  node.addEventListener('input', function (e) {

   const newValue = e.target.value

   self.vm[exp] = newValue // 实现 view 到 model 的绑定

  })

 },

}

Copy after login

在上述代码的 compileTest 函数中看到了期盼已久的 Watcher 实例化,对 Watcher 作用模糊的朋友可以往上回顾下 Watcher 的作用。另外在 compileModel 函数中看到了本文最开始提到的双向绑定流中的 View 到 Model 是借助 input 监听事件变化实现的。

项目地址

本文记录了些阅读 mvvm 框架源码关于双向绑定的心得,并动手实践了一个简版的 mvvm 框架,不足之处在所难免,欢迎指正。

上面是我整理给大家的,希望今后会对大家有帮助。

相关文章:

通过微信小程序如何实现验证码获取倒计时效果

ES6 迭代器和 for.of循环(详细教程)

在vue中使用better-scroll滚动插件

在VUE + UEditor中如何实现单图片跨域上传功能

The above is the detailed content of How the MVVM framework resolves two-way binding. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

How to evaluate the cost-effectiveness of commercial support for Java frameworks How to evaluate the cost-effectiveness of commercial support for Java frameworks Jun 05, 2024 pm 05:25 PM

Evaluating the cost/performance of commercial support for a Java framework involves the following steps: Determine the required level of assurance and service level agreement (SLA) guarantees. The experience and expertise of the research support team. Consider additional services such as upgrades, troubleshooting, and performance optimization. Weigh business support costs against risk mitigation and increased efficiency.

How does the learning curve of PHP frameworks compare to other language frameworks? How does the learning curve of PHP frameworks compare to other language frameworks? Jun 06, 2024 pm 12:41 PM

The learning curve of a PHP framework depends on language proficiency, framework complexity, documentation quality, and community support. The learning curve of PHP frameworks is higher when compared to Python frameworks and lower when compared to Ruby frameworks. Compared to Java frameworks, PHP frameworks have a moderate learning curve but a shorter time to get started.

How do the lightweight options of PHP frameworks affect application performance? How do the lightweight options of PHP frameworks affect application performance? Jun 06, 2024 am 10:53 AM

The lightweight PHP framework improves application performance through small size and low resource consumption. Its features include: small size, fast startup, low memory usage, improved response speed and throughput, and reduced resource consumption. Practical case: SlimFramework creates REST API, only 500KB, high responsiveness and high throughput

Performance comparison of Java frameworks Performance comparison of Java frameworks Jun 04, 2024 pm 03:56 PM

According to benchmarks, for small, high-performance applications, Quarkus (fast startup, low memory) or Micronaut (TechEmpower excellent) are ideal choices. SpringBoot is suitable for large, full-stack applications, but has slightly slower startup times and memory usage.

Golang framework documentation best practices Golang framework documentation best practices Jun 04, 2024 pm 05:00 PM

Writing clear and comprehensive documentation is crucial for the Golang framework. Best practices include following an established documentation style, such as Google's Go Coding Style Guide. Use a clear organizational structure, including headings, subheadings, and lists, and provide navigation. Provides comprehensive and accurate information, including getting started guides, API references, and concepts. Use code examples to illustrate concepts and usage. Keep documentation updated, track changes and document new features. Provide support and community resources such as GitHub issues and forums. Create practical examples, such as API documentation.

How to choose the best golang framework for different application scenarios How to choose the best golang framework for different application scenarios Jun 05, 2024 pm 04:05 PM

Choose the best Go framework based on application scenarios: consider application type, language features, performance requirements, and ecosystem. Common Go frameworks: Gin (Web application), Echo (Web service), Fiber (high throughput), gorm (ORM), fasthttp (speed). Practical case: building REST API (Fiber) and interacting with the database (gorm). Choose a framework: choose fasthttp for key performance, Gin/Echo for flexible web applications, and gorm for database interaction.

Java Framework Learning Roadmap: Best Practices in Different Domains Java Framework Learning Roadmap: Best Practices in Different Domains Jun 05, 2024 pm 08:53 PM

Java framework learning roadmap for different fields: Web development: SpringBoot and PlayFramework. Persistence layer: Hibernate and JPA. Server-side reactive programming: ReactorCore and SpringWebFlux. Real-time computing: ApacheStorm and ApacheSpark. Cloud Computing: AWS SDK for Java and Google Cloud Java.

What are the common misunderstandings in the learning process of Golang framework? What are the common misunderstandings in the learning process of Golang framework? Jun 05, 2024 pm 09:59 PM

There are five misunderstandings in Go framework learning: over-reliance on the framework and limited flexibility. If you don’t follow the framework conventions, the code will be difficult to maintain. Using outdated libraries can cause security and compatibility issues. Excessive use of packages obfuscates code structure. Ignoring error handling leads to unexpected behavior and crashes.

See all articles