vue3通过Proxy+Reflect实现响应式,vue2通过defineProperty来实现

Proxy

Proxy是什么

Proxy是ES6中增加的类,表示代理。

如果我们想要监听对象的操作过程,可以先创建一个代理对象,之后所有对于对象的操作,都由代理对象来完成,代理对象可以监听到我们对于原对象进行了哪些操作。

Proxy怎么使用

Proxy是一个类,通过new关键字创建对象,传入原对象和处理监听的捕获器

const user = {
  name: 'alice'
} const proxy = new Proxy(user, {}) console.log(proxy)
proxy.name = 'kiki'
console.log(proxy.name)
console.log(user.name)

对代理所作的操作,同样会作用于原对象

什么是捕获器

捕获器就是用来监听对于对象操作的方法

const user = {
  name: 'alice',
  age: 18
}
const proxy = new Proxy(user, {
  get: function(target, key){
    console.log(`调用${key}属性的读取操作`)
    return target[key]
  },
  set: function(target, key, value){
    console.log(`调用${key}属性的设置操作`)
    target[key] = value
  }
})
proxy.name = 'kiki'
console.log(proxy.age)

以上get、set是捕获器中常用的两种,分别用于对象数据的“读取”操作和“设置”操作

有哪些捕获器

Proxy里对应捕获器与普通对象的操作和定义是一一对应的

  1. handler.getPrototypeOf()

    Object.getPrototypeOf 方法的捕捉器。

  2. handler.setPrototypeOf()

    Object.setPrototypeOf 方法的捕捉器。

  3. handler.isExtensible()

    Object.isExtensible 方法的捕捉器。

  4. handler.preventExtensions()

    Object.preventExtensions 方法的捕捉器。

  5. handler.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor 方法的捕捉器。

  6. handler.defineProperty()

    Object.defineProperty 方法的捕捉器。

  7. handler.has()

    in 操作符的捕捉器。

  8. handler.get()

    属性读取操作的捕捉器。

  9. handler.set()

    属性设置操作的捕捉器。

  10. handler.deleteProperty()

    delete 操作符的捕捉器。

  11. handler.ownKeys()

    Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。

  12. handler.apply()

    函数调用操作的捕捉器。

  13. handler.construct()

    new 操作符的捕捉器。

将这些捕获器以及对应的对象操作写在了以下示例中

const user = {
  name: "alice",
  age: 18
};
const proxy = new Proxy(user, {
  get(target, key) {
    console.log("执行了get方法");
    return target[key];
  },
  set(target, key, value) {
    target[key] = value;
    console.log("执行了set方法");
  },
  has(target, key) {
    return key in target;
  },
  deleteProperty(target, key){
    console.log('执行了delete的捕获器')
    delete target[key]
  },
  ownKeys(target){
    console.log('ownKeys')
    return Object.keys(target)
  },
  defineProperty(target, key, value){
    console.log('defineProperty', target, key, value)
    return true
  },
  getOwnPropertyDescriptor(target, key){
    return Object.getOwnPropertyDescriptor(target, key)
  },
  preventExtensions(target){
    console.log('preventExtensions')
    return Object.preventExtensions(target)
  },
  isExtensible(target){
    return Object.isExtensible(target)
  },
  getPrototypeOf(target){
    return Object.getPrototypeOf(target)
  },
  setPrototypeOf(target, prototype){
    console.log('setPrototypeOf', target, prototype)
    return false
  }
}); console.log(proxy.name);
proxy.name = "kiki";
console.log("name" in proxy);
delete proxy.age
console.log(Object.keys(proxy))
Object.defineProperty(proxy, 'name', {
  value: 'alice'
})
console.log(Object.getOwnPropertyDescriptor(proxy, 'name'))
console.log(Object.preventExtensions(proxy))
console.log(Object.isExtensible(proxy))
console.log(Object.getPrototypeOf(proxy))
Reflect.setPrototypeOf(proxy, {})

通过捕获器去监听对象的修改、查询、删除等操作

以上捕获器中只有apply和constructor是属于函数对象的

function foo(){}

const proxy = new Proxy(foo, {
  apply: function(target, thisArg, args){
    console.log('执行proxy的apply方法', target, thisArg, args)
    return target.apply(thisArg, args)
  },
  construct: function(target, argArray){
    console.log('执行proxy的construct方法',target, argArray)
    return new target()
  }
}) proxy.apply({}, [1,2])
new proxy('alice', 18)

apply方法执行apply捕获器,new操作执行constructor捕获器

Reflect

Reflect是什么

Reflect也是ES6新增的一个API,表示反射。它是一个对象,提供了很多操作对象的方法,类似于Object中的方法,比如 Reflect.getPrototypeOf 和 Object.getPrototypeOf。

早期操作对象的方法都是定义在Object上,但Object作为构造函数,直接放在它身上并不合适,所以新增Reflect对象来统一操作,并且转换了对象中in、delete这样的操作符

Reflect中有哪些方法

Reflect中的方法与Proxy中是一一对应的

  1. Reflect.apply(target, thisArgument, argumentsList)

    对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似。

  2. Reflect.construct(target, argumentsList[, newTarget])

    对构造函数进行 new 操作,相当于执行 new target(...args)。

  3. Reflect.defineProperty(target, propertyKey, attributes)和 Object.defineProperty() 类似。如果设置成功就会返回 true

  4. Reflect.deleteProperty(target, propertyKey)

    作为函数的delete操作符,相当于执行 delete target[name]。

  5. Reflect.get(target, propertyKey[, receiver])

    获取对象身上某个属性的值,类似于 target[name]。

  6. Reflect.getOwnPropertyDescriptor(target, propertyKey)

    类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符,  否则返回 undefined.

  7. Reflect.getPrototypeOf(target)

    类似于 Object.getPrototypeOf()。

  8. Reflect.has(target, propertyKey)

    判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。

  9. Reflect.isExtensible(target)

    类似于 Object.isExtensible().

  10. Reflect.ownKeys(target)

    返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受enumerable影响).

  11. Reflect.preventExtensions(target)

    类似于 Object.preventExtensions()。返回一个Boolean。

  12. Reflect.set(target, propertyKey, value[, receiver])

    将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。

  13. Reflect.setPrototypeOf(target, prototype)

    设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回true。

将之前通过Proxy设置代理的对象操作全都变为Reflect

const user = {
  name: "alice",
  age: 18
};
const proxy = new Proxy(user, {
  get(target, key) {
    console.log("执行了get方法");
    return Reflect.get(target, key)
  },
  set(target, key, value) {
    Reflect.set(target, key, value)
    console.log("执行了set方法");
  },
  has(target, key) {
    return Reflect.has(target, key)
  },
  deleteProperty(target, key){
    Reflect.deleteProperty(target, key)
  },
  ownKeys(target){
    console.log('ownKeys')
    return Reflect.ownKeys(target)
  },
  defineProperty(target, key, value){
    console.log('defineProperty', target, key, value)
    return true
  },
  getOwnPropertyDescriptor(target, key){
    return Reflect.getOwnPropertyDescriptor(target, key)
  },
  preventExtensions(target){
    console.log('preventExtensions')
    return Reflect.preventExtensions(target)
  },
  isExtensible(target){
    return Reflect.isExtensible(target)
  },
  getPrototypeOf(target){
    return Reflect.getPrototypeOf(target)
  },
  setPrototypeOf(target, prototype){
    console.log('setPrototypeOf', target, prototype)
    return false
  }
});
console.log(proxy.name);
proxy.name = "kiki";
console.log(Reflect.has(proxy, 'name'));
delete proxy.age
console.log(Reflect.ownKeys(proxy))
Reflect.defineProperty(proxy, 'name', {
  value: 'alice'
})
console.log(Reflect.getOwnPropertyDescriptor(proxy, 'name'))
console.log(Reflect.preventExtensions(proxy))
console.log(Reflect.isExtensible(proxy))
console.log(Reflect.getPrototypeOf(proxy))
Reflect.setPrototypeOf(proxy, {})

实现的效果是完全一致的

Reflect的receiver

Reflect中在进行get/set捕获器操作的时候,还有一个入参是receiver,指的是代理对象,用于改变this指向

const user = {
  _name: 'alice',
  get name(){
    return this._name
  },
  set name(value){
    this._name = value
  }
}
const proxy = new Proxy(user, {
  get: function(target, key, receiver){
    console.log('get操作', key)
    return Reflect.get(target, key, receiver)
  },
  set: function(target, key, receiver){
    console.log('set操作', key)
    return Reflect.set(target, key, receiver)
  },
})
console.log(proxy.name)
proxy.name = 'kiki'

(1) 如果没有receiver,那么当修改name属性时,objProxy先执行key为name时的get操作

(2) 然后代理到obj里的get方法,读取this的_name属性,此时的this是obj,会直接修改

obj._name,不会再经过objProxy

(3) 增加了receiver之后,执行obj的get方法,读取this的_name属性,此时this是proxy

对象,所以会再次到get的捕获器中

set操作同理

Reflect中的constructor

用于改变this的指向

function Person(){
}
function Student(name, age){
  this.name = name;
  this.age = age
}
const student = Reflect.construct(Student, ['aclie', 18], Person) console.log(student)
console.log(student.__proto__ === Person.prototype)

此时创建的student对象虽然拥有Student的属性和方法,但是它的this指向Person

vue的响应式

通过以下步骤一步步实现响应式

1、定义一个数组收集所有的依赖

  • 定义全局变量reactiveFns用来保存所有的函数
  • 定义方法watchFn,入参为函数,代码体为将入参保存进全局变量中
  • 修改对象的值,遍历全局变量,执行每一个函数
let user = {
  name: "alice",
};
let reactiveFns = [];
function watchFn(fn) {
  reactiveFns.push(fn);
}
watchFn(function () {
  console.log("哈哈哈");
});
watchFn(function () {
  console.log("hello world");
});
function foo(){
  console.log('普通函数')
}
user.name = "kiki";
reactiveFns.forEach((fn) => {
  fn();
});

此时通过响应式函数 watchFn 将所有需要执行的函数收集进了数组中,然后当变量的值发生变化时,手动遍历执行所有的函数

2.收集依赖类的封装

以上只有一个数组来收集对象的执行函数,真实情况下,不止一个对象需要对操作状态进行监听,需要监听多个对象就可以使用类。

  • 定义Depend类,给每个实例对象提供addDepend方法用于添加绑定的方法, notify方法用于执行该实例的reactiveFns属性上添加的所有方法
  • 封装响应式函数watchFn,函数里调用实例对象的appDepend方法
  • 修改对象的值,调用实例对象的notify方法
class Depend {
  constructor() {
    this.reactiveFns = [];
  }
  addDepend(fn) {
    this.reactiveFns.push(fn);
  }
  notify() {
    this.reactiveFns.forEach((fn) => {
      fn();
    });
  }
}
let user = {
  name: "alice",
  age: 18,
};
const depend = new Depend();
function watchFn(fn) {
  depend.addDepend(fn);
}
watchFn(function(){
  console.log('啦啦啦啦啦啦')
})
watchFn(function(){
  console.log('hello hello')
})
function foo(){
  console.log('foo')
}
user.name = 'kiki'
depend.notify()

将收集操作和依次执行函数的方法都定义在类中

3.自动监听对象的变化

以上仍然是我们自己手动调用执行函数的方法,以下自动监听

  • 在通过类收集依赖的基础上,增加Proxy来定义对象,Reflect执行对象的方法
  • 在set方法中执行实例对象depend的notify方法
  • 修改代理proxy属性的值
class Depend {
  constructor() {
    this.reactiveFns = [];
  }
  addDepend(fn) {
    this.reactiveFns.push(fn);
  }
  notify() {
    this.reactiveFns.forEach((fn) => {
      fn();
    });
  }
}
let user = {
  name: "alice",
  age: 18,
};
const depend = new Depend();
function watchFn(fn) {
  depend.addDepend(fn);
}
const proxy = new Proxy(user, {
  get: function (target, key, receiver) {
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    Reflect.set(target, key, value, receiver);
    depend.notify();
  },
});
watchFn(function () {
  console.log("name变化执行的函数");
});
watchFn(function () {
  console.log("age变化执行的函数");
});
function foo() {
  console.log("foo");
}
proxy.name = "kiki";
proxy.age = 20;

此时的问题是,修改了对象的任一属性,所有的函数都会调用,没有按照一一对应的关系来保存对象的属性和对应的函数

4、收集依赖的管理

  • 定义weakMap用来管理对象,[对象名, map],定义map保存 [对象的属性名, 实例对象depend]
  • 定义获取depend实例对象的方法getDepend,先从weakMap中获取map,如果没有就new 一个,再从map中获取depend对象,如果没有再new一个
  • 在set方法中获取depend对象,调用notify方法
class Depend {
  constructor() {
    this.reactiveFns = [];
  }
  addDepend(fn) {
    this.reactiveFns.push(fn);
  }
  notify() {
    this.reactiveFns.forEach((fn) => {
      fn();
    });
  }
}
let user = {
  name: "alice",
  age: 18,
};
const depend = new Depend();
function watchFn(fn) {
  depend.addDepend(fn);
}
const weakMap = new WeakMap()
function getDepend(obj, key){
  let map = weakMap.get(obj)
  if(!map){
    map = new Map()
    weakMap.set(obj, map)
  }
  let depend = map.get(key)
  if(!depend){
    depend = new Depend()
    map.set(key, depend)
  }
  return depend
}
const proxy = new Proxy(user, {
  get: function (target, key, receiver) {
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    Reflect.set(target, key, value, receiver);
    const depend = getDepend(target, key)
    depend.notify();
  },
});
watchFn(function () {
  console.log("name变化执行的函数");
});
watchFn(function () {
  console.log("age变化执行的函数");
});
function foo() {
  console.log("foo");
}
proxy.name = "kiki";
proxy.age = 20;

此时proxy对应的depend是没有值的,所以此时没有任何打印的数据

5、正确管理收集的依赖

  • 全局定义变量activeReactiveFn指向watchFn传入的方法,执行传入的方法,再将 activeReactiveFn指向null
  • watchFn传入时便会被执行一次,用于代码对象的get方法中收集依赖
  • Depend类中使用addDepend方法无需传参,直接使用全局的activeReactiveFn
  • get方法中通过getDepend获取depend,并使用addDepend方法,收集依赖
class Depend {
  constructor() {
    this.reactiveFns = [];
  }
  addDepend(fn) {
    this.reactiveFns.push(fn);
  }
  notify() {
    this.reactiveFns.forEach((fn) => {
      fn();
    });
  }
}
let user = {
  name: "alice",
  age: 18,
};
let activeReactiveFn = null;
function watchFn(fn) {
  activeReactiveFn = fn;
  fn();
  activeReactiveFn = null;
}
const weakMap = new WeakMap();
function getDepend(obj, key) {
  let map = weakMap.get(obj);
  if (!map) {
    map = new Map();
    weakMap.set(obj, map);
  }
  let depend = map.get(key);
  if (!depend) {
    depend = new Depend();
    map.set(key, depend);
  }
  return depend;
}
const proxy = new Proxy(user, {
  get: function (target, key, receiver) {
    const depend = getDepend(target, key)
    depend.addDepend(activeReactiveFn)
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    Reflect.set(target, key, value, receiver);
    const depend = getDepend(target, key);
    depend.notify();
  },
});
watchFn(function () {
  console.log(proxy.name, "name变化执行的函数");
  console.log(proxy.name, "name变化执行的函数 again");
});
watchFn(function () {
  console.log(proxy.age, "age变化执行的函数");
});
function foo() {
  console.log("foo");
}
proxy.name = "kiki";

此时已经能够根据属性值的变化而执行对应的函数了,但同一个函数会执行两次

6、重构

  • 当函数中调用两次proxy的属性时,会将同一个函数添加到数组中两次,所以将 reactiveFn数据结构由数组变成set
  • 定义reactive函数用来收集处理需要代理及响应式的对象
let activeReactiveFn = null;
class Depend {
  constructor() {
    this.reactiveFns = new Set();
  }
  addDepend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn);
    }
  }
  notify() {
    this.reactiveFns.forEach((fn) => {
      fn();
    });
  }
}
function watchFn(fn) {
  activeReactiveFn = fn;
  fn();
  activeReactiveFn = null;
}
const weakMap = new WeakMap();
function getDepend(obj, key) {
  let map = weakMap.get(obj);
  if (!map) {
    map = new Map();
    weakMap.set(obj, map);
  }
  let depend = map.get(key);
  if (!depend) {
    depend = new Depend();
    map.set(key, depend);
  }
  return depend;
}
function reactive(obj){
  return new Proxy(obj, {
    get: function (target, key, receiver) {
      const depend = getDepend(target, key);
      depend.addDepend();
      return Reflect.get(target, key, receiver);
    },
    set: function (target, key, value, receiver) {
      Reflect.set(target, key, value, receiver);
      const depend = getDepend(target, key);
      depend.notify();
    },
  });
}
const user = reactive({
  name: "alice",
  age: 18,
})
const info = reactive({
  message: 'hello'
})
watchFn(function () {
  console.log(user.name, "name变化执行的函数");
  console.log(user.name, "name变化执行的函数 again");
});
watchFn(function () {
  console.log(user.age, "age变化执行的函数");
});
watchFn(function(){
  console.log(info.message, "message发生变化了")
})
function foo() {
  console.log("foo");
}
user.name = "kiki";
user.age = 20;
info.message = 'ooo'

此时已实现vue3的响应式~

vue2的实现原理

vue2的实现就是将Proxy和Reflect替换成了Object.defineProperty和Object本身的一些方法

let activeReactiveFn = null;
class Depend {
  constructor() {
    this.reactiveFns = new Set();
  }
  addDepend() {
    if (activeReactiveFn) {
      this.reactiveFns.add(activeReactiveFn);
    }
  }
  notify() {
    this.reactiveFns.forEach((fn) => {
      fn();
    });
  }
}
function watchFn(fn) {
  activeReactiveFn = fn;
  fn();
  activeReactiveFn = null;
}
const weakMap = new WeakMap();
function getDepend(obj, key) {
  let map = weakMap.get(obj);
  if (!map) {
    map = new Map();
    weakMap.set(obj, map);
  }
  let depend = map.get(key);
  if (!depend) {
    depend = new Depend();
    map.set(key, depend);
  }
  return depend;
}
function reactive(obj){
  Object.keys(obj).forEach(key=>{
    let value = obj[key]
    Object.defineProperty(obj, key, {
      get: function () {
        const depend = getDepend(obj, key);
        depend.addDepend();
        return value
      },
      set: function (newValue) {
        value = newValue
        const depend = getDepend(obj, key);
        depend.notify();
      },
    })
  })
  return obj
}
const user = reactive({
  name: "alice",
  age: 18,
})
const info = reactive({
  message: 'hello'
})
watchFn(function () {
  console.log(user.name, "name变化执行的函数");
  console.log(user.name, "name变化执行的函数 again");
});
watchFn(function () {
  console.log(user.age, "age变化执行的函数");
});
watchFn(function(){
  console.log(info.message, "message发生变化了")
})
function foo() {
  console.log("foo");
}
user.name = "kiki";
user.age = 20;
info.message = 'ooo'

和上面实现的效果是一致的

以上就是通过Proxy和Reflect实现vue的响应式原理,关于js高级,还有很多需要开发者掌握的地方,可以看看我写的其他博文,持续更新中~

通过Proxy和Reflect实现vue的响应式原理的更多相关文章

  1. 一探 Vue 数据响应式原理

    一探 Vue 数据响应式原理 本文写于 2020 年 8 月 5 日 相信在很多新人第一次使用 Vue 这种框架的时候,就会被其修改数据便自动更新视图的操作所震撼. Vue 的文档中也这么写道: Vu ...

  2. vue.js响应式原理解析与实现

    vue.js响应式原理解析与实现 从很久之前就已经接触过了angularjs了,当时就已经了解到,angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很 ...

  3. vue深入响应式原理

    vue深入响应式原理 深入响应式原理 — Vue.jshttps://cn.vuejs.org/v2/guide/reactivity.html 注意:这里说的响应式不是bootsharp那种前端UI ...

  4. Vue 数据响应式原理

    Vue 数据响应式原理 Vue.js 的核心包括一套“响应式系统”.“响应式”,是指当数据改变后,Vue 会通知到使用该数据的代码.例如,视图渲染中使用了数据,数据改变后,视图也会自动更新. 举个简单 ...

  5. 深入解析vue.js响应式原理与实现

    vue.js响应式原理解析与实现.angularjs是通过脏检查来实现数据监测以及页面更新渲染.之后,再接触了vue.js,当时也一度很好奇vue.js是如何监测数据更新并且重新渲染页面.vue.js ...

  6. Vue的响应式原理

    Vue的响应式原理 一.响应式的底层实现 1.Vue与MVVM Vue是一个 MVVM框架,其各层的对应关系如下 View层:在Vue中是绑定dom对象的HTML ViewModel层:在Vue中是实 ...

  7. vue系列---响应式原理实现及Observer源码解析(一)

    _ 阅读目录 一. 什么是响应式? 二:如何侦测数据的变化? 2.1 Object.defineProperty() 侦测对象属性值变化 2.2 如何侦测数组的索引值的变化 2.3 如何监听数组内容的 ...

  8. Vue.js响应式原理

      写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出. 文章的原地址:answershuto/learnV ...

  9. Vue的响应式原理---(v-model中的双向绑定原理)

    Vue响应式原理 不要认为数据发生改变,界面跟着更新是理所当然. 具体代码实现:https://gitee.com/ahaMOMO/Vue-Responsive-Principle.git 看下图: ...

  10. Vue.js 响应式原理

    1. Vue2.x 基于 Object.defineProperty 方法实现响应式(Vue3 将采用 Proxy) Object.defineProperty(obj, prop, descript ...

随机推荐

  1. 推荐一个.Ner Core开发的配置中心开源项目

    当你把单体应用改造为微服务架构,相应的配置文件,也会被分割,被分散到各个节点.这个时候就会产生一个问题,配置信息是分散的.冗余的,变成不好维护管理.这个时候我们就需要把配置信息独立出来,成立一个配置中 ...

  2. 2021-03-10:一个数组上共有 N 个点,序号为0的点是起点位置,序号为N-1 的点是终点位置。现在需要依次的从 0 号点走到 N-1 号点。但是除了 0 号点和 N-1 号点,他可以在其余的 N-2 个位置中选出一个点,并直接将这个点忽略掉,问从起点到终点至少走多少距离?

    2021-03-10:一个数组上共有 N 个点,序号为0的点是起点位置,序号为N-1 的点是终点位置.现在需要依次的从 0 号点走到 N-1 号点.但是除了 0 号点和 N-1 号点,他可以在其余的 ...

  3. Create Vite App 支持 OpenTiny 啦🎉

    大家好,我是 Kagol,个人公众号:前端开源星球. 一个月前,日日自新写了一篇介绍 Create Vite App 开源项目的文章: 基于vite 4.x 快速搭建开箱即用,高度可定制化模版脚手架 ...

  4. 解决git 本地代码与远程仓库冲突问题

    在使用协同开发难免会出现同时修改某个文件导致代码冲突的问题 * branch master -> FETCH_HEAD error: Your local changes to the foll ...

  5. 前端开发如何更好的避免样式冲突?级联层(CSS@layer)

    作者:vivo 互联网前端团队 - Zhang Jiqi 本文主要讲述了CSS中的级联层(CSS@layer),讨论了级联以及级联层的创建.嵌套.排序和浏览器支持情况.级联层可以用于避免样式冲突,提高 ...

  6. 使用 ChatGPT 的 7 个技巧 | Prompt Engineering 学习笔记

    概述 前段时间在 DeepLearning 学了一门大火的 Prompt 的课程,吴恩达本人授课,讲的通俗易懂,感觉受益匪浅,因此在这里总结分享一下我的学习笔记. 为什么要学习 Prompt ? 因为 ...

  7. 从n个不同元素中有放回的取出r个且不计顺序,有多少种不同的取法?

    从n个不同元素中有放回的取出r个且不计顺序,有多少种不同的取法? 答案是:\(C_{n+r-1}^r\) 解析 因为是有放回地取出,所以同一个元素可能会被取多次,并且取出的元素是不计顺序的,那么如果我 ...

  8. C# 图片转PDF,PDF增加水印文字

    好久没写博客了,今天给大家分享一个图片转PDF的相关操作,也算是一次总结吧. 首先需要准备动态库itextsharp.dll,这个dll去网上下载,都可以下载到,C#对PDF的操作都是基于这个类库来实 ...

  9. FPGA加速技术:在数据中心和云计算中的应用

    目录 1. 引言 2. 技术原理及概念 3. 实现步骤与流程 3.1 准备工作:环境配置与依赖安装 3.2 核心模块实现 3.3 集成与测试 4. 应用示例与代码实现讲解 4.1. 应用场景介绍 4. ...

  10. uniapp微信小程序转支付宝小程序踩坑(持续更新)

    首先第一个,真有被折磨到! // 微信正常使用,支付宝不行 <image src="https://static.dabapiao.com/images/coupon-index.pn ...