前言

我们每天写vue代码时都在用defineProps,但是你有没有思考过下面这些问题。为什么defineProps不需要import导入?为什么不能在非setup顶层使用definePropsdefineProps是如何将声明的 props 自动暴露给模板?

举几个例子

我们来看几个例子,分别对应上面的几个问题。

先来看一个正常的例子,common-child.vue文件代码如下:

<template>
<div>content is {{ content }}</div>
</template> <script setup lang="ts">
defineProps({
content: String,
});
</script>

我们看到在这个正常的例子中没有从任何地方import导入defineProps,直接就可以使用了,并且在template中渲染了props中的content

我们再来看一个在非setup顶层使用defineProps的例子,if-child.vue文件代码如下:

<template>
<div>content is {{ content }}</div>
</template> <script setup lang="ts">
import { ref } from "vue";
const count = ref(10); if (count.value) {
defineProps({
content: String,
});
}
</script>

代码跑起来直接就报错了,提示defineProps is not defined

通过debug搞清楚上面几个问题

在我的上一篇文章 vue文件是如何编译为js文件 中已经带你搞清楚了vue文件中的<script>模块是如何编译成浏览器可直接运行的js代码,其实底层就是依靠vue/compiler-sfc包的compileScript函数。

当然如果你还没看过我的上一篇文章也不影响这篇文章阅读,这里我会简单说一下。当我们import一个vue文件时会触发@vitejs/plugin-vue包的transform钩子函数,在这个函数中会调用一个transformMain函数。transformMain函数中会调用genScriptCodegenTemplateCodegenStyleCode,分别对应的作用是将vue文件中的<script>模块编译为浏览器可直接运行的js代码、将<template>模块编译为render函数、将<style>模块编译为导入css文件的import语句。genScriptCode函数底层调用的就是vue/compiler-sfc包的compileScript函数。

一样的套路,首先我们在vscode的打开一个debug终端。

然后在node_modules中找到vue/compiler-sfc包的compileScript函数打上断点,compileScript函数位置在/node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js。在debug终端上面执行yarn dev后在浏览器中打开对应的页面,比如:http://localhost:5173/ 。此时断点就会走到compileScript函数中,我们在debug中先来看看compileScript函数的第一个入参sfcsfc.filename的值为当前编译的vue文件路径。由于每编译一个vue文件都要走到这个debug中,现在我们只想debug看看common-child.vue文件,所以为了方便我们在compileScript中加了下面这样一段代码,并且去掉了在compileScript函数中加的断点,这样就只有编译common-child.vue文件时会走进断点。

compileScript函数

我们再来回忆一下common-child.vue文件中的script模块代码如下:

<script setup lang="ts">
defineProps({
content: String,
});
</script>

我们接着来看compileScript函数的入参sfc,在上一篇文章 vue文件是如何编译为js文件 中我们已经讲过了sfc是一个descriptor对象,descriptor对象是由vue文件编译来的。descriptor对象拥有template属性、scriptSetup属性、style属性,分别对应vue文件的<template>模块、<script setup>模块、<style>模块。在我们这个场景只关注scriptSetup属性,sfc.scriptSetup.content的值就是<script setup>模块中code代码字符串,sfc.source的值就是vue文件中的源代码code字符串。详情查看下图:

compileScript函数内包含了编译script模块的所有的逻辑,代码很复杂,光是源代码就接近1000行。这篇文章我们不会去通读compileScript函数的所有功能,只会讲处理defineProps相关的代码。下面这个是我简化后的代码:

function compileScript(sfc, options) {
const ctx = new ScriptCompileContext(sfc, options);
const startOffset = ctx.startOffset;
const endOffset = ctx.endOffset;
const scriptSetupAst = ctx.scriptSetupAst; for (const node of scriptSetupAst.body) {
if (node.type === "ExpressionStatement") {
const expr = node.expression;
if (processDefineProps(ctx, expr)) {
ctx.s.remove(node.start + startOffset, node.end + startOffset);
}
}
if (node.type === "VariableDeclaration" && !node.declare || node.type.endsWith("Statement")) {
// ....
}
} ctx.s.remove(0, startOffset);
ctx.s.remove(endOffset, source.length); let runtimeOptions = ``;
const propsDecl = genRuntimeProps(ctx);
if (propsDecl) runtimeOptions += `\n props: ${propsDecl},`; const def =
(defaultExport ? `\n ...${normalScriptDefaultVar},` : ``) +
(definedOptions ? `\n ...${definedOptions},` : "");
ctx.s.prependLeft(
startOffset,
`\n${genDefaultAs} /*#__PURE__*/${ctx.helper(
`defineComponent`
)}({${def}${runtimeOptions}\n ${
hasAwait ? `async ` : ``
}setup(${args}) {\n${exposeCall}`
);
ctx.s.appendRight(endOffset, `})`); return {
//....
content: ctx.s.toString(),
};
}

compileScript函数中首先调用ScriptCompileContext类生成一个ctx上下文对象,然后遍历vue文件的<script setup>模块生成的AST抽象语法树。如果节点类型为ExpressionStatement表达式语句,那么就执行processDefineProps函数,判断当前表达式语句是否是调用defineProps函数。如果是那么就删除掉defineProps调用代码,并且将调用defineProps函数时传入的参数对应的node节点信息存到ctx上下文中。然后从参数node节点信息中拿到调用defineProps宏函数时传入的props参数的开始位置和结束位置。再使用slice方法并且传入开始位置和结束位置,从<script setup>模块的代码字符串中截取到props定义的字符串。然后将截取到的props定义的字符串拼接到vue组件对象的字符串中,最后再将编译后的setup函数代码字符串拼接到vue组件对象的字符串中。

ScriptCompileContext

ScriptCompileContext类中我们主要关注这几个属性:startOffsetendOffsetscriptSetupAsts。先来看看他的constructor,下面是我简化后的代码。

import MagicString from 'magic-string'

class ScriptCompileContext {
source = this.descriptor.source
s = new MagicString(this.source)
startOffset = this.descriptor.scriptSetup?.loc.start.offset
endOffset = this.descriptor.scriptSetup?.loc.end.offset constructor(descriptor, options) {
this.s = new MagicString(this.source);
this.scriptSetupAst = descriptor.scriptSetup && parse(descriptor.scriptSetup.content, this.startOffset);
}
}

在前面我们已经讲过了descriptor.scriptSetup对象就是由vue文件中的<script setup>模块编译而来,startOffsetendOffset分别就是descriptor.scriptSetup?.loc.start.offsetdescriptor.scriptSetup?.loc.end.offset,对应的是<script setup>模块在vue文件中的开始位置和结束位置。

descriptor.source的值就是vue文件中的源代码code字符串,这里以descriptor.source为参数new了一个MagicString对象。magic-string是由svelte的作者写的一个库,用于处理字符串的JavaScript库。它可以让你在字符串中进行插入、删除、替换等操作,并且能够生成准确的sourcemapMagicString对象中拥有toStringremoveprependLeftappendRight等方法。s.toString用于生成返回的字符串,我们来举几个例子看看这几个方法你就明白了。

s.remove( start, end )用于删除从开始到结束的字符串:

const s = new MagicString('hello word');
s.remove(0, 6);
s.toString(); // 'word'

s.prependLeft( index, content )用于在指定index的前面插入字符串:

const s = new MagicString('hello word');
s.prependLeft(5, 'xx');
s.toString(); // 'helloxx word'

s.appendRight( index, content )用于在指定index的后面插入字符串:

const s = new MagicString('hello word');
s.appendRight(5, 'xx');
s.toString(); // 'helloxx word'

我们接着看constructor中的scriptSetupAst属性是由一个parse函数的返回值赋值,parse(descriptor.scriptSetup.content, this.startOffset)parse函数的代码如下:

import { parse as babelParse } from '@babel/parser'

function parse(input: string, offset: number): Program {
try {
return babelParse(input, {
plugins,
sourceType: 'module',
}).program
} catch (e: any) {
}
}

我们在前面已经讲过了descriptor.scriptSetup.content的值就是vue文件中的<script setup>模块的代码code字符串,parse函数中调用了babel提供的parser函数,将vue文件中的<script setup>模块的代码code字符串转换成AST抽象语法树

现在我们再来看compileScript函数中的这几行代码你理解起来就没什么难度了,这里的scriptSetupAst变量就是由vue文件中的<script setup>模块的代码转换成的AST抽象语法树

const ctx = new ScriptCompileContext(sfc, options);
const startOffset = ctx.startOffset;
const endOffset = ctx.endOffset;
const scriptSetupAst = ctx.scriptSetupAst;

流程图如下:

processDefineProps函数

我们接着将断点走到for循环开始处,代码如下:

for (const node of scriptSetupAst.body) {
if (node.type === "ExpressionStatement") {
const expr = node.expression;
if (processDefineProps(ctx, expr)) {
ctx.s.remove(node.start + startOffset, node.end + startOffset);
}
}
}

遍历AST抽象语法树,如果当前节点类型为ExpressionStatement表达式语句,并且processDefineProps函数执行结果为true就调用ctx.s.remove方法。这会儿断点还在for循环开始处,在控制台执行ctx.s.toString()看看当前的code代码字符串。

从图上可以看见此时toString的执行结果还是和之前的common-child.vue源代码是一样的,并且很明显我们的defineProps是一个表达式语句,所以会执行processDefineProps函数。我们将断点走到调用processDefineProps的地方,看到简化过的processDefineProps函数代码如下:

const DEFINE_PROPS = "defineProps";
function processDefineProps(ctx, node, declId) {
if (!isCallOf(node, DEFINE_PROPS)) {
return processWithDefaults(ctx, node, declId);
}
ctx.propsRuntimeDecl = node.arguments[0];
return true;
}

processDefineProps函数中首先执行了isCallOf函数,第一个参数传的是当前的AST语法树中的node节点,第二个参数传的是"defineProps"字符串。从isCallOf的名字中我们就可以猜出他的作用是判断当前的node节点的类型是不是在调用defineProps函数,isCallOf的代码如下:

export function isCallOf(node, test) {
return !!(
node &&
test &&
node.type === "CallExpression" &&
node.callee.type === "Identifier" &&
(typeof test === "string"
? node.callee.name === test
: test(node.callee.name))
);
}

isCallOf函数接收两个参数,第一个参数node是当前的node节点,第二个参数test是要判断的函数名称,在我们这里是写死的"defineProps"字符串。我们在debug console中将node.typenode.callee.typenode.callee.name的值打印出来看看。

从图上看到node.typenode.callee.typenode.callee.name的值后,可以证明我们的猜测是正确的这里isCallOf的作用是判断当前的node节点的类型是不是在调用defineProps函数。我们这里的node节点确实是在调用defineProps函数,所以isCallOf的执行结果为true,在processDefineProps函数中是对isCallOf函数的执行结果取反。也就是!isCallOf(node, DEFINE_PROPS)的执行结果为false,所以不会走到return processWithDefaults(ctx, node, declId);

我们接着来看processDefineProps函数:

function processDefineProps(ctx, node, declId) {
if (!isCallOf(node, DEFINE_PROPS)) {
return processWithDefaults(ctx, node, declId);
}
ctx.propsRuntimeDecl = node.arguments[0];
return true;
}

如果当前节点确实是在执行defineProps函数,那么就会执行ctx.propsRuntimeDecl = node.arguments[0];。将当前node节点的第一个参数赋值给ctx上下文对象的propsRuntimeDecl属性,这里的第一个参数其实就是调用defineProps函数时给传入的第一个参数。为什么写死成取arguments[0]呢?是因为defineProps函数只接收一个参数,传入的参数可以是一个对象或者数组。比如:

const props = defineProps({
foo: String
}) const props = defineProps(['foo', 'bar'])

记住这个在ctx上下文上面塞的propsRuntimeDecl属性,后面生成运行时的props就是根据propsRuntimeDecl属性生成的。

至此我们已经了解到了processDefineProps中主要做了两件事:判断当前执行的表达式语句是否是defineProps函数,如果是那么将解析出来的props属性的信息塞的ctx上下文的propsRuntimeDecl属性中。

我们这会儿来看compileScript函数中的processDefineProps代码你就能很容易理解了:

for (const node of scriptSetupAst.body) {
if (node.type === "ExpressionStatement") {
const expr = node.expression;
if (processDefineProps(ctx, expr)) {
ctx.s.remove(node.start + startOffset, node.end + startOffset);
}
}
}

遍历AST语法树,如果当前节点类型是ExpressionStatement表达式语句,再执行processDefineProps判断当前node节点是否是执行的defineProps函数。如果是defineProps函数就调用ctx.s.remove方法将调用defineProps函数的代码从源代码中删除掉。此时我们在debug console中执行ctx.s.toString(),看到我们的code代码字符串中已经没有了defineProps了:

现在我们能够回答第一个问题了:

为什么defineProps不需要import导入?

因为在编译过程中如果当前AST抽象语法树的节点类型是ExpressionStatement表达式语句,并且调用的函数是defineProps,那么就调用remove方法将调用defineProps函数的代码给移除掉。既然defineProps语句已经被移除了,自然也就不需要import导入了defineProps了。

genRuntimeProps函数

接着在compileScript函数中执行了两条remove代码:

ctx.s.remove(0, startOffset);
ctx.s.remove(endOffset, source.length);

这里的startOffset表示script标签中第一个代码开始的位置, 所以ctx.s.remove(0, startOffset);的意思是删除掉包含<script setup>开始标签前面的所有内容,也就是删除掉template模块的内容和<script setup>开始标签。这行代码执行完后我们再看看ctx.s.toString()的值:

接着执行ctx.s.remove(endOffset, source.length);,这行代码的意思是将</script >包含结束标签后面的内容全部删掉,也就是删除</script >结束标签和<style>模块。这行代码执行完后我们再来看看ctx.s.toString()的值:

由于我们的common-child.vuescript模块中只有一个defineProps函数,所以当移除掉template模块、style模块、script开始标签和结束标签后就变成了一个空字符串。如果你的script模块中还有其他js业务代码,当代码执行到这里后就不会是空字符串,而是那些js业务代码。

我们接着将compileScript函数中的断点走到调用genRuntimeProps函数处,代码如下:

let runtimeOptions = ``;
const propsDecl = genRuntimeProps(ctx);
if (propsDecl) runtimeOptions += `\n props: ${propsDecl},`;

genRuntimeProps名字你应该已经猜到了他的作用,根据ctx上下文生成运行时的props。我们将断点走到genRuntimeProps函数内部,在我们这个场景中genRuntimeProps主要执行的代码如下:

function genRuntimeProps(ctx) {
let propsDecls;
if (ctx.propsRuntimeDecl) {
propsDecls = ctx.getString(ctx.propsRuntimeDecl).trim();
}
return propsDecls;
}

还记得这个ctx.propsRuntimeDecl是什么东西吗?我们在执行processDefineProps函数判断当前节点是否为执行defineProps函数的时候,就将调用defineProps函数的参数node节点赋值给ctx.propsRuntimeDecl。换句话说ctx.propsRuntimeDecl中拥有调用defineProps函数传入的props参数中的节点信息。我们将断点走进ctx.getString函数看看是如何取出props的:

getString(node, scriptSetup = true) {
const block = scriptSetup ? this.descriptor.scriptSetup : this.descriptor.script;
return block.content.slice(node.start, node.end);
}

我们前面已经讲过了descriptor对象是由vue文件编译而来,其中的scriptSetup属性就是对应的<script setup>模块。我们这里没有传入scriptSetup,所以block的值为this.descriptor.scriptSetup。同样我们前面也讲过scriptSetup.content的值是<script setup>模块code代码字符串。请看下图:

这里传入的node节点就是我们前面存在上下文中ctx.propsRuntimeDecl,也就是在调用defineProps函数时传入的参数节点,node.start就是参数节点开始的位置,node.end就是参数节点的结束位置。所以使用content.slice方法就可以截取出来调用defineProps函数时传入的props定义。请看下图:

现在我们再回过头来看compileScript函数中的调用genRuntimeProps函数的代码你就能很容易理解了:

let runtimeOptions = ``;
const propsDecl = genRuntimeProps(ctx);
if (propsDecl) runtimeOptions += `\n props: ${propsDecl},`;

这里的propsDecl在我们这个场景中就是使用slice截取出来的props定义,再使用\n props: ${propsDecl},进行字符串拼接就得到了runtimeOptions的值。如图:

看到runtimeOptions的值是不是就觉得很熟悉了,又有name属性,又有props属性。其实就是vue组件对象的code字符串的一部分。name拼接逻辑是在省略的代码中,我们这篇文章只讲props相关的逻辑,所以name不在这篇文章的讨论范围内。

现在我们能够回答前面提的第三个问题了。

defineProps是如何将声明的 props 自动暴露给模板?

编译时在移除掉defineProps相关代码时会将调用defineProps函数时传入的参数node节点信息存到ctx上下文中。遍历完AST抽象语法树后,然后从上下文中存的参数node节点信息中拿到调用defineProps宏函数时传入props的开始位置和结束位置。再使用slice方法并且传入开始位置和结束位置,从<script setup>模块的代码字符串中截取到props定义的字符串。然后将截取到的props定义的字符串拼接到vue组件对象的字符串中,这样vue组件对象中就有了一个props属性,这个props属性在template模版中可以直接使用。

拼接成完整的浏览器运行时js代码

我们再来看compileScript函数中的最后一坨代码;

const def =
(defaultExport ? `\n ...${normalScriptDefaultVar},` : ``) +
(definedOptions ? `\n ...${definedOptions},` : "");
ctx.s.prependLeft(
startOffset,
`\n${genDefaultAs} /*#__PURE__*/${ctx.helper(
`defineComponent`
)}({${def}${runtimeOptions}\n ${
hasAwait ? `async ` : ``
}setup(${args}) {\n${exposeCall}`
);
ctx.s.appendRight(endOffset, `})`); return {
//....
content: ctx.s.toString(),
};

这里先调用了ctx.s.prependLeft方法给字符串开始的地方插入了一串字符串,这串拼接的字符串看着脑瓜子痛,我们直接在debug console上面看看要拼接的字符串是什么样的:

看到这串你应该很熟悉,除了前面我们拼接的nameprops之外还有部分setup编译后的代码,其实这就是vue组件对象的code代码字符串的一部分。

当断点执行完prependLeft方法后,我们在debug console中再看看此时的ctx.s.toString()的值是什么样的:

从图上可以看到vue组件对象上的name属性、props属性、setup函数基本已经拼接的差不多了,只差一个})结束符号,所以执行ctx.s.appendRight(endOffset, }));将结束符号插入进去。

我们最后再来看看compileScript函数的返回对象中的content属性,也就是ctx.s.toString()content属性的值就是vue组件中的<script setup>模块编译成浏览器可执行的js代码字符串。

为什么不能在非setup顶层使用defineProps

同样的套路我们来debug看看if-child.vue文件,先来回忆一下if-child.vue文件的代码。

<template>
<div>content is {{ content }}</div>
</template> <script setup lang="ts">
import { ref } from "vue"; const count = ref(10);
if (count.value) {
defineProps({
content: String,
});
}
</script>

将断点走到compileScript函数的遍历AST抽象语法树的地方,我们看到scriptSetupAst.body数组中有三个node节点。

从图中我们可以看到这三个node节点类型分别是:ImportDeclarationVariableDeclarationIfStatement。很明显这三个节点对应的是我们源代码中的import语句、const定义变量、if 模块。我们再来回忆一下compileScript函数中的遍历AST抽象语法树的代码:

function compileScript(sfc, options) {
// 省略..
for (const node of scriptSetupAst.body) {
if (node.type === "ExpressionStatement") {
const expr = node.expression;
if (processDefineProps(ctx, expr)) {
ctx.s.remove(node.start + startOffset, node.end + startOffset);
}
} if (
(node.type === "VariableDeclaration" && !node.declare) ||
node.type.endsWith("Statement")
) {
// ....
}
}
// 省略..
}

从代码我们就可以看出来第三个node节点,也就是在if中使用defineProps的代码,这个节点类型为IfStatement,不等于ExpressionStatement,所以代码不会走到processDefineProps函数中,也不会执行remove方法删除掉调用defineProps函数的代码。当代码运行在浏览器时由于我们没有从任何地方import导入defineProps,当然就会报错defineProps is not defined

总结

现在我们能够回答前面提的三个问题了。

  • 为什么defineProps不需要import导入?

    因为在编译过程中如果当前AST抽象语法树的节点类型是ExpressionStatement表达式语句,并且调用的函数是defineProps,那么就调用remove方法将调用defineProps函数的代码给移除掉。既然defineProps语句已经被移除了,自然也就不需要import导入了defineProps了。

  • 为什么不能在非setup顶层使用defineProps

    因为在非setup顶层使用defineProps的代码生成AST抽象语法树后节点类型就不是ExpressionStatement表达式语句类型,只有ExpressionStatement表达式语句类型才会走到processDefineProps函数中,并且调用remove方法将调用defineProps函数的代码给移除掉。当代码运行在浏览器时由于我们没有从任何地方import导入defineProps,当然就会报错defineProps is not defined

  • defineProps是如何将声明的 props 自动暴露给模板?

    编译时在移除掉defineProps相关代码时会将调用defineProps函数时传入的参数node节点信息存到ctx上下文中。遍历完AST抽象语法树后,然后从上下文中存的参数node节点信息中拿到调用defineProps宏函数时传入props的开始位置和结束位置。再使用slice方法并且传入开始位置和结束位置,从<script setup>模块的代码字符串中截取到props定义的字符串。然后将截取到的props定义的字符串拼接到vue组件对象的字符串中,这样vue组件对象中就有了一个props属性,这个props属性在template模版中可以直接使用。

关注公众号:前端欧阳,解锁我更多vue干货文章,并且可以免费向我咨询vue相关问题。

为什么defineProps宏函数不需要从vue中import导入?的更多相关文章

  1. Vue中import和require的对比

    Vue中import和require的对比 一.前言 ​ vue框架想必是我们前端朋友们必学的知识点,说它难也没有那么难,说简单也没有那么简单,主要技术就是那么几个,可是里面的细节很多,有些时候我们会 ...

  2. vue中excel导入导出组件

    vue中导入导出excel,并根据后台返回类型进行判断,导入到数据库中 功能:实现js导入导出excel,并且对导入的excel进行展示,当excel标题名称和数据库的名称标题匹配时,则对应列导入的数 ...

  3. Vue中import引入模块路径时的@符号

    1.ES6 模块主要有两个功能:export 和 import export:用户对外输出本模块(一个文件可以理解为一个模块,比如 aaa.js bbb.js)变量的接口 . import:用于在一个 ...

  4. Vue中import from的来源--省略后缀与加载文件夹

    Vue使用import ... from ...来导入组件,库,变量等.而from后的来源可以是js,vue,json.这个是在webpack.base.conf.js中设置的: module.exp ...

  5. vue 中import和export如何一起使用(一)

    前段时间碰到一个问题,vue js中要使用import来加载第三方的js,但是后面使用exports.XXX的话会报exports is not defined.那要怎么解决呢? 首先,我们要了解ES ...

  6. Vue中import '@...'的意思

    转载: https://blog.csdn.net/xiazeqiang2018/article/details/81325996 写项目的时候看到很多导入都是@开头,这是webpack的路径别名,相 ...

  7. Vue中import用法

    1. 引入第三方插件 第三方常用插件参考https://blog.csdn.net/vbirdbest/article/details/86527886 2. 导入 css 文件 import 'iv ...

  8. vue中import引入模块路径中@符号是什么意思

    在编写vue文件中引入模块 import model from "@/common/model"; 这里路径前面的“@”符号表示什么意思? resolve: { // 自动补全的扩 ...

  9. vue中import xxx from 和 import {xxx} from的区别

    1.import xxx from import FunName from ‘../xxx’ 对应js中的引用: export defualt function FunName() { return ...

  10. 在vue中import()语法不能传入变量

    解决办法: 一定要用变量的时候,可以通过字符串模板来提供部分信息给webpack:例如import(`./path/${myFile}`), 这样编译时会编译所有./path下的模块,但运行时确定my ...

随机推荐

  1. linux下面权限的含义以及修改

    linux中的权限 前言 数字权限 三位数字权限 读(r) 写(w) 执行(x) 无权限(-) 三位数字权限的转换 如何设置权限 最高位的含义 四位数字权限 SUID SGID SBIT 四位数字权限 ...

  2. 【二】MADDPG多智能体算法实现(parl)【追逐游戏复现】

    相关文章: [一]MADDPG-单智能体|多智能体总结(理论.算法) [二]MADDPG多智能体深度强化学习算法算法实现(parl)--[追逐游戏复现] 程序链接:直接fork:MADDPG多智能体深 ...

  3. 高可用linux 服务器搭建

    最原始的服务部署,为单点部署,即直接把服务部署在一个服务器上.如果服务器出现故障,或者服务因为某个异常而挂掉,则服务就会发生中断.单点部署出现故障的概率最高. 后来,出现了网关,比如 nginx ko ...

  4. 使用XAG配置GoldenGate在RAC集群环境中的高可用

    背景:本文是根据实际客户测试需求整理,因为客户OGG所在环境只有GI集群,数据库部署在其他位置,所以会有一些差异,但核心思路一致,已完全测试通过,整理出来供大家参考. 1.前期准备 2.创建ACFS文 ...

  5. Linux-rsync命令用法详解

    从字面意思上,rsync 可以理解为 remote sync(远程同步),但它不仅可以远程同步数据(类似于 scp 命令),还可以本地同步数据(类似于 cp 命令).不同于 cp 或 scp 的一点是 ...

  6. 探索C语言中的联合体与枚举:数据多面手的完美组合!

    ​ 欢迎大家来到贝蒂大讲堂 养成好习惯,先赞后看哦~ 所属专栏:C语言学习 贝蒂的主页:Betty's blog 1. 联合体的定义 联合体又叫共用体,它是一种特殊的数据类型,允许您在相同的内存位置存 ...

  7. 用ELK分析每天4亿多条腾讯云MySQL审计日志(3)--下载日志

    当初分析日志,麻烦的是腾讯云的SQL审计日志下载,有下列限制: 1,单次最多1000万条下载 2,单个实例最多生成5条日志文件,多的要先删除以前文件才能生成   腾讯云日志文件生成界面:      一 ...

  8. 解决linux下zip文件解压后中文乱码问题

    最近项目上碰到在windows上压缩了一些图片,图片文件名称都是中文的,scp到linux下用unzip解压后文件名 全是乱码,找了半天解决方案,下面这个亲测可行,特记录一下,与大家分享: 原因: 由 ...

  9. 标准运算符替代函数之operator模块

    # 官网参考示例地址 https://docs.python.org/zh-cn/3/library/operator.html # operator模块提供了一套与python的内置的运算符对应的高 ...

  10. iOS的Runtime知识点繁杂难啃,真的理解它的思想,你就豁然开朗了

    一.Runtime 1.概念: 概念:Runtime是Objective-c语言动态的核心,即运行时.在面向对象的基础上增加了动态运行,达到很多在编译时确定方法推迟到了运行时,从而达到动态修改.确定. ...