关于 Vue.js 双向数据绑定基本实现认知

写在前面


  • 很早的一篇博客,整理了部分,蹭假期整理完
  • 博文内容涉及:
  • 双向数据绑定 实现方式简单介绍
  • 基于发布订阅数据劫持双向数据绑定两种不同实现(ES5/ES6) Demo,以及代码简单分析
  • Object.defineProperty && Proxy API 介绍以及特性对比
  • 理解不足小伙伴帮忙指正 😃,生活加油

对每个人而言,真正的职责只有一个:找到自我。然后在心中坚守其一生,全心全意,永不停息。所有其它的路都是不完整的,是人的逃避方式,是对大众理想的懦弱回归,是随波逐流,是对内心的恐惧 ——赫尔曼·黑塞《德米安》


双向数据绑定介绍

在前端框架中,特别是响应式框架(如Vue.js, Angular等)中,双向数据绑定(Two-way data binding)是一个核心特性,它允许开发者在UI和数据之间建立直接的、双向的联系(MVVM)。下面是一些实现双向数据绑定的常见做法:

脏值检查(Dirty Checking)

脏值检查是一种简单的双向数据绑定策略。它周期性地检查数据模型(Model)是否发生了变化,如果发生了变化,则更新视图(View)。脏值检查通常涉及一个“检查周期”或“轮询间隔”,在这个间隔内,框架会遍历所有绑定,并检查是否有任何变化。

然而,脏值检查并不高效,因为它可能需要对整个数据模型进行不必要的遍历,即使数据实际上并没有改变。此外,它也不能立即反映变化,因为它依赖于轮询间隔。

数据劫持(Data Interception)

数据劫持(也称为数据代理或对象劫持)是一种更高效的双向数据绑定策略。它依赖于JavaScriptObject.defineProperty()方法(在ES5中引入),该方法允许你定义或修改对象的属性,包括getter和setter方法。

Vue.js 的早期版本中,当一个对象被用作数据模型时,Vue 会遍历它的所有属性,并使用 Object.defineProperty() 将它们转化为getter/setter,以便在数据变化时能够立即感知到。当视图需要读取数据模型时,getter方法会被调用;当视图需要更新数据模型时,setter方法会被调用,并且可以在这里触发视图的更新。

Vue.js 3.0 开始,引入了更高效的响应式系统,称为Proxy-based reactive systemVue.js 3.0 及以后的版本使用ES6的Proxy来实现双向数据绑定。通过使用Proxy,Vue.js可以更灵活地劫持整个对象,并监视对象的新增和删除属性操作,以及数组的索引和长度变化。

发布者-订阅者模式(Publisher-Subscriber Pattern)

发布者-订阅者模式是一种软件设计模式,它允许一个或多个发布者(Publisher)发布事件,而零个或多个订阅者(Subscriber)会监听这些事件,并在事件发生时执行相应的操作。

在双向数据绑定的上下文中,数据模型可以被视为发布者,而视图则是订阅者。当数据模型发生变化时,它会发布一个事件(通常是一个“change”事件),而所有订阅了这个事件的视图都会收到通知,并更新自己以反映新的数据。

这种模式允许数据模型和视图之间实现松散的耦合,因为它们之间不需要直接通信;它们只需要知道如何发布和监听事件即可。此外,这种模式还具有良好的可扩展性,因为你可以轻松地添加新的发布者或订阅者,而无需修改现有的代码。

MVVM

Vue.js 双向绑定的简单实现

Vue.js 使用了数据劫持(通过Object.defineProperty()、ES6的Proxy)和发布者-订阅者模式(通过自定义的Dep类和Watcher类)来实现其双向数据绑定机制。而Angular则使用了脏值检查Zone.js库(它类似于数据劫持,但工作方式略有不同)来实现类似的功能。

在这里插入图片描述

Object.defineProperty 数据劫持 Demo

下面的 Demo 简化了 Vue.js 实现,通过数据劫持、订阅者和发布者的机制,实现了将数据和DOM节点进行绑定,并在数据变化时自动更新相关的DOM节点,从而实现了简单的双向数据绑定功能。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Two-way data-binding</title>
</head>
<body><div id="app"><input type="text" v-model="text"><br />{{ text }}</div><script>/**@Time    :   2024/05/03 10:59:55*@Desc    :   数据劫持 或者叫数据监听*/function observe(obj, vm) {// 获取所有的数据,构造数据劫持Object.keys(obj).forEach((key) => {console.log(1, "劫持的数据对象为", key, "值为", obj[key])defineReactive(vm, key, obj[key]);});}/**@Time    :   2024/05/03 10:44:59*@Desc    :   响应式处理函数  */function defineReactive(obj, key, val) {let dep = new Dep();console.log(2, "创建发布者:", dep)Object.defineProperty(obj, key, {enumerable: true,configurable: true,// 使用getter/setter 来实现数据劫持  get: _ => {// 注册订阅者,如果发布者存在if (Dep.target) {dep.addSub(Dep.target);}return val   // 返回属性值},set: (newVal) => {if (newVal === val) {return}val = newVal;console.log(5, "更新监听的数据", key, newVal)// 通知相关的订阅者进行更新dep.notify();}});}/**@Time    :   2024/05/03 10:48:55*@Desc    :   简化的虚拟DOM编译和更新的示例*/function nodeToFragment(node, vm) {var flag = document.createDocumentFragment();var child;console.log(3, "编译虚拟 Dom 节点")while (child = node.firstChild) {compile(child, vm);flag.appendChild(child);}// 返回虚拟 Domreturn flag;}/**@Time    :   2024/05/03 10:52:42*@Desc    :   编译DOM节点*/function compile(node, vm) {// 节点类型为元素if (node.nodeType === 1) {// 获取所有的属性var attr = node.attributes;// 解析属性for (var i = 0; i < attr.length; i++) {if (attr[i].nodeName == 'v-model') {var name = attr[i].nodeValue; // 获取v-model绑定的属性名// 添加 input 事件node.addEventListener('input', function (e) {// 给相应的 data 属性赋值,进而触发该属性的set方法vm[name] = e.target.value;debugger});// 将data的值赋给该node,这里触发 getter 方法// 但是不进行注册node.value = vm[name];node.removeAttribute('v-model');// 构造订阅者new Watcher(vm, node, name, 'input');}}}let reg = /\{\{(.*)\}\}/;// 节点类型为 textif (node.nodeType === 3) {if (reg.test(node.nodeValue)) {var name = RegExp.$1; // 获取匹配到的字符串name = name.trim();// 构造订阅者new Watcher(vm, node, name, 'text');}}}/**@Time    :   2024/05/03 11:53:27*@Desc    :   订阅者*/function Watcher(vm, node, name, nodeType) {//  this为watcher函数Dep.target = this;//  console.log(this);this.name = name;this.node = node;this.vm = vm;this.nodeType = nodeType;this.update();Dep.target = null;}Watcher.prototype = {update() {this.get();//  如果是文本,直接更新 `nodeValue`if (this.nodeType == 'text') {this.node.nodeValue = this.value;}// 如果是输入标签,更新 value 的值if (this.nodeType == 'input') {this.node.value = this.value;}console.log(6.2, "通知 Dom", this.nodeType, "数据为", this.value)},// 获取 data 中的属性值get() {// 触发相应属性的 get,这里会进行订阅者注册this.value = this.vm[this.name];console.log(6.1, "获取", this.nodeType, "数据最新的值:", this.value)}}/**@Time    :   2024/05/03 11:48:38*@Desc    :   发布者*/function Dep() {this.subs = []}Dep.prototype = {addSub(sub) {this.subs.push(sub);console.log(4, "suds:", this.subs.length, "注册订阅者:", sub)},notify() {console.log(6, "通知订阅者:", this.subs)this.subs.forEach((sub) => {sub.update();});}};/**@Time    :   2024/05/03 11:07:01*@Desc    :   */function Vue(options) {this.data = options.data;let data = this.data;// 构造数据劫持observe(data, this);let id = options.el;let dom = nodeToFragment(document.getElementById(id), this);// 编译完成后,将dom返回到app中进行挂载document.getElementById(id).appendChild(dom);}/**@Time    :   2024/05/03 11:09:06*@Desc    :   定义 Vue 对象,传递需要挂载的元素,双向绑定的数据对象*/let vm = new Vue({el: 'app',data: {text: 'hello world'}});</script>
</body>

在这里插入图片描述

简单分析一下干了什么:

observe 函数用于数据劫持,它接收一个对象和Vue实例作为参数。它通过遍历对象的属性,并调用defineReactive 函数来定义属性的getter和setter,从而实现对属性的劫持和监视。

function observe(obj, vm) {// 获取所有的数据,构造数据劫持Object.keys(obj).forEach((key) => {console.log(1,"劫持的数据对象为", key, "值为",obj[key])defineReactive(vm, key, obj[key]);});
}

defineReactive 函数定义了属性的 getter和setter。它创建了一个Dep对象作为发布者,getter 中注册订阅者(Watcher),setter中更新属性的值并通知相关的订阅者进行更新。

function defineReactive(obj, key, val) {let dep = new Dep(); console.log(2,"创建发布者:",dep)Object.defineProperty(obj, key, {enumerable: true,  configurable: true,  // 使用getter/setter 来实现数据劫持  get: _=> {// 注册订阅者,如果发布者存在if (Dep.target) {dep.addSub(Dep.target);}return val   // 返回属性值},set: (newVal) => {if (newVal === val) {return} val = newVal;console.log(5,"更新监听的数据",key,newVal)// 通知相关的订阅者进行更新dep.notify(); }});
}

nodeToFragment 函数用于将DOM节点转换为虚拟DOM(DocumentFragment)。它遍历DOM节点的子节点,并调用compile函数来解析和编译节点。

function nodeToFragment(node, vm) {var flag = document.createDocumentFragment();var child;console.log(3,"编译虚拟 Dom 节点")while (child = node.firstChild) {compile(child, vm);flag.appendChild(child);}// 返回虚拟 Domreturn flag;
}

compile 函数用于编译DOM节点。对于元素节点,它解析其属性,并处理带有v-model属性的输入节点,实现双向数据绑定。对于文本节点,它解析其中的双括号表达式({{...}}),并创建一个订阅者(Watcher)来监听相关的数据变化。

addEventListener 用于挂载 input 监听事件,当数据发生变化时,会触发 VM 中的 set 方法的数据劫持,从而调用 dep.notify() 方法,实现对所有订阅的通知

function compile(node, vm) {// 节点类型为元素if (node.nodeType === 1) {// 获取所有的属性var attr = node.attributes;// 解析属性for (var i = 0; i < attr.length; i++) {if (attr[i].nodeName == 'v-model') {var name = attr[i].nodeValue; // 获取v-model绑定的属性名// 添加 input 事件node.addEventListener('input', function (e) {// 给相应的 data 属性赋值,进而触发该属性的set方法vm[name] = e.target.value;debugger});node.value = vm[name]; // 将data的值赋给该nodenode.removeAttribute('v-model'); // 构造订阅者new Watcher(vm, node, name, 'input');}}}let reg = /\{\{(.*)\}\}/;// 节点类型为 textif (node.nodeType === 3) {if (reg.test(node.nodeValue)) {var name = RegExp.$1; // 获取匹配到的字符串name = name.trim();// 构造订阅者new Watcher(vm, node, name, 'text');}}
}

Watcher对象表示一个订阅者。在构造函数中,它将自身赋值给Dep.target,然后通过调用update方法来获取数据并更新DOM节点的值。update方法根据节点类型(文本或输入)更新节点的nodeValue或value属性。在第一次获取值的时候会进行订阅者注册

function Watcher(vm, node, name, nodeType) {//  this为watcher函数Dep.target = this;//  console.log(this);this.name = name;this.node = node;this.vm = vm;this.nodeType = nodeType;this.update();Dep.target = null;
}
Watcher.prototype = {update () {this.get();//  如果是文本,直接更新 `nodeValue`if (this.nodeType == 'text') {this.node.nodeValue = this.value;}// 如果是输入标签,更新 value 的值if (this.nodeType == 'input') {this.node.value = this.value;}console.log(6.2,"通知 Dom",this.nodeType, "数据为",this.value)},// 获取 data 中的属性值get () {// 触发相应属性的 get,这里会进行订阅者注册this.value = this.vm[this.name]; console.log(6.1,"获取",this.nodeType,"数据最新的值:",this.value)}
}

Dep对象表示一个发布者,用于管理订阅者(Watchers)。它有一个subs数组用于存储订阅者,在addSub方法中添加订阅者,而在notify方法中通知所有订阅者进行更新。

function Dep() {this.subs = []
}
Dep.prototype = {addSub (sub) {this.subs.push(sub);console.log(4, "suds:", this.subs.length, "注册订阅者:", sub)},notify () {console.log(6, "通知订阅者:", this.subs)this.subs.forEach((sub) => {sub.update();});}
};

Vue对象是自定义的框架的入口点。它接收一个选项对象,其中包含要挂载的元素的选择器和双向绑定的数据对象。在构造函数中,它调用observe函数进行数据劫持,然后调用nodeToFragment函数将DOM节点转换为虚拟DOM,并将其挂载到指定的元素上。

function Vue(options) {this.data = options.data;let data = this.data;// 构造数据劫持observe(data, this);let id = options.el;let dom = nodeToFragment(document.getElementById(id), this);// 编译完成后,将dom返回到app中进行挂载document.getElementById(id).appendChild(dom);}/**@Time    :   2024/05/03 11:09:06*@Desc    :   定义 Vue 对象,传递需要挂载的元素,双向绑定的数据对象*/let vm = new Vue({el: 'app',data: {text: 'hello world'}});

ES6的Proxy 数据劫持 Demo

在 Vue.js 3.0 开始,使用了ES6的Proxy来实现数据劫持。下面的 Demo 演示了如何使用Proxy来进行数据劫持

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>双向数据绑定Demo</title>
</head>
<body><div id="app"><input type="text" v-model="text"><br />{{ text }}</div><script>/**@Time    :   2024/05/03 10:59:55*@Desc    :   数据劫持 或者叫数据监听*/function observeProxy(obj) {// 构造代理对象debuggerlet dep = new Dep();const vm = new Proxy(obj, {get(target, key) {// 注册订阅者if (Dep.target) {dep.addSub(Dep.target);}return target[key];},set(target, key, value) {if (value === target[key]) {return true;}target[key] = value;// 通知相关的订阅者进行更新dep.notify();return true;}});console.log(1, "构造代理对象", vm)return vm}/**@Time    :   2024/05/03 10:48:55*@Desc    :   简化的虚拟DOM编译和更新的示例*/function nodeToFragment(node, vm) {var flag = document.createDocumentFragment();var child;console.log(3, "编译虚拟 Dom 节点")while (child = node.firstChild) {compile(child, vm);flag.appendChild(child);}// 返回虚拟 Domreturn flag;}/**@Time    :   2024/05/03 10:52:42*@Desc    :   编译DOM节点*/function compile(node, vm) {// 节点类型为元素if (node.nodeType === 1) {// 获取所有的属性var attr = node.attributes;// 解析属性for (var i = 0; i < attr.length; i++) {if (attr[i].nodeName == 'v-model') {var name = attr[i].nodeValue; // 获取v-model绑定的属性名// 添加 input 事件node.addEventListener('input', function (e) {// 给相应的 data 属性赋值,进而触发该属性的set方法vm.vm[name] = e.target.value;});debugger// 将data的值赋给该node,这里触发 getter 方法node.value = vm.vm[name];debuggernode.removeAttribute('v-model');// 构造订阅者new Watcher(vm, node, name, 'input');}}}let reg = /\{\{(.*)\}\}/;// 节点类型为 textif (node.nodeType === 3) {if (reg.test(node.nodeValue)) {var name = RegExp.$1; // 获取匹配到的字符串name = name.trim();// 构造订阅者new Watcher(vm, node, name, 'text');}}}/**@Time    :   2024/05/03 11:53:27*@Desc    :   订阅者*/function Watcher(vm, node, name, nodeType) {//  this为watcher函数Dep.target = this;console.log(this);this.name = name;this.node = node;this.vm = vm.vm;this.nodeType = nodeType;this.update();Dep.target = null;}Watcher.prototype = {update () {this.get();//  如果是文本,直接更新 `nodeValue`if (this.nodeType == 'text') {debuggerthis.node.nodeValue = this.value;}// 如果是输入标签,更新 value 的值if (this.nodeType == 'input') {debuggerthis.node.value = this.value;}console.log(6.2, "通知 Dom", this.nodeType, "数据为", this.value)},// 获取 data 中的属性值get () {// 触发相应属性的 getthis.value = this.vm[this.name];console.log(6.1, "获取", this.nodeType, "数据最新的值:", this.value)}}/**@Time    :   2024/05/03 11:48:38*@Desc    :   发布者*/function Dep() {this.subs = []}Dep.prototype = {addSub (sub) {this.subs.push(sub);console.log(4, "suds:", this.subs.length, "注册订阅者:", sub)},notify () {console.log(6, "通知订阅者:", this.subs)this.subs.forEach((sub) => {sub.update();});}};/**@Time    :   2024/05/03 11:07:01*@Desc    :   */function Vue(options) {this.data = options.data;let data = this.data;// 构造数据劫持this.vm = observeProxy(data);let id = options.el;let dom = nodeToFragment(document.getElementById(id), this);// 编译完成后,将dom返回到app中进行挂载document.getElementById(id).appendChild(dom);}/**@Time    :   2024/05/03 11:09:06*@Desc    :   定义 Vue 对象,传递需要挂载的元素,双向绑定的数据对象*/let vm = new Vue({el: 'app',data: {text: 'hello world'}});</script>
</body>
</html>

和最上面的 Demo 相比较,observeProxy 方法没有直接修改 VM 对象,Proxy 本身并没有提供一种方法来修改对象属性,所以这里返回一个代理对象Proxy 给了 VM 的 vm 属性,把 需要劫持的数据嵌套了一层放到了 VM 对象。

function Vue(options) {this.data = options.data;let data = this.data;// 构造数据劫持this.vm = observeProxy(data);let id = options.el;let dom = nodeToFragment(document.getElementById(id), this);// 编译完成后,将dom返回到app中进行挂载document.getElementById(id).appendChild(dom);
}
function observeProxy(obj) {// 构造代理对象debuggerlet dep = new Dep();const vm = new Proxy(obj, {get(target, key) {// 注册订阅者if (Dep.target) {dep.addSub(Dep.target);}return target[key];},set(target, key, value) {if (value === target[key]) {return true;}target[key] = value;// 通知相关的订阅者进行更新dep.notify();return true;}});console.log(1, "构造代理对象", vm)return vm
}

Object.defineProperty && Proxy API 介绍

Object.defineProperty

Object.definePropertyES5引入的一个特性,它允许我们将自定义的逻辑应用于对象的属性访问和修改。它可以定义一个新属性或修改现有属性,并定义属性的行为,例如读取(get)和写入(set)时的操作。

const obj = {};Object.defineProperty(obj, 'name', {get() {console.log('读取name属性');return this._name;},set(value) {console.log('设置name属性');this._name = value;}
});obj.name = 'John'; // 输出:设置name属性
console.log(obj.name); // 输出:读取name属性和John

Proxy API

ProxyES6引入的另一个特性,它提供了对对象的拦截和自定义行为的能力。Proxy可以拦截对象上的各种操作,包括属性的读取、写入、函数调用等。通过Proxy,我们可以对对象的访问和修改进行自定义处理

const obj = {name: 'John'
};const proxy = new Proxy(obj, {get(target, key) {console.log(`访问属性:${key}`);return target[key];},set(target, key, value) {console.log(`设置属性:${key} = ${value}`);target[key] = value;return true;}
});proxy.name = 'Jane'; // 输出:设置属性:name = Jane
console.log(proxy.name); // 输出:访问属性:name 和 Jane

简单比较

用法行为:

  • Object.defineProperty:需要逐个定义每个属性的行为,即显式地指定对象的某个属性需要进行拦截和处理。这需要修改现有的对象定义,使其符合拦截要求。这种操作是显式的,需要直接操作对象本身,并且需要事先知道要拦截的属性。后期的操作还是使用目标对象

  • Proxy:创建代理对象时需要提供一个处理器对象,该处理器对象定义了拦截器方法,用于拦截和处理各种操作。代理对象会完全地代理目标对象,并将所有操作转发给目标对象,因此无需修改目标对象本身。这种操作是隐式的,代理对象会在后台拦截和处理所有操作,而不需要直接操作目标对象。代理对象可以在外部对目标对象进行拦截和处理,而目标对象本身保持不变。后期的操作对象是代理对象,而不是目标对象.

拦截能力:

  • Object.defineProperty:主要用于拦截对象的属性读取和写入操作,也可以通过get和set定义一些自定义逻辑。它只能拦截属性级别的操作,无法拦截其他操作。
  • Proxy:具有更强大的拦截能力,可以拦截对象上的多种操作,包括属性的读取、写入、删除、函数调用等。可以通过代理对象的不同处理器方法来自定义拦截逻辑。

动态属性和删除属性:

  • Object.defineProperty:在对象创建后无法动态添加或删除拦截的属性。
  • Proxy可以动态添加和删除属性,并在拦截器中处理相应的操作。

兼容性:

  • Object.defineProperty:相对来说,较好地支持各种现代浏览器和旧版本浏览器,包括IE9+。
  • Proxy:较新的特性,不被所有旧版本浏览器支持,特别是在IE浏览器中不被支持。如果需要在不支持Proxy的环境中运行,需要使用其他解决方案或使用polyfill进行兼容处理。

博文部分内容参考

© 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 😃


https://liruilong.blog.csdn.net/article/details/117675985


© 2018-2024 liruilonger@gmail.com, All rights reserved. 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/6667.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Libcity笔记:原子文件

1 介绍 Libcity中的数据以原子文件的形式存在 2 原子文件类别 对于不同的交通预测任务&#xff0c;可能用到不同的原子文件&#xff0c;同一个数据集不一定包含全部六种原子文件 网格数据需要按照先行后列的顺序遍历OD数据需要按照先起点后终点的顺序遍历 2.1 geo 存储地理…

opengauss概述-基础知识篇-备考华为高斯

目录 &#x1f9e8;考前准备: &#x1f3a1;数据库操作语言 ✨OLTP和OLAP &#x1f3af;常用函数 &#x1f9f2;字符处理函数 关于 left 和 right 特别重点的字符串函数 &#x1f9f2;数字操作函数 关于 ceil 和 floor &#x1f9f2;时间和日期处理函数 &#x1f9f…

专项技能训练五《云计算网络技术与应用》实训7-1:安装mininet

文章目录 mininet安装1. 按6-1教程安装opendaylight控制器。2. 按6-2教程安装RYU控制器。3. 按5-1教程安装openvswitch虚拟交换机并开启服务。4. 将老师所给mininet安装包试用winSCP传送至电脑端。5. 安装net-tools。6. 安装mininet7. 安装完成后&#xff0c;使用命令建立拓扑&…

【网络安全产品】---应用防火墙(WAF)

what Web应用防火墙&#xff08;Web Application Firewall) WAF可对网站或者App的业务流量进行恶意特征识别及防护&#xff0c;在对流量清洗和过滤后&#xff0c;将正常、安全的流量返回给服务器&#xff0c;避免网站服务器被恶意入侵导致性能异常等问题&#xff0c;从而保障…

快速了解Django:核心概念解析与实践指南

title: 快速了解Django&#xff1a;核心概念解析与实践指南 date: 2024/5/1 20:31:41 updated: 2024/5/1 20:31:41 categories: 后端开发 tags: Django核心路由系统视图系统ORM管理中间件Web框架登录装饰器 第一章&#xff1a;Django简介 背景和发展历程&#xff1a; Djan…

计算机毕业设计springboot基于vue电商抢购限时秒杀系统ch0h8

技术栈 ide工具&#xff1a;IDEA 或者eclipse 编程语言: java 数据库: mysql5.7以上版本 可选框架&#xff1a;ssmspringboot都有的 前端&#xff1a;vue.jsElementUI 详细技术&#xff1a;springbootSSMvueMYSQLMAVEN 数据库工具&#xff1a;Navicat/SQLyog都可以 开发工具 Ec…

【算法设计与分析】六、动态规划:(二)上机-1、地牢逃生【理论到程序】

文章目录 一、题目1、问题2、输入输出要求3、样例说明4、数据范围 二、思路1、GPT4 - fail算法解释C 实现 2、Claude3 - fail问题分析算法实现 3、个人拙见 - succeed 三、代码实现 一、题目 1、问题 用一个 nn 的矩阵表示一座地牢&#xff0c;矩阵中第 i 行第 j 列的方格的值…

力扣每日一题106:从中序与后序遍历序列构造二叉树

题目 中等 相关标签 相关企业 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7], postorder …

OpenCV(五) —— 人脸识别模型训练与 Windows 下的人脸识别

本文主要内容&#xff1a; 如何训练 OpenCV 的人脸识别模型如何在 Windows 下利用 OpenCV 进行人脸识别 1、概述 人脸识别需要人脸模型&#xff08;特征集合&#xff09;的支持&#xff0c;人脸定位的速度与准确度取决于模型。 OpenCV 提供了已经训练好的模型&#xff0c;无…

KAN网络认识

首先&#xff0c;这是一个基于柯尔莫哥洛夫-阿诺德表示定理的网络。这个定理指出如果函数f是定义在有界域上的多变量连续函数&#xff08;即最终要拟合的非线性函数是连续的&#xff09;&#xff0c;那么该函数就可以表示为多个单变量、加法连续函数的有线组合。 对于机器学习…

大数据BI可视化(Echarts组件)项目开发-熟悉交互API5.0

全局echarts对象 init初始化 registerTheme注册主题 var mCharts echarts.init(document.querySelector("div"), itcast)registerMap地图图表 connect 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8&qu…

OpenCV(六) —— Android 下的人脸识别

本篇我们来介绍在 Android 下如何实现人脸识别。 上一篇我们介绍了如何在 Windows 下通过 OpenCV 实现人脸识别&#xff0c;实际上&#xff0c;在 Android 下的实现的核心原理是非常相似的&#xff0c;因为 OpenCV 部分的代码改动不大&#xff0c;绝大部分代码可以直接移植到 …

ubuntu安装LVGL/lv_img_conv并在thinkphp中进行调用生成bin文件

项目需求&#xff1a;需要处理图片成为bin文件&#xff0c;并以二进制的方式传给蓝牙设备&#xff0c;当前仅介绍如何安装&#xff0c;对lvgl功能和简介不做过多描述 项目库地址&#xff1a;https://github.com/lvgl/lv_img_conv 安装过程比较简单 一&#xff0c;确保node.j…

mall-cook本地部署运行

下载源代码 https://github.com/wangyuan389/mall-cook 下载好之后解压&#xff0c;删除.github和yarn.lock&#xff0c;因为使用pnpm 启动文档部署 切换到packages\mall-cook-document&#xff0c;删除yarn.lock&#xff0c;安装依赖包pnpm install 执行pnpm dev启动文档…

C语言/数据结构——(用双链表实现数据的增删查改)

一.前言 嗨嗨嗨&#xff0c;大家好久不见&#xff01;前面我们已经通过数组实现数据的增删查改、单链表实现数据的增删查改&#xff0c;现在让我们尝试一下使用双链表实现数据的增删查改吧&#xff01; 二.正文 如同往常一样&#xff0c;对于稍微大点的项目来说&#xff0c;…

推荐网站(2)今日热榜合集,看不同软件的热点事件

当我们想要看微博&#xff0c;今日头条&#xff0c;bilibili等等今日热点时&#xff0c;需要打开对应的app查看&#xff0c;但是有了这个网站我们可以看不同平台的热点消息&#xff0c;甚至京东&#xff0c;淘宝等购物软件&#xff0c;也能看到热销总榜。 链接直达&#xff1a;…

day-28 除自身以外数组的乘积

思路 利用两个空数组left&#xff08;left[i]表示nums下标从0到i的乘积&#xff09;和right&#xff08;right[i]表示nums下标从n-1到i的乘积&#xff09; 解题方法 返回数组nums[i]right[i1]*left[i-1],第一个元素和最后一个元素单独考虑 Code class Solution {public int[…

【计算机网络】计算机网络的定义和分类

一.定义 计算机网络并没有一个精确和统一的定义&#xff0c;在计算机网络发展的不同阶段&#xff0c;人们对计算机网络给出了不同的定义&#xff0c;这些定义反映了当时计算机网络技术的发展水平。 例如计算机网络早期的一个最简单定义&#xff1a;计算机网络是一些互连的、自…

c#Excel:2.写入Excel表 3.读取Excel表

--写入Excel表-- 该例首先从数据库aq中读取学生信息表staq(参考数据库章节)&#xff0c;然后将学生信息表中的数据写入Excel表格中 &#xff08;1&#xff09;在OfficeOperator类库项目的ExcelOperator类中定义索引器&#xff0c;用于获取Excel表格中的单元格&#xff0c;代码…

队列集使用

文章目录 前言一、功能描述二、功能实现 前言 在实际使用中我们可能要从多个队列中得到数据&#xff0c;就需要队列集。 一、功能描述 创建两个队列Queue1、Queue2。Task1往Queue1写数据、Task2往Queue2写数据。Task3使用QueueSet监测这两个队列。 二、功能实现 创建两个队列…