手动实现Vue3 & 原理解析:setup环境 & reactive函数 & effect函数(一)

发表于 2年以前  | 总阅读数:745 次

前言

本篇解析参阅 vue3源码、崔大的mini-vue、霍春阳大佬的《Vuejs设计与实现》尽可能记录我的Vue3源码阅读学习过程。我会结合自己的思考,提出问题,找到答案,附在每一篇的底部。希望大家能在我的文章中也能一起学习,一起进步,有 get 到东西的可以给作者一个小小的赞作为鼓励吗?谢谢大家!

手写简易vue3 setup环境 && reactive函数 && effect函数

setup环境

npm init 命令生成 package.json

当前项目主要采用 ts 来编写,用 jest 来做单元测试

说明:ts 会使用 any 类型,希望能把重点放在 vue3 的实现原理,如需要 会在后面做修改补充

所以需要安装如下的依赖包:

  1. jest (核心包)
  2. typescript (核心包)
  3. @types/jest (jest 语法 ts 解释)
  4. ts-jest (预处理 ts 的 jest 预制)
  5. @babel/core (babel 核心)
  6. @babel/preset-env (perset-env 预设)
  7. @babel/preset-typescript (babel ts 预设)
  8. babel-jest (jest es依赖包)

附带安装指令:npm install jest typescript @types/jest ts-jest @babel/core @babel/preset-env @babel/preset-typescript babel-jest \--save-dev

ts config 命令创建 tsconfig.json

重点介绍以下配置:

"target": "es2016",  // 为发出的JavaScript设置JavaScript语言版本,并包含兼容的库声明。
"lib": [   // 指定一组描述目标运行时环境的捆绑库声明文件。
      "DOM",
      "ES2015"
    ],
"types": ["jest"],   // 指定要包含而不在源文件中引用的类型包名称。
"noImplicitAny": false,    // 隐式 any 声明不会报错
复制代码

创建 babel.config.js 文件,配置 babel 预设规则,配置如下:

module.exports = {
  presets: [
    ["@babel/preset-env", { targets: { node: "current" } }],
    "@babel/preset-typescript"
  ]
}
复制代码

创建 jest.config.js 文件,配置 jest 依赖包,配置如下

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};
复制代码

至此,初始开发环境 setup 完毕

reactive 函数

众所周知,vue3 采用 Proxy 来代理对象,通过劫持方法来实现响应式

reactive函数就是将传入的对象变成一个代理对象

reactive 函数的初步实现

初步实现:

export function reactive(raw) {
  return new Proxy(raw, {
    get(target, key) {
      const res = Reflect.get(target, key)
      // TODO 依赖收集
      // tarck(target, key)
      return res
    },
    set(target, key, value) {
      const res = Reflect.set(target, key, value)
      // TODO 触发依赖
      // trigger(target, key, value)
      return res
    }
  })
}
复制代码

相关问题:Proxy是什么?Reflect是什么?为什么要用Reflect?[1]

接下来,我们需要实现 在 get 中实现 依赖收集 以及 在 set 中实现 触发依赖

依赖收集 & 触发依赖

依赖收集我们将它封装为一个 track 函数,在触发代理对象的 get 拦截器的时候 去收集依赖

触发依赖我们将它封装为一个 trigger 函数,在触发代理对象的 set 拦截器的时候去 触发依赖

这里首先要依赖一个 副作用函数产生的 activeEffect[2]

欢迎回来,这时候我们已经知道了 activeEffect 的由来

依赖收集:

我们想要收集依赖,得知道是哪个对象(target) 的哪个 key 吧?还得知道对应这个 key 有哪些依赖

这里我们采用的方法是:

  1. 利用 全局 WeakMap 来保存所有对象
  2. 利用 Map 来保存对象中所有 key
  3. 利用 Set 来保存 key 中的依赖

类似这样的结构:

WeakMap ├─ Map Obj1 │ ├─ Set Obj1.key1 │ │ ├─ ReactiveEffectA │ │ └─ ReactiveEffectB │ ├─ Set Obj1.key2 │ └─ Set Obj1.key3 ├─ Map Obj2 └─ Map Obj3

依赖执行:

依赖的执行就比较简单了,就是根据传入的 target 和 key 去取出所有的 ReactiveEffect 实例并执行方法

// 最外层用来保存每一个 target 的 weakMap
const targetMap = new WeakMap()
/**
 * get 依赖收集函数
 * @param target 传入的对象
 * @param key 对应属性的 key
 */
export function track(target, key) {
  // 拿到当前 target 对应的 map (每个对应的 target 底下应该保存着自己的 key 的 map)
  let depsMap = targetMap.get(target)
  // 拿不到,说明需要新建一个 map 并存入 weakMap
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  // 拿到当前 key 的对应的 set (每个对应 key 底下应该保存着自己的 set, set里边是所有的依赖 ReactiveEffect)
  let dep = depsMap.get(key)
  // 拿不到,说明需要创建一个新的 set 并存入对应的 map
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  // 已经在 dep 中就不用 add 了
  if (dep.has(activeEffect)) return
  dep.add(activeEffect) // 把对应的 ReactiveEffect 实例加入 set 里
}

/**
 * set 触发依赖
 * @param target 传入的对象
 * @param key 对应属性的 key
 */
export function trigger(target, key) {
  let depsMap = targetMap.get(target)
  let dep = depsMap.get(key)
  // 找到对应的 ReactiveEffect 实例,执行他们的 run 方法
  for (const effect of dep) {
    effect.run()
  }
}
复制代码

实现 readonly

我们现在有一个需求,响应式对象应该是只读的,不允许修改

根据需求,我们不难得到修改方案:

  1. get 方法没必要收集依赖了
  2. set 方法应该抛出一个警告,不允许修改

那么我们可以新增 readonly 函数,返回一个 和 reactive 不一样的代理对象:

export function readonly(raw) {
  return new Proxy(raw, {
    get(target, key) {
      const res = Reflect.get(target, key)
      // 不需要 track
      return res
    },
    set(target, key, value) {
      // 输出一个 warning
      console.warn(`key: ${key} set failed, because ${target} is readonly`)
      return true
    }
  })
}
复制代码

那显然我们发现这个代码和我们的 reactive 极其相似,我们应该将共同的代码抽离出来,因此我们:

语义化 new Proxy

export function createActiveObject(raw: any, baseHandlers) {
  return new Proxy(raw, baseHandlers)
}
复制代码

实现 createSetter 和 createGetter 方法并分别导出 handlers

// 缓存 get set readonlyGet 这样只有触发代理的时候才会调用函数
const get = createGetter()
const set = createSetter()
const readonlyGet = createGetter(true)
// 对 get 包一层,isReadonly 默认为 fasle
function createGetter(isReadonly = false) {
  return function get(target, key) {
    const res = Reflect.get(target, key)
    // 不是只读的才要 track
    if (!isReadonly) {
      track(target, key)
    }
    return res
  }
}
// 对 set 包一层
function createSetter() {
  return function set(target, key, value) {
    const res = Reflect.set(target, key, value)
    trigger(target, key)
    return res
  }
}
// 当调用 reactive 的时候传入这个 handlers
export const mutableHandlers = {
  get,
  set,
}
// 当调用 readonly 的时候传入这个 handlers
export const readonlyHandlers = {
  get: readonlyGet,
  set(target, key, value) {
    console.warn(`key: ${key} set failed, because ${target} is readonly`)
    return true
  }
}
复制代码

那么封装完成后,我们去生成响应式的代理对象/只读的代理对象就可以调用以下方法:

export function reactive(raw) {
  return createActiveObject(raw, mutableHandlers)
}
export function readonly(raw) {
  return createActiveObject(raw, readonlyHandlers)
}
复制代码

是不是清爽了非常多呢!!

实现 isReactive / isReadonly 方法

我们现在还需要两个方法 分别用来判断当前的这个对象是不是 响应式/只读 对象

那么我们需要增加 ReactiveFlag元组 、 isReactive方法 、 isReadonly方法,修改 createGetter方法:

export const enum ReactiveFlags {
  IS_REACTIVE = "__v_isReactive",
  IS_READONLY = "__v_isReadonly"
}
export function isReactive(obj) {
  // 访问obj的xxxx属性会触发 get 方法
  // 当 obj 不是一个响应式的时候 由于没有 isRecvtive属性 所以会是一个undefined 这时候用 !! 把它变成一个布尔类型
  return !!obj[ReactiveFlags.IS_REACTIVE]
}
export function isReadonly(obj) {
  // 同上
  return !!obj[ReactiveFlags.IS_READONLY]
}

function createGetter(isReadonly = false) {
  return function get(target, key) {
    // 根据这里的判断来返回 isReadonly 就可以了
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    }
    const res = Reflect.get(target, key)
    if (!isReadonly) {
      track(target, key)
    }
    return res
  }
}
复制代码

这里并不复杂,就是通过 创建 get 时的 isReadonly 参数来返回响应的值即可

实现 shallowReactive / shallowReadonly 函数

我们还希望面对一个嵌套对象,我们不想他内部的属性对象也变成一个 响应式/只读 的代理对象,在 vue2 里我们可以利用 object.freeze 来使得内部对象无法被数据劫持。

那在 vue3 我们要怎么实现呢?其实基于上边的代码,我们只需要停止对内部对象做递归即可。

那我们需要创建对 createGetter 做修改,并利用 Object.assign(我们已经语义化成了 extend) 来实现对应的 handlers :

// 分别创建getter
const shallowReactiveGet = createGetter(false, true)
const shallowReadonlyGet = createGetter(true, true)
// 注意这里入参增加了 shallow 参数默认为false
function createGetter(isReadonly = false, shallow = false) {
  return function get(target, key) {
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    }
    const res = Reflect.get(target, key)
    // 这里就是关键了!!!!如果不需要深度响应式 那么直接返回 res
    if (shallow) return res
    // 如果 res 是对象 那么还需要深层次的实现响应式 
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res)
    }
    if (!isReadonly) {
      track(target, key)
    }
    return res
  }
}
// reactive 的 set 还是不变,只是修改 getter  extend 其实就是 Object.assign
export const shallowReactiveHandlers = extend({}, mutableHandlers, {
  get: shallowReactiveGet
})
// readonly 的 set 还是不变,只是修改 getter  extend 其实就是 Object.assign
export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
  get: shallowReadonlyGet
})
复制代码

接着我们需要创建 shallowReactive 、 shallowReadonly 方法,使用这两个handler:

export function shallowReactive(raw) {
  return createActiveObject(raw, shallowReactiveHandlers)
}

export function shallowReadonly(raw) {
  return createActiveObject(raw, shallowReadonlyHandlers)
}
复制代码

ok! 大功告成,这里的实现也并不复杂。让我们接着往下看!

effect 函数

effect 函数我们也称作 副作用函数

顾名思义,就是当 effect 函数被执行的时候它可能会直接或者间接的影响其他函数的执行,这就是产生了副作用

effect 函数 初步实现

当我们调用 effect 函数的时候 需要传入一个 执行函数 fn

内部生成一个 ReactiveEffect 实例后执行 这个 fn

从这个命名上我们也能知道 reactive 和 effect 的关系是十分密切的

let activeEffect // 保存当前正在执行的 effect
class ReactiveEffect{
  private _fn: any
  constructor(fn) {
    this._fn = fn
  }
  run() {
    // 收集当前的 effect 实例 赋值到全局变量 activeEffect
    activeEffect = this
    // 执行方法并返回
    return this._fn()
  }
}
export function effect(fn) {
  // 生成 ReactiveEffect 实例
  const _effect = new ReactiveEffect(fn)
  // 执行方法
  _effect.run()
}
复制代码

关键点就是我们的这个 activeEffect 变量啦,当我们调用 effect函数 的时候他会生成一个 ReactiveEffect 实例,并保存到全局

这里我们可以回到 reactive 的依赖收集以及触发依赖[3]

effect 函数优化 ———— 调用 effect 的时候应该返回当前的执行函数

我们希望 调用 effect 的时候我们也能得到这个 effect 函数,我们手动执行 传入的 fn

附 jest 测试用例:

it('should return runner when call effect', () => {
  let age = 10
  // runner 拿到 effect 创建的 runner
  const runner = effect(() => {
    age++
    return 'Age'
  })
  // 调用 effect 的时候 age++ 了所以应该是 11
  expect(age).toBe(11)
  // r 执行 runner 这时候 age又 ++ 了 所以应该是 12 了
  const r = runner()
  expect(age).toBe(12)
  // r 自身拿到 return 的 'Age'
  expect(r).toBe('Age')
});
复制代码

因此我们需要对我们的 effect 函数做出以下修改:

export function effect(fn, options: any = {}) {
  const _effect = new ReactiveEffect(fn)
  _effect.run()
  // run 函数内部涉及 activeEffect 的赋值 (activeEffect = this) 所以这里应该bind一下
  const runner: any = _effect.run.bind(_effect)
  runner.effect = _effect
  return runner
}
复制代码

这样子之后我们调用 effect 之后就能拿到这个 runner 对应的其实就是 ReactiveEffect 实例 的 run 方法

effect 函数优化 ———— scheduler函数选项

我们希望 effect 可以传入 一个 scheduler函数选项

当传入了 scheduler 的时候,

  1. 只有首次执行 effect 的时候会执行 fn
  2. 后续当响应式对象 触发 set 的时候 fn 不会执行 而是执行 scheduler
  3. 当执行 runner 的时候 会再次执行 fn

附上相应的 jest 测试用例:

/**
 * 1. 通过 effect 的第二个参数给定的 一个 scheduler 的 fn
 * 2. effect 第一次执行的时候 还会执行 fn
 * 3. 当响应式对象 触发 set 的时候 fn 不会执行 而是执行 scheduler
 * 4. 如果当执行 runner 的时候 会再次执行 fn
 */
it('scheduler', () => {
  let dummy
  let run: any
  // 定义一个 scheduler 函数,给 全局变量 run 赋值拿到 runner
  const scheduler = jest.fn(() => {
    run = runner
  })
  // 创建响应式对象
  const obj =  reactive({ age: 13 })
  // 执行 effect 并拿到 runner,这里 effect 我们传入了 scheduler函数 ,它被包裹在一个对象内部
  const runner = effect(
    () => {
      dummy = obj.age
    },
    { scheduler }
  )
  // 断言 scheduler 一开始不会被调用
  expect(scheduler).not.toHaveBeenCalled()
  expect(dummy).toBe(13)
  obj.age++
  // age++ 不会去调用 dummy = obj.age 而是应该调用 传入的 scheduler 
  expect(scheduler).toHaveBeenCalledTimes(1)
  expect(dummy).toBe(13)
  // 调用了 scheduler 后 run 拿到 runner,这时候调用 run 才去执行 dummy = obj.age 
  run()
  expect(dummy).toBe(14)
});
复制代码

为此,我们需要对我们的 effect函数 、 ReactiveEffect类 、trigger函数 做出修改:

class ReactiveEffect{
  private _fn: any
  // 接收可选参数 scheduler 并设置为 public
  constructor(fn, public scheduler?) {
    this._fn = fn
  }
  run() {
    // ...没有变化
  }
}
// 注意这里,我们让 effect 接收第二个参数 options
export function effect(fn, options: any = {}) {
  // 这里创建 ReactiveEffect 实例的时候 我们把 scheduler 带上了
  const _effect = new ReactiveEffect(fn, options.scheduler)
  _effect.run()
  const runner: any = _effect.run.bind(_effect)
  runner.effect = _effect
  return runner
}
export function trigger(target, key) {
  let depsMap = targetMap.get(target)
  let dep = depsMap.get(key)
  for (const effect of dep) {
    // 这个 effect 有 scheduler 的时候我们就执行 scheduler 不执行 run 了
    if (effect.scheduler) {
      effect.scheduler()
    } else {
      effect.run()
    }
  }
}
复制代码

ok,这样我们就能实现我们的要求了,重复一遍!!

传入 scheduler函数 选项时,effect 只有在初始化的时候执行 fn,当 set(trigger触发) 的时候,执行的是 scheduler函数,而 fn 我们可以通过 runner 手动执行

effect 函数优化 ———— stop 方法 以及 onStop hooks

我们又有新的需求了!!!

我们希望有一个 stop 方法,当我们调用 stop方法时 响应式对象属性被修改 不会触发 执行依赖(run)的动作,原传入的依赖还是要可以手动执行(runner执行)的

我们还希望每次执行完 stop 之后会触发一个 onStop 的hooks,这个 onStop 作为 effect 的 options 之一

先看测试用例:

it('stop', () => {
  let dummy
  const obj =  reactive({ prop: 1 })
  const runner = effect(() => {
    dummy = obj.prop
  })
  obj.prop = 2
  expect(dummy).toBe(2)
  // stop 了 runner 去修改响应式的时候 就不应该触发了
  stop(runner)
  obj.prop = 3
  expect(dummy).toBe(2)
  // 被 stop 了的 runner 仍然可以手动执行
  runner()
  expect(dummy).toBe(3)
  obj.prop++
  // 上边 如果换成  obj.prop++ 那么其实是不通过的
  // 原因:obj.prop++ 会先访问 obj.prop 属性 (obj.prop = obj.prop + 1),这就会触发 get ,又去收集依赖了
  // expect(dummy).toBe(4)
  // // 由于依赖被重新收集,所以又变成响应式了
  // obj.prop = 5
  // expect(dummy).toBe(5)
  // 调了 stop 的话在 track 存入依赖前 return 后
  expect(dummy).toBe(3)
  runner()
  expect(dummy).toBe(4)
});
/**
 * onStop hooks,在调用 stop 方法后应该被执行
 */
it('onStop', () => {
  const obj =  reactive({ foo: 1 })
  const onStop = jest.fn()
  let dummy
  const runner = effect(
    () => {
      dummy = obj.foo
    }, 
    { onStop }
  )
  stop(runner)
  expect(onStop).toBeCalledTimes(1)
});
复制代码

为此,我们先整理下我们要做的事:

  1. 新增 stop 方法
  2. ReactiveEffect 实例应该要能接收 onStop 函数,如果用户传了 在 stop方法执行后应该调用

注意前方高能!!!!!

修改 effect函数 、 ReactiveEffect类 、track函数,新增 stop方法 、 cleanupEffect方法 、 isTracking方法 ,新增 shouldTrack 全局变量 如下:

let shouldTrack // 当前这个 实例需不需要 收集依赖
class ReactiveEffect{
  private _fn: any
  deps = [] // 反向收集 set 数组
  active = true // 是否清空过
  onStop?: () => void // onStop hooks
  constructor(fn, public scheduler?) {
    this._fn = fn
  }
  run() {
    // 被 stop 了直接执行 fn 不需要再次去收集依赖 shouldTrack 为false
    if (!this.active) return this._fn()
    // 收集
    activeEffect = this
    shouldTrack = true
    const result = this._fn()
    // 重置
    shouldTrack = false
    return result
  }
  stop() {
    // 利用 active 来缓存stop结果,这样就不需要每次都去 cleanupEffect
    if (this.active) {
      cleanupEffect(this)
      this.active = false
    }
    // 如果传入了 onStop 就执行
    if (this.onStop) this.onStop()
  }
}
// 清除 对应 set 下的当前实例
function cleanupEffect(effect) {
  effect.deps.forEach((dep: any) => {
    dep.delete(effect)
  })
  effect.deps.length = 0
}

export function track(target, key) {
  // 没有 effect || 不应该 track  直接 return
  if (!isTracking()) return
  // ... 中间没有变化
  // 已经在 dep 中就不用 add 了
  if (dep.has(activeEffect)) return
  dep.add(activeEffect) // 把对应的 effect 实例加入 set 里
  activeEffect.deps.push(dep) // 实例的 deps 属性收集当前的 set
}
// 判断当前的 实例 需不需要收集依赖
function isTracking() { 
  return shouldTrack && activeEffect !== undefined
}

export function effect(fn, options: any = {}) {
  const _effect = new ReactiveEffect(fn, options.scheduler)
  // options 处理
  // _effect.onStop = options.onStop 太笨了,考虑后面还有其他option 我们可以使用 Object.assign
  // extned 语义化处理 options : const extend = Object.assign
  // 实际上是调用 Object.assign(_effect, options)
  extend(_effect, options)
  // ... 后面没有变化
}
// 新增 stop 方法
export function stop(runner) {
  runner.effect.stop()
}
复制代码

OK。我们可以看到这一次我们是加了很多东西,不要怕,我们来重点解释一下。

ReactiveEffect类中的 deps数组 属性 当我们把 ReactiveEffect实例 加入到 对应 key 的 Set集合中时,我们把这个 Set 给存储到这个实例的 deps中,方便我们在 cleanupEffect方法 中清除当前的 实例

shouldTrack全局变量保证了我们在触发到 get(track方法) 的时候能够知道当前应不应该收集依赖,我们重点看一下测试用例 stop中,当我们访问 obj.prop++ 的时候,实际上它执行的是 obj.prop = obj.prop + 1,那么这里他是会触发到响应对象(obj, prop)的 get 方法的。这样子我们就能保证 实例被 stop 后即使触发 get 也不会再去收集依赖了

isTracking方法 实际上是对 shouldTrack 和 activeEffect 做一个判断封装

问题:Proxy是什么?Reflect是什么?为什么要用Reflect?

Proxy 可以创建一个代理对象,实现对其他对象的代理(注意只能代理对象,无法对原始值代理)

代理:对一个对象基本语义代理,允许我们拦截并重新定义对一个对象的基本操作

Reflect 是一个全局对象,Relfect 下的方法与 Proxy 的拦截器方法名字相同 (换句话说,你能在 Proxy 的拦截器中找到的方法,在 Relfect 中都能找到同名函数)

Reflect 的功能:

提供了一个访问对象属性的默认行为,实际上以下的行为是等价的:

const obj = { foo: 1 }

// 直接读取 
console.log(obj.foo) // 1
// 使用 Reflect.get 读取
console.log(Reflect.get(obj, 'foo'))
复制代码

那么新的问题来了:既然他们等价,我干嘛还要用 Reflect 呢?直接 obj.foo 不香吗?

实际上 Reflect 的函数可以接收第三个参数,即函数调用过程中的 this

比如:

const obj = { foo: 1 }
console.log(Reflect.get(obj, 'foo', { foo: 2 })) // 输出的是 2 而不是 1
复制代码

当然 Reflect 还有其他的功能特性:JS 标注内置对象--Reflect[4]

我们这里暂时只关心这个,因为它与响应式数据的实现密切相关。

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/eT-I0-sfsrNm2-hjeQe0cw

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 目录