为什么要放弃 $ 语法糖提案

news2024/11/27 14:30:23

《最新,Vue 中的响应性语法糖已废弃》

本文标题中的 $ 语法糖指的就是上文中的响应式语法糖 (Reactivity Transform),那为什么不写 Reactivity Transform 呢?因为这个名实在是太长了…

大家觉得被废弃是因为分不清是正常变量还是响应式变量的居多

下面这个评论说的有一定道理:

Vue 的官网现在已经变成这样了:

以后会不会变成这样:

23次方,一共8种不一样的写法。不对,无虚拟 DOM 模式只能用 Composition API,所以应该不到 8 种写法,你看这不就分裂了嘛!虽说这几种不同的写法也能看懂吧,但每个人都有不同的偏好不同的写法总归不太好。

而且你能保证 Vue 不会又改写法吗?Vue 总是受人启发:受 Angular 启发的双向绑定、受 React 启发的虚拟 DOM、受 React Hooks 启发的 Composition API、受 Svelte 启发的语法糖(一开始用的是 Svelte 的 label 写法)、受 Solid 启发的 Vapor Mode(无虚拟 DOM 模式)

  • 高情商:集百家之长

  • 低情商:方案整合商

开玩笑的哈~ Vue 还是有很多自己的东西的,不过它确实老是抄袭各种框架受各种框架的启发,太杂糅了。今天受这个框架启发做出来这种新 feature、明天又受那个框架启发做出来了另一种新 feature… 估计等 Vue4 出来的时候肯定又是受到了什么其他框架的启发…

我在《无虚拟 DOM 版 Vue 即将到来》这篇文章下看到这样一条评论:

大家觉得这个人说的有没有道理呢?反正我现在感觉 Vue 的各个方案有点太杂糅了,有点像是方案整合商集百家之长,以后指不定就发展成这样了:

当你去网上搜索一些解决方案时,能看到数十种不同的写法是一种什么体验……

不过这条评论真的是高情商:

  • 低情商:Vue 这是啥流行抄啥

  • 高情商:只用 Vue 就能体会到各种流行的技术趋势

跑题了,咱们来说一说 $ 语法糖,它可绝不只有分不清到底是不是响应式变量这一个缺点,它的缺点比优点多得多,我们来具体分析一下。

分析

我们也不要一上来就说这个语法糖有多么多么的不好,如果真这么不好的话尤总也不至于费这么大劲来推动这个提案了对不?这个语法糖在某些情况下确实会大幅改善我们的开发体验,但在另一些情况下不仅不会帮助我们改善体验,反而会增加我们的心智负担,我们来看下面这个案例:

let x = $(0)
let y = $(0)

const update = e => {
 x = e.x
 y = e.y
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))

$watch([x, y], ([x, y]) => console.log(x, y))

看上去很美好是不是,我们终于不用再写 .value 了。

不过像这种逻辑我们通常都会提取出去封装成一个函数,因为有可能有很多个组件都用到了获取鼠标位置这个逻辑,你不想在每个用到该逻辑的组件里都复制一遍相同的逻辑吧?那我们就这样:

// useMouse.js
export const useMouse = (dom = window) => {
  let x = $(0)
  let y = $(0)

  const update = e => {
    x = e.x
    y = e.y
  }
  onMounted(() => dom.addEventListener('mousemove', update))
  onUnmounted(() => dom.removeEventListener('mousemove', update))

  return { x, y }
}
import { useMouse } from './useMouse.js'

let { x, y } = useMouse()

$watch([x, y], ([x, y]) => console.log(x, y))

如果这么写你就会惊讶的发现根本不生效,因为编译过后就相当于:

import { ref } from 'vue'

export const useMouse = (dom = window) => {
 let x = ref(0)
 let y = ref(0)

 const update = e => {
   x.value = e.x
   y.value = e.y
 }
 onMounted(() => dom.addEventListener('mousemove', update))
 onUnmounted(() => dom.removeEventListener('mousemove', update))

 return {
   x: x.value,
   y: y.value
 }
}

这就相当于把一个普通值给 return 出去了,普通值是没法在取值或改值时运行一些其他逻辑的,所以我们还不能把值直接 return 出去,而是把这个响应式变量本身给 return 出去:

import { ref } from 'vue'

export const useMouse = (dom = window) => {
  let x = ref(0)
  let y = ref(0)

  const update = e => {
    x.value = e.x
    y.value = e.y
  }
  onMounted(() => dom.addEventListener('mousemove', update))
  onUnmounted(() => dom.removeEventListener('mousemove', update))

  return { x, y }
}

所以编译必须还要有还原的功能,把响应式的值给还原成响应式变量:

export const useMouse = (dom = window) => {
 let x = $(0)
 let y = $(0)

 const update = e => {
   x = e.x
   y = e.y
 }
 onMounted(() => dom.addEventListener('mousemove', update))
 onUnmounted(() => dom.removeEventListener('mousemove', update))

 return $$({ x, y })
}

但这样又要写 .value 了:

import { useMouse } from './useMouse.js'

let { x, y } = useMouse()

console.log(x.value, y.value)

因为编译器是分析不出来一个函数的返回值到底是不是响应式变量的,所以就又得用一个 $ 来告诉编译器这个函数的返回值有响应式变量:

import { useMouse } from './useMouse.js'

let { x, y } = $(useMouse())

console.log(x, y)

大家不觉得这样很麻烦吗?而且搞出那么多莫名其妙的 $ 、$$ 变量。写一堆这玩意真的没感觉比 .value 好到哪去,而且我们还要随时记得某个变量是响应式的,不然在传递的过程中就有可能失去响应性:

// logValue.js
// 接收一个响应式变量并在其变化时将其打印出来

export const logValue = arg => { // 在提案中并未找到如何用语法糖转换函数的参数
 // 也就是说在这种情况下可能没有什么完美的解决方案 那就又要写 .value 了:
 console.log(arg.value)
 // 不过也不是没有解决方案 我们可以用 $computed 来关联一下:
 let argument = $computed(() => arg.value)
 // 这样就可以不用写 .value 了:
 console.log(argument)
 // 但缺点就是太麻烦了 参数少的时候还可以 参数多的时候还能每个都这么写吗?
 // 而且还要为变量取个不同的名字 这对于我们这些英文不好的人来说简直就是场灾难
 $watch(argument, value => console.log(value))
}
import { logValue } from './logValue.js'

let a = $(0)

logValue(a) // 这么传就错啦
logValue($$(a)) // 一定要写成这样

// 假如有函数是需要响应式变量和普通变量混着传的:
let b = 0
logValue($$(a), b, { a: $$(a), b }) // 写成这样真的很乱

还有需要把 ref 变量传给 reactive 字段的情况:

let a = $(0)

const obj = reactive({ a })

console.log(obj.a) // 0
a++
console.log(obj.a) // 还是 0


// 必须写成这样
const obj = reactive({ a: $$(a) })
console.log(obj.a) // 0
a++
console.log(obj.a) // 1

所以说语法糖只能某些情况下改善我们的开发体验,前提就是你不要把响应式变量传来传去的。但 Vue3 的核心卖点之一不就是 Composition API 么?中文官网管这个叫组合式 API,关键词是组合Vue 还把提取出去的可复用函数叫 Composables,翻译过来就是可组合的,如果不把响应式变量传来传去那还组合个P呀!

这个问题可不是只有 Vue 有,来看下 Solid.js 吧:

import { createSignal } from 'solid'

export const useMouse = (dom = window) => {
 const [x, setX] = createSignal(0)
 const [y, setY] = createSignal(0)

 dom.addEventListener('mousemove', ({ x, y }) => {
   setX(x)
   setY(y)
 })

 return {
   x: x(),
   y: y()
 }
}

同样会有响应式值与响应式变量的问题,只不过就是把 .value 变成了 ()

// 假如有个响应式变量 a

// 打印的是响应式值
console.log(a.value) // Vue
console.log(a()) // Solid

//打印的是响应式变量
console.log(a) // Vue & Solid

是不是看过很多文章说 Solid.js 和 React Hooks 很像、写起来很舒服、什么比 React 还 react 之类的文章?实际上真的就只是 API 设计的相似而已,只要我们想,我们同样也可以把 Vue 的 API 封装成 React 那样:

import { ref } from 'vue'

const useState = value => {
 const result = ref(value)
 const getter = () => result.value
 const setter = newValue => result.value = newValue
 return [getter, setter]
}

const [num, setNum] = useState(0)
setNum(1)

那是不是这样封装一下,Vue 也变得比 React 还 react 了?应该不难看出这只是在自欺欺人罢了,我们传值时照样还得区分到底应该传的是响应式变量本身还是响应式变量的值。

Vue2 为何没这个问题

不知大家有没有思考过:为什么 Vue2 时代大家从来就没听说过丢失响应性、没听过要出什么语法糖之类的问题呢?听过最多有关于语法糖的可能就是 v-model 的双向绑定功能其实就是 @input="xxx" + :value="xxx" 的语法糖。

这是因为 Vue2 时代用的都是 this.xxx,咱们所有的响应式变量全都挂载到了 this 上。取值时 this.xxx 会触发 getter、改值时 this.xxx = xxx 会触发 setter

你可以简单的理解成这样:

// 用 Vue3 来写一段伪代码
import { reactive, watchEffect } from 'vue'

const this = reactive({
  a: 1,
  b: 2,
  c: 3
})

watchEffect(() => console.log(this.a))
this.a++

当然这只是一段伪代码,真这么写是会报错的:

因为 this 是一个关键字,正因为它是一个关键字所以咱们用 this.xxx 才会显得这么的自然。而我们现在的响应式变量都需要自己起名,自己起的名不是关键字,所以用 xx.xxx 就老觉得麻烦,就老想给它解构:

import { reactive, watchEffect, toRefs } from 'vue'

const user = reactive({
  name: 'AngularBaby',
  age: 34,
  beautiful: true
})

console.log(user.name) // 有些人觉得这样写很麻烦
const { name } = user  // 就老想给它解构
console.log(name)      // 结果就是失去了响应性

// 想要保持响应性 写法就变得更麻烦了
const { name } = toRefs(user)
console.log(name.value) 

而且之前用 this 还有一个显著的好处就是只要写法正确,操作 this 上的属性就不用担心响应式的问题,没有那么多心智负担。甚至有人会简单的理解为只要是 this.xxx 就一定会有响应:

export default {
  data () {
    return { a: 1 }
  },
  mounted () {
    this.a = 2  // 没有心智负担 因为我们知道自己是在改变 this 上的属性
    this.a++    // 正确改变 this 上的属性就会存在响应

    let b = 2   // 也没有心智负担 因为我们知道这不是 this 上的属性
    b++         // 我们不会期待这段代码会有任何的响应
  }
}

这样很容易区分哪些是响应式变量而哪些不是,即使有人真的写成了这样:

export default {
  data () {
    return { a: 1 }
  },
  mounted () {
    let { a } = this
    a++  // 我们不会期待这段代码会有任何的响应
  }
}

这里也很容易能够看出来我们这样并没有修改 this 上的属性,所以并不会正确响应也是理所应当的一件事。

还有复用逻辑,Vue2 时代有很多人用 Mixins 来复用逻辑:

import mouse from 'mouse.mixin.js'
import position from 'position.mixin.js'

export default {
  mixins: [mouse, position],
  mounted () {
    this.x  // 哪来的 x ?
    this.y  // 哪来的 y ?
            // 除了 xy 还有没有其他的未知 this.xxx ?
  }
}

可以看到 Mixins 存在很多的弊端,比方说数据来源不清晰、容易产生冲突变量之类的。如果不去看源码的话谁能知道 this.x 到底是 mouse 中的 x 还是 position 的 x 呢?正是由于 Vue2 没有一个完美的复用机制,所以尤大才下定决心将 Vue3 改造成函数式。但函数式没了 this 就又失去了 Vue2 时期的那种… 我不知该怎么形容 Vue2 时期的 this.xxx 哈,舒服?自然?反正我是比较喜欢 this.xxx 这种写法的,虽然这种写法是受 Angular 启发(集百家之长)

而且我还比较喜欢的一点就是一些全局挂载的属性:

this.$el
this.$refs
this.$nextTick(() => { /* ... */ })

直接 this.$xxx 就出来了,不用引,既方便又快捷。当然这种方式也有不少坏处,比方说容易被覆盖、不利于 Tree Shaking 之类的…

但我还真的蛮喜欢这种写法的:

// main.js
import Vue from 'vue'

Vue.prototype.$toast = msg => { /* ... */ }

this.$toast('Success!')

如今就会变得就稍麻烦一些:

import toast from './toast.js'

toast('Success!')

虽说后者其实更好,但有没有这样一种可能:既恢复到 Vue2 时期用 this 的便捷、又能享受到 Vue3 组合式的好处:

// 幻想中的写法

this.$data.a = 1  // 相当于 Vue2 时期的 data: { a: 1 } 最终会挂载到 this 上变成 this.a
this.$computed.b = () => this.a * 2  // 相当于 Vue2 时期的 computed: { b () { return this.a * 2 } } 最终会挂载到 this 上变成 this.b

this.$watch.b = value => console.log(value)  // 相当于 Vue2 时期的 watch: { b: value => console.log(value) }

let timer
this.$mounted = () => {
  timer = setInterval(() => this.a++, 1000)
}
this.$unMounted = () => clearInterval(timer)

复用逻辑:

// 幻想中的写法

import useMouse from './useMouse.js'

({ x: this.$computed.x, y: this.$computed.y } = useMouse())
this.$effect = () => console.log(this.x, this.y)

// 如果用数组解构将会更加的便捷
[this.x, this.y] = useMouse()
this.$effect = () => console.log(this.x, this.y)

这样我们的心智负担就又能回到 this 时期了:只要改变 this 属性就会存在响应,否则就无响应,那这个方案有实现的可能吗?在 ES5 时代无可能,但在 ES6 Proxy 的加持下我认为还是可以实现的,那么接下来我们就来试一下。

实验

首先我们回顾一下 Vue3.0 没有 setup 语法糖时期的写法:

<template></template>

<script>
import { defineComponent } from 'vue'

export default defineComponent({
  setup () {
    console.log(this) // undefined
  }
})
</script>

原版的 this 指向为 undefined,那我们怎么改变它的指向呢?我们可以自己写一个 defineComponent

// defineComponent.js

import { defineComponent, reactive } from 'vue'

export default options => {
  const { setup } = options
  if (typeof setup === 'function') {
    options.setup = setup.bind(reactive({}))
  }
  return defineComponent(options)
}

这样 setup 的指向就变成了 reactive({}),当我们在操作 this 的时候就相当于在操作 reactive({})。但这样并不能满足我们的需求,我们想要的是当我们 this.$data.a 的时候会在 this 上挂载个 a 属性,所以我们要把 reactive 换成一个 Proxy

// createThis.js
import { defineComponent, reactive } from 'vue'

const createData = target => new Proxy({}, {
  get: (_, key) => Reflect.get(target, key),
  set (_, key, value) {
    if (Reflect.getOwnPropertyDescriptor(target, key)) {
      console.error(`this.$data.${key} is already defined!`)
      return false
    }
    return Reflect.set(target, key, value)
  }
})

export default () => {
  const that = reactive({})
  const $data = createData(that)
  return new Proxy(that, {
    get (target, key) {
      if (key === '$data') {
        return $data
      }
      return Reflect.get(target, key)
    },
    set (target, key, value) {
      if (key === '$data') {
        return console.warn('this.$data is readonly!')
      }
      return Reflect.set(target, key, value)
    }
  })
}
// defineComponent.js

import { defineComponent } from 'vue'
import createThis from './createThis.js'

export default options => {
  const { setup } = options
  if (typeof setup === 'function') {
    const that = createThis()
    options.setup = (...args) => {
      setup.apply(that, args)
      return that
    }
  }
  return defineComponent(options)
}

也就是说我们利用 Proxy 来把 $data 给代理出去了,当我们访问 $data 的时候其实已经是另一个代理对象了,在这个代理对象上设置的属性全部都设置到 this 上。this 现在就相当于 reactive({}),所以 this.$data.a = 1就相当于 reactive({ a: 1 }),我们来试一下:

<template>
  <h1>{{ a }}</h1>
</template>

<script>
import { defineComponent as vueDefineComponent, reactive } from 'vue'

export default defineComponent({
  setup () {
    this.$data.a = 1
    setInterval(() => this.a++, 1000)
  }
})

function createData (target) {
  return new Proxy({}, {
    get: (_, key) => Reflect.get(target, key),
    set (_, key, value) {
      if (Reflect.getOwnPropertyDescriptor(target, key)) {
        console.error(`this.$data.${key} is already defined!`)
        return false
      }
      return Reflect.set(target, key, value)
    }
  })
}

function createThis () {
  const that = reactive({})
  const $data = createData(that)
  return new Proxy(that, {
    get (target, key) {
      if (key === '$data') {
        return $data
      }
      return Reflect.get(target, key)
    },
    set (target, key, value) {
      if (key === '$data') {
        return console.warn('this.$data is readonly!')
      }
      return Reflect.set(target, key, value)
    }
  })
}

function defineComponent (options) {
  const { setup } = options
  if (typeof setup === 'function') {
    const that = createThis()
    options.setup = (...args) => {
      setup.apply(that, args)
      return that
    }
  }
  return vueDefineComponent(options)
}
</script>

<style>
html, body, #app {
  height: 100vh
}
#app {
  display: grid;
  place-items: center;
}
</style>

完美运行,只要你能搞懂上面的那段代码,那么接下来的 $computed$watch$watchEffect$readonly$shallow$nextTick$mounted$unMounted 等一大堆 API 相信你也知道该怎么做了,我就不在这里占用过多的篇幅了。

不过这玩意还是有一定的弊端,只能拿来当玩具玩玩所以我才懒得实现的那么全乎。那么它究竟有多大的弊端呢?

弊端

Vue3 比 Vue2 更优秀的一个点是支持 tree shaking,在你仅仅只用了 Vue 的某几项功能的情况下打包体积会小很多。但我们刚刚的做法无疑是开了历史的倒车,又回去了!并且随着 Vue3.2 的崛起,setup 语法糖得到了大多数人的认可,因为它确实很方便。但这样我们就无法修改 this 指向了:

<template>
  <h1>{{ a }}</h1>
</template>

<script setup>
this.$data.a = 1 // 怎么修改 this 指向
</script>

有人可能会说加个函数不就得了:

<template>
  <h1>{{ a }}</h1>
</template>

<script setup>
import setup from './setup.js'

setup(() => {
  this.$data.a = 1
})
</script>

这样虽然可以修改 this 指向,但随之而来的就是 <template> 模板里面访问不到 a 这个变量了,除非我们写成这样:

<template>
  <h1>{{ a }}</h1>
</template>

<script setup>
import { toRefs } from 'vue'
import setup from './setup.js'

const { a, b, c, d, e, f } = toRefs(setup(() => {
  this.$data.a = 1
  this.$data.b = 2
  this.$data.c = 3
  this.$data.d = 4
  this.$data.e = 5
  this.$data.f = 6
}))
</script>

我相信没人会愿意写成这样,所以我们必须借助 babel 插件来完成编译,思路是把 this 编译成 reactive({}),类似于下面这样:

// 编译前
this.$data.a = 1
this.$data.b = 2
this.$data.c = 3
this.$data.d = 4
this.$data.e = 5
this.$data.f = 6

// 编译后
import { reactive } from 'vue'
import createThis from 'createThis.js'
import createData from 'createData.js'

const that = createThis(reactive({}))
createData(that)

that.$data.a = 1
that.$data.b = 2
that.$data.c = 3
that.$data.d = 4
that.$data.e = 5
that.$data.f = 6

不过这样还是会引入我们刚刚写的那些代码,虽然代码量并不高,但如果压根就不引入任何额外的代码才好,所以如果能编译成这样才是最完美的:

// 编译前
this.$data.a = 1
this.$data.b = 2
this.$data.c = 3
this.$data.d = 4
this.$data.e = 5
this.$data.f = 6

console.log(this.a)

// 编译后
import { reactive } from 'vue'

const that = reactive({
  a: 1,
  b: 2,
  c: 3,
  d: 4,
  e: 5,
  f: 6
})

console.log(that.a)

但如果这样编译的话又有可能发生如下情况:

import useXxx from './useXxx'

this.$data.a = 1

useXxx.call(this)

这样会被编译成:

import { reactive } from 'vue'
import useXxx from './useXxx'

const that = reactive({ a: 1 })

useXxx.call(that)

万一这个 useXxx 里写了这样一段逻辑:

// useXxx.js

expurt default function () {
  this.$watch.a = value => console.log(value)
}

这样就不会按照我们所期待方式去运行了,因为在编译后就相当于:

// 伪代码

const obj = reactive({ a: 1 })

useXxx.call(obj)

function useXxx () {
  this.$watch.a = value => console.log(value)
}

这样会直接报错,因为 reactive({ a: 1 }).$watch 是 undefinedundefined.a 会报错,所以并没有特别完美的解决方案。最好是检测如果没把 this 作为参数传走或者没有哪个函数用了 fn.call(this) 来把 this 指向当前上下文的话,就按照最完美的方式(不引入任何杂七杂八的代码)编译。否则就引入一点运行时,反正也没多少:

// 编译前
import useMouse from 'useMouse'

this.$data.a = 1
this.$watch.a = value => console.log(value)

this.$mounted = () => window.addEventListener(...)
this.$unmounted = () => window.removeEventListener(...)

[this.x, this.y] = useMouse.call(this)

// 编译后
import { reactive } from 'vue'
import createThis from 'createThis'
import createData from 'createData'
import createWatch from 'createWatch'
import createMounted from 'createMounted'
import createUnmounted from 'createUnmounted'

const that = createThis(reactive({
  a: 1
}))
createData(that)
createWatch(that)
createMounted(that)
createUnmounted(that)

that.$data.a = 1
that.$watch.a = value => console.log(value)

that.$mounted = () => window.addEventListener(...)
that.$unmounted = () => window.removeEventListener(...)

[that.x, that.y] = useMouse.call(that)

但仔细一想还是有可能有 bug,比方说你这个组件里没用到 this.$readonly,但 useMouse 用了的话,那岂不是又要报错。那就在 Vue 组件之外也编译,如果在外面有用到 this.$xxx,那就在相应的位置:

// 编译前
export default function useMouse () {
  this.$readonly.a = 1
}

// 编译后
import createReadonly from 'createReadonly'

export default function useMouse () {
  createReadonly(this)
  this.$readonly.a = 1
}

缺陷

这种写法不仅仅是有弊端,还有一个非常严重的缺陷。虽然刚刚我们设想了一下用编译的方案来解决弊端的可能,但有个最大的缺陷是连编译都无法解决的。这个最大的缺陷就是对 TS 的支持,如果不用 TS 还好,但如果你的项目里有用 TS,那么这种写法就完全没法用:

不知怎么才能让 TS 也支持这种想法,查了国内外很多资料,最后找到了这两篇文章:

《TypeScript plugin 实践 —— 类型,编辑器与业务价值》

《基于 TypeScript 的开发者体验增强 - 朝夕相处却始终被忽视的领域》

也不知道这个 TS Language Service 有没有可能能够实现我们这种语法,感兴趣的小伙伴可以好好研究一下。我们目前只实现了运行时方案,但编译方案才是未来。写这篇文章的目的是希望给大家提供一个思路,看看大家觉得这个想法怎么样。万一大家觉得这个想法非常好,把它推给官方,官方实现了呢?

当然上述的那些话也可能仅仅只是过于美好的想象,现实很有可能是压根儿就没有人对这个想法感兴趣,官方也认为这是在开历史的倒车并且对 TS 支持不好不予实现。

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

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

相关文章

chatgpt赋能Python-pycharm关联anaconda

使用PyCharm关联Anaconda打造高效Python开发环境 PyCharm是一个流行的Python集成开发环境&#xff08;IDE&#xff09;&#xff0c;而Anaconda则是一个免费、开源的Python发行版。PyCharm与Anaconda的结合使得开发者能够在一个统一的环境中开发Python应用程序。本文将探讨如何…

Vivado综合属性系列之五 USE_DSP

目录 一、前言 二、USE_DSP 2.1 工程代码 2.2 结果 2.3 参考资料 一、前言 ​对于逻辑运算&#xff0c;加、减、乘、除等可以使用逻辑资源LUT实现&#xff0c;也可以使用专用的运算资源DSP来实现。在一些计算量较大时&#xff0c;使用DSP可以节省许多逻辑资源&#xff0c;…

【V2G】电动汽车接入电网优化调度研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

目录显示变应用程序需要权限的解决方法

在平日使用电脑的过程中&#xff0c;好多人通常会一些状况&#xff0c;其中最常见的就是目录变应用程序了&#xff0c;许多用户都不确定电脑目录变应用程序打不开怎么办&#xff0c;这种状况不要焦急&#xff0c;下面小编就来给大伙讲一讲电脑目录骤然目录的方法&#xff0c;一…

Windows上配置访问共享

Windows上配置访问共享 方法一&#xff1a;FTP共享方法二&#xff1a;文件共享 方法一&#xff1a;FTP共享 参考该链接&#xff1a;https://blog.csdn.net/qq_41101213/article/details/94214121 方法二&#xff1a;文件共享 本案例是在windows防火墙开启的状态下配置的 给本…

Redis主从

搭建主从架构 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离。 5之前叫slave&#xff0c;之后叫replica好像 共包含三个节点&#xff0c;一个主节点&#xff0c;两个从节点。 这里我们会…

chatgpt赋能Python-pandas归一化

深入了解Pandas归一化&#xff1a;什么是归一化&#xff0c;为什么需要归一化&#xff1f; Pandas是Python中最常用的数据分析库之一。Pandas提供了许多功能&#xff0c;使数据分析变得更加轻松。其中&#xff0c;归一化是数据分析中一个重要的概念&#xff0c;它将数据缩放到…

chatgpt赋能Python-pycharm如何横向输出

PyCharm如何横向输出 如果你是Python开发者&#xff0c;那么你一定会使用到PyCharm这个IDE&#xff0c;而且PyCharm的强大也是得到大家的一致认可的。但是&#xff0c;有没有遇到过需要横向输出的情况呢&#xff1f;比如输出一个很长的字符串&#xff0c;但是你希望它能够在你…

【利用AI刷面试题】AI:十道不常见的TypeScript面试题

文章目录 前言&#x1f60f;以下是关于 TypeScript 的一些偏僻的面试题&#x1f61d;1. 泛型约束中的 "extends" 关键字有哪些用法和含义&#xff1f;2. 什么是交叉类型&#xff08;Intersection Types&#xff09;&#xff1f;如何在 TypeScript 中定义和使用它们&a…

chatgpt赋能Python-pycharm如何加库

Pycharm如何加库&#xff1a;A Complete Guide Pycharm是一款强大的Python集成开发环境(IDE)&#xff0c;它提供了许多有用的工具和功能来简化Python开发过程。尽管Pycharm已经内置了许多功能和工具&#xff0c;但我们在开发过程中仍然需要使用第三方库来提高效率。在本文中&a…

MySQL 常用/见函数

目录 日期函数 字符串函数 数学函数 其它函数 日期函数 获得时分秒&#xff1a; select current_time(); ---------------- | current_time() | ---------------- | 13 :51:21 | ---------------- 日期就是date 获得时间戳&#xff1a; select current_timestamp()…

chatgpt赋能Python-pycharm中添加库

PyCharm中添加库&#xff1a;让你的Python开发更加高效 Python是一种高级编程语言&#xff0c;因其易学易用的特性被广泛应用于Web开发、数据分析、人工智能等领域。PyCharm是Python开发的一种强大的IDE&#xff08;集成开发环境&#xff09;&#xff0c;它提供了各种有用的功…

【P24】JMeter 正则表达式用户参数(RegEx User Parameters)

文章目录 一、准备工作二、测试计划设计 一、准备工作 慕慕生鲜&#xff1a; http://111.231.103.117/#/login 进入网页后&#xff0c;登录&#xff0c;页面提供了账户和密码 搜索框输入“虾” 右键检查或按F12&#xff0c;打开调试工具&#xff0c;点击搜索 二、测试计划设…

Hantek 5000系列示波器原理图研究

Hantek 5000系列是Hantek&#xff08;汉泰&#xff09;2010年左右推出的入门型号示波器&#xff0c;最高采样率1GSa/s&#xff0c;带宽200M。2010年的时候&#xff0c;EEVblog论坛的大佬tinhead给出了该系列示波器的详细原理图&#xff08;下载&#xff09;&#xff0c;图的可读…

RapidVideOCR(视频硬字幕提取工具)新增exe版

引言 考虑到提取视频字幕的小伙伴大多不是程序员行当&#xff0c;为了降低使用门槛&#xff0c;特此推出界面版的RapidVideOCR Desktop. RapidVideOCR Desktop需要搭配VideoSubFinder使用。它们两个关系如下图所示&#xff1a; #mermaid-svg-md1FaKkTWKdZahtd {font-family:&q…

Git常用命令rebase

Git常用命令rebase 1、git常用命令rebase rebase 会把你当前分支的 commit 放到公共分支的最后面&#xff0c;所以叫变基&#xff0c;就好像你从公共分支又重新拉出来这个 分支一样。 例如如果你从 master 拉了个 feature 分支出来&#xff0c;然后你提交了几个 commit&…

VScode添加右键运行、并设置每次运行前都清屏即去除之前的输出

一、添加右键运行 下载安装运行插件即可 二、运行前清屏 在运行插件中设置 找到Code-runner: Clear Previous Output&#xff0c;把√打上即可

同样都是PoE交换机,标准PoE交换机、非标准PoE交换机和非PoE交换机三者到底有啥区别?

网络交换机是企业和组织中构建局域网、企业网络和数据中心网络的重要组成部分。其中最常见的类型之一是PoE交换机。PoE交换机是一种允许通过网络线路提供电源和数据传输的交换机&#xff0c;这种技术可以为设备提供电力&#xff0c;避免了需要附加电源的麻烦。 本文将介绍PoE交…

从零制作操作系统——环境搭建以及HelloWorld

从零制作操作系统——环境搭建以及HelloWorld 起因 最近在学习操作系统&#xff0c;尝试自己照着书搓一个出来。 环境搭建 基础环境 我们的操作系统在x86平台的Linux下进行编写和运行。编辑器用的VIM。 我的系统是Fedora 36&#xff0c;当然你也可以使用Ubuntu或者其他Li…

码云(Gitee)与Git配置

前提 本文配置的前提是已经申请好了码云(gitee)的账号和电脑上已经安装好了git 1.配置gitee的ssh公钥 在gitee的个人设置里面配置ssh公钥 就是将公钥复制到右侧的框中, 并点击确定即可。 1.1生成ssh公钥 右键鼠标打开git bash here 输入如下命令&#xff0c;邮箱就填自己…