前端学习笔记 3:Vue 工程

news2024/9/20 6:14:40

前端学习笔记 3:Vue 工程

上一篇文章介绍了如何在单一 Html 页面中使用 Vue,本文介绍如何从头开始用 Vue 构建一个前端工程项目。

1.环境准备

Vue 框架代码的创建依赖于 Node.js,因此需要先安装 Node.js。

2.创建和启动

2.1.创建

通过以下命令可以创建 Vue 的框架代码:

npm create vue@latest
  • 该命令执行后会先检查是否安装 create-vue 工具,如果没有,就安装。然后再使用 create-vue 创建框架代码。
  • npm(Node Package Manager)是 NodeJS 的包管理工具,类似于 Linux 的 RPM 或 YUM。

在执行过程中会询问是否启用一些功能模块:

Vue.js - The Progressive JavaScript Framework

√ 请输入项目名称: ... vue-project
√ 是否使用 TypeScript 语法? ... 否 / 是
√ 是否启用 JSX 支持? ... 否 / 是
√ 是否引入 Vue Router 进行单页面应用开发? ... 否 / 是
√ 是否引入 Pinia 用于状态管理? ... 否 / 是
√ 是否引入 Vitest 用于单元测试? ... 否 / 是
√ 是否要引入一款端到端(End to End)测试工具? » 不需要
√ 是否引入 ESLint 用于代码质量检测? ... 否 / 是

默认是

安装好框架代码后,还需要进入工程目录安装相关依赖:

cd .\vue-project\
npm install

2.2.启动

执行npm run dev命令可以启动 Vue 项目:

VITE v5.0.10  ready in 2711 ms

➜  Local:   http://localhost:5173/
➜  Network: use --host to expose
➜  press h + enter to show help

就像控制台提示信息中显示的,运行 Vue 项目的 Nodejs 服务端地址默认是 http://localhost:5173/,前往该地址就能看到一个默认的欢迎页面。

除了命令行启动外,还可以用 VSCode 启动:

image-20231231190912113

如果界面上没有 NPM 脚本 这一栏,可以通过这里开启:

image-20231231191034895

3.快速开始

下面简单分析一下工程默认的欢迎页的显示逻辑。

实际上浏览器访问时加载的是index.html文件:

image-20231231192645949

该文件通过<script type="module" src="/src/main.js"></script>这行代码以模块化的方式加载了 JS 文件/src/main.js

image-20231231192815009

main.js中,通过import { createApp } from 'vue'导入了 Vue 的createApp函数,与之前不同的是,因为已经用 npm 工具安装了本地依赖(npm install),所以这里是通过本地导入,而非从在线的 JS 文件导入 Vue 函数。

npm 安装的本地依赖模块位于node_modules目录下。

这里最重要的是import App from './App.vue',从App.vue文件导入了一个 App 对象,之后的代码createApp(App).mount('#app')使用该对象作为参数创建了 Vue 实例。

App.vue文件包含三部分:

image-20231231193442841

事实上,在开发基于 Vue 的前端项目时,我们主要工作是创建和修改 Vue 文件。

作为示例,这里可以创建一个Hello.vue

<template>
    <h1>Hello World!</h1>
</template>
<style>
h1 {
    color: aqua;
}
</style>

要在页面中显示,还需要在main.js中替换导入代码:

import App from './Hello.vue'

在 Vue 文件中同样可以像前文那样为 Vue 实例提供数据和方法:

<script>
export default {
    data() {
        return {
            msg: 'Hello World!'
        }
    }
}
</script>
<template>
    <h1>{{ msg }}</h1>
</template>

这里通过export default {...}指定了Hello.vue文件默认导出的对象内容,该对象会在main.js中导入为App对象,也就是用于创建 Vue 实例的参数对象,因此我们可以在export default {...}定义的默认导出对象中定义datamethods等 Vue 需要的方法或属性。

除了上面的方式外,还可以借助ref函数定义数据或方法:

<script setup>
import {ref} from 'vue'
const msg = ref('Hello World!')
</script>

注意,这里的scriptsetup属性。

4.API 风格

Vue 的 API 有两种风格,这里分别用两种风格编写同样的功能页面进行说明。

4.1.选项式

定义一个 Count.vue 文件:

<script>
export default {
    data() { //定义响应式数据
        return {
            num: 0
        }
    },
    methods: { // 定义 Vue 方法
        count() {
            this.num++;
        }
    },
    mounted(){ // 定义钩子函数
        console.log("Vue 实例已加载...")
    }
}
</script>
<template>
    <button @click="count">count:{{ num }}</button>
</template>

要让该文件生效,还要在Hello.vue中导入:

<script setup>
import {ref} from 'vue'
const msg = ref('Hello World!')
import Count from './Count.vue'
</script>
<template>
    <h1>{{ msg }}</h1>
    <br/>
    <Count/>
</template>

注意 >Count/> 标签,该标签的位置代表 Count.vue 文件中的模版插入的位置。

选项式的优点在于结构简单,便于理解,缺点是代码结构过于死板,不够灵活。

4.2.组合式

<script setup>
import { ref, onMounted } from 'vue'
// 定义响应式数据
const num = ref(0)
// 定义 Vue 方法
function count() {
    num.value++;
}
// 定义钩子函数
onMounted(() => {
    console.log("Vue 实例已加载...")
})
</script>
<template>
    <button @click="count">count:{{ num }}</button>
</template>

在组合式 API 中,需要用ref函数定义响应式数据,用特定的函数(比如 onMounted)定义 Vue 生命周期的钩子方法。特别需要注意的是,组合式 API 中,ref定义的响应式数据有一个value属性,表示响应式数据的值,因此这里在count函数中,自增使用的是num.value++而非num++

5 常用函数

5.1.setup

在组合式 API 中,script标签上使用了一个setup属性,这个属性实际上是一个语法糖,如果不使用这种简便写法,代码可能需要写成下面的形式:

<script>
export default {
    setup() {
        const message = 'Hello World!'
        const logMessage = () => {
            console.log(message)
        }
        return { message, logMessage }
    }
}
</script>
<template>
    {{ message }}
    <button @click="logMessage">log message</button>
</template>

在上面这个示例中,setup是 Vue 生命周期的钩子函数,其中定义的变量和方法通过返回值的方式暴露,这样就可以在模版中使用。

使用语法糖后可以简写为:

<script setup>
const message = 'Hello World!'
const logMessage = () => {
    console.log(message)
}
</script>
<template>
    {{ message }}
    <button @click="logMessage">log message</button>
</template>

不再需要以返回值方式暴露定义的变量和方法。

setup 钩子会在 beforeCreate 钩子之前调用:

image-20240107114330397

可以通过以下代码证实:

<script>
export default {
    setup() {
        console.log('setup()')
    },
    beforeCreate() {
        console.log('beforeCreate()')
    }
}
</script>

5.2.reactive & ref

普通变量改变后是不会触发视图改变的:

<script setup>
let count = 0
const increase = () => {
    count++
}
</script>
<template>
    <button @click="increase">{{ count }}</button>
</template>

点击事件虽然会改变 count 的值,但按钮上的文字并不会同样改变。

如果要让数据改变反应到视图,就需要使用响应式数据,可以通过 vue 提供的reactiveref函数实现。

reactive函数可以接收一个对象,并返回一个响应式数据:

<script setup>
import {reactive} from 'vue'
const counter = reactive({
    num: 0
})
const increase = ()=>{
    counter.num++
}
</script>
<template>
    <button @click="increase">{{ counter.num }}</button>
</template>

注意,reactive只能接收对象,不能处理基础类型。

ref的适用范围比reactive更广,它可以接收基础类型或对象,返回一个响应式对象:

<script setup>
import { ref } from 'vue'
const count = ref(0)
const increase = () => {
    count.value++
}
</script>
<template>
    <button @click="increase">{{ count }}</button>
</template>

注意,ref返回的是一个响应式对象,要对值进行修改,需要使用xxx.value属性,在模版中可以直接使用,不需要使用.value

5.3.computed

computed 可以用于基于已有的响应式数据的计算,返回的响应式数据将随着参与计算的响应式数据的改变而改变:

<script setup>
import { ref, computed } from 'vue';
const num1 = ref(0)
const num2 = ref(0)
const sum = computed(() => {
    return num1.value + num2.value
})
</script>
<template>
    <input type="number" v-model="num1" style="width: 50px;"/>+<input type="number" v-model="num2" style="width: 50px;"/>={{ sum }}
</template>

需要注意的是:

  • 不要在 Computed 函数中加入计算之外的内容
  • 不要直接修改 Computed 函数返回的计算后的变量的值

5.4.watch

5.4.1.监听单个

watch可以监听响应式数据的改变,响应式数据的值发生变化时会触发绑定的函数:

<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const increase = () => {
    count.value++
}
watch(count, (newVal, oldVal) => {
    console.log("count发生改变,newVal:" + newVal + " oldVal:" + oldVal)
})
</script>
<template>
    <button @click="increase">{{ count }}</button>
</template>

5.4.2.监听多个

可以用watch监听多个响应式数据,其中任意一个值发生变化就会触发绑定的函数:

<script setup>
import { ref, watch } from 'vue';
const num1 = ref(0)
const num2 = ref(0)
const sum = ref(num1.value + num2.value)
const history = ref([])
watch([num1, num2], ([newNum1, newNum2], [oldNum1, oldNum2]) => {
    history.value.push({
        num1: oldNum1,
        num2: oldNum2,
        sum: oldNum1 + oldNum2
    })
    sum.value = newNum1 + newNum2
})
</script>
<template>
    <li v-for="his in history">{{ his.num1 }}+{{ his.num2 }}={{ his.sum }}</li>
    <input type="number" v-model="num1" style="width: 50px;" />+<input type="number" v-model="num2" style="width: 50px;" />={{ sum }}
</template>

这里用watch同时监听两个输入框绑定的响应式数据,值发生变化后保存旧值以及计算结果到历史记录,并计算新值的和。这里用watch实现了类似computed的功能,且赋予了更多功能(历史记录)。

5.4.3.immediate

watch可以添加一个immediate参数,这样会在页面加载后立即执行一次回调函数,而不是等到监听的数据改变时才执行:

<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const increase = () => {
    count.value++
}
watch(count, (newVal, oldVal) => {
    console.log("count发生改变,newVal:" + newVal + " oldVal:" + oldVal)
}, { immediate: true })
</script>
<template>
    <button @click="increase">{{ count }}</button>
</template>

可以在控制台看到,页面刷新后会立即输出:

count发生改变,newVal:0 oldVal:undefined

5.4.4.深层监听

默认情况下,watch 是浅层监听,也就是说,当监听的响应式数据是对象而非基本数据时,其中的嵌套属性发生变化,是不会触发监听回调的:

<script setup>
import { ref, watch } from 'vue';
const computer = ref({
    num1: 0,
    num2: 0,
})
const sum = ref(computer.value.num1 + computer.value.num2)
const history = ref([])
watch(computer, (newComputer, oldComputer) => {
    history.value.push({
        num1: oldComputer.num1,
        num2: oldComputer.num2,
        sum: oldComputer.num1 + oldComputer.num2
    })
    sum.value = newComputer.num1 + newComputer.num2
})
</script>
<template>
    <li v-for="his in history">{{ his.num1 }}+{{ his.num2 }}={{ his.sum }}</li>
    <input type="number" v-model="computer.num1" style="width: 50px;" />+<input type="number" v-model="computer.num2"
        style="width: 50px;" />={{ sum }}
</template>

上面代码中的监听实际上并不会生效。

如果要监听对象中嵌套的属性变化,需要使用深层监听

watch(computer, (newComputer, oldComputer) => {
    history.value.push({
        num1: oldComputer.num1,
        num2: oldComputer.num2,
        sum: oldComputer.num1 + oldComputer.num2
    })
    sum.value = newComputer.num1 + newComputer.num2
}, { deep: true })

只需要在watch参数中加入{deep: true}即可。

5.4.5.精确监听

如果要对响应式对象中的某个属性值进行监听,可以使用精确监听

watch(() => computer.value.num1, (newNum1, oldNum1) => {
    history.value.push({
        num1: oldNum1,
        num2: computer.value.num2,
        sum: oldNum1 + computer.value.num2
    })
    sum.value = newNum1 + computer.value.num2
})

此时watch需要两个参数,都是函数,第一个函数返回需要监听的数据,第二个函数是监听触发时的函数。

6 生命周期

组合式 API 下生命周期函数与选项式API下命名有所不同:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以在导出后直接使用:

<script setup>
import { onMounted } from 'vue';
onMounted(()=>{
    console.log("onMounted is called.")
})
</script>

同一个生命周期函数可以使用多次,会在合适的时候依次调用对应的回调:

<script setup>
import { onMounted } from 'vue';
onMounted(()=>{
    console.log("onMounted is called 1.")
})
onMounted(()=>{
    console.log("onMounted is called 2.")
})
</script>

7.父子通信

7.1.父传子

父组件要传递信息给子组件,要将信息附加在子组件的属性上:

<script setup>
import SonVue from './Son.vue';
</script>
<template>
    <div id="app">
        <SonVue message="Hello World!"/>
    </div>
</template>

子组件可以使用编译器宏函数defineProps获取信息:

<script setup>
defineProps({
    message: String
})
</script>
<template>
    <div>{{ message }}</div>
</template>

编译器宏函数不需要导入就可以直接使用。

如果需要在子组件的脚本中使用信息,可以通过宏函数的返回值:

<script setup>
const props = defineProps({
    message: String
})
console.log(props.message)
</script>
<template>
    <div>{{ message }}</div>
</template>

除了传递静态数据,还可以传递响应式数据,这需要父组件进行数据绑定:

<script setup>
import SonVue from './Son.vue';
import { ref } from 'vue';
const count = ref(0)
const doCount = () => {
    setTimeout(() => {
        if (count.value >= 20) {
            return
        }
        count.value++
        doCount()
    }, 1000)
}
doCount()
</script>
<template>
    <div id="app">
        <SonVue :count="count" message="Hello World!" />
    </div>
</template>

子组件可以用同样的方式接收:

<script setup>
const props = defineProps({
    message: String,
    count: Number
})
console.log(props.message)
</script>
<template>
    <div>{{ message }}</div>
    <br/>
    <div>{{ count }}</div>
</template>

刷新后可以看到页面中子组件里count值的变化。

7.2.子传父

首先,需要在父组件中定义一个函数,并绑定到子组件的事件上:

<script setup>
import Son2Vue from './Son2.vue';
const getMessage = (msg) => {
    console.log("Get message from son: " + msg)
}
</script>
<template>
    <div id="app">
        <Son2Vue @get-message="getMessage"></Son2Vue>
    </div>
</template>

在子组件中,可以通过编译器宏函数defineEmits获取一个emits对象,可以通过该对象执行之前绑定的事件:

<script setup>
const emits = defineEmits(['get-message'])
const sendMessage = () => {
    emits('get-message', 'Hello World!')
}
</script>
<template>
    <button @click="sendMessage">send message to father</button>
</template>

7.3.跨层通信

可以使用provideinject函数在跨多层组件之间进行通信:

Top.vue

<script setup>
import { provide } from 'vue';
import MiddleVue from './Middle.vue';
provide('top-msg', 'Hello World!')
</script>
<template>
    <MiddleVue></MiddleVue>
</template>

Middle.vue

<script setup>
import FloorVue from './Floor.vue';
</script>
<template>
    <FloorVue></FloorVue>
</template>

Floor.vue

<script setup>
import { inject } from 'vue';

const topMsg = inject('top-msg')
</script>
<template>
    {{ topMsg }}
</template>

就像上边的示例那样,需要在顶层组件中使用provide(key, value)提供数据,在底层组件中用inject(key)获取数据。

用这种方法同样可以传递响应式数据:

<script setup>
import { provide, ref } from 'vue';
import MiddleVue from './Middle.vue';
provide('top-msg', 'Hello World!')
const count = ref(0)
provide('top-count', count)
setTimeout(() => {
    count.value = 100
}, 3000)
</script>
<template>
    <MiddleVue></MiddleVue>
</template>
<script setup>
import { inject } from 'vue';

const topMsg = inject('top-msg')
const topCount = inject('top-count')
</script>
<template>
    {{ topMsg }}
    <br/>
    {{ topCount }}
</template>

还可以传递函数:

<script setup>
// ...
const increase = () => {
    count.value++
}
provide('top-increase', increase)
</script>
<template>
    <MiddleVue></MiddleVue>
</template>
<script setup>
// ...
const topIncrease = inject('top-increase')
</script>
<template>
    {{ topMsg }}
    <br/>
    {{ topCount }}
    <br/>
    <button @click="topIncrease">increase</button>
</template>

8.模版引用

8.1.引用 DOM 对象

可以通过ref对象获取模版或DOM对象的引用:

<script setup>
import { onMounted, ref } from 'vue';
// 定义 Ref 对象
const h1Ref = ref(null)
// 在组件挂载后使用 Ref 对象
onMounted(()=>{
    console.log(h1Ref.value)
})
</script>
<template>
    <!-- 绑定 Ref 对象 -->
    <h1 ref="h1Ref">Hello World!</h1>
</template>

需要注意的是,需要在组件挂载完毕后才能获取 DOM 对象,因此这里将打印 DOM 对象内容的逻辑放在onMounted钩子中。

8.2.引用子模版

获取模版引用的方式是类似的:

<script setup>
import { onMounted, ref } from 'vue';
import Son3Vue from './Son3.vue';
const sonRef = ref(null)
onMounted(() => {
    console.log(sonRef.value)
})
</script>
<template>
    <Son3Vue ref="sonRef" />
</template>

模版Son3.vue的内容如下:

<script setup>
const message = 'Hello World!'
const logMessage = () => {
    console.log(message)
}
</script>
<template></template>

需要注意的是,默认情况下子模版中定义的变量和方法对父模版是不可见的,所以这里打印的子模版对象中并没有messagelogMessage属性。如果要暴露子模版的属性给父模版,可以使用编译器宏函数defineExpose

<script setup>
const message = 'Hello World!'
const logMessage = () => {
    console.log(message)
}
defineExpose({
    message,
    logMessage
})
</script>
<template></template>

现在就可以在父模版中使用子模版的属性和方法:

<script setup>
import { onMounted, ref } from 'vue';
import Son3Vue from './Son3.vue';
const sonRef = ref(null)
onMounted(() => {
    console.log(sonRef.value)
    sonRef.value.logMessage()
})
</script>
<template>
    <Son3Vue ref="sonRef" />
</template>

9.案例

下面是一个简单案例,用 Vue 工程的方式创建一个ArticleList.vue,用于展示文章列表和搜索框。

在编写这个 Vue 文件之前,需要先在本地安装 axios 的依赖:

npm install axios

下面是ArticleList.vue的完整内容:

<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
// 创建表格对应的响应式数据
const articles = ref([])
// Vue 实例初始化后加载表格数据
onMounted(() => {
    axios.get("http://localhost:8080/article/getAll")
        .then(result => {
            articles.value = result.data
        })
        .catch(error => {
            console.log(error)
        })
})
// 创建搜索条件对应的响应式数据
const conditions = ref({
    category: '',
    state: ''
})
// 定义搜索绑定事件
const search = () => {
    axios.get("http://localhost:8080/article/search", { params: {...conditions.value} })
        .then(result => {
            articles.value = result.data
        })
        .catch(error => {
            console.log(error)
        })
} 
</script>
<template>
    <div>
        文章分类:<input type="text" v-model="conditions.category"/>
        发布状态:<input type="text" v-model="conditions.state"/>
        <button @click="search">搜索</button>
        <br />
        <br />
        <table border="1">
            <tr>
                <td>文章标题</td>
                <td>分类</td>
                <td>发表时间</td>
                <td>状态</td>
                <td>操作</td>
            </tr>
            <tr v-for="article in articles">
                <td>{{ article.title }}</td>
                <td>{{ article.category }}</td>
                <td>{{ article.time }}</td>
                <td>{{ article.state }}</td>
                <td>
                    <button>编辑</button>
                    <button>删除</button>
                </td>
            </tr>
        </table>
    </div>
</template>

注意,这里search函数内调用axios.get方法传参时使用了 ES6 的解构赋值:

{ params: {...conditions.value} }

这样可以让代码更简洁。

当然你也可以使用传统匿名函数分别给属性赋值的方式。

9.1.封装函数

上面的案例有一个缺陷——通过 Axios 调用接口获取数据的部分没有单独封装成函数,这样不利于其它部分的代码进行复用。更好的做法是将这些会被复用的逻辑抽取成单独的函数保存在单独的 JS 文件中,在需要使用的地方导入所需的函数并进行调用。

首先在src目录下创建一个/api/article.js文件:

import axios from 'axios'

export async function getAllArticlesService(){
    return await axios.get("http://localhost:8080/article/getAll")
    .then(result => {
        return result.data
    })
    .catch(error => {
        console.log(error)
    })
}

ArticleList.vue 进行重构:

import { getAllArticlesService } from '@/api/article.js'
// ...
// Vue 实例初始化后加载表格数据
onMounted(async () => {
    articles.value = await getAllArticlesService()
})

需要注意的是,这里使用了awaitasync关键字,这是因为抽取后的函数getAllArticlesService中的axios.get本质上是异步调用,因此没办法同步地获取其返回值,所以需要在调用时添加await关键字将其变成同步调用,而此时进行调用的函数(getAllArticlesService)本身变成了异步,所以要添加async关键字。同理,在调用onMounted钩子方法时,同样需要给作为参数的匿名函数加上async,并且在其中的getAllArticlesService调用加上await

  • 如果不使用awaitasync,就不会有任何数据加载。因为异步调用的关系,响应式数据的赋值语句实际上还没有等到异步调用执行并返回就已经被主线程执行完毕,所以不会有任何实际数据被赋值。
  • 在导入时,如果导入的是本地src目录下的资源,可以使用@/代表src目录。

搜索文章相关调用同样可以进行封装,这里不再赘述。

9.2.axios 实例

上面的案例还存在一个瑕疵,单个 JS 文件中的多次 Axios 调用实际上使用的是相同的服务端域名(HOST),只是具体的接口路径不同。针对这个问题,可以使用 Axios 实例进行简化和统一设置:

import axios from 'axios'
const instance = axios.create({
    baseURL: 'http://localhost:8080'
});

export async function getAllArticlesService() {
    return await instance.get("/article/getAll")
        .then(result => {
            return result.data
        })
        .catch(error => {
            console.log(error)
        })
}

9.3.axios 拦截器

使用axios进行异步请求时,往往需要对响应结果进行相似的处理,比如:

instance.get("/article/getAll")
        .then(result => {
            return result.data
        })
        .catch(error => {
            console.log(error)
        })

对此,可以创建一个单独的 axios 实例进行复用,并且在这个实例上定义请求/响应拦截器对请求或响应进行统一处理。

添加/util/request.js

import axios from 'axios'
const instance = axios.create({
    baseURL: 'http://localhost:8080'
});
instance.interceptors.response.use(
    result => {
        return result.data
    },
    error => {
        console.log(error)
        return Promise.reject(error);
    }
)
export default instance

interceptors.response.use用于设置响应拦截器,接收两个参数,分别为调用成功(HTTP 状态码 2XX)和调用失败(HTTP 状态码不是 2XX)时的回调函数。

article.js中导入:

import request from '@/util/request.js'

export async function getAllArticlesService() {
    return await request.get("/article/getAll")
}

export async function searchService(conditions) {
    return await request.get("/article/search", { params: conditions })
}

因为实例request设置了响应拦截器对结果进行统一处理,所以这里不需要再使用.then.catch进行处理。

实际上内层的awaitasync关键字是可以省略的,只要最外层调用有即可:

import request from '@/util/request.js'

export function getAllArticlesService() {
    return request.get("/article/getAll")
}

export function searchService(conditions) {
    return request.get("/article/search", { params: conditions })
}

谢谢阅读,本文的完整示例代码见这里。

10.参考资料

  • 1.1 ES6 教程 | 菜鸟教程 (runoob.com)
  • 拦截器 | Axios中文文档 | Axios中文网 (axios-http.cn)
  • 黑马程序员前端Vue3小兔鲜电商项目实战

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

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

相关文章

【Docker】可以将TA用于什么,简单了解下

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是是《Docker容器》序列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深…

阿里云2024年优惠券活动大全

阿里云作为中国领先的云计算服务提供商&#xff0c;一直致力于为用户提供优质、高效、安全、可靠的云计算服务。为了更好地回馈用户&#xff0c;阿里云会不定期地推出各种优惠券活动&#xff0c;让用户在享受云计算服务的同时&#xff0c;也能享受到更多的实惠。 1、阿里云云小…

流量超了不容小觑,做到这些完全不用担心

流量告急的小伙伴们总是会有这种烦恼&#xff0c;一不注意就用超了&#xff0c;话费蹭蹭的没了&#xff0c;尤其是到月底的时候&#xff0c;一不留神&#xff0c;套餐说超就超。今天小编就给大家总结几个办法&#xff0c;很有效的让小伙伴们再也不用被“流量突然用完”的恐慌支…

大甩卖——代码全家桶!!!

Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 Python轴承故障诊断 (一)短时傅里叶变换STFT Python轴承故障诊断 (二)连续小波变换CWT_pyts 小波变换 故障-CSDN博客 Python轴承故障诊断 (三)经验模态分解EMD_轴承诊断 pytorch-CSDN博客 Pytorch-…

Linux文件系统与日志分析

目录 一、Linux文件系统 1、inode与block 2、查看inode号码的命令 3、inode包含文件的元信息 4、Linux系统文件的三个主要时间属性 5、用户通过文件名打开文件时系统内部过程 6、inode的大小 7、inode的特点 二、日志 1、日志的功能 2、日志文件的分类 3、系统日志…

JavaScript基础(24)_dom查询练习(一)

<!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><link rel"stylesheet" href"../browser_default_style/reset.css"><title>dom查询练习一</title><style>.text {widt…

c语言:求1-100的奇数和|练习题

一、题目 求1-100以内的奇数和 二、代码截图【带注释】 三、源代码【带注释】 #include <stdio.h> //思路分析 //1、一个除以2&#xff0c;除不尽的&#xff0c;便是奇数 //设计常量N为100&#xff0c;常量随时可以变动 #define N 100 int main() { int sum0;//设…

【Python】不一样的Ansible(一)

不一样的Ansible——进阶学习 前言正文概念Ansible CorePlugins和Modules 插件插件类型编写自定义插件基本要求插件选项文档标准编写插件 添加一个本地插件注册为内置插件指定插件目录 其他一些技巧更改Strategy 结语 前言 Ansible 是一个极其简单的 IT 自动化引擎&#xff0c…

MISRA C 解读

说明&#xff1a;本文由vector官方视频整理而来&#xff0c;原视频链接解读MISRA C_哔哩哔哩_bilibili 1、MISRA 简介 1.1 发起 MISRA (The Motor Industry Software Reliability Association ) 汽车工业软件可靠性联会&#xff0c;起先作为研究车载嵌入式软件制备准则的开发…

FFmpeg读取Assets资源文件

在Android开发中我们经常把原生资源文件放在assets目录下以供需要时读取&#xff0c;通过API提供的resources.assets.open(filename)/openFd(filenam)方法可以非常方便获得InputStream或FileDescriptor&#xff08;文件标识符&#xff09;&#xff0c;但是在使用FFmpeg读取Asse…

CTF-PWN-沙箱逃脱-【seccomp和prtcl-1】

文章目录 啥是seccomp#ifndef #define #endif使用使用格式 seccomp无参数条件禁用系统调用有参数条件禁用系统调用 prctl实例 seccomp_export_bpf 啥是seccomp 就是可以禁用掉某些系统调用&#xff0c;然后只能允许某些系统调用 #ifndef #define #endif使用 #ifndef #defin…

Neo4j恢复

主要记录从备份文件中恢复Neo4j 误删数据 为了模拟误删除场景&#xff0c;我们查询Person&#xff0c;并模拟误操作将其进行删除&#xff1b; match(p:Person) return p Step1&#xff1a; 关闭服务 Step2&#xff1a; 恢复数据 找到Neo4j的数据文件夹&#xff0c;我的安…

Linux第18步_安装“Ubuntu系统下的C语言编GCC译器”

Ubuntu系统没有提供C/C的编译环境&#xff0c;因此还需要手动安装build-essential软件包&#xff0c;它包含了 GNU 编辑器&#xff0c;GNU 调试器&#xff0c;和其他编译软件所必需的开发库和工具。本节用于重点介绍安装“Ubuntu系统下的C语言编译器GCC”和使用。 1、在安装前…

矢量,矢量化的梯度下降以及多元线性回归

一、矢量 定义&#xff1a;按照特定顺序排列的元素集合。可以被视为一维数组。 在机器学习中的作用&#xff1a; 特征表示&#xff1a;在机器学习任务中&#xff0c;输入数据通常以矢量的形式表示。例如&#xff0c;图像可以表示为像素值的矢量&#xff0c;文本可以表示为词…

CodeGPT,你的智能编码助手—CSDN出品

CodeGPT是由CSDN打造的一款生成式AI产品&#xff0c;专为开发者量身定制。 无论是在学习新技术还是在实际工作中遇到的各类计算机和开发难题&#xff0c;CodeGPT都能提供强大的支持。其涵盖的功能包括代码优化、续写、解释、提问等&#xff0c;还能生成精准的注释和创作相关内…

springCould中的gateway-从小白开始【9】

目录 1.&#x1f35f;网关是什么 2.&#x1f37f;gateway是什么 3.&#x1f95a;gateway能什么 4.&#x1f32d;核心概念 5.&#x1f9c2;工作流程 6.&#x1f9c8;实例 7.&#x1f953;gateway网关配置的方式 8.&#x1f373;配置动态路由 9.&#x1f9c7;pred…

软件开发和网络安全哪个更好找工作?

为什么今年应届毕业生找工作这么难&#xff1f; 有时间去看看张雪峰今年为什么这么火就明白了。 这么多年人才供给和需求错配的问题&#xff0c;在经济下行的今年&#xff0c;集中爆发。 供给端&#xff0c;大学生越来越多。需求端&#xff0c;低端工作大家不愿去&#xff0c;高…

springboot集成cas客户端

Background 单点登录SSO(Single Sign ON)&#xff0c;指在多个应用系统中&#xff0c;只需登录一次&#xff0c;即可在多个应用系统之间共享登录。统一身份认证CAS&#xff08;Central Authentication Service&#xff09;是SSO的开源实现&#xff0c;利用CAS实现SSO可以很大程…

年销5万的岚图没有爆款

作者 | 辰纹 来源 | 洞见新研社 3款车一年卖了5万台&#xff0c;这个销量不算多&#xff0c;可对于岚图来说&#xff0c;却很不容易&#xff0c;CEO卢放称这是“一场翻身仗”&#xff0c;在写给全体员工的“家信”中表达谢意&#xff0c;称是“大家的团结奋斗&#xff0c;驱动…

代码随想录刷题题Day28

刷题的第二十八天&#xff0c;希望自己能够不断坚持下去&#xff0c;迎来蜕变。&#x1f600;&#x1f600;&#x1f600; 刷题语言&#xff1a;C Day28 任务 ● 343. 整数拆分 ● 96.不同的二叉搜索树 1 整数拆分 343. 整数拆分 思路&#xff1a; 动态规划 &#xff08;1&a…