为什么defineProps宏函数不需要从vue中import导入?

前言

我们每天写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终端。
debug-terminal

然后在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文件时会走进断点。
debug-common

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字符串。详情查看下图:

common-child

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.sources = new MagicString(this.source)startOffset = this.descriptor.scriptSetup?.loc.start.offsetendOffset = this.descriptor.scriptSetup?.loc.end.offsetconstructor(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抽象语法树

parser

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

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

流程图如下:
progress1

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代码字符串。
for-toString

从图上可以看见此时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的值打印出来看看。
isCallOf

从图上看到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了:
remove-toString

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

为什么defineProps不需要import导入?

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

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()的值:
remove1

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

由于我们的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代码字符串。请看下图:
block-content

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

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

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

这里的propsDecl在我们这个场景中就是使用slice截取出来的props定义,再使用\n props: ${propsDecl},进行字符串拼接就得到了runtimeOptions的值。如图:
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模版中可以直接使用。

progress3

拼接成完整的浏览器运行时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上面看看要拼接的字符串是什么样的:
append

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

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

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

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

为什么不能在非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节点。
if-node-list

从图中我们可以看到这三个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相关问题。

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

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

相关文章

【算法面试题】-06

智能成绩表 题目描述 小明来到学校当老师&#xff0c;需要将学生按考试总分或单科分数进行排名&#xff0c;你能帮帮他吗&#xff1f; 输入描述 第 1 行输入两个整数&#xff0c;学生人数 n 和科目数量 m。 0 < n < 100 0 < m < 10 第 2 行输入 m 个科目名称&…

探索性数据分析EDA的数据可视化

大家好&#xff0c;数据可视化是探索性数据分析的重要组成部分&#xff0c;因为它有助于分析和可视化数据&#xff0c;以获得对数据分布、变量之间的关系和潜在异常值的启示性见解。Python具有丰富的库&#xff0c;可以快速高效地创建可视化。 在Python中&#xff0c;通常使用…

MIT 6.S081---Lab: locks

Memory allocator (moderate) 修改kernel/kalloc.c&#xff0c;修改kmem声明并定义结构体数组&#xff1a; 修改kernel/kalloc.c中的kinit函数&#xff0c;对kmemList进行初始化&#xff1a; 修改kernel/kalloc.c中的kfree函数&#xff0c;获取当前的cpuid并将释放的内存添加到…

C语言知识点总结00-C语言知识点目录

专栏主页&#xff1a; 数据结构算法程序设计基础C语言知识点总结https://blog.csdn.net/seeker1994/category_12585732.html 最优算法100例00-最优算法100例目录 数据结构知识点总结00-知识点目录 ...... C语言知识点目录 程序设计基础C语言知识点总结 1 概述 2 数…

CSS 【详解】响应式布局(明天内容)

响应式布局&#xff1a; 同一页面在不同的屏幕上有不同的布局&#xff0c;即一套代码自适应不同的屏幕。 常用 单位&#xff1a; 像素&#xff08;px&#xff09;&#xff1a;像素是最常用的长度单位&#xff0c;它表示屏幕上的一个物理像素点。例如&#xff0c;width: 200px; …

Java学习笔记------常用API(二)

Object 无有参构造 public Object() 空参构造 成员方法&#xff1a; public String toString() 返回对象的字符串表示 public boolean equals(object obj) 比较两个对象是否相等 Object默认用号比较地址值&#xff0c;需要重写才能比较属性值 protected O…

使用Anaconda创建Python指定版本的虚拟环境

由于工作的需要和学习的需要&#xff0c;需要创建不同Python版本的虚拟环境。 比如zdppy的框架&#xff0c;主要支持的是Python3.8的版本&#xff0c;但是工作中FastAPI主要使用的是3.11的版本&#xff0c;所以本地需要两套Python环境。 决定使用Anaconda虚拟环境管理的能力&…

【小白学机器学习8】统计里的自由度DF=degree of freedom, 以及关于df=n-k, df=n-k-1, df=n-1 等自由度公式

目录 1 自由度 /degree of freedom / df 1.1 物理学的自由度 1.2 数学里的自由度 1.2.1 数学里的自由度 1.2.2 用线性代数来理解自由度&#xff08;需要补充&#xff09; 1.2.3 统计里的自由度 1.3 统计学里自由度的定义 2 不同对象的自由度 2.1 纯公式的自由度&#…

xss.haozi.me靶场“0x0B-0x12”通关教程

君衍. 一、0x0B 实体编码绕过二、0x0C script绕过三、0x0D 注释绕过四、0X0E ſ符号绕过五、0x0F 编码解码六、0x10 直接执行七、0x11 闭合绕过八、0x12 闭合绕过 一、0x0B 实体编码绕过 我们首先构造payload进行测试&#xff1a; 这里我们可以看到全部转为了大写&#xff0c…

2024年3月份实时获取地图边界数据方法,省市区县街道多级联动【附实时geoJson数据下载】

首先&#xff0c;来看下效果图 在线体验地址&#xff1a;https://geojson.hxkj.vip&#xff0c;并提供实时geoJson数据文件下载 可下载的数据包含省级geojson行政边界数据、市级geojson行政边界数据、区/县级geojson行政边界数据、省市区县街道行政编码四级联动数据&#xff0…

一台GTX1080显卡的怪兽,我可不能错过这个机会!

标题&#xff1a;我花了30块钱买了一台电脑主机。 这个配置能赚钱吗&#xff1f; 1. 收购惊喜 那是一个阳光明媚的下午&#xff0c;我在水管修理店里闲逛。 突然&#xff0c;一位老顾客手里拿着一台旧电脑主机匆匆走了进来。 他说&#xff1a;“小王&#xff0c;你能帮我看看…

【算法训练营】周测3

清华大学驭风计划课程链接 学堂在线 - 精品在线课程学习平台 (xuetangx.com) 如果需要答案代码可以私聊博主 有任何疑问或者问题&#xff0c;也欢迎私信博主&#xff0c;大家可以相互讨论交流哟~~ 考题11-3 题目描述 输入格式 输出格式 输出到标准输出。 若可以通关&…

STM32的GPIO初始化配置-学习笔记

简介&#xff1a; 由于刚开始没有学懂GPIO的配置原理&#xff0c;导致后面学习其它外设的时候总是产生阻碍&#xff0c;因为其它外设要使用前&#xff0c;大部分都要配置GPIO的初始化&#xff0c;因此这几天重新学习了一遍GPIO的配置&#xff0c;记录如下。 首先我们要知道芯片…

力扣701. 二叉搜索树中的插入操作

思路&#xff1a;往二叉搜索树中插入一个值&#xff0c;树的结构有多种符合的情况&#xff0c;那我们可以选一种最容易的插入方式&#xff0c;反正只需要插入一个值而已&#xff0c;我们不难发现&#xff0c;不管插入什么值&#xff0c;都可以安排插入到叶子节点上。 再利用二叉…

传统SessionID,Cookie方式与SringSecurity+JWT验证方式

在Spring Boot框架中&#xff0c;可以使用Spring Session来处理会话管理。Spring Session允许开发者在不同的存储后端&#xff08;如Redis、数据库等&#xff09;之间共享和管理会话状态。通过Spring Session&#xff0c;开发者可以轻松地实现会话管理、会话失效以及跨多个节点…

Redux Toolkit

本文作者为 360 奇舞团前端开发工程师 阅读本文章前&#xff0c;需要先了解下 redux 的基本概念与用法&#xff0c;Redux Toolkit 是建立在 Redux 基础之上的工具包&#xff0c;因此需要对 Redux 的基本概念有一定的了解&#xff0c;包括 Action、Reducer、Store、Middleware 等…

【C语言】如何规避野指针

✨✨ 欢迎大家来到莉莉的博文✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 目录 一、概念&#xff1a; 二、野指针成因&#xff1a; 2.1. 指针未初始化 2.2 指针越界访问 3. 指针指向的空间释放 三、如何规避野指针 3.…

专题二 - 滑动窗口 - leetcode 904. 水果成篮 | 中等难度

leetcode 904. 水果成篮 leetcode 904. 水果成篮 | 中等难度1. 题目详情1. 原题链接2. 基础框架 2. 解题思路1. 题目分析2. 算法原理3. 时间复杂度 3. 代码实现4. 知识与收获 leetcode 904. 水果成篮 | 中等难度 1. 题目详情 你正在探访一家农场&#xff0c;农场从左到右种植…

OpenResty使用Lua大全(一)Lua语法入门实战

文章目录 系列文章索引一、OpenResty使用Lua入门1、hello world2、nginx内部变量 二、Lua入门1、简介1、hello world2、基本语法&#xff08;1&#xff09;注释&#xff08;2&#xff09;数据类型&#xff08;3&#xff09;变量&#xff08;4&#xff09;函数&#xff08;5&…

c++基础语法

文章目录 前言命名空间命名空间的使用 缺省参数缺省参数的使用 函数重载函数重载的作用函数重载的使用函数重载原理 引用引用的使用引用的使用场景引用和指针 extern Cinlineauto范围fornullptr 前言 大家好我是jiantaoyab&#xff0c;这篇文章给大家带来的是c语言没有的一些特…