彻底搞懂 Node.js 中的 Require 机制(源码分析到手写实践)

发表于 3年以前  | 总阅读数:390 次

本文你能学到什么

  1. 自己手写实现一个 require,面试用也可以。
  2. 如何看 Node.js 源码
  3. require 函数是如何产生的?为什么在 module 中可以直接使用。
  4. require 加载原生模块时候如何处理的,为什么 require('net') 可以直接找到
  5. Node.jsrequire 会出现循环引用问题吗?
  6. require 是同步还是异步的?为什么?
  7. exportsmodule.exports 的区别是什么?
  8. 你知道 require 加载的过程中使用了 vm 模块吗?vm 模块是做什么的?vm 模块除了 require 源码用到还有哪些应用场景。

请注意我上面提出的问题,本文学完后看看是否都搞能懂。最好跟着练习一遍手写 require 源码,美滋滋。

什么是 CommonJS

每一个文件就是一个模块,拥有自己独立的作用域,变量,以及方法等,对其他的模块都不可见。CommonJS 规范规定,每个模块内部,module 变量代表当前模块。这个变量是一个对象,它的exports属性(即module.exports)是对外的接口。

模块分类

  • 原生(核心)模块:Node 提供的模块我们都称之为原生模块

  • 内建模块:Node.js 原生提供的模块中,由纯 C/C++ 编写的称为内建模块

  • 全局模块:Node.js在启动时,会生成一个全局量 process

  • 除了上面两种可以直接 require 的所有原生模块

  • 文件模块:用户编写的模块

  • 普通文件模块:node_modules 下面的模块,或者我们自己开发时候写的每个文件内容。

  • C++ 扩展模块:用户自己编写的 C++ 扩展模块或者第三方 C++ 扩展模块

模块加载

介绍了上面的模块分类,正常应该到介绍不同模块的加载环节,这里不这样,只列出目录。先带你看一遍源码,再手写一下,然后我想你自己总结一下这几种模块的加载区别。

加载 Node.js 原生模块

本文不包括直接调用内建纯C/C++模块,也不推荐这样使用,因为我们正常调用的原生模块都是通过 js封装一层,它们自己再去调用,你想直接调用的 Node.js提供的存C/C++ 内建模块,js 封装的一层也都能做到。那部分内容放在 Node.jsC++ 那些事的文章中介绍。

require 加载普通文件模块

require 加载 C++ 扩展文件模块

require 加载原理(源码分析与手写)

require 源码解析图

画了一个源码导图,可以直接跟着导图学一遍配上文章讲解,效果更佳。(导图太大了上传不清晰,需要找我要吧。)

require 源码并不复杂,这里采用的是边看源码边手写的方式讲解(我们最终实现的require 是简易版本,一些源码提到,但是简易版本不会实现),实现 require 其实就是实现整个 Node.js 的模块加载机制,Node.js 的模块加载机制总结下来一共八步。

网上一些文章只分成了3-4步,我这里做了一下细化,为了彻底搞懂我开篇提到的一些问题。

1. 基础准备阶段

Node.js 模块加载的主流程都在 Module 类中,在源码的https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L150 中进行了基础 Module 类定义,这个构造函数中的内容主要做一些值的初始化,我们自己对照着实现下,为了和源码有一个区别,本文使用 KoalaModule 命名。

function KoalaModule(id = '') {
  this.id = id;       // 这个id其实就是我们require的路径
  this.path = path.dirname(id);     // path是Node.js内置模块,用它来获取传入参数对应的文件夹路径
  this.exports = {};        // 导出的东西放这里,初始化为空对象
  this.filename = null;     // 模块对应的文件名
  this.loaded = false;      // loaded用来标识当前模块是否已经加载
}

KoalaModule._cache = Object.create(null); //创建一个空的缓存对象
KoalaModule._extensions = Object.create(null); // 创建一个空的扩展点名类型函数对象(后面会知道用来做什么)

然后在源码中你会找到 require 函数,在 KoalaModule 的原型链上,我们实现下

Module.prototype.require = function(id) {
    return Module._load(id, this, /* isMain */ false);
};

在源码中你会发现又调用了_load函数,找到源码中的 _load 函数,(源码位置:https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L724)下面的所有步骤都是在这个函数中完成调用和 return的,实现简易版_load函数。

KoalaModule._load = function (request) {    // request是我们传入的路劲参数
  // 2.路径分析并定位到文件
  const filename = KoalaModule._resolveFilename(request);

  // 3.判断模块是否加载过(缓存判断)
  const cachedModule = koalaModule._cache[filename];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }
  // 4. 去加载 node 原生模块中
  /*const mod = loadNativeModule(filename, request);
   if (mod && mod.canBeRequiredByUsers) return mod.exports;*/

  // 5. 如果缓存不存在,我们需自行加载模块,new 一个 KoalaModule实例
  // 加载完成直接返回module.exports
  const module = new KoalaModule(filename);

  // 6. load加载之前加入缓存,这也是不会造成循环引用问题的原因,但是循环引用,这个缓存里面的exports可能还没有或者不完整
  KoalaModule._cache[filename] = module;
  // 7. module.load 真正的去加载代码
  module.load(filename);
  // 8. 返回模块的module.exports 
  return module.exports;
}

这个函数的源码中有一些其他逻辑的细节判断,有兴趣的小伙伴再学习下,我提出了核心主干。

2. 路径分析并定位到文件

找到源码中的 _resolveFilename 函数,这个方法是通过用户传入的require参数来解析到真正的文件地址。(源码地址:https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L816)

这个函数源码中比较复杂,因为 require传递过来的值需要一层一层的判断,同时支持多种参数:内置模块,相对路径,绝对路径,文件夹和第三方模块等等,如果是文件夹或者第三方模块还要解析里面的 package.jsonindex.js。这里简单处理,只实现通过相对路径和绝对路径来查找文件,并支持判断文件jsjson后缀名判断:

KoalaModule._resolveFilename = function (request) {
  const filename = path.resolve(request);   // 获取传入参数对应的绝对路径
  const extname = path.extname(request);    // 获取文件后缀名
  // 如果没有文件后缀名,判断是否可以添加.js和.json
  if (!extname) {
    const exts = Object.keys(KoalaModule._extensions);
    for (let i = 0; i < exts.length; i++) {
      const currentPath = `${filename}${exts[i]}`;

      // 如果拼接后的文件存在,返回拼接的路径
      if (fs.existsSync(currentPath)) {
        return currentPath;
      }
    }
  }
  return filename;
}

3. 判断模块是否加载过(缓存判断)

判断这个找到的模块文件是否缓存过,如果缓存过,直接返回 cachedModule.exports, 这里就会想到一个问题为什么在 Node.js 中模块重复引用也不会又性能问题,因为做了缓存。(源码位置:https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L747)

  const cachedModule = koalaModule._cache[filename];
  if (cachedModule !== undefined) {
    return cachedModule.exports;
  }

4. 去加载 node 原生模块

如果没有进行缓存过,会调用一个加载原生模块的函数,这里分析一下。

注意:第四部分代码我们没有进行手写实现,在_load中进行了注释,但是这里进行了一遍分析,我们写的代码是如何调用到原生模块,本部分涉及到你可能会不想看的C内容,其实也可以忽略掉,过一遍就能知道最后的结论为什么是那样了,不然看了书记不住为什么这样。

比如我们require(net),走完前面的缓存判断就会到达这个loadNativeModule函数(源码位置:https://github.com/nodejs/node/blob/802c98d65de40f245781f591a0b3b38336d1af94/lib/internal/modules/cjs/helpers.js#L31)

const mod = loadNativeModule(filename, request);
  if (mod && mod.canBeRequiredByUsers) return mod.exports;

继续往下看 loadNativeModule 函数的调用。

function loadNativeModule(filename, request) {
 // 这里判断下是不是在原生js模块中  ,NativeModule在bootstrap/loader.js中定义
  const mod = NativeModule.map.get(filename);
  if (mod) {
    debug('load native module %s', request);
    mod.compileForPublicLoader();
    return mod;
  }
}

mod 是一个 NativeModule 对象,这个对象很常见,在 node启动一个文件时候也会用到(源码位置:https://github.com/nodejs/node/blob/802c98d65de40f245781f591a0b3b38336d1af94/lib/internal/bootstrap/loaders.js#L161)

然后到了 mod 的核心函数mod.compileForPublicLoader();,下面这段代码相对源码删除了一些非核心部分。

compileForPublicLoader() {  
    this.compileForInternalLoader();  
    return this.exports;  
}  

compileForInternalLoader() {  
    if (this.loaded || this.loading) {  
      return this.exports;  
    }  
    // id就是我们要加载的模块,比如net 
    const id = this.id;  
    this.loading = true;  

    try {  
      const fn = compileFunction(id);  
      fn(this.exports, nativeModuleRequire, this, process, internalBinding, primordials);  
      this.loaded = true;  
    } finally {  
      this.loading = false;  
    }  
    return this.exports;  
  }  

我们重点看compileFunction这里的逻辑。该函数是node_native_module_env.cc模块导出的函数。具体的代码就不贴了,通过层层查找,最后到 node_native_module.ccNativeModuleLoader::CompileAsModule

MaybeLocal<Function> NativeModuleLoader::CompileAsModule(  
    Local<Context> context,  
    const char* id,  
    NativeModuleLoader::Result* result) {  
5.  
6.  Isolate* isolate = context->GetIsolate();  
7.  // 函数的形参  
8.  std::vector<Local<String>> parameters = {  
9.      FIXED_ONE_BYTE_STRING(isolate, "exports"),  
10.      FIXED_ONE_BYTE_STRING(isolate, "require"),  
11.      FIXED_ONE_BYTE_STRING(isolate, "module"),  
12.      FIXED_ONE_BYTE_STRING(isolate, "process"),  
13.      FIXED_ONE_BYTE_STRING(isolate, "internalBinding"),  
14.      FIXED_ONE_BYTE_STRING(isolate, "primordials")};  
15.  // 编译出一个函数  
16.  return LookupAndCompile(context, id, ¶meters, result);  
17.}

我们继续看LookupAndCompile

MaybeLocal<Function> NativeModuleLoader::LookupAndCompile(  
    Local<Context> context,  
    const char* id,  
    std::vector<Local<String>>* parameters,  
    NativeModuleLoader::Result* result) {  

  Isolate* isolate = context->GetIsolate();  
  EscapableHandleScope scope(isolate);  

  Local<String> source;  
  // 找到原生js模块的地址  
  if (!LoadBuiltinModuleSource(isolate, id).ToLocal(&source)) {  
    return {};  
  }  
  // ‘net’ + ‘.js’
  std::string filename_s = id + std::string(".js");  
  Local<String> filename =  
      OneByteString(isolate, filename_s.c_str(), filename_s.size());  
  // 省略一些参数处理  
  // 脚本源码  
  ScriptCompiler::Source script_source(source, origin, cached_data);  
  // 编译出一个函数  
  MaybeLocal<Function> maybe_fun =  
      ScriptCompiler::CompileFunctionInContext(context,  
                                               &script_source,  
                                               parameters->size(),  
                                               parameters->data(),  
                                               0,  
                                               nullptr,  
                                               options);  
    Local<Function> fun = maybe_fun.ToLocalChecked();  
  return scope.Escape(fun);  
}  

LookupAndCompile 函数首先找到加载模块的源码,然后编译出一个函数。我们看一下LoadBuiltinModuleSource 如何查找模块源码的。

MaybeLocal<String> NativeModuleLoader::LoadBuiltinModuleSource(Isolate* isolate, const char* id) {  
  const auto source_it = source_.find(id);  
  return source_it->second.ToStringChecked(isolate);  
}  

这里是 idnet,通过该 id_source 中找到对应的数据,那么_source 是什么呢?Nodejs 为了提高效率,把原生 js 模块的源码字符串直接转成 ascii 码存到**内存**里。这样加载这些模块的时候,就不需要硬盘 io 了。直接从内存读取就行。_source 的定义(在 node_javascript.cc里,负责编译nodejs 源码或者执行 js2c.py 生成)。

结论:Node.js 在启动时候直接从内存中读取内容,我们通过 require 加载 net 原生模块时,通过 NativeModulecompileForInternalLoader,最终会在 _source 中找到对应的源码字符串,然后编译成一个函数,然后去执行这个函数,执行函数的时候传递 nativeModuleRequireinternalBinding两个函数,nativeModuleRequire用于加载原生 js 模块,internalBinding用于加载纯C++ 编写的内置模块

const fn = compileFunction(id);  
fn(this.exports, nativeModuleRequire, this, process, internalBinding, primordials);  

5. 创建一个 KoalaModule 实例

如果不是原生 node 模块,就会当作普通文件模块加载,自己创建一个 KoalaModule 实例,去完成加载。

  const module = new KoalaModule(filename);

6. 添加缓存

我把这一小步骤单独提出的原因,想说明的是先进行缓存的添加,然后进行的模块代码的加载,这样就会出现下面的结论,Node.js 官网也有单独介绍,可以自己试一下。

  1. main 加载aa 在真正加载前先去缓存中占一个位置
  2. a 在正式加载时加载了 b
  3. b 又去加载了 a,这时候缓存中已经有 a 了,所以直接返回 a.exports,这时候 exports 很有可能是不完整的内容。

7. module.load 真正的去加载代码

不在缓存,不是原生模块,缓存已经添加完,我们通过这个 load 函数去加载文件模块,源码中位置(https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L936)

源码中,会有一个获取文件扩展名的函数findLongestRegisteredExtension 这个方法的具体内容是有扩展名的取扩展名,没有的都按照 .js 为扩展名处理,在这个之前会判断一下 Module._extesions 支持的扩展名,不是所有都支持。

我们自己实现一下 load 函数。

KoalaModule.prototype.load = function (filename) {
  // 获取文件后缀名(我们忽略掉了findLongestRegisteredExtension函数,有兴趣小伙伴自己实现)
  const extname = path.extname(filename);

  // 根据不同的后缀名去进行不同的处理
  KoalaModule._extensions[extname](this, filename);

  this.loaded = true;
}

获取到扩展名之后,根据不通的扩展名去执行不同的扩展名函数,源码中支持的扩展名有.js,.json,.node;

7.1. 加载.js

定位到加载 .js 的源码位置(https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1092)

我们自己实现一下执行.js代码。

KoalaModule._extensions['.js'] = function (module, filename) {
  const content = fs.readFileSync(filename, 'utf8');
  module._compile(content, filename);
}

KoalaModule._extensions_compile 函数的执行。找到对应的源码位置(https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1037),源码中这里还使用 proxy,我们进行一下简单实现。

KoalaModule.wrapper = [
  '(function (exports, require, module, __filename, __dirname) { ',
  '\n});'
];

KoalaModule.wrap = function (script) {
  return KoalaModule.wrapper[0] + script + KoalaModule.wrapper[1];
};

KoalaModule.prototype._compile = function (content, filename) {
  const wrapper = KoalaModule.wrap(content);    // 获取包装后函数体

  // vm是 Node.js 的虚拟机模块,runInThisContext方法可以接受一个字符串并将它转化为一个函数
  // 返回值就是转化后的函数,compiledWrapper是一个函数
  const compiledWrapper = vm.runInThisContext(wrapper, {
    filename,
    lineOffset: 0,
    displayErrors: true,
  });
  const dirname = path.dirname(filename);
  // 调用函数,这里一定注意传递进的内容。
  compiledWrapper.call(this.exports, this.exports, this.require, this,
    filename, dirname);
}

这里注意两个地方

  • 使用 vm 进行模块代码的执行,模块代码外面进行了一层包裹,以便注入一些变量。
'(function (exports, require, module, __filename, __dirname) { ',
  '\n});'
  • 最终执行代码的函数传递的参数
  1. this: compiledWrapper函数是通过 call 调用的,第一个参数就是里面的this,这里我们传入的是 this.exports,也就是 module.exports
  2. exports: compiledWrapper 函数正式接收的第一个参数是 exports,我们传的也是 this.exports,所以 js 文件里面的 exports 也是对module.exports 的一个引用。
  3. require: 这个方法我们传的是 this.require,其实就是KoalaModule.prototype.require 函数,也就是 KoalaModule._load
  4. module: 我们传入的是 this,也就是当前模块的实例。
  5. __filename:文件所在的绝对路径。
  6. __dirname: 文件所在文件夹的绝对路径。

以上两点也是我们能在 JS 模块文件里面直接使用这几个变量的原因。

7.2. 加载 .json

加载 .json 文件比较简单,直接使用 JSONParse就可以,定位到源码位置(https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1117),注意这里不要忘记异常抛出 我们简单实现下:

KoalaModule._extensions['.json'] = function (module, filename) {
  const content = fs.readFileSync(filename, 'utf8');
   try {
   module.exports = JSONParse(content);
  } catch (err) {
    throw err;
  }
}

7.3 加载 .node

我们自己实现的 C++ 插件或者第三方 C++ 插件,经过编译后会变为.node 扩展名文件。.node 文件的本质是动态链接库,找到对应源码位置(https://github.com/nodejs/node/blob/master/lib/internal/modules/cjs/loader.js#L1135)

Module._extensions['.node'] = function(module, filename) {  
  // ...  
  //return process.dlopen(module, path.toNamespacedPath(filename)); 
};  

直接调了process.dlopen,该函数在node.js里定义。

const rawMethods = internalBinding('process_methods');  
process.dlopen = rawMethods.dlopen;  

找到process_methods模块对应的是node_process_methods.cc。

env->SetMethod(target, "dlopen", binding::DLOpen);

这里也过多分析,放到 Node.jsC++ 那些事那篇文章一起讲解。

8. 返回模块的 module.exports

return module.exports;

在模块中的代码可以修改 module.exports 的值,不管是从缓存获取,加载原生模块还是普通文件模块,module.exports 都是最终的导出结果

require 原理理解后的思考

require加载是同步还是异步

这个问题我直接告诉你同步还是异步,你可能过一阵又忘记了,我之前就是这样。看过源码或者手写一遍就知道了,代码中所有文件相关操作都是使用的同步,举几个例子:

fs.existsSync(currentPath)
fs.readFileSync(filename, 'utf8');

exports和module.exports的区别究竟是什么?

在源码中可以发现,

compiledWrapper.call(this.exports, this.exports, this.require, this,
    filename, dirname);

require在执行文件时候,传递的两个参数 参数一:this.exports = {}; 参数二:thisthis就是 module 所以 module.exports = {};

所以在一个模块没有做任何代码编写之前 exports === module.exports 都是 {} ,共同指向一个引用

看一张图理解这里更清楚:

但是大多数情况下我们开发时,经常会这样导出

exports = {
    name:'kaola'
}

或者这样写

module.export = {
    value:'程序员成长指北‘
}

上面这两种情况就是给 exports 或者 module.exports 重新赋值了,改变了引用地址,两个属性就不再===

关键点:require一个文件,之前在手写代码时你会发现,最终导出的内容是return module.exports;.所以你对exports的重新赋值不会改变模块的导出内容,只是改变了exports这个变量而已。导出内容永远是module.exports

require 会不会造成循环引用的问题

自行去看源码实现中的第 7 步,应就懂了。

require是怎么来的,为什么可以直接在一个文件中使用require

require 到的文件,在 vm 模块最终执行的时,对代码进行了一层包裹,并且把对应的参数传递进去执行。

包裹后的代码:

`(function (exports, require, module, __filename, __dirname) { ${script} \n});`

执行时的代码:

  const wrapper = this.wrap(content);    // 获取包装后函数体
    // vm是nodejs的虚拟机模块,runInThisContext方法可以接受一个字符串并将它转化为一个函数
    const compiledWrapper = vm.runInThisContext(wrapper, {
        filename,
        lineOffset: 0,
        displayErrors: true,
    });

    const dirname = path.dirname(filename);

    compiledWrapper.call(this.exports, this.exports, this.require, this,
        filename, dirname);

在这里小伙伴对 vm 模块有疑问的话可以看我的下一篇文章 冷门 Node.js模块 vm 学习必不可少。

通过代码发现 require 函数实际已经传递到了执行的 module 文件中,所以requiremodule 文件中可以直接调用了,同时也应该明白了为什么那几个变量可以直接获取了 dirname,filename 等。

学习补充

CommonJs 与 ES6Module的区别?

区别 require/exports import/export
出现的时间/地点不同 2009年出生 出自CommonJS 2015年出生 出自ECMAScript2015(ES6)
不同端的使用限制 100 999
加载方式 同步加载,运行时动态加载,加载的是一个对象,对象需要在脚本运行完成后才会生成 异步加载,静态编译,ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成
输出对比 输出的是一个值的拷贝,一旦输出一个值,模块内部的变化不会影响到这个值 输出的是值的引用,JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。若文件引用的模块值改变,require 引入的模块值不会改变,而 import 引入的模块值会改变。
使用方式 上面手写过程中已经说了使用方式 import的使用方式

Node.js 中的 vm 模块是什么?

写不动了,喝完奶茶的动力过去了,我要去睡觉了,后面发一篇小文章介绍 vm 模块吧!有兴趣的小伙伴也可以直接去 Node.js 官网学习下。

总结

看源码最好带着一些目的,开篇的一些问题只知道结果并不都知道原因就是我写本文的目的,看源码的过程中也能学到一些内容,比如一些地方你会发现源码是比你写的好,嘿嘿。require 的源码中还是有很多细节点可以学习和分析的,比如这里忽略了 isMain 主文件判断,启动时候 require 的使用(这个会在另一篇文章 Node.js 的启动源码分析中介绍),以及在 load 方法中执行模块文件时候使用到的 proxy,都可以学习下,本文是一个思路,下篇见。

参考文章:

  • https://github.com/nodejs/node/blob/master Node.js 源码官网
  • http://www.ruanyifeng.com/blog/2015/11/circular-dependency.html 阮一峰关于循环加载的讲解
  • https://cnodejs.org/topic/567104f61d2912ce2a35aa88 Node.js 源码分析
  • https://juejin.im/post/6844903677954621447 Node.js 源码启动分析
  • https://github.com/theanarkh/understand-nodejs/blob/master/ Node.js源码分析

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

 相关推荐

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

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

发布于: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次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录