vue2双向数据绑定基本原理

news2025/1/21 5:59:22

vue2的双向数据绑定(又称响应式)原理,是通过数据劫持结合发布订阅模式的方式来实现的,通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调来渲染视图。也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变。

Object.defineProperty

  • 第一个参数 object对象
  • 第二个参数 属性名
  • 第三个参数 属性描述符 这里只介绍getset
function defineReactive(data, key, value) {
    Object.defineProperty(data, key, {
        get() {
            console.log('此处进行依赖的收集')
            return value
        },
        set(newValue) {
            if(newValue === value) return;
            console.log('此处通知变化,执行更新操作')
            value = newValue;
        }
    })
}

const data = {
    message: '1',
    msg:'2'
}

let keys = Object.keys(data);

for(let i = 0; i< keys.length; i++) {
    let key = keys[i];
    let value = data[key];
    // 把data中的每一项变成响应式数据
    defineReactive(data,key, value)
}

console.log(data.msg);
data.message = 'nihao'

在这里插入图片描述

从这个例子我们可以简单理解一下数据劫持,当我们用到了某个数据(比如在页面上、在计算属性中、在watch中),那么肯定会对这个数据进行访问,所以只要在getter中对数据的使用进行拦截,对这个数据的使用(也就是此数据的依赖)进行收集,收集好之后方便我们进行后续的处理。当我们改变某个数据的时候,需要用setter进行拦截,对我们在getter里面收集到的依赖进行响处理,来改变视图。

双向数据绑定及模版编译

vue初始化

  • 初始化数据
    • 初始化data
    • 初始化watch
    • 初始化computed
  • 挂载
let vm = new Vue({
  el: '#app',
  data() {
    return {
      msg:'1',
      message: 'hello',
      obj: {
        a:1,
        b:2
      }
    }
  },
  watch: {
    message: function (newValue, oldValue) {
      console.log(newValue, oldValue)
    }
  },
  computed: {
    mm: function () {
      return this.message + this.msg;
    }
  },
})
Vue.prototype._init = function(options) {
  // 保存实例
  let vm = this;
  vm.$options = options;

  initState(vm);

  if(vm.$options.el) {
    vm.$mount();
  }
}

渲染Watcher(RenderWatcher)依赖的收集

初始化data

  • 观察数据,把data下对象都变成响应式数据
  • 每一个响应式数据,都有一个对应的实例化dep,作用是对依赖进行收集以及数据改变更新视图

Observer

export function defineReactive(data, key, value) {
  let childOb =  observe(value) // 递归把对象变成响应式

  let dep = new Dep(key);
  // 不兼容IE8及以下
  Object.defineProperty(data, key, {
    get() {
      if(Dep.target) {
        dep.depend();
        if(childOb) {
          childOb.dep.depend(); // 数组的依赖收集
          dependArray(value)
        }
      }
      return value;
    },
    set(newValue) {
      if(newValue === value) return
      observe(newValue)
      value = newValue
      dep.notify()
    }
  });
}

class Observer {
  constructor(data) {
    this.dep = new Dep();
    
    // 为了让数组上有dep
    Object.defineProperty(data, '__ob__', {
      get: () => this
    })

    if(Array.isArray(data)) {
      // 数组单独处理
      data.__proto__ = newArrayProtoMethods;
      observeArray(data); // 对数组本身存在的对象进行观察
    } else {
      this.walk(data)
    }
    
  }
  
  walk(data) {
    // 拿到key值
    let keys = Object.keys(data)
    
    for(let i = 0; i< keys.length; i++) {
      let key = keys[i]
      let value = data[key]
      // 变成响应式数据
      defineReactive(data, key, value)
    }
  }
}

Dep

  • 对响应数据的依赖进行收集
  • 数据改变时,对依赖进行update处理
  • depend函数使depwatcher相互依赖
    在这里插入图片描述
let id = 0;

class Dep {
  constructor() {
    this.id = id++;
    this.subs = [];
  }

  // 发布订阅模式

  // 订阅
  addSub(watcher) {
    this.subs.push(watcher)
  }

  // 发布
  notify() {
    this.subs.forEach(watcher => watcher.update());
  }


  // 会把watcher存到dep当中,还会把dep存到
  depend() {
    if(Dep.target) {
      Dep.target.addDep(this);
    }
  }
}



let stack = [];

// watcher进栈
export function pushTarget(watcher) {
  Dep.target = watcher;
  stack.push(watcher)
}

// watcher出栈
export function popTarget() {
  stack.pop()
  Dep.target = stack[stack.length - 1];
}

以在页面上渲染message为例

import Vue from 'vue';

let vm = new Vue({
  el: '#app',
  data() {
    return {
      message: 'hello',
    }
  },
})
<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">
    {{ message }}
  </div>
</body>
</html>

$mount

  • 实例化一个RenderWatcher,传入当前的实例和页面渲染函数
  • _update里面涉及到模版编译,我们可以先忽略模版编译的内容,只需要知道模版编译的时候要取实例上的对象比如vm.message
Vue.prototype.$mount = function() {
  let vm = this;
  let el = vm.$options.el;
  el = vm.$el = query(el);
  // 首次渲染组件或者因为数据变化重新渲染组件
  let updateComponent = () => {
    vm._update();
  }
  // 渲染watcher
  new Watcher(vm, updateComponent);
}


Vue.prototype._update = function() {
  let vm = this;
  let el = vm.$el;

  // 不直接操作dom,把dom先保存到文件碎片里面,文件碎片是保存在内存中的,把操作结果一次塞到dom中去
  let node = document.createDocumentFragment();
  let firstChild;

  while(firstChild = el.firstChild) {
    node.appendChild(firstChild);
  }

  // 编译 处理{{ message }} -> vm.message -> 触发message的getter -> 进行依赖的收集
  compiler(node, vm);

  el.appendChild(node)
}

Watcher

class Watcher {
  constructor(vm , exprOrFn, cb = () => {}, opts = {}) {
    this.vm = vm;
    this.exprOrFn = exprOrFn;
    this.getter = exprOrFn;
    this.depsId = new Set()
    this.deps = []
    this.id = id++; // 每次实例化watcher的时候id++,可以通过不同的id区分不同的watcher
    this.get(); 
  }
  get() {
    pushTarget(this); // Dep.target = 当前的watcher
    this.getter();
    popTarget();
    return value;
  }

  
  update() {
     this.get();
  }

  depend() {
    // 作用把watcher里面的addDep执行
    let i = this.deps.length;
    while(i--) {
      this.deps[i].depend();
    }
  }

  // watcher和dep互相依赖
  addDep(dep) {
    let id = dep.id;
    if(!this.depsId.has(id)) {
      this.depsId.add(id);
      this.deps.push(dep); // 把dep存在了watcher中
      dep.addSub(this) // 把watcher存在dep中
    }
  }
}

执行getter的时候,先把当前的RenderWatcherpush到stack中,并且把Dep.target设置为当前的renderWatcher。其次时执行渲染函数,取实例上的值vm.message,这时候会走到messagesetter,执行dep.depend()操作,depend会把当前的dep存在RenderWatcher中,也会把当前的renderWatcher存在dep中。执行完后把RenderWatcherpop掉,并且把Dep.target指向前一个依赖。

在这里插入图片描述

改变数据

let vm = new Vue({
  el: '#app',
  data() {
    return {
      message: 'hello',
    }
  },
})

setTimeout(() =>{
  vm.message = 'hi'
}, 3000);
set(newValue) {
  if(newValue === value) return
  observe(newValue)
  value = newValue
  dep.notify()
}
notify() {
    this.subs.forEach(watcher => watcher.update());
}

改变响应式数据的时候,会在setter里面进行拦截,执行dep.notify,他会循环执行dep.subs里面保存watcher(即依赖)的update函数,这里等同于get函数,又会执行同上的操作,数据的改变会导致视图的变化。

用户Watcher(UserWatcher)依赖的收集

初始化watch

let vm = new Vue({
  el: '#app',
  data() {
    return {
      message: 'hello',
    }
  },
  watch: {
    message: function (newValue, oldValue) {
      console.log(newValue, oldValue)
    }
  },
})

setTimeout(() =>{
  vm.message = 'hi'
}, 3000);
function createWatcher(vm, key, handler) {
  return vm.$watch(key, handler)
}

function initWatch(vm) {
  let watch = vm.$options.watch; // 拿到实例上的watcher
  for(let key in watch) {
    let handler = watch[key]
    createWatcher(vm, key, handler)
  }
}

Vue.prototype.$watch = function (expr, handler) {
  let vm = this;
  new Watcher(vm, expr, handler, {user: true}); // user: true  区分于渲染watcher
}

初始化watch的时候,会遍历每一个属性,每一个属性都有一个实例化的watcher,这种watcher叫做UserWatcher

class Watcher {
  constructor(vm , exprOrFn, cb = () => {}, opts = {}) {
    this.vm = vm;
    this.exprOrFn = exprOrFn;
    this.getter = function () {  
      return getValue(exprOrFn, vm)  // vm.message
    }

    if(opts.user) {
      this.user = true;
    }

    this.cb = cb;
    this.opts = opts;
    this.depsId = new Set()
    this.deps = []
    this.id = id++; // 每次实例化watcher的时候id++,可以通过不同的id区分不同的watcher

    this.value = this.get(); // 老的oldValue
  }
  get() {
    pushTarget(this); // Dep.target = 当前的watcher
    let value = this.getter.call(this.vm);
    if(this.value !== value) {
      this.cb(value, this.value)
    }
    popTarget();
    return value;
  }

  update() {
    this.get(); 
  }
    

  run() {
    let value = this.get(); // 新的newValue
    if(this.value !== value) {
      this.cb(value, this.value)
    }
  }

  depend() {
    // 作用把watcher里面的addDep执行
    let i = this.deps.length;
    while(i--) {
      this.deps[i].depend();
    }
  }

  // watcher和dep互相依赖
  addDep(dep) {
    let id = dep.id;

    if(!this.depsId.has(id)) {
      this.depsId.add(id);
      this.deps.push(dep); // 把dep存在了watcher中
      dep.addSub(this) // 把watcher存在dep中
    }
  }
}

实例化UserWatcher的时候,执行get,此时Dep.target指向UserWatcher,并且stack中push这个UserWatcher,getValue的时候会取值vm.message,所以会执行dep.depend进行依赖的收集,此时message的dep中就存在这个userWatcher了。之后把UserWatcherstack中pop掉。

在这里插入图片描述

当3s钟后数据改变的时候,setter拦截,dep.notify(),将messge的dep中subs遍历执行依赖的update,又要执行pushTarget、getter、popTareget等操作
在这里插入图片描述

计算属性Watcher(ComputedWatcher)依赖的收集

初始化computed

let vm = new Vue({
  el: '#app',
  data() {
    return {
      msg:'1',
      message: 'hello',
    }
  },
  computed: {
    mm: function () {
      return this.message + this.msg;
    }
  },
})

setTimeout(() =>{
  vm.message = 'hi'
}, 3000);
function createComputedGetter(vm, key) {
  let watcher = vm._watchersComputed[key]; // 当前的计算属性watcher

  return function () {
    if(watcher) {
      if(watcher.dirty) {
        watcher.evaluate()
      }

      if(Dep.target) {
        watcher.depend();
      }

      return watcher.value;
    }
  }
}

function initComputed(vm, computed) {
  let watchers =  vm._watchersComputed =  Object.create(null); // 创建空对象,为了在实例里面更加方便的访问到计算属性的watcher
  for (let key in computed) {
    let userDef = computed[key];
    watchers[key] = new Watcher(vm, userDef, () => {}, {lazy: true}); // lazy: true 表明计算属性的watcher

    Object.defineProperty(vm, key, {
      get: createComputedGetter(vm, key)
    })
  }
}
let id = 0;

class Watcher {
  constructor(vm , exprOrFn, cb = () => {}, opts = {}) {
    this.vm = vm;
    this.exprOrFn = exprOrFn;
    if(typeof exprOrFn === 'function') { // 渲染watcher的时候执行vm._update()渲染页面
      this.getter = exprOrFn;
    } else {
      this.getter = function () {  // 用户watcher的时候为了获取到oldValue和newValue
        return getValue(exprOrFn, vm)
      }
    }

    if(opts.user) {
      this.user = true;
    }

    this.lazy = opts.lazy;
    this.dirty = opts.lazy; //  和计算属性的缓存相关,判断需不需要重新计算, 默认为true是需要重新计算
    this.cb = cb;
    this.opts = opts;
    this.depsId = new Set()
    this.deps = []
    this.id = id++; // 每次实例化watcher的时候id++,可以通过不同的id区分不同的watcher

    // 计算属性一开始不去获取值
    this.value = this.lazy ? undefined :this.get(); // 老的oldValue
  }
  get() {
    pushTarget(this); // Dep.target = 当前的watcher
    let value = this.getter.call(this.vm);
    popTarget();
    return value;
  }

  evaluate() {
    this.value = this.get();
    this.dirty = false;
  }

  update() {
    if(this.lazy) {  // lazy判断计算属性的watcher
      this.dirty = true;  // dirty判断是否重新取值
    } else { 
      // 批量更新
      queueWatcher(this); // 防止数据批量改变的时候执行多次
      // this.get(); // 直接更新,当数据批量改变的时候会执行多次
    }
  }
    

  run() {
    let value = this.get(); // 新的newValue
    if(this.value !== value) {
      this.cb(value, this.value)
    }
  }

  depend() {
    // 作用把watcher里面的addDep执行
    let i = this.deps.length;
    while(i--) {
      this.deps[i].depend();
    }
  }

  // watcher和dep互相依赖
  addDep(dep) {
    let id = dep.id;

    if(!this.depsId.has(id)) {
      this.depsId.add(id);
      this.deps.push(dep); // 把dep存在了watcher中
      dep.addSub(this) // 把watcher存在dep中
    }
  }
}

计算属性初始化的时候,需要遍历每一个值,每一个计算属性都有一个对应的实例化wathcer,这个watcher是ComputedWatcher。计算属性和data,watch不同的是,他不能通过vm.的方式取到值,所以也要进行拦截。当执行$mount的时候,先实例化一个RenderWatcher,执行pushTarget,此时的Dep.target指向的是当前的RenderWatcher,在执行getter的时候取vm.mm的值,因为进行了getter拦截处理,所以会执行watcher.evaluate,此时的Dep.target指向的是当前的ComputedWatcher

在这里插入图片描述

然后执行popTarget操作,此Dep.target指向了RenderWatcher,这时候存在一个问题,此时的依赖中update函数是不能渲染页面的,所以要办法在dep中存入RenderWatcher,办法就是执行ComputedWatcherdepend函数,经过这个操作之后messagemsg,都分别保存了两个依赖,数据改变的时候,视图也会改变

在这里插入图片描述

小结

Observer

  • 使用Object.defineProperty把data数据变成响应式对象,使每一个对象都对应的实例化dep
  • 在getter中进行依赖的收集,即dep.depend()
  • 在setter中进行派发更新,即dep.notify()

Dep

  • Watcher实例的管理器
  • Dep.target指向的是当前的Watcher实例
  • depend()方法会将当前的dep实例保存到watcher中,也会保存当前的watcher

Watcher

  • 它是一个观察者,数据改变的时候执行更新操作
  • 有三种RenderWatcherUserWatcherComputedWatcher
  • 数据变 -> 使用数据的视图变 -> RenderWatcher
  • 数据变 -> 使用数据的计算属性变 -> 使用计算属性的视图变 -> ComputedWatcher
  • 数据变 -> 开发者主动注册的回调函数执行 -> UserWatcher

模版编译及v-model的简单实现

这里不涉及虚拟DOM等复杂逻辑,只是简单替换

Vue.prototype._update = function() {
  let vm = this;
  let el = vm.$el;

  // 不直接操作dom,把dom先保存到文件碎片里面,文件碎片是保存在内存中的,把操作结果一次塞到dom中去
  let node = document.createDocumentFragment();
  let firstChild;

  while(firstChild = el.firstChild) {
    node.appendChild(firstChild);
  }

  // 编译 处理{{ message }} -> vm.message
  compiler(node, vm);

  el.appendChild(node)
}

el下的节点都保存到文件碎片中,经过编译之后差值表达式里面的内容会被替换成真正的数据,在把结果添加到dom上

const reg = /\{\{((?:.|\r?\n)+?)\}\}/g; // 任意字符或者换行 ?: 表是不捕获分组

export function compiler (node, vm) {
  let childNodes = node.childNodes;
  // 把类数组转换成数组
  let childNodesArray = [...childNodes];
  
  childNodesArray.forEach(child => {
    if(child.nodeType === 1) {
      if(child.hasAttribute('v-model')) {
        const vmKey = child.getAttribute('v-model').trim();
        const value = getValue(vmKey, vm);
        child.value = value;
        child.addEventListener('input', () => {
          const keyArr = vmKey.split('.')
          const obj = keyArr.slice(0, keyArr.length - 1).reduce((newObj, k) => newObj[k], vm)
          const leafKey = keyArr[keyArr.length - 1]
          obj[leafKey] = child.value;
        })
      }
      // 元素节点
      compiler(child, vm)
    } else if (child.nodeType === 3) {
      // 文本节点
      compilerText(child, vm)
    }
  })
}

export function compilerText (node, vm) {
  if(!node.expr) {
    node.expr = node.textContent; // 把{{ message }} 保存,防止改变数据的时候匹配不到
  }
  node.textContent = node.expr.replace(reg, function(...args){
  // ['{{ message }}', ' message ', 5, '\n    {{ message }}\n    ']
    let key = trimSpace(args[1])
    return JSON.stringify(getValue(key,vm)) 
  })
}

export function getValue(expr,vm) {
  // obj.a
  let keys = expr.split('.');
  return keys.reduce((prevValue, curValue) => {
    prevValue = prevValue[curValue]
    return prevValue
  }, vm);
}

// 去掉空格
export function trimSpace(str) {
  return str.replace(/\s+/g, '')
}
  • 对节点进行循环处理,发现文本节点,进行差值表达式的替换,通过reduce完成链式取值,将插值替换成真正的值
  • 发现元素节点,看节点上是否存在v-model属性,获取到v-model绑定的键值,赋值给input节点,并且监听该节点的input事件,将改变的值重新复制给数据,赋值的过程,又会触发dep.notify,实现了视图的改变影响数据的改变。

在这里插入图片描述

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

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

相关文章

vue3+vite项目,安装依赖运行报错“failed to load config from xxx,TypeError: vite.createFilter is not a function”

问题 今天从GitHub上拉下来了一个vue3vite项目&#xff0c;之前就是安装依赖就可以运行了&#xff0c;但是今天一直报错&#xff0c;显示TypeError: vite.createFilter is not a function 错误原因 vite版本与安装的依赖版本不匹配 近期vite3发布&#xff0c;但我们使用的…

node-sass版本不兼容问题(已解决)

node-sass版本不兼容问题&#xff08;已解决&#xff09; 估计很多小伙伴都遇到node-sass版本不兼容的问题&#xff0c;今天分享给大家如何定位问题&#xff0c;怎样去查找并兼容自己项目中的nod-sass版本&#xff01; 文章目录node-sass版本不兼容问题&#xff08;已解决&…

JavaScript之Ajax-axios表单提交

目录 一.表单概念 二.表单提交 三.FormData语法 四.头像上传模板 五.请求体类型 六.图书管理(增删改查) 七.axios语法优化写法 优化1: axios全局配置 优化2: 默认的method 优化3: axios的快捷方法 一.表单概念 form标签&#xff08;表单&#xff09;是用来收集用户…

JavaWeb《一》概念、服务器部署及servlet

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; 本文是javaweb的第一篇&#xff0c;首先介绍了javaweb&#xff0c;然后进行了一个简单的web服务器部署&#xff0c;把我的一个网页发布到了云端&#xff0c;且叫他小Sa&#xff0c;目前啥也没有&#xff0c;之后会使…

vue3插槽、具名插槽、作用域插槽-足够入门了!

vue3 插槽 前言 这篇文章介绍vue组件的插槽&#xff01;包括&#xff1a;插槽概念&#xff0c;具名插槽&#xff0c;作用域插槽等等&#xff0c;看完不会你打我。哈哈哈&#xff0c;开玩笑的&#xff0c;不多说&#xff0c;上刺刀&#xff01;&#xff01; 1. 概念 插槽&…

给饿了么Radio 单选框添加点击事件(vue2)

前言 最近有一个这样的需求&#xff0c;当点击不合格时打开一个弹窗进行不合格数据的录入。问题点在于当你想对不合格数据进行修改时&#xff0c;点击不合格是没有反应的&#xff1b;因为Radio 单选框只提供了一个change 事件 解决 这里说明一下&#xff0c;项目是vue2的项目…

Vue 全套教程(一),入门 Vue 必知必会

Vue(一) 文章目录Vue(一)一、Vue简介1. 介绍2. 特点二、HelloWorld三、v-bind指令四、v-model指令五、el与data的两种写法5.1 el的两种写法5.2 data的两种写法六、MVVM模型七、Object.defineProperty方法八、数据代理8.1 数据代理概念8.2 Vue中的数据代理九、事件处理9.1 基本使…

【前端】Vue+Element UI案例:通用后台管理系统-导航栏

文章目录目标代码0.数据1.其他准备2.结构3.动态显示数据4.主题&#xff1a;背景色&#xff0c;点击悬停效果5.去除padding6.去除下拉框7.标题8.路由跳转总代码组件CommonAside.vue文件结构参考参考视频&#xff1a; VUE项目&#xff0c;VUE项目实战&#xff0c;vue后台管理系统…

Vue父传子详细教程

本文父组件&#xff1a;shopping.vue 子组件&#xff1a;shoplist.vue 1.父组件引入子组件 1.1.引入组件 import shoplist from ../shoplist.vue 1.2.注册组件 components: { shoplist } 1.3.使用组件 <shoplist></shoplist> 2.父组件定…

vue-admin-beautiful:npm ERR! code ERESOLVE npm ERR! code E451

记录一个离谱的编译错误。 克隆vue-admin-beautiful项目&#xff0c;master分支可以正常拉取&#xff0c;vue3.0-antdv编译报下面的错误。 尝试一&#xff1a; $ cnpm install Install fail! Error: GET https://registry.npmmirror.com/vab-config response 451 status Error…

Java-web实现用户登录、注册功能

目录环境搭建数据库用户登录需求分析代码实现编写UserMapper类编写User类编写loginServlet类编写login.html编写login.css用户注册需求分析代码编写编写UserMapper类编写registerServlet类编写register.html编写register.css编写SqlSessionFactory工具类项目总体架构运行展示案…

css绘制一个Pinia小菠萝

效果如下&#xff1a; pinia小菠萝分为头部和身体&#xff0c;头部三片叶子&#xff0c;菠萝为身体 头部 先绘制头部的盒子&#xff0c;将三片叶子至于头部盒子中 先绘制中间的叶子&#xff0c;利用border-radius实现叶子的效果&#xff0c;可以借助工具来快速实现圆角的预想…

SpringBoot 单元测试——JUnit5

目录 1、JUnit5概述 1.1、JUnit5 构成 1.2、JUnit5 配置 1.2.1、导入 Junit5 开发场景 1.2.2、Junit5 开发场景自动导入依赖项 2、JUnit5 使用 2.1、Jnuit5 测试代码开发 2.1.1、测试代码格式 2.1.2、测试样例 2.2、JUnit5常用注解 2.2.1、Test :表示方法是测试方法。…

【云原生 | 23】Docker运行Web服务实战之Tomcat

作者简介&#xff1a;&#x1f3c5;云计算领域优质创作者&#x1f3c5;新星计划第三季python赛道第一名&#x1f3c5; 阿里云ACE认证高级工程师&#x1f3c5; ✒️个人主页&#xff1a;小鹏linux &#x1f48a;个人社区&#xff1a;小鹏linux&#xff08;个人社区&#xff09;欢…

利用vue+高德地图API 实现用户的运动轨迹

利用vue高德地图API 实现用户的运动轨迹 高德地图网址&#xff1a;https://lbs.amap.com/api/jsapi-v2/guide/abc/prepare 任务一&#xff1a;实现地图显示 先完成准备工作&#xff0c;这一步是后面工作的基础。准备工作部分参考了&#xff1a; https://blog.csdn.net/qq_5…

vue3中vite的@路径别名与path中的resolve

使用路径引用 在vue3中&#xff0c;想用符号代替./…/这种相对路径引用使用&#xff0c; 前者相当于从根目录往后找&#xff0c;后者相当于从后往前找。 具体方法就是在vite.config.js中 import { resolve } from "path"export default defineConfig({plugins: [v…

Vue动态组件

等疫情结束了&#xff0c;要开始爬爬山、看看海&#xff0c;做些不会后悔的事情 一、概念 1. 示例 组件是可复用的 Vue 实例&#xff0c;且带有一个名字&#xff0c;这里实现一个最简单的组件&#xff1a; 父组件&#xff1a; <template><div><h1>Father…

【让CSDN的浪漫弥漫女神节】_Unity基础不动山不摇_回顾篇

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

promise 以及经典面试题

1.Promise 它是一个ES6提出一个新语法&#xff0c;用来优化异步代码的写法。promise&#xff1a;承诺 ● 生活中&#xff0c;它是用来表述 对将来要发生的事情的肯定。 例如 &#xff1a; 高中生说&#xff0c;老师&#xff0c;我会考上一所好大学的&#xff1b;销售员说&…

刷题日常计~JS④

作者 : SYFStrive 博客首页 : 点击跳转HomePage &#x1f4dc;&#xff1a; 初编程JavaScript之每天10&#x1f5e1;5题 &#x1f449; 从质变到量变&#x1f4aa; &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区…