【Vue】响应式中数组的特殊处理

Vue 响应式中对数组的处理

前两节的内容:

Vue 数据劫持

Vue 响应式初步

0. 为什么需要对数组特殊处理?

在响应式初步那一篇文章的最后,我们提到过,需要对数组进行特殊的处理,为什么?

如果仍然用我们之前写的 demo 来简单模拟响应式的话,那么对于一个数组 arr,当我们访问这个数组时,同样会触发它身上的 getter 和 setter,但需要注意的是,我们在使用数组时,并不是仅仅有一般的读写操作,更多时候,我们会通过一些常用的数组方法去操作数组,例如:

arr.push(...)
arr.unshift(...)
arr.splice(...)
...

此时我们应该如何做到响应式呢?

Vue 中给出的方法是:对 js 中7个会改变数组的方法进行重写。这七个方法分别是:push, pop, unshift, shift, splice, reverse, sort

接下来,在我们的 demo 中简单地实现一下。

首先,需要对之前的 Observer 类进行一些修改,加入对数组类型的处理:

class Observer {
  constructor(value) {
    this.value = value
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 如果是数组类型数据的话就特殊处理
      // 代理原型
      ...
      // 监听数组内容
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
  
  observeArray(arr) {
    // 对数组内部的对象类型数据进行监听
    arr.forEach((i) => observe(i))
  }
  ...
}

接下来,就是对数组方法进行监听。

1. 代理原型

基于对原型链的理解,我们知道,当调用 arr 身上的某一方法如 push 时,实际上是顺着原型链找到了 Array.prototype 然后调用了它身上的 push 方法。

那么,如果我们想要在调用 push 时,对其进行拦截,让其执行我们自己定义的方法,一般我们想到的都是重写该方法,但实际上还有另一种方法 — 代理原型。

所谓代理原型,实际上就是在数组对象和其原型 Array.prototype 之间做一层代理,当通过数组对象调用某些特定的方法时,就会触发我们的代理,在不影响原方法执行的情况下,实现响应式。

如下图:

在这里插入图片描述

接下来就是实现了:

首先是 Observer 类中

// 定义两个全局变量
const arrayPrototype = Array.prototype	// 保存数组的原型
// 增加代理原型 proxyPrototype 且 proxyPrototype.__proto__ === arrayProrotype
const proxyPrototype = Object.create(arrayPrototype)

// Observer 类
class Observer {
  constructor(value) {
    this.value = value
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 如果是数组类型数据的话就特殊处理
      // 代理原型
      Object.setPrototypeOf(value, proxyPrototype)
      // 监听数组内容
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
  
  observeArray(arr) {
    // 对数组内部的对象类型数据进行监听
    arr.forEach((i) => observe(i))
  }
  ...
}

接下来对上面的7个方法进行代理:

// 在 array.js 中编写
const reactiveMethods = [
    'push',
    'pop',
    'unshift',
    'shift',
    'splice',
    'reverse',
    'sort',
]

reactiveMethods.forEach((method) => {
    // 取出原方法
    const originalMethod = arrayPrototype[method]
    // 在我们的代理原型上定义该方法的响应式版本
    Object.defineProperty(proxyPrototype, method, {
        value: function reactiveMethod(...args) {
            // 首先确保调用不受影响
            const result = originalMethod.apply(this, args)
            // 派发更新
            ...
            return result
        },
        enumerable: false,
    	writable: true,
    	configurable: true
    })
})

现在遇到了一个问题:如何派发更新?

2. 派发更新的实现

在对象类型数据的处理中,我们是首先在 defineReactive 方法中形成一个 dep 实例的闭包,然后在 setter 中通过 dep.notify() 依次通知相关的 watcher 实例来实现派发更新。这样保证了每一个响应式数据都有其自己的 dep 实例。

而这里,数组中的各项数据的确是拥有其自己的 dep 实例的,但是我们想要的是为数组对象本身准备一个 dep 实例,那么我们应该在哪里定义这一 dep 实例呢?

在前面数据劫持的学习实现中,为了防止对某一数据进行重复劫持,我们在每一个被劫持过的数据身上,都添加了一个属性 __ob__ ,并将该数据对应的 Observer 类实例存入了该属性中。

那么,同理,数组对象身上应该也存在这一属性:

在这里插入图片描述

由于该属性指向当前数据对应的 Observer 类实例,且两者是一一对应的,所以,此时我们只需要在 Observer 实例身上定义一个 dep 实例,就能够维持我们之前的特性:每一个响应式数据都有其自己的 dep 实例

那么就需要对 Observer 类进行一些修改:

// Observer 类
class Observer {
  constructor(value) {
    this.value = value
    // 声明该数据对应的 dep 实例
    this.dep = new Dep()
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 如果是数组类型数据的话就特殊处理
      // 代理原型
      Object.setPrototypeOf(value, proxyPrototype)
      // 监听数组内容
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
  
  observeArray(arr) {
    // 对数组内部的对象类型数据进行监听
    arr.forEach((i) => observe(i))
  }
  ...
}

接下来,就可以对我们前面的代码进行补全。同时还有一个小细节需要注意:当使用 push、unshift、splice 这三个方法操作数组时,可能会向数组中增加元素,那么这些增加的元素也需要被劫持一下。

// 在 array.js 中编写
const reactiveMethods = [
    'push',
    'pop',
    'unshift',
    'shift',
    'splice',
    'reverse',
    'sort',
]

reactiveMethods.forEach((method) => {
    // 取出原方法
    const originalMethod = arrayPrototype[method]
    // 在我们的代理原型上定义该方法的响应式版本
    Object.defineProperty(proxyPrototype, method, {
        value: function reactiveMethod(...args) {
            // 首先确保调用不受影响
            const result = originalMethod.apply(this, args)
            // 获取数组对象的 Observer 类实例
            const ob = this.__ob__
            // 对三种方法特殊处理
            let appended = null
            switch (method) {
                case 'push':
                case 'unshift':
                    appended = args
                    break;
                case 'splice':
                    // splice 方法中,第三个以及以后的参数是新增的数据
                    appended = args.slice(2)
            }
            // 如果有新增的数据,则对这些新增的数据进行劫持
            if (appended) ob.observerArray(appended)
            // 通过dep实例派发更新
            ob.dep.notify()
            return result
        },
        enumerable: false,
    	writable: true,
    	configurable: true
    })
})

完成了派发更新的逻辑,接下来还需要解决依赖收集的问题。

3. 依赖收集的实现

对于下面的数据:

const obj = {
	a: 1,
	arr: [
        {
            b: 2,
            c: 3,
        },
        {
            d: 4
        }
    ]
}

由于我们前面为被劫持的数据都添加了 __ob__ 属性,所以,被劫持后的数据实际上会变成下面这种形式:

const obj = {
	a: 1,
	arr: [
        {
            b: 2,
            c: 3,
            __ob__: {...}	// 数组中对象数据的Observer实例
        },
        {
            d: 4,
            __ob__: {...}	// 数组中对象数据的Observer实例
        },
        __ob__: {...}	// 数组对象arr的Observer实例(实际上的数组对象结构并不是这样的,这里只是简化的写法)
    ],
    __ob__: {...}	// obj对象的Observer实例
}

在前面的数据劫持时,我们在 observer 方法的最后,将新创建或已有的 Observer 类实例返回了出来,并在 defineReactive 方法中,用变量 childOb 接收到了该实例:

// 数据劫持
function defineReactive(data, key, value = data[key]) {
  const dep = new Dep()
  // 对当前属性的下一层属性进行劫持,并拿到当前数据对应的Observer实例
  let childOb = observe(val)
  // 对当前属性进行拦截
  Object.defineProperty(data, key, {
    get: function reactiveGetter() {
      // 收集依赖
      dep.depend()
      return value
    },
    set: function reactiveSetter(newValue) {
      if (newValue === value) return
      value = newValue
      // 触发依赖,并更新Observer实例
      childOb = observe(newValue)
      dep.notify()
    }
  })
}

即,当前的闭包中,我们不仅可以通过变量 dep 拿到该数据对应的 dep 实例,还可以通过 childOb.dep 拿到 dep 实例。

在针对对象类型数据的处理中,我们是通过变量 dep 指向的 dep 实例来进行依赖收集以及派发更新,但是这里对于数组类型的数据,我们是通过 __ob__.dep 或者说 childOb.dep 来进行派发更新。即对于对象类型和数组类型的数据,其会存在两个 dep 实例,一个是在 defineReactive 方法的闭包中,一个则在其对应的 Observer 实例对象身上!

因此,只要能够保证 __ob__.dep 与当前闭包中的变量 dep 这两个 dep 实例中保存的 watcher 相同,就能保证依赖收集以及派发更新不会出现问题。

所以,我们需要对原本的 getter 进行修改:

get: function reactiveGetter() {
    // 同时向两个dep实例中收集依赖
    // 由于此时的 Dep.target 变量指向某一watcher,所以只需要每次收集依赖时,都同时向两个dep实例中收集依赖,就能保证两个dep实例中保存的watcher相同
    dep.depend()
    childOb.dep.depend()
    return value
},

但是,我们还需要考虑一种特殊情况:在 observer 方法中,对于普通类型的数据,我们不会进行处理,即,普通类型的数据身上并不会有 __ob__ 属性!也就是说,普通类型数据的 childOb 可能为空,但是,在 defineReactive 方法的闭包中,变量 dep 仍然存在,且能够收集到该数据的依赖,所以此时我们仅需要向变量 dep 指向的 dep 实例中收集依赖就行了:

get: function reactiveGetter() {
    // 收集依赖
    dep.depend()
    if (childOb) {
      childOb.dep.depend() 
    }
    return value
},

这样就完成了依赖的收集。

4. 注意

考虑下面的情况:

当我们仅仅改变数组中一个对象的某一属性的值时,是否会触发更新?

const arr = [
    {
        a: 1
    }
]
// 劫持该数据
observer(arr)
// 劫持后的数据变为如下形式
const arr = [
    {
        a: 1,
        __ob__: {...}
    },
    __ob__: {...}
]

当某一watcher依赖于该数组时,会执行以下流程:

在 watcher 的构造函数中,会访问该数组,触发其 getter,然后在 getter 中触发依赖收集,从而使得 watcher 被收集到数组 arr__ob__.dep 的依赖数组中,但此时需要注意,arr[0] 这一数据的 __ob__.dep 中并没有收集到这个 watcher。显然,我们的实现对于这种情况是不会触发派发更新的。

但是,在 Vue 的源码中认为,只要依赖了该数组,就等价于依赖了数组中的所有元素,即只要数组中的任意元素更新了,依赖该数组的地方也需要更新,这实际上是合理的。

所以,我们需要在收集依赖时做出一些修改:

function defineReactive(data, key, value = data[key]) {
  const dep = new Dep()
  // 对当前属性的下一层属性进行劫持,并拿到当前数据对应的Observer实例
  let childOb = observe(val)
  // 对当前属性进行拦截
  Object.defineProperty(data, key, {
    get: function reactiveGetter() {
        // 收集依赖
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          // 新增
          if (Array.isArray(val)) {
              dependArray(val)
          }
        }
        return value
    },
    set: function reactiveSetter(newValue) {
      if (newValue === value) return
      value = newValue
      // 触发依赖,并更新Observer实例
      childOb = observe(newValue)
      dep.notify()
    }
  })
}

function dependArray(array) {
  for (let e of array) {
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}

5. 完整demo

这已经是第三版的demo了:
首先是两个全局变量

// public.js
// 定义两个全局变量
const arrayPrototype = Array.prototype	// 保存数组的原型
// 增加代理原型 proxyPrototype 且 proxyPrototype.__proto__ === arrayProrotype
const proxyPrototype = Object.create(arrayPrototype)
// demo.js
// observer 方法
function observer (value) {
    if (!isObject(value)) {
        return
    }
    var ob;
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
        ob = value.__ob__;
    } else {
        ob = new Observer(value);
    }
    return ob
}

// Observer 类
export class Observer {
  constructor(value) {
    this.value = value
    // 声明该数据对应的 dep 实例
    this.dep = new Dep()
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 如果是数组类型数据的话就特殊处理
      // 代理原型
      Object.setPrototypeOf(value, proxyPrototype)
      // 监听数组内容
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
  // 遍历下一层属性,执行defineReactive
  walk (obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
  observeArray(arr) {
    // 对数组内部的对象类型数据进行监听
    arr.forEach((i) => observe(i))
  }
}

// def 方法,用于为当前正在拦截的数据添加 __ob__ 属性
function def (obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  });
}

// 数据劫持
function defineReactive(data, key, value = data[key]) {
  const dep = new Dep()
  // 对当前属性的下一层属性进行劫持,并拿到当前数据对应的Observer实例
  let childOb = observe(value)
  // 对当前属性进行拦截
  Object.defineProperty(data, key, {
    get: function reactiveGetter() {
        // 收集依赖
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          // 新增
          if (Array.isArray(value)) {
              dependArray(value)
          }
        }
        return value
    },
    set: function reactiveSetter(newValue) {
      if (newValue === value) return
      value = newValue
      // 触发依赖,并更新Observer实例
      childOb = observe(newValue)
      dep.notify()
    }
  })
}

function dependArray(array) {
  for (let e of array) {
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}

// Dep 类
class Dep {
  constructor() {
    this.subs = []
  }
  // 依赖收集
  depend() {
    if (Dep.target) {
      this.addSub(Dep.target)
    }
  }
  // 通知更新
  notify() {
    const subs = [...this.subs]
    subs.forEach((s) => s.update())
  }
  // 添加订阅
  addSub(sub) {
    this.subs.push(sub)
  }
}

// 全局变量 Dep.target
Dep.target = null

// 用于暂存 Dep.target 指向的栈
const targetStack = []

// 入栈
function pushTarget (_target) {
    targetStack.push(Dep.target)	// 保存当前 Dep.target
    Dep.target = _target
}
        
// 出栈
function popTarget () {
    Dep.target = targetStack.pop()
}

// Watcher 类
class Watcher {
  constructor(data, expression, cb) {
      this.data = data;	    // 要实现响应式的对象
      this.expression = expression;	// 依赖属性的访问路径
      this.cb = cb;	    // 依赖的回调
      this.value = this.get() // 访问目标属性以触发getter从而发起依赖收集流程
  }
  // 访问当前实例依赖的属性,并将全局变量指向自身
  get() {
    pushTarget(this)
    const value = parsePath(this.data, this.expression)
    popTarget()
    return value
  }
  // 收到更新通知后,进行更新,并触发依赖回调
  update() {
    const oldValue = this.value
    this.value = parsePath(this.data, this.expression)
    this.cb.call(this.data, this.value, oldValue)
  }
}

// 工具函数,用于根据指定访问路径,取出某一对象下的指定属性
function parsePath(obj, expression) {
  const segments = expression.split('.')
  for (let key of segments) {
    if (!obj) return
    obj = obj[key]
  }
  return obj
}
// demo.js
// observer 方法
function observer (value) {
    if (!isObject(value)) {
        return
    }
    var ob;
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
        ob = value.__ob__;
    } else {
        ob = new Observer(value);
    }
    return ob
}

// Observer 类
export class Observer {
  constructor(value) {
    this.value = value
    // 声明该数据对应的 dep 实例
    this.dep = new Dep()
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      // 如果是数组类型数据的话就特殊处理
      // 代理原型
      Object.setPrototypeOf(value, proxyPrototype)
      // 监听数组内容
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
  // 遍历下一层属性,执行defineReactive
  walk (obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i])
    }
  }
  observeArray(arr) {
    // 对数组内部的对象类型数据进行监听
    arr.forEach((i) => observe(i))
  }
}

// def 方法,用于为当前正在拦截的数据添加 __ob__ 属性
function def (obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  });
}

// 数据劫持
function defineReactive(data, key, value = data[key]) {
  const dep = new Dep()
  // 对当前属性的下一层属性进行劫持,并拿到当前数据对应的Observer实例
  let childOb = observe(value)
  // 对当前属性进行拦截
  Object.defineProperty(data, key, {
    get: function reactiveGetter() {
        // 收集依赖
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          // 新增
          if (Array.isArray(value)) {
              dependArray(value)
          }
        }
        return value
    },
    set: function reactiveSetter(newValue) {
      if (newValue === value) return
      value = newValue
      // 触发依赖,并更新Observer实例
      childOb = observe(newValue)
      dep.notify()
    }
  })
}

function dependArray(array) {
  for (let e of array) {
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e)) {
      dependArray(e)
    }
  }
}

// Dep 类
class Dep {
  constructor() {
    this.subs = []
  }
  // 依赖收集
  depend() {
    if (Dep.target) {
      this.addSub(Dep.target)
    }
  }
  // 通知更新
  notify() {
    const subs = [...this.subs]
    subs.forEach((s) => s.update())
  }
  // 添加订阅
  addSub(sub) {
    this.subs.push(sub)
  }
}

// 全局变量 Dep.target
Dep.target = null

// 用于暂存 Dep.target 指向的栈
const targetStack = []

// 入栈
function pushTarget (_target) {
    targetStack.push(Dep.target)	// 保存当前 Dep.target
    Dep.target = _target
}
        
// 出栈
function popTarget () {
    Dep.target = targetStack.pop()
}

// Watcher 类
class Watcher {
  constructor(data, expression, cb) {
      this.data = data;	    // 要实现响应式的对象
      this.expression = expression;	// 依赖属性的访问路径
      this.cb = cb;	    // 依赖的回调
      this.value = this.get() // 访问目标属性以触发getter从而发起依赖收集流程
  }
  // 访问当前实例依赖的属性,并将全局变量指向自身
  get() {
    pushTarget(this)
    const value = parsePath(this.data, this.expression)
    popTarget()
    return value
  }
  // 收到更新通知后,进行更新,并触发依赖回调
  update() {
    const oldValue = this.value
    this.value = parsePath(this.data, this.expression)
    this.cb.call(this.data, this.value, oldValue)
  }
}

// 工具函数,用于根据指定访问路径,取出某一对象下的指定属性
function parsePath(obj, expression) {
  const segments = expression.split('.')
  for (let key of segments) {
    if (!obj) return
    obj = obj[key]
  }
  return obj
}
// array.js
const reactiveMethods = [
    'push',
    'pop',
    'unshift',
    'shift',
    'splice',
    'reverse',
    'sort',
]

// 代理原型
reactiveMethods.forEach((method) => {
    // 取出原方法
    const originalMethod = arrayPrototype[method]
    // 在我们的代理原型上定义该方法的响应式版本
    Object.defineProperty(proxyPrototype, method, {
        value: function reactiveMethod(...args) {
            // 首先确保调用不受影响
            const result = originalMethod.apply(this, args)
            // 获取数组对象的 Observer 类实例
            const ob = this.__ob__
            // 对三种方法特殊处理
            let appended = null
            switch (method) {
                case 'push':
                case 'unshift':
                    appended = args
                    break;
                case 'splice':
                    // splice 方法中,第三个以及以后的参数是新增的数据
                    appended = args.slice(2)
            }
            // 如果有新增的数据,则对这些新增的数据进行劫持
            if (appended) ob.observerArray(appended)
            // 通过dep实例派发更新
            ob.dep.notify()
            return result
        },
        enumerable: false,
    	writable: true,
    	configurable: true
    })
})

github 仓库地址:Vue 响应式原理demo

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue3(六)-基础入门之自定义组件与插槽、ref通信

一、全局组件 html: <div id"app"><mytemplace></mytemplace> </div>javascript: <script>const { createApp } Vueconst app createApp({})app.component(mytemplace, {template: <div><button>返回</button>…

Resnet

是什么样的原因导致更深的网络导致的训练效果更差呢&#xff1f; 梯度消失和梯度爆炸 随着网络层数的不断加深&#xff0c;梯度消失和梯度爆炸的现象会越来越明显&#xff0c; 梯度消失&#xff1a;假设每一层的误差梯度是一个小于1的数&#xff0c;那么在我们反向传播过程中…

<软考高项备考>《论文专题 - 26 整合管理(4) 》

6 过程5-监控项目工作 6.1 问题 4W1H过程1-制定项目章程做什么跟踪、审查和报告整体项目进展&#xff0c;以实现项目管理计划中确定的绩效目标的过程&#xff1b;作用&#xff1a;①让干系人了解项目的当前状态并认可为处理绩效问题而采取的行动;②通过成本和进度预测&#x…

Docker 高级网络 - 自定义网桥实现容器间通信

目录 一、容器间容通信 1.1、解释 1.2、网络相关操作指令 1.2.1、查看 docker 的网络列表 1.2.2、创建网络自定义桥 1.2.3、删除某一个网络 1.2.4、查看某一个网络细节 1.2.5、运行多个容器在指定的网络中 一、容器间容通信 1.1、解释 简单来讲就是&#xff1a;容器间通…

华为Auth-HTTP服务器任意文件读取漏洞

华为Auth-Http Server 1.0存在任意文件读取&#xff0c;攻击者可通过该漏洞读取任意文件。 1.漏洞级别 高危 2.漏洞搜索 fofa server"Huawei Auth-Http Server 1.0"3.漏洞复现 构造 GET /umweb/passwd HTTP/1.1 Host: User-Agent: Mozilla/5.0 (Macintosh; I…

APP开发详解:数字药店系统源码

数字药店系统的兴起&#xff0c;不仅为消费者提供了更加便捷的购药体验&#xff0c;也为药店管理和药品销售带来了全新的机遇。 一、明确系统的基本功能&#xff1a; 1.用户注册与登录 2.药品浏览与搜索 3.购物车与结算。 4.在线支付与订单管理 二、开发环境与技术栈选择 …

blackbox黑盒监控部署(k8s内)tensuns专用

一、前言 部署在k8s中需要用到deployment、configmap、service服务 二、部署 创建存放yaml的目录 mkdir /opt/blackbox-exporter && cd /opt/blackbox-exporter 编辑blackbox配置文件&#xff0c;使用configmap挂在这 vi configmap.yaml apiVersion: v1 kind: Confi…

c语言-表达式求值

目录 前言一、隐式类型转换1.1 整型提升 二、算术转换三、操作符的属性四、问题表达式总结 前言 表达式求值的顺序一部分由操作符的优先级和结合性决定。 有些表达式的操作数在求值的过程中可能需要转换为其他类型 一、隐式类型转换 隐式类型转换是在编译器自动进行的类型转换…

TYPE C 接口知识

1、Type C 概述 Type-C口有4对TX/RX分线&#xff0c;2对USBD/D-&#xff0c;一对SBU&#xff0c;2个CC&#xff0c;另外还有4个VBUS和4个地线。 当Type-C接口仅用作传输DP信号时&#xff0c;则可利用4对TX/RX&#xff0c;从而实现4Lane传输&#xff0c;这种模式称为DPonly模式…

手持机定制_手持终端_rfid手持终端设备开发解决方案

智物通讯PDA手持终端方案以联发科64位八核MT6771芯片为核心&#xff0c;配备Android 10系统&#xff0c;以提供更高的运行速度和更低的功耗。存储器方面&#xff0c;则有2GB LPDDR332GB eMMC&#xff0c;同时也可选择4GB64GB、8GB128GB的配置&#xff0c;以确保设备的顺畅运行。…

PYTHON基础:python-plotly模拟随机过程

python-plotly模拟掷骰子随机过程 虽然投掷骰子看起来是随机事件&#xff0c;但只要使用正确的算法进行模拟&#xff0c;实际上它可以被精确地预测和分析。在数学领域&#xff0c;人们常常使用概率论和统计学知识来解释各种随机事件的规律和特征。而在赌场等场合&#xff0c;骰…

【教程】将Python转为C语言并编译生成二进制文件

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 本教程以DGL版本的GCN为例&#xff0c;其他也相似。 1、安装cython、gcc&#xff1a; sudo apt install cython gcc -y2、安装DGL、PyTorch&#xff1a; pip3 install torch torchvision torchaudio pip insta…

电商数据分析-03-电商数据采集

参考 最最最全数据仓库建设指南&#xff0c;速速收藏&#xff01;&#xff01; 第1章 数据仓库概念 数据仓库规划 1.1 数仓搭建 我们这里所说的数据仓库&#xff0c;是基于大数据体系的&#xff0c;里面包含标签类目&#xff0c;区别于传统的数据仓库。下面我们来将这张图分解…

信息网络协议基础-IPv6协议

文章目录 概述为什么引入IP服务模型IPv4的可扩展性问题解决方法***CIDR(Classless Inter-Domain Routing, 无类别域间寻路)前缀汇聚***前缀最长匹配***NAT(网络地址转换)存在的问题解决方案路由表配置***局限性IPv6协议头标IPv6地址表示前缀类型单播地址链路局部地址(Link-Loca…

元道经纬相机信息化赋能光伏电站运维管理

近年来&#xff0c;我国光伏产业高速发展&#xff0c;尤其以分布式光伏发电项目增长迅速&#xff0c;为更好服务新能源发电&#xff0c;大力推广电能替代。与此同时&#xff0c;电力企业亟需改变落后的管理模式&#xff0c;借助信息化软件提升管理效率。 为了进一步提升光伏电…

SpringBoot3 Web开发

注&#xff1a;SpringBoot的Web开发能力&#xff0c;由SpringMVC提供。 0. WebMvcAutoConfiguration原理 1. 生效条件 AutoConfiguration(after { DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class }) …

大创项目推荐 深度学习LSTM新冠数据预测

文章目录 0 前言1 课题简介2 预测算法2.1 Logistic回归模型2.2 基于动力学SEIR模型改进的SEITR模型2.3 LSTM神经网络模型 3 预测效果3.1 Logistic回归模型3.2 SEITR模型3.3 LSTM神经网络模型 4 结论5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 …

信号与线性系统翻转课堂笔记12——时域取样定理

信号与线性系统翻转课堂笔记12 The Flipped Classroom12 of Signals and Linear Systems 对应教材&#xff1a;《信号与线性系统分析&#xff08;第五版&#xff09;》高等教育出版社&#xff0c;吴大正著 一、要点 &#xff08;1&#xff09;了解信号取样的概念&#xff1…

医院云HIS系统源码,saas多医院版,适用于专科医院、集团医院、基层医院

医院云HIS系统源码&#xff0c;自主研发&#xff0c;自主版权&#xff0c;电子病历病历4级 系统概述&#xff1a; 一款满足基层医院各类业务需要的云HIS系统。该系统能帮助基层医院完成日常各类业务&#xff0c;提供病患挂号支持、病患问诊、电子病历、开药发药、会员管理、统…

9.传统的轨道画线算法(完成)

轨道画线分为以下步骤&#xff1a; 1.读取摄像头图片 2.图片灰度处理&#xff0c;截取轨道区域的图片 3.中值滤波处理&#xff0c;并区域取均值后做期望差的绝对值。本人通过一些轨道图片实验&#xff0c;用这种方法二值化得到的效果比caany算子等方法的效果好 4.二值化后再…