html 数据双向绑定,Vue双向数据绑定篇

本文介绍了Vue.js的基本概念,包括Vue的渐进式框架特性,以及它如何解决数据双向绑定和组件化管理的问题。文章详细探讨了MVVM模式,解释了从jQuery到MVVM的前端发展历程,并对比了手动绑定、脏值检查和数据劫持等主流双向绑定实现。此外,文章通过实例展示了如何在Vue中实现数据绑定,并提到了观察者模式在数据绑定中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Vue简介

1.1 Vue是什么

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

由于笔者水平有限,如有不足和不正确的地方,请评论指出。

1.2 Vue解决了什么问题数据的双向绑定

组件化管理

1.3 怎么学习Vue

二、 MVVM

2.1 顺便摘要下廖雪峰JavaScript教程的一段前端的发展史

在上个世纪的1989年,欧洲核子研究中心的物理学家Tim Berners-Lee发明了超文本标记语言(HyperText Markup Language),简称HTML,并在1993年成为互联网草案。从此,互联网开始迅速商业化,诞生了一大批商业网站。

最早的HTML页面是完全静态的网页,它们是预先编写好的存放在Web服务器上的html文件。浏览器请求某个URL时,Web服务器把对应的html文件扔给浏览器,就可以显示html文件的内容了。

如果要针对不同的用户显示不同的页面,显然不可能给成千上万的用户准备好成千上万的不同的html文件,所以,服务器就需要针对不同的用户,动态生成不同的html文件。一个最直接的想法就是利用C、C++这些编程语言,直接向浏览器输出拼接后的字符串。这种技术被称为CGI:Common Gateway Interface。

很显然,像新浪首页这样的复杂的HTML是不可能通过拼字符串得到的。于是,人们又发现,其实拼字符串的时候,大多数字符串都是HTML片段,是不变的,变化的只有少数和用户相关的数据,所以,又出现了新的创建动态HTML的方式:ASP、JSP和PHP——分别由微软、SUN和开源社区开发。

在ASP中,一个asp文件就是一个HTML,但是,需要替换的变量用特殊的标记出来了,再配合循环、条件判断,创建动态HTML就比CGI要容易得多。

但是,一旦浏览器显示了一个HTML页面,要更新页面内容,唯一的方法就是重新向服务器获取一份新的HTML内容。如果浏览器想要自己修改HTML页面的内容,就需要等到1995年年底,JavaScript被引入到浏览器。

有了JavaScript后,浏览器就可以运行JavaScript,然后,对页面进行一些修改。JavaScript还可以通过修改HTML的DOM结构和CSS来实现一些动画效果,而这些功能没法通过服务器完成,必须在浏览器实现。

第一阶段,直接用JavaScript操作DOM节点,使用浏览器提供的原生API:var dom = document.getElementById('name');

dom.innerHTML = 'Homer';

dom.style.color = 'red';

第二阶段,由于原生API不好用,还要考虑浏览器兼容性,jQuery横空出世,以简洁的API迅速俘获了前端开发者的芳心$('#name').text('Homer').css('color', 'red');

第三阶段,MVC模式,需要服务器端配合,JavaScript可以在前端修改服务器渲染后的数据。

现在,随着前端页面越来越复杂,用户对于交互性要求也越来越高,想要写出Gmail这样的页面,仅仅用jQuery是远远不够的。MVVM模型应运而生。

MVVM最早由微软提出来,它借鉴了桌面应用程序的MVC思想,在前端页面中,把Model用纯JavaScript对象表示,View负责显示,两者做到了最大限度的分离。

把Model和View关联起来的就是ViewModel。ViewModel负责把Model的数据同步到View显示出来,还负责把View的修改同步回Model。

其实从jq语法的引入操作DOM结构,变的容易的多了。但是如果能直接该表javaScript对象就能导致DOM结构做出对应的变化,那该多好呀,而MVVM就把开发者从DOM的繁琐步骤中解脱出来了,而更加关注Mode的变化。

三、步步为营

3.1 主流双向绑定的做法手动绑定

脏值检查(angular.js)

数据劫持

3.2 简要概述以上做法:

双向绑定从本质上来说无非两部分 Model->View 与  View->Model

3.2.1 首先是Model->View的思路

model无非是个Object,或者是如Vue里面是个全局的vm.data

view 在html上无疑是个树形的标签结构,所以也就是node这样结构

最直接的做法遍历。

先看下最基本的vue代码

html

    

{{text}}    

{{input}}

可以看到Vue里面绑定数据无非两种,  其中 v-model加载<>中,也就是给标签增加新的属性,和data-的方式增加属性一般无二,(PS:顺便提及小程序中函数传参,运用就是这样的方法)。

So, a:for也罢,v-model也罢,或者其他各种种种无非是标识符不同而已,万变不离其中。第二部分就是关于'{{}}',因为其实在标签内部,比如

{{input}}

可以看到, {{input}}并不作为app的子节点,所以当为元素节点的是,判断是否有子节点,有则再次调用scan函数。

所以有了,第一简单的方法就是每次改变data数值的时候,直接再次调用scan函数(PS:因为scan方法因为 先遍历node列表,再遍历该节点的属性,所以会是双层遍历)

也就是简单绑定的方法/**

* 设置数据后扫描

*/

function mvSet(key, value){

data[key] = value;

scan();

}

第二种脏值检查

直接封装和执行$digest() 或$apply()/**

* 脏循环检测

* @param  {[type]} elems [description]

* @return {[type]}       [description]

*/

var digest = function(elems) {        /**

* 扫描带指令的节点属性

*/

for (var i = 0, len = elems.length; i = 0) {                    /**

* 调用属性指令

*/

var dataKey = elem.getAttribute('ng-bind') || undefined;                    /**

* 进行脏数据检测,如果数据改变,则重新执行指令,否则跳过

*/

if(elem.command[attr.nodeValue] !== data[dataKey]){

command[attr.nodeValue].call(elem, data[dataKey]);

elem.command[attr.nodeValue] = data[dataKey];

}

}

}

}

}

第三种方式 采用Object.defineProperty对数据对象做属性get和set的监听,但是需要注意的是为了保存传进来的数值,并且避免无效循环,采用如下方法用于独立的函数,value来存储对应的对应的数值。function defineProperty(vm, key, val){    Object.defineProperty(vm, key, {        get: function (){            return val;

},        set: function (newValue){            document.getElementById("show").innerHTML = newValue;            document.getElementById("input").value = newValue;            if(newValue === val){                return;

}

val = newValue;

}

});

}

function observe(data, vm){    Object.keys(data).forEach(function(key){

defineProperty(vm, key, data[key]);

});

3.2.2 View->Model

View到Model无非一些可以改变的标签,比如input等,而view到Model基本的思路都是原生的事件的一些方法。比如如下代码。document.getElementById('input').addEventListener('keyup', function (e) {

obj.txt = e.target.value;

});

3.2.3 关于设计模式

从Model->View以及后面的从 View->Model相信大家也能看到,其实这三种绑定方式,最大区别体现在Model->层。虽然我们可以通过遍历的方式对应地修改对应的标签的属性。也能通过我们自己指定的标识符比如’v-model‘, 'ng-text','{{}}',甚至比如采用自己的名称的前缀比如笔者的'sl-text'等等来采用需要双向绑定的标签元素采用列表的统一管理,这样能减少遍历次数,也可以对于v-model绑定的属性,通过列表添加到该标签,作为其的一个属性,但是是否还能进一步优化。

引用一张''Header First"设计模式上的观察者模式一图,如下:

AAffA0nNPuCLAAAAAElFTkSuQmCC

image.png

在javascript没有像协议这样的语法,不过原理还是一致,改良好的双向数据绑定模型如下代码。//第三部分

function Watcher(vm, node, name, nodeType){

Dep.target = this;        this.vm = vm;        this.node = node;        this.name = name;        this.nodeType = nodeType;        this.update();

Dep.target = null;

}

Watcher.prototype = {        update: function(){            this.get();            if (this.nodeType === 'text') {                this.node.nodeValue = this.value;

}            if (this.nodeType === 'input') {                this.node.value = this.value;

}

},        get: function(){            this.value = this.vm[this.name];

}

}    function Dep(){        this.subs = [];

}

Dep.prototype = {        addSub: function(sub){            this.subs.push(sub);

},        notify: function(){            this.subs.forEach(function(sub){

sub.update();

});

}

}    //第二部分

function defineProperty(vm, key, val){        var dep = new Dep();        Object.defineProperty(vm, key, {            get: function (){                if(Dep.target){

dep.addSub(Dep.target);

}                return val;

},            set: function (newValue){                if(newValue === val){                    return;

}

val = newValue;

dep.notify();

}

});

}    function observe(data, vm){        //Object.keys(data)返回data的key数组

Object.keys(data).forEach(function(key){

defineProperty(vm, key, data[key]);

});

}    //第一部分

function compile(node, vm){        if(node.nodeType === 1){            var attr = node.attributes;            for(let i = 0; i

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

vm[name] = e.target.value;

});

node.value = vm[name];

node.removeAttribute('v-model');                    new Watcher(vm, node, name, "input");

}

}            if (child = node.firstChild) {

compile(child, vm);

}

}        if(node.nodeType === 3){            let reg = /\{\{(.*)\}\}/;            if(reg.test(node.nodeValue)){                let name = RegExp.$1;

name = name.trim();                // node.nodeValue = vm.data[name];

new Watcher(vm, node, name, "text");

}

}

}    function nodeToFragment(node, vm){        var flag = document.createDocumentFragment();        var child;        while(child = node.firstChild){

compile(child, vm);

flag.appendChild(child);

}        return flag;

}    function Vue(options){        var id = options.el;        var data = options.data;

observe(data, this);        var dom = nodeToFragment(document.getElementById(id), this);        document.getElementById(id).appendChild(dom);

}    var vm = new Vue({        el: 'app',        data: {            input: 'hello'

}

});

大体逻辑表现为,首先定义观察者Watcher,并在编译函数compile()中对每个节点添加观察着Watcher,当接收到分发者指令时,调用update方法更新视图。接下来定义消息分发者Dep,Dep维护观察者数组,当值发生变化时,通知各观察者调用update方法。

AAffA0nNPuCLAAAAAElFTkSuQmCC

image.png

四、附上源码

AAffA0nNPuCLAAAAAElFTkSuQmCC

image.png

作者:破晓霜林

链接:https://www.jianshu.com/p/4cfbeddc5db6

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值