iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > 其他 >一文详解Vue2/Vue3的响应式原理
  • 317
分享到

一文详解Vue2/Vue3的响应式原理

Vue.js 2023-05-14 21:05:28 317人浏览 薄情痞子
摘要

this.$delete(this.student, 'name');// 删除student对象属性name this.$set(this.student, 'age', '21');//

this.$delete(this.student, 'name');// 删除student对象属性name
this.$set(this.student, 'age', '21');// 添加student对象属性age
this.$set(this.student.hobby, 0, '王者');// 更新student对象属性hobby数组

image.png

  • Proxy:解决了上面两个弊端,proxy可以实现:

  • 可以直接监听对象而非对象属性,可以监听对象添加额外属性的变化;

    const user = {name:'张三'}
    const obj = new Proxy(user,{
      get:function (target,key){
        console.log("get run");
        return target[key];
      },
      set:function (target,key,val){
        console.log("set run");
        target[key]=val;
        return true;
      }
    })
    obj.age = 22;
    console.log(obj); // 监听对象添加额外属性打印set run!
  • 可以直接监听数组的变化

    const obj = new Proxy([2,1],{
      get:function (target,key){
        console.log("get run");
        return target[key];
      },
      set:function (target,key,val){
        console.log("set run");
        target[key]=val;
        return true;
      }
    })
    obj[0] = 3;
    console.log(obj); // 监听到了数组元素的变化打印set run!
  • Proxy 返回的是一个新对象,而 Object.defineProperty 只能遍历对象属性直接修改。

  • 支持多达13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是Object.defineProperty 不具备的。

  • 总的来说,vue3响应式使用Proxy解决了Vue2的响应式的诟病,从原理上说,它们所做的事情都是一样的,依赖收集依赖更新

    1 Vue2响应式原理

    这里基于Vue2.6.14版本进行分析

    Vue2响应式:通过Object.defineProperty()对每个属性进行监听,当对属性进行读取的时候就会触发getter,对属性修改的时候就会触发setter。首先我们都知道Vue实例中有data属性定义响应式数据,它是一个对象。我们看看下面例子的data:

    data(){
     return {
      name: 'Sapper',
      hobby: ['游戏', '原神'],
      obj: {
        name: '张三',
        student: {
          major: '软件工程',
          class: '1班',
        }
      }
     }
    }

    image.png从上图我们可以看到,data中的每一个属性都会带 __ob__ 属性,它是一个Observer对象,其实Vue2中响应式的关键就是这个对象,在data中的每一个属性都会带get、set方法,而Vue源码中其实把get、set分别定义为ReactiveGetter、reactiveSetter,这些东西怎么添加进去的。Vue2又是怎么数据变化同时实时渲染页面?先看看下面的图:

    image.png给data属性创建Observer实例:通过初注册响应式函数initState中调用了initData函数实现为data创建Observer实例。

    image.png

    function initData(vm: Component) {
      // 获取组件中声明的data属性
      let data: any = vm.$options.data
      // 对new Vue实例下声明、组件中声明两种情况的处理
      data = vm._data = isFunction(data) ? getData(data, vm) : data || {}
      ...
      // observe data
      const ob = observe(data) // 为data属性创建Observer实例
      ob && ob.vmCount++
    }

    通过Observer实例把data中所有属性转换成getter/setter形式来实现响应性:对data属性分为两种情况处理:对象属性处理(defineReactive实现)和数组属性处理。数组怎么处理(后面再详细说明)

    注意地,由于Vue实例的data永远都是一个对象,所以data里面包含的数组类型只有对象属性数组属性

    image.png在getter收集依赖,在setter中触发依赖:当读取data中的数据时,会在get方法中收集依赖,当修改data中的数据时,会在set方法中通知依赖更新。defineReactive方法中主要是做四件事情:创建Dep实例给对象属性添加get/set方法收集依赖通知依赖更新

    image.png

    从上面我们知道了dep.depend()实现了依赖收集,dep.notify()实现了通知依赖更新,那么Dep类究竟做了什么?我们先看看下面的图:

    image.png从图中我们得明确一点,谁使用了变化的数据,也就是说哪个依赖使用了变化的数据,其实就是Dep.taget,它就是我们需要收集的依赖,是一个Watcher实例对象,其实Watcher对象有点类似watch监听器,我们先看一个例子:

    vm.$watch('a.b.c',function(newVal,oldVal)){....}

    怎么监听多层嵌套的对象,其实就是通过.分割为对象,循环数组一层层去读数据,最后一后拿到的就是想要对的数据。

    export function parsePath (path){
     const segment = path.split('.');
     return function(obj){
     ...
       for(let i=0;i<segment.length;i++){
         if(!obj) return;
         obj = obj[segment[i]]
       }
       return obj
     }
    }

    当嵌套对象a.b.c属性发生变化时,就会触发第二个参数中的函数。也就是说a.b.c就是变化的数据,当它的值发生变化时,通知Watcher,接着Watcher触发第二个参数执行回调函数。我们看看Watcher类源码,是不是发现了cb其实就与watch的第二参数有异曲同工之妙。

    export default class Watcher implements DepTarget {
      vm?: Component | null
      cb: Function
      deps: Array<Dep>
      ...
      constructor(vm: Component | null,expOrFn: string | (() => any),cb: Function,...) {
        ...
        this.getter = parsePath(expOrFn)// 解析嵌套对象
        ...
      }
      get() { // 读取数据
        ...
        return value
      }
    
      aDDDep(dep: Dep) {
        ...
        dep.addSub(this)//添加依赖
        ...
      }
      cleanupDeps() {// 删除依赖
        ...
        dep.removeSub(this)
        ...
      }
      update() {// 通知依赖更新
       this.run()
       ...
      }
    
      run() {
       ...
       this.cb.call(this.vm, value, oldValue)
      }
      ...
      depend() { // 收集依赖
        let i = this.deps.length
        while (i--) {
          this.deps[i].depend()
        }
      }
      ...
    }

    实现对数组的监听:从最开始的例子,我们了解对象以及嵌套对象的监听,但是Object.defineProperty是用来监听对象指定属性的变化,不支持数组监听,那么数组又是怎么监听?我们上面说了data中的数据被赋予响应性都是在Observer中实现的,那么监听的实现也是在Observer对象中实现的,先对数组的特定方法做自定义处理,为了拦截数组元素通知依赖更新,然后才通过observeArray函数遍历创建Observer实例,主要分为两种情况:

    // 源码Observer类中对数组处理的部分代码
    if (Array.isArray(value)) {
      if (hasProto) {
        protoAugment(value, arrayMethods)
      } else {
        copyAugment(value, arrayMethods, arrayKeys)
      }
      this.observeArray(value)
    }
    • 当浏览器支持__ proto __ 对象:强制赋值当前arrayMethods给target的__ proto __ 对象,直接给当前target数组带上自定义封装的数组方法,从而实现监听数组变化。其实arrayMethods处理后就是下面这样一个对象:

      image.png

      const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
      console.log(arrayKeys);// ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
      copyAugment(value, arrayMethods, arrayKeys)
      
      function copyAugment (target: Object, src: Object, keys: Array<string>) {
        for (let i = 0, l = keys.length; i < l; i++) {
          const key = keys[i]
          def(target, key, src[key])// 遍历数组元素通过为元素带上
        }
      }
    • 当浏览器不支持__ proto __ 对象:遍历数组元素通过defineProperty定义为元素带上自定义封装的原生数组方法,由于自定义数组方法中做了拦截通知依赖更新,从而实现监听数组的变化。

      const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
      console.log(arrayKeys);// ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
      copyAugment(value, arrayMethods, arrayKeys)
      
      function copyAugment (target: Object, src: Object, keys: Array<string>) {
        for (let i = 0, l = keys.length; i < l; i++) {
          const key = keys[i]
          def(target, key, src[key])// 遍历数组元素通过为元素带上
        }
      }

      对数组的Array原生方法做了自定义封装的源码如下,在自定义方法中拦截通知依赖更新。image.png

      // 遍历target实现创建Observer实例
      observeArray (items: Array<any>) {
       for (let i = 0, l = items.length; i < l; i++) {
        observe(items[i])
       }
      }

    Vue2响应式原理小结:

    • 给data创建Observer实例
    • Observer类实现对数据封装getter、setter的响应性
    • 针对数组类型数据,自定义封装Array原生方法,在封装过程中拦截执行通知依赖更新
    • 真正通过Watcher通知依赖更新,通过run方法中的cb回调函数,实现类似watch侦听器第二参数中监听变化后的操作

    Vue3响应式原理

    这里基于Vue3.2.41版本进行分析

    其实Vue3的响应原理与Vue2的响应原理都差不多,唯一不同的就是它们的实现方式,Vue3通过创建Proxy的实例对象而实现的,它们都是收集依赖、通知依赖更新。而Vue3中把依赖命名为副作用函数effect,也就是数据改变发生的副作用,我们先来看一下例子:

    const house = {status:'未出租',price:1200,type:'一房一厅'};
    const obj = new Proxy(house, {
      get (target, key) {
        return target[key];
      },
      set (target, key, newVal) {
        target[key] = newVal;
        return true;
      }
    })
    function effect () {
      console.log('房子状态:'+obj.status);
    }
    
    effect () // 触发了proxy对象的get方法
    obj.status = '已出租!';
    effect ()

    通过Proxy创建一个代理对象,把house代理给obj,obj是代理对象,house是被代理对象。house对象中数据改变,由于effect函数读取了对象属性,所以当数据改变,也需要及时更新副作用函数effect。但是问题来了,假如对象中多个属性的,依赖于数据变化的多个副作用函数,数据变化一次都需要执行一次,代码写起来就会很冗余,所以我们需要这样处理:

    const objset = new Set();
    const obj = new Proxy(house, {
      // 拦截读取操作
      get (target, key) {
        objSet.add(effect) // 收集effect
        return target[key];
      },
      set (target, key, newVal) {
        target[key] = newVal;
        objSet.forEach(fn=>fn()) // 遍历effect
        return true;
      }
    })

    把副作用函数都存到Set实例中,Set可以过滤重复数据,然后在获取数据中收集副作用函数,在修改数据中遍历执行副作用函数,这样就简化了代码,不需要每次改变都要执行一次了,也就是修改一次数据及时更新effect。虽然上面已经实现了响应式的雏形了,但是还需要解决很多问题:

    假如这个副作用函数是一个匿名函数,这时候需要怎么处理? 添加一个全局变量临时存储。

    effect (()=>console.log('房子状态:'+obj.status)) // 上面的例子会直接报not define
    // 添加一个全局变量activeEffect存储依赖函数,这样effect就不会依赖函数的名字了
    let activeEffect;
    function effect (fn) {
     activeEffect = fn;
     // 执行副作用函数
     fn()
    }

    假如读取不存在的属性的时候,副作用函数发生什么? 副作用函数会被重新执行,由于目标字段与副作用函数没有建立明确的函数联系。所以这就需要引入唯一key辨识每一个数据的副作用函数,以target(目标数据)、key(字段名)、effectFn(依赖)。看下图:

    setTimeout(() => {
      obj.notExit = '不存在的属性';
    }, 1000)

    分三种情况分析副作用函数存储数据唯一标识

    • 两个副作用函数同时读取同一个对象的属性值

    image.png

    • 一个副作用函数中读取了同一个对象不同属性

    image.png

    • 不同副作用函数中读取两个不同对象的相同属性

    image.png所以为了解决这些不同情况的副作用保存问题,所以Vue3引入了Weak、Map、Set三个集合方法来保存对象属性的相关副作用函数:

    image.png

    const weakMap = new WeakMap();
    let activeEffect;
    const track = ((target,key)=>{
      if(!activeEffect){
          return;
        }
        // 从weakMap中获取当前target对象
        let depsMap = weakMap.get(target);
        if(!depsMap){
          weakMap.set(target,(depsMap=new Map()))
        }
        // 从Map中属性key获取当前对象指定属性
        let deps = depsMap.get(key)
        if(!deps){
          // 副作用函数存储
          depsMap.set(target,(deps=new Set()))
        }
        deps.add(activeEffect)  
    })
    const trigger = ((target,key)=>{
      // 从weakMap中获取当前target对象
      const depsMap = weakMap.get(target);
        if(!depsMap) return;
        // 从Map中获取指定key对象属性的副作用函数集合
        const effects = depsMap.get(key);
        effects&&effects.forEach(fn=>fn())
    })

    WeakMap与Map的区别是? 区别就是垃圾回收器是否回收的问题,WeakMap对象对key是弱引用,如果target对象没有任何引用,可以被垃圾回收器回收,这就需要它了。相对于WeakMap,不管target是否引用,Map都不会被垃圾回收,容易造成内存泄露。我们看一下下面例子:

    const map = new Map();
    const weakMap = new WeakMap();
    (function(){
      const foo = {foo:1};
      const bar = {bar:2};
      map.set(foo,1);
      weakMap.set(bar,2);
    })() // 函数执行完,weakMap内的所有属性都被垃圾回收器回收了
    setTimeout(() => {
     console.log(weakMap);// 刷新页面发现weakMap里面没有属性了
    }, 2000)

    假如在一个副作用函数中调用了对象的两个属性,但是有布尔值控制,按正常来说,副作用函数只能执行一次get获取值的,但是我们现有的实现方法还实现不了,我们看看下面例子。

    const effectFn = (() => {
      const str = obj.status ? '' : obj.type;
    })
    const obj = new Proxy(house, {
      get(target, key) {
        console.log('get run!');// 打印了两次
        ...
      },
      set(target, key, newVal) {
       ...
      }
    })

    通过这个例子,我们是不是需要解决这个问题,也就是当每次副作用函数执行时,我们可以先把它从所有与之关联的依赖集合中删除。我们看看源码例子:

    // 清空副作用函数依赖的集合
    function cleanupEffect(effect: ReactiveEffect) {
      const { deps } = effect
      if (deps.length) {
        for (let i = 0; i < deps.length; i++) {
          deps[i].delete(effect)
        }
        deps.length = 0
      }
    }

    嵌套副作用函数处理:由于副作用函数可能是嵌套,比如副作用函数中effectFn1中有还有一个副作用函数effectFn2,以上面的方法对于嵌套函数的处理用全局变量 activeEffect 来存储通过 effect 函数注册的副作用函数,这意味着同一时刻 activeEffect 所存储的副作用函数只能有一个。当副作用函数发生嵌套时,内层副作用函数的执行会覆盖 activeEffect 的值,并且永远不会恢复到原来的值。看了很多资料举例用effect栈存储,是的没错,当执行副作用函数的时候把它入栈,执行完毕后把它出栈。现在我们一起看一下源码怎么处理的:

    • 按位跟踪标记递归深度方式优化方案):通过用二进制位标记当前嵌套深度的副作用函数是否记录过,如果记录过就,如果已经超过最大深度,因为采用降级方案,是全部删除然后重新收集副作用函数的。

      let effectTrackDepth = 0 // 当前副作用函数递归深度
      export let trackOpBit = 1 // 在track函数中执行当前的嵌套副作用函数的标志位
      const maxMarkerBits = 30 // 最大递归深度支持30位,

      为什么需要设置30位,因为31位会溢出。

      // 每次执行 effect 副作用函数前,全局变量嵌套深度会自增1
      trackOpBit = 1 << ++effectTrackDepth
      
      // 执行完副作用函数后会自减
      trackOpBit = 1 << --effectTrackDepth;

      为什么是左移一位,是因为第一位也就是说当前深度只是1,所以保持不变,不用管,从第二位开始。

        if (effectTrackDepth <= maxMarkerBits) {
          // 执行副作用函数之前,使用 `deps[i].w |= trackOpBit`对依赖dep[i]进行标记,追踪依赖
          initDepMarkers(this)
        } else {
          // 降级方案:完全清理
          cleanupEffect(this)
        }

      如何判断当前依赖是否已记录过,通过按位与判断是否有位已经标识,有就大于0:

      //代表副作用函数执行前被 track 过
      export const wasTracked = (dep: Dep): boolean => (dep.w & trackOpBit) > 0
      //代表副作用函数执行后被 track 过
      export const newTracked = (dep: Dep): boolean => (dep.n & trackOpBit) > 0

      清理依赖

      export const finalizeDepMarkers = (effect: ReactiveEffect) => {
        const { deps } = effect
        if (deps.length) {
          let ptr = 0
          for (let i = 0; i < deps.length; i++) {
            const dep = deps[i]
            // 有 was 标记但是没有 new 标记,应当删除
            if (wasTracked(dep) && !newTracked(dep)) {
              dep.delete(effect)
            } else {
              // 需要保留的依赖
              deps[ptr++] = dep
            }
            // 清空,把当前位值0,先按位非,再按位与
            dep.w &= ~trackOpBit
            dep.n &= ~trackOpBit
          }
          // 保留依赖的长度
          deps.length = ptr
        }
      }
    • 完全清理方式(降级方案):逐个清理掉当前依赖集合deps中每个依赖。

      function cleanupEffect(effect: ReactiveEffect) {
        const { deps } = effect
        if (deps.length) {
          for (let i = 0; i < deps.length; i++) {
            deps[i].delete(effect)
          }
          deps.length = 0
        }
      }

    响应式可调度性scheduler:trigger 动作触发副作用函数重新执行时,有能力决定副作用函数执行的时机、次数以及方式。

    Vue3响应式的6个细节我们都了解了,我们可以对副作用工作流做一个全面总结如图:

    image.pngVue3响应式的关键在于两个函数:track(收集依赖)和trigger(触发依赖)。

    // target: 响应式代理对象, type: 订阅类型(get、hase、iterate), key: 要获取的target的键值
    export function track(target: object, type: TrackOpTypes, key: unknown) {
    // 如果允许追踪, 并且当前有正在运行的副作用
      if (shouldTrack && activeEffect) {
      // 获取当前target订阅的副作用集合, 如果不存在, 则新建一个
        let depsMap = targetMap.get(target)
        if (!depsMap) {
          // 获取对应属性key订阅的副作用, 如果不存在, 则新建一个
          targetMap.set(target, (depsMap = new Map()))
        }
        let dep = depsMap.get(key)
        if (!dep) {
          depsMap.set(key, (dep = createDep()))
        }
        ...
        // 处理订阅副作用
        trackEffects(dep, eventInfo)
      }
    }
    
    export function trackEffects(dep: Dep,debuggerEventExtrainfo?: DebuggerEventExtraInfo) {
      let shouldTrack = false
      if (effectTrackDepth <= maxMarkerBits) { // 如果当前追踪深度不超过最大深度(30), 则添加订阅
        if (!newTracked(dep)) { // 如果未订阅过, 则新建
          dep.n |= trackOpBit // 据当前的追踪标识位设置依赖的new值
          shouldTrack = !wasTracked(dep) // 开启订阅追踪
        }
      } else {
        shouldTrack = !dep.has(activeEffect!)
      }
    
      if (shouldTrack) {
        dep.add(activeEffect!) // 将当前正在运行副作用作为新订阅者添加到该依赖中
        activeEffect!.deps.push(dep) // 缓存依赖到当前正在运行的副作用依赖数组
        ...
      }
    }
    // 根据不同的type从depsMap取出,放入effects,随后通过run方法将当前的`effect`执行
    export function trigger(target: object,type: TriggerOpTypes,key?: unknown,newValue?: unknown,oldValue?: unknown,oldTarget?: Map<unknown, unknown> | Set<unknown>) {
      const depsMap = targetMap.get(target) // 获取响应式对象的副作用Map, 如果不存在说明未被追踪, 则不需要处理
      if (!depsMap) {
        return
      }
      let deps: (Dep | undefined)[] = []
      // 如果是清除操作,那就要执行依赖原始数据的所有监听方法。因为所有项都被清除了。
      if (type === TriggerOpTypes.CLEAR) { // clear
        // 如果是调用了集合的clear方法, 则要对其所有的副作用进行处理
        deps = [...depsMap.values()]
      } else if (key === 'length' && isArray(target)) {
        const newLength = Number(newValue)
        depsMap.forEach((dep, key) => {
          if (key === 'length' || key >= newLength) {
            deps.push(dep)
          }
        })
      } else { // set add delete
        // key不为void 0,则说明肯定是SET | ADD | DELETE这三种操作 
        // 然后将依赖这个key的所有监听函数推到相应队列中
        if (key !== void 0) {
          deps.push(depsMap.get(key))
        }
        switch (type) { // 根据不同type取出并存入deps
          case TriggerOpTypes.ADD:
             // 如果原始数据是数组,则key为length,否则为迭代行为标识符
            if (!isArray(target)) {
              deps.push(depsMap.get(ITERATE_KEY))
              if (isMap(target)) {
                deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
              }
            } else if (isIntegerKey(key)) {
              deps.push(depsMap.get('length'))
            }
            break
          case TriggerOpTypes.DELETE:
           // 如果原始数据是数组,则key为length,否则为迭代行为标识符
            if (!isArray(target)) {
              deps.push(depsMap.get(ITERATE_KEY))
              if (isMap(target)) {
                deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
              }
            }
            break
          case TriggerOpTypes.SET:
            if (isMap(target)) {
              deps.push(depsMap.get(ITERATE_KEY))
            }
            break
        }
      }
      ...
        const effects: ReactiveEffect[] = []
        for (const dep of deps) {
          if (dep) {
            effects.push(...dep)
          }
        }
        // 遍历effects元素执行run函数
        triggerEffects(createDep(effects))
      }
    }

    Vue3响应式原理小结:

    Vue3中的副作用函数其实就是Vue2的依赖

    • activeEffect解决匿名函数问题
    • WeakMap、Map、Set存储对象属性的相关副作用函数
    • 处理副作用函数时,假如有多个响应式属性,控制只触发生效的属性或用到的属性
    • 嵌套副作用函数,使用二进制位记录嵌套副作用,通过控制二进制位是否清理嵌套副作用实现层级追踪
    • track()实现依赖收集、层级依赖追踪、依赖清理(解决嵌套副作用)
    • trigger()当某个依赖值发生变化时触发的, 根据依赖值的变化类型, 会收集与依赖相关的不同副作用处理对象, 然后逐个触发他们的 run 函数, 通过执行副作用函数获得与依赖变化后对应的最新值

    学习视频分享:vuejs入门教程编程基础视频)

    以上就是一文详解Vue2/Vue3的响应式原理的详细内容,更多请关注编程网其它相关文章!

    --结束END--

    本文标题: 一文详解Vue2/Vue3的响应式原理

    本文链接: https://www.lsjlt.com/news/206859.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    本篇文章演示代码以及资料文档资料下载

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档
    猜你喜欢
    • 小程序速报[No.90] | 小程序最新资讯,错过还要等2周
      ⭐本期内容速览:1、平台支付手续费调整通知2、关于canvas v2 保存图片的实现3、小程序变现锦囊:交易解决方案上线,新能力助力好生意4、交易类目限制订单金额及账期调整通知5、助力留资转化率,留资组件内测开启6、官方小程序上线,提升经营...
      99+
      2024-05-14
      头条 抖音 小程序 更新
    • 代码级质量技术之基本框架介绍
      一、背景代码级质量技术:顾名思义为了服务质量更好,涉及到代码层面的相关技术,特别要指出的是,代码级质量技术不单纯指代码召回技术,如静态代码扫描、单元测试等。研究代码级质量技术主要有以下几个方面的原因:1、随着精准测试等概念的兴起,对代码覆盖...
      99+
      2024-05-14
      代码级质量 框架
    • jquery清除同级div
      随着Web应用不断复杂,JavaScript框架变得越来越重要。其中最受欢迎的框架之一是jQuery,jQuery是一个流行的JavaScript库,它可以使处理文档元素、处理事件和创建特效更加容易。在这篇文章中,我们将学习如何使用jQue...
      99+
      2024-05-14
    • jquery 查询筛选
      JQuery是一款非常优秀的JavaScript库,提供了许多便捷的方法使得前端开发变得更加快捷高效。在JQuery中,查询筛选是其最常用的功能之一,本文将详细介绍JQuery查询筛选的相关部分。一、概述JQuery的查询筛选机制是其最重要...
      99+
      2024-05-14
    • jquery 图片无法显示
      在网页开发过程中,经常会用到图片来丰富页面内容,但有时会出现图片无法显示的情况,这个问题通常与文件路径或文件名有关。本文将介绍使用 jQuery 快速解决图片无法显示的问题。一、确认图片文件路径最常见的原因是图片文件路径错误或者图片文件不存...
      99+
      2024-05-14
    • jquery移除只读
      在使用HTML表单时,经常需要将一些输入框设置为只读状态,以防止用户对这些信息进行修改。但有时候需要在特定情况下,将这些输入框的只读属性移除,以便用户可以进行修改或编辑。这时候就需要使用jQuery来移除只读属性。jQuery是一种Java...
      99+
      2024-05-14
    • vue选择地址怎么做
      Vue是一种流行的JavaScript框架,用于构建交互式Web应用程序。在许多Web应用程序中,选择地址是常见的功能需求。本文将介绍如何使用Vue实现选择地址,包括如何使用第三方库和如何自定义Vue组件。一、使用第三方库一个常见的选择地址...
      99+
      2024-05-14
    • VUe双中括号属性如何使用
      Vue是一种流行的JavaScript框架,它使用双花括号(“{{”和“}}”)语法来实现属性与视图之间的数据绑定。VUe框架中的模板显示了被框架监控的变量的特定属性,当这些变量的值改变时,这些属性也跟随改变,从而在视图中自动更新相应的值。...
      99+
      2024-05-14
    • vue refs 方法参数
      随着前端技术的不断发展,Vue框架毫无疑问成为了前端框架中最受欢迎的一种。Vue组件的复杂性也越来越高,因此,Vue框架提供了许多API来使得开发更加灵活和高效。其中之一就是refs方法。refs 方法用于在Vue中访问组件的实例或元素。这...
      99+
      2024-05-14
    • vue.js函数求和
      Vue.js是一个流行的JavaScript框架,它使得前端开发变得更容易和更快速。在Vue.js中,我们可以通过函数来实现对数据的操作,并实现一些加减乘除等简单的数学计算。本文将介绍如何使用Vue.js实现一个函数求和功能。首先,我们需要...
      99+
      2024-05-14
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作