基本介绍

vue.js采用数据劫持结合发布-订阅模式的方式,通过Object.defineProperty()来劫持各个属性的getter,setter,在数据变动时发布消息给订阅者,触发响应的监听回调。

主要功能:

  • 实现一个指令解析器Compile
  • 实现一个数据监听器Observer
  • 实现一个Watcher去更新视图

入口函数和Compile编译类实现

HTML代码示例:

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head> <body>
<div id="app">
<h2>{{person.name}} -- {{person.age}}</h2>
<h3>{{person.fav}}</h3>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<h3>{{msg}}</h3>
<div v-text="msg"></div>
<div v-text="person.fav"></div>
<div v-html="htmlStr"></div>
<input type="test" v-model="msg">
<button v-on:click="handleClick">点击我on</button>
<button @click="handleClick">点击我@</button>
<img v-bind:src="imgUrl" />
<img :src="imgUrl" />
</div>
<script src="./MVue.js"></script>
<script>
let vm = new MVue({
el: '#app',
data: {
person: {
name: '编程哥',
age: 18,
fav: '看电影'
},
msg: '学习MVVM实现原理',
htmlStr: '大家学习得怎么样',
imgUrl: './logo.png'
},
methods: {
handleClick () {
console.log(this)
}
}
})
</script>
</body> </html>

MVue.js代码示例:

const compileUtil = {
getValue(expr, vm) {
return expr.split(".").reduce((pre, cur) => {
return pre[cur];
}, vm.$data);
},
text(node, expr, vm) {
// expr:msg 学习MVVM实现原理 // <div v-text="person.fav"></div>
let value;
if (expr.indexOf("{{") !== -1) {
value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getValue(args[1], vm);
});
} else {
value = this.getValue(expr, vm);
}
this.updater.textUpdater(node, value);
},
html(node, expr, vm) {
const value = this.getValue(expr, vm);
this.updater.htmlUpdater(node, value);
},
model(node, expr, vm) {
const value = this.getValue(expr, vm);
this.updater.modelUpdater(node, value);
},
on(node, expr, vm, eventName) {
const fn = vm.$options.methods && vm.$options.methods[expr];
node.addEventListener(eventName, fn.bind(vm), false);
},
bind(node, expr, vm, attrName) {
const value = this.getValue(expr, vm);
this.updater.bindUpdater(node, attrName, value);
},
// 更新的函数
updater: {
textUpdater(node, value) {
node.textContent = value;
},
htmlUpdater(node, value) {
node.innerHTML = value;
},
modelUpdater(node, value) {
node.value = value;
},
bindUpdater(node, attrName, value) {
node.setAttribute(attrName, value);
},
},
}; class Compiler {
constructor(el, vm) {
this.el = this.isElementNode(el) ? el : document.querySelector(el);
this.vm = vm;
// 1.获取文档碎片对象,放入内存中会减少页面的回流和重绘
const fragment = this.node2Fragment(this.el);
// 2.编译模板
this.Compiler(fragment); // 3.追加子元素到根元素
this.el.appendChild(fragment);
} /*
<h2>{{person.name}} -- {{person.age}}</h2>
<h3>{{person.fav}}</h3>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<h3>{{msg}}</h3>
<div v-test="msg"></div>
<div v-html="htmlStr"></div>
<input type="test" v-model="msg">
*/
Compiler(fragment) {
// 获取子节点
const childNodes = fragment.childNodes;
[...childNodes].forEach((child) => {
// console.log(child);
if (this.isElementNode(child)) {
// 是元素节点
// 编译元素节点
// console.log("元素节点", child);
this.compilerElement(child);
} else {
// 文本节点
// 编译文本节点
// console.log("文本节点", child);
this.compilerText(child);
} if (child.childNodes && child.childNodes.length) {
this.Compiler(child);
}
});
} compilerElement(node) {
const attributes = node.attributes;
[...attributes].forEach((attr) => {
const { name, value } = attr;
if (this.isDirective(name)) {
// 是一个指令 v-text v-html v-model v-on:click v-bind:src
const [, directive] = name.split("-"); // text html model on:click bind:src
const [dirName, eventName] = directive.split(":"); // text html model on bind
// 更新数据,数据驱动视图
compileUtil[dirName](node, value, this.vm, eventName); // 删除有指令的标签上的属性
node.removeAttribute("v-" + directive);
} else if (this.isEventName(name)) {
// @click="handleClick"
const [, eventName] = name.split("@");
compileUtil["on"](node, value, this.vm, eventName);
// 删除标签上的绑定的事件
node.removeAttribute("@" + eventName);
} else if (this.isAttrName(name)) {
// :src="imgUrl"
const [, attrName] = name.split(":");
compileUtil["bind"](node, value, this.vm, attrName);
// 删除标签上绑定的属性
node.removeAttribute(":" + attrName);
}
});
}
compilerText(node) {
// {{}} v-text
const content = node.textContent;
if (/\{\{(.+?)\}\}/.test(content)) {
compileUtil["text"](node, content, this.vm);
}
} isAttrName(attrName) {
return attrName.startsWith(":");
} isEventName(attrName) {
return attrName.startsWith("@");
} isDirective(attrName) {
return attrName.startsWith("v-");
} node2Fragment(el) {
// 创建文档碎片
const f = document.createDocumentFragment();
let firstChild;
while ((firstChild = el.firstChild)) {
f.appendChild(firstChild);
}
return f;
}
isElementNode(node) {
// 判断是否是元素节点对象
return node.nodeType === 1;
}
} class MVue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
this.$options = options;
if (this.$el) {
// 1.实现一个数据观察者
// 2.实现一个指令解析器
new Compiler(this.$el, this);
}
}
}

实现Observer劫持并监听所有属性

新建一个Observer.js类,示例代码如下:

class Observer {
constructor(data) {
this.observe(data);
}
observe(data) {
if (data && typeof data === "object") {
Object.keys(data).forEach((key) => {
this.defineReactive(data, key, data[key]);
});
}
}
defineReactive(obj, key, value) {
// 递归遍历劫持
this.observe(value);
Object.defineProperty(obj, key, {
configurable: false,
enumerable: true,
get() {
// 订阅数据变化时,往Dep中添加观察者
return value;
},
set: (newVal) => {
this.observe(newVal);
if (newVal !== value) {
value = newVal;
}
},
});
}
}

html代码引用:

<script src="./Observer.js"></script>
<script src="./MVue.js"></script>

在Mvue.js类中的调用方式:

class MVue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
this.$options = options;
if (this.$el) {
// 1.实现一个数据观察者
new Observer(this.$data);
// 2.实现一个指令解析器
new Compiler(this.$el, this);
}
}
}

实现观察者Watcher和依赖收集器Dep

Observer.js代码中新增类Dep(收集观察者与通知观察者)和Watcher:

class Watcher {
constructor(vm, expr, cb) {
this.vm = vm;
this.expr = expr;
this.cb = cb;
// 先把旧值保存起来
this.oldVal = this.getOldVal();
}
getOldVal() {
Dep.target = this;
let oldVal = compileUtil.getValue(this.expr, this.vm);
Dep.target = null;
return oldVal;
}
update() {
const newVal = compileUtil.getValue(this.expr, this.vm);
if (newVal !== this.oldVal) {
this.cb(newVal);
}
}
} class Dep {
constructor() {
this.subs = [];
}
// 收集观察者
addSub(watcher) {
this.subs.push(watcher);
}
// 通知观察者去更新
notify() {
console.log("观察者", this.subs);
this.subs.forEach((w) => w.update());
}
} class Observer {
constructor(data) {
this.observe(data);
}
observe(data) {
if (data && typeof data === "object") {
Object.keys(data).forEach((key) => {
this.defineReactive(data, key, data[key]);
});
}
}
defineReactive(obj, key, value) {
// 递归遍历劫持
this.observe(value);
const dep = new Dep();
Object.defineProperty(obj, key, {
configurable: false,
enumerable: true,
get() {
// 订阅数据变化时,往Dep中添加观察者
Dep.target && dep.addSub(Dep.target);
return value;
},
set: (newVal) => {
this.observe(newVal);
if (newVal !== value) {
value = newVal;
}
// 告诉Dep通知变化
dep.notify();
}
});
}
}

MVue.js代码:

const compileUtil = {
getValue(expr, vm) {
return expr.split(".").reduce((pre, cur) => {
return pre[cur];
}, vm.$data);
},
getContentValue(expr, vm) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getValue(args[1], vm);
});
},
text(node, expr, vm) {
// expr:msg 学习MVVM实现原理 // <div v-text="person.fav"></div>
let value;
if (expr.indexOf("{{") !== -1) {
// {{person.name}} -- {{person.age}}
value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
new Watcher(vm, args[1], () => {
this.updater.textUpdater(node, this.getContentValue(expr, vm));
});
return this.getValue(args[1], vm);
});
} else {
new Watcher(vm, expr, (newVal) => {
this.updater.textUpdater(node, newVal);
});
value = this.getValue(expr, vm);
}
this.updater.textUpdater(node, value);
},
html(node, expr, vm) {
const value = this.getValue(expr, vm);
new Watcher(vm, expr, (newVal) => {
this.updater.htmlUpdater(node, newVal);
});
this.updater.htmlUpdater(node, value);
},
model(node, expr, vm) {
const value = this.getValue(expr, vm);
// 绑定更新函数 数据=>视图
new Watcher(vm, expr, (newVal) => {
this.updater.modelUpdater(node, newVal);
});
this.updater.modelUpdater(node, value);
},
on(node, expr, vm, eventName) {
const fn = vm.$options.methods && vm.$options.methods[expr];
node.addEventListener(eventName, fn.bind(vm), false);
},
bind(node, expr, vm, attrName) {
const value = this.getValue(expr, vm);
this.updater.bindUpdater(node, attrName, value);
},
// 更新的函数
updater: {
textUpdater(node, value) {
node.textContent = value;
},
htmlUpdater(node, value) {
node.innerHTML = value;
},
modelUpdater(node, value) {
node.value = value;
},
bindUpdater(node, attrName, value) {
node.setAttribute(attrName, value);
}
}
}; class Compiler {
constructor(el, vm) {
this.el = this.isElementNode(el) ? el : document.querySelector(el);
this.vm = vm;
// 1.获取文档碎片对象,放入内存中会减少页面的回流和重绘
const fragment = this.node2Fragment(this.el);
// 2.编译模板
this.Compiler(fragment); // 3.追加子元素到根元素
this.el.appendChild(fragment);
} /*
<h2>{{person.name}} -- {{person.age}}</h2>
<h3>{{person.fav}}</h3>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<h3>{{msg}}</h3>
<div v-test="msg"></div>
<div v-html="htmlStr"></div>
<input type="test" v-model="msg">
*/
Compiler(fragment) {
// 获取子节点
const childNodes = fragment.childNodes;
[...childNodes].forEach((child) => {
// console.log(child);
if (this.isElementNode(child)) {
// 是元素节点
// 编译元素节点
// console.log("元素节点", child);
this.compilerElement(child);
} else {
// 文本节点
// 编译文本节点
// console.log("文本节点", child);
this.compilerText(child);
} if (child.childNodes && child.childNodes.length) {
this.Compiler(child);
}
});
} compilerElement(node) {
const attributes = node.attributes;
[...attributes].forEach((attr) => {
const { name, value } = attr;
if (this.isDirective(name)) {
// 是一个指令 v-text v-html v-model v-on:click v-bind:src
const [, directive] = name.split("-"); // text html model on:click bind:src
const [dirName, eventName] = directive.split(":"); // text html model on bind
// 更新数据,数据驱动视图
compileUtil[dirName](node, value, this.vm, eventName); // 删除有指令的标签上的属性
node.removeAttribute("v-" + directive);
} else if (this.isEventName(name)) {
// @click="handleClick"
const [, eventName] = name.split("@");
compileUtil["on"](node, value, this.vm, eventName);
// 删除标签上的绑定的事件
node.removeAttribute("@" + eventName);
} else if (this.isAttrName(name)) {
// :src="imgUrl"
const [, attrName] = name.split(":");
compileUtil["bind"](node, value, this.vm, attrName);
// 删除标签上绑定的属性
node.removeAttribute(":" + attrName);
}
});
}
compilerText(node) {
// {{}} v-text
const content = node.textContent;
if (/\{\{(.+?)\}\}/.test(content)) {
compileUtil["text"](node, content, this.vm);
}
} isAttrName(attrName) {
return attrName.startsWith(":");
} isEventName(attrName) {
return attrName.startsWith("@");
} isDirective(attrName) {
return attrName.startsWith("v-");
} node2Fragment(el) {
// 创建文档碎片
const f = document.createDocumentFragment();
let firstChild;
while ((firstChild = el.firstChild)) {
f.appendChild(firstChild);
}
return f;
}
isElementNode(node) {
// 判断是否是元素节点对象
return node.nodeType === 1;
}
} class MVue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
this.$options = options;
if (this.$el) {
// 1.实现一个数据观察者
new Observer(this.$data);
// 2.实现一个指令解析器
new Compiler(this.$el, this);
}
}
}

实现双向的数据绑定和Proxy代理

MVue.js代码:

const compileUtil = {
getValue(expr, vm) {
return expr.split(".").reduce((pre, cur) => {
return pre[cur];
}, vm.$data);
},
setValue(expr, vm, inputVal) {
// 将expr的点语法的字符串转换成数组并设置最有一个值为inputVal
return expr.split(".").reduce((pre, cur, index, arr) => {
if (index === arr.length - 1) {
pre[cur] = inputVal;
}
return pre[cur];
}, vm.$data);
},
getContentValue(expr, vm) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getValue(args[1], vm);
});
},
text(node, expr, vm) {
// expr:msg 学习MVVM实现原理 // <div v-text="person.fav"></div>
let value;
if (expr.indexOf("{{") !== -1) {
// {{person.name}} -- {{person.age}}
value = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
new Watcher(vm, args[1], () => {
this.updater.textUpdater(node, this.getContentValue(expr, vm));
});
return this.getValue(args[1], vm);
});
} else {
new Watcher(vm, expr, (newVal) => {
this.updater.textUpdater(node, newVal);
});
value = this.getValue(expr, vm);
}
this.updater.textUpdater(node, value);
},
html(node, expr, vm) {
const value = this.getValue(expr, vm);
new Watcher(vm, expr, (newVal) => {
this.updater.htmlUpdater(node, newVal);
});
this.updater.htmlUpdater(node, value);
},
model(node, expr, vm) {
const value = this.getValue(expr, vm);
// 绑定更新函数 数据=>视图
new Watcher(vm, expr, (newVal) => {
this.updater.modelUpdater(node, newVal);
});
// 视图=>数据=>视图
node.addEventListener("input", (e) => {
// 设置值
this.setValue(expr, vm, e.target.value);
});
this.updater.modelUpdater(node, value);
},
on(node, expr, vm, eventName) {
const fn = vm.$options.methods && vm.$options.methods[expr];
node.addEventListener(eventName, fn.bind(vm), false);
},
bind(node, expr, vm, attrName) {
const value = this.getValue(expr, vm);
this.updater.bindUpdater(node, attrName, value);
},
// 更新的函数
updater: {
textUpdater(node, value) {
node.textContent = value;
},
htmlUpdater(node, value) {
node.innerHTML = value;
},
modelUpdater(node, value) {
node.value = value;
},
bindUpdater(node, attrName, value) {
node.setAttribute(attrName, value);
}
}
}; class Compiler {
constructor(el, vm) {
this.el = this.isElementNode(el) ? el : document.querySelector(el);
this.vm = vm;
// 1.获取文档碎片对象,放入内存中会减少页面的回流和重绘
const fragment = this.node2Fragment(this.el);
// 2.编译模板
this.Compiler(fragment); // 3.追加子元素到根元素
this.el.appendChild(fragment);
} /*
<h2>{{person.name}} -- {{person.age}}</h2>
<h3>{{person.fav}}</h3>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<h3>{{msg}}</h3>
<div v-test="msg"></div>
<div v-html="htmlStr"></div>
<input type="test" v-model="msg">
*/
Compiler(fragment) {
// 获取子节点
const childNodes = fragment.childNodes;
[...childNodes].forEach((child) => {
// console.log(child);
if (this.isElementNode(child)) {
// 是元素节点
// 编译元素节点
// console.log("元素节点", child);
this.compilerElement(child);
} else {
// 文本节点
// 编译文本节点
// console.log("文本节点", child);
this.compilerText(child);
} if (child.childNodes && child.childNodes.length) {
this.Compiler(child);
}
});
} compilerElement(node) {
const attributes = node.attributes;
[...attributes].forEach((attr) => {
const { name, value } = attr;
if (this.isDirective(name)) {
// 是一个指令 v-text v-html v-model v-on:click v-bind:src
const [, directive] = name.split("-"); // text html model on:click bind:src
const [dirName, eventName] = directive.split(":"); // text html model on bind
// 更新数据,数据驱动视图
compileUtil[dirName](node, value, this.vm, eventName); // 删除有指令的标签上的属性
node.removeAttribute("v-" + directive);
} else if (this.isEventName(name)) {
// @click="handleClick"
const [, eventName] = name.split("@");
compileUtil["on"](node, value, this.vm, eventName);
// 删除标签上的绑定的事件
node.removeAttribute("@" + eventName);
} else if (this.isAttrName(name)) {
// :src="imgUrl"
const [, attrName] = name.split(":");
compileUtil["bind"](node, value, this.vm, attrName);
// 删除标签上绑定的属性
node.removeAttribute(":" + attrName);
}
});
}
compilerText(node) {
// {{}} v-text
const content = node.textContent;
if (/\{\{(.+?)\}\}/.test(content)) {
compileUtil["text"](node, content, this.vm);
}
} isAttrName(attrName) {
return attrName.startsWith(":");
} isEventName(attrName) {
return attrName.startsWith("@");
} isDirective(attrName) {
return attrName.startsWith("v-");
} node2Fragment(el) {
// 创建文档碎片
const f = document.createDocumentFragment();
let firstChild;
while ((firstChild = el.firstChild)) {
f.appendChild(firstChild);
}
return f;
}
isElementNode(node) {
// 判断是否是元素节点对象
return node.nodeType === 1;
}
} class MVue {
constructor(options) {
this.$el = options.el;
this.$data = options.data;
this.$options = options;
if (this.$el) {
// 1.实现一个数据观察者
new Observer(this.$data);
// 2.实现一个指令解析器
new Compiler(this.$el, this);
this.proxyData(this.$data);
}
} proxyData(data) {
for (const key in data) {
Object.defineProperty(this, key, {
get() {
return data[key];
},
set(newVal) {
data[key] = newVal;
}
});
}
}
}

HTML示例代码:

<!DOCTYPE html>
<html lang="en"> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head> <body>
<div id="app">
<h2>{{person.name}} -- {{person.age}}</h2>
<h3>{{person.fav}}</h3>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<h3>{{person.td.msg}}</h3>
<div v-text="msg"></div>
<div v-text="person.fav"></div>
<div v-html="htmlStr"></div>
<input type="test" v-model="person.td.msg">
<button v-on:click="handleClick">点击我on</button>
<button @click="handleClick">点击我@</button>
<img v-bind:src="imgUrl" />
<img :src="imgUrl" />
</div>
<script src="./Observer.js"></script>
<script src="./MVue.js"></script>
<script>
let vm = new MVue({
el: '#app',
data: {
person: {
td: {
msg: '系统td'
},
name: '编程哥',
age: 18,
fav: '看电影'
},
msg: '学习MVVM实现原理',
htmlStr: '大家学习得怎么样',
imgUrl: './logo.png'
},
methods: {
handleClick () {
this.person.name = '学习222';
}
}
})
</script>
</body> </html>

核心面试题讲解:阐述一下MVVM响应式原理

Vue是采用数据劫持配合发布订阅模式的方式,通过Object.defineProperty()来劫持各个属性的getter和setter,在数据变动时,发布消息给依赖收集器(Dep),去通知观察者(Watcher)做出对应的回调函数,去更新视图。

MVVM作为绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听数据变化,通过Compile来解析编译模板指令,最终利用Watcher建立Observer、Compile之间的桥梁,达到数据变化=>视图更新;视图交互变化=>数据model变更的双向绑定效果

vue源码解析-实现一个基础的MVVM框架的更多相关文章

  1. 【vuejs深入二】vue源码解析之一,基础源码结构和htmlParse解析器

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. vuejs是一个优秀的前端mvvm框架,它的易用性和渐进式的理念可以使每一个前端开发人员感到舒服,感到easy.它内 ...

  2. 【vuejs深入三】vue源码解析之二 htmlParse解析器的实现

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. 昨天博主分析了一下在vue中,最为基础核心的api,parse函数,它的作用是将vue的模板字符串转换成ast,从而 ...

  3. Vue源码解析---数据的双向绑定

    本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定 核心思想是ES5的Object.defineProperty()和发布-订阅模式 整体结构 改造Vue实例中的dat ...

  4. Vue源码解析之nextTick

    Vue源码解析之nextTick 前言 nextTick是Vue的一个核心功能,在Vue内部实现中也经常用到nextTick.但是,很多新手不理解nextTick的原理,甚至不清楚nextTick的作 ...

  5. 【VUE】Vue 源码解析

    Vue 源码解析 Vue 的工作机制 在 new vue() 之后,Vue 会调用进行初始化,会初始化生命周期.事件.props.methods.data.computed和watch等.其中最重要的 ...

  6. Vue 源码解析:深入响应式原理(上)

    原文链接:http://www.imooc.com/article/14466 Vue.js 最显著的功能就是响应式系统,它是一个典型的 MVVM 框架,模型(Model)只是普通的 JavaScri ...

  7. Vue源码解析(一):入口文件

    在学习Vue源码之前,首先要做的一件事情,就是去GitHub上将Vue源码clone下来,目前我这里分析的Vue版本是V2.5.21,下面开始分析: 一.源码的目录结构: Vue的源码都在src目录下 ...

  8. Vue源码解析之数组变异

    力有不逮的对象 众所周知,在 Vue 中,直接修改对象属性的值无法触发响应式.当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变. 这是什么原因? 原因在于: Vue 的响应式 ...

  9. Fresco源码解析 - 创建一个ImagePipeline(一)

    在Fresco源码解析 - 初始化过程分析章节中, 我们分析了Fresco的初始化过程,两个initialize方法中都用到了 ImagePipelineFactory类. ImagePipeline ...

  10. vue源码解析之observe

    一. vue文档中有"由于 JavaScript 的限制,Vue 不能检测以下数组的变动",是否真是由于JavaScript的限制,还是出于其他原因考虑 当你利用索引直接设置一个数 ...

随机推荐

  1. Django配置为连接到Microsoft SQL Server

    可以将Django配置为连接到Microsoft SQL Server 2019.为此,你需要更改数据库设置中的一些配置选项.首先,确保你已经安装了 django 和 pyodbc 这两个库:   p ...

  2. PowerShell pnpm 报错

    Vue3> pnpm run dev pnpm : 无法加载文件 D:\program files\nodejs\node_global\pnpm.ps1.未对文件 D:\program fil ...

  3. 【译】用 GitHub Copilot 提交注释揭开历史的神秘面纱

    您是否曾经难以理解一个提交在做什么或者为什么要做?在审查或协作代码更改时,您是否希望有更多的清晰度和上下文?如果您的回答是肯定的,那么您会喜欢 GitHub Copilot 为您所做的--生成提交注释 ...

  4. 题解:AT_xmascon21_b Bad Mood

    AT_xmascon21_b Bad Mood 题意 给定你一个 \(n\times m\) 的矩形. 以一条对角线为基础上,制作一个无向图,该图的顶点对应于格子的共有 \((m+1) \times ...

  5. WordPress基础之基本SEO设置

    基础内容,不会涉及过深,在谷歌SEO教程中会做详细的介绍,我这里只简单讲下. 1. SEO介绍 SEO,又名搜索引擎优化(Search Engine Optimization,缩写为SEO)是透过了解 ...

  6. 基于EasyTcp4Net开发一个功能较为完善的去持久化聊天软件

    之前自己写了一篇介绍TCP的一些常用的功能介绍和特征,并且用代码做了示例,最终开发了一个EasyTcp4Net的TCP工具库,其最大的特色就是使用了微软提供的高性能库中的一些数据结构来处理TCP数据. ...

  7. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-60 - 判断元素是否显示 - 下篇

    1.简介 有些页面元素的生命周期如同流星一闪,昙花一现.我们也不知道这个元素在没在页面中出现过,为了捕获这一美好瞬间,让其成为永恒.我们就来判断元素是否显示出现过. 在操作元素之前,可以先判断元素的状 ...

  8. Gartner web3 的未来市场前景

    内行人叫web3 外行人叫元宇宙 元宇宙(Metaverse) Gartner将元宇宙定义为一个由通过虚拟技术增强的物理和数字现实融合而成的集体虚拟共享空间.这个空间具有持久性,能够提供增强沉浸式体验 ...

  9. 【PHP】5版本 过程式操作MySQL

    建立连接和释放连接: # 连接参数 $sever = 'localhost:3309'; $username = 'root'; $password = 'root'; # 调用连接方法,如果失败结束 ...

  10. 【Excel】VBA编程 02 访问MySQL数据库

    1.配置Windows连接驱动ODBC 因为是访问MySQL,则对应的ODBC驱动由MySQL厂商发布 https://dev.mysql.com/downloads/connector/odbc/ ...