creamidea / creamidea.github.com Goto Github PK
View Code? Open in Web Editor NEW冰糖火箭筒&&蜂蜜甜甜圈
Home Page: https://creamidea.github.io/
冰糖火箭筒&&蜂蜜甜甜圈
Home Page: https://creamidea.github.io/
一个网络网盘。截止目前(2013-11-16):10G
JS 的全局变量和局部变量
Webpack 对 publicPath 的解释
Webpack 的 bootstrap 过程
运行时,webpack bootstrap 代码中,对于需要加载的 JS/CSS 会增加 webpack_require.p
前缀,如
// manifest.js
// ...
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "http://xx.com/public/"; // 赋值publicPath的值
//...
/******/ script.src = __webpack_require__.p + "" + ({"0":"A","1":"B"}[chunkId]||chunkId) + ".js";
// ...
编译时,webpack 会分析代码,如果识别到 __webpack_public_path__="http://xx.com/public/"
,则会处理成 __webpack_require__.p = "http://xx.com/public/"
(这里也解释了,为什么 window.__webpack_public_path__
不起作用,原因就在这里:webpack 分析失败。)
// expr 数据结构
// https://juejin.cn/post/7062971609862111239
{
type: "Identifier",
start: 0,
end: 100,
loc: {
start: { line: 1, column: 0 },
end: { line: 1, column :100},
},
range:[0, 100],
name: "__webpack_public_path__",
}
__webpack_public_path__
不是简单的 JS 变量,它会在 webpack 处理阶段被转码成
// 某模块内
function(module, __webpack_exports__, __webpack_require__) {
// ...
__webpack_require__.p = __webpack_public_path__ 配置的地址
// ...
}
这里也就没有全局变量、局部变量等概念,也不能画蛇添足,在 __webpack_public_path__
前面增加 window
又因为 import
作用于提升,代码运行存在时机问题。为了保证 __webpack_public_path__
配置生效,就必须要保证代码能在尽可能前的地方运行。所以最好的方式:
创建 public-path.js 文件,文件内容 __webpack_public_path__ = xxx
。并且在工程的第一行引入 import 'public-path.js';
今天早上来提交昨晚的修改,但是最终给ithub提示她生成失败,还发了一封电子邮件:
The page build failed with the following error:
page build failed
For information on troubleshooting Jekyll see:
https://help.github.com/articles/using-jekyll-with-pages#troubleshooting
If you have any questions please contact us at https://github.com/contact.
What will happen when the user click the back button in browser?
And how to know whether the user click the back button?
分析从 Nodejs Doc Worker Threads 开始,并基于 v14.18.2 源码进行分析,期间参考以下 3 篇文章
总的来说,Worker Thread 的 Thread 不是「传统」意义上的系统级 Thread,也不是 Chrome 里面的 Worker,而是自己实现了一套 Worker Thread 机制,以及 MessagePort(用于线程之间通信方式)。每一个 Worker 都有自己独立的 V8 Context(可以理解为一个独立的 Tab 标签),libuv(事件循环),Nodejs 实例(比如内部 env 等状态维持)。线程间可以通过 ArrayBuffers, SharedArrayBuffer, Atomics 等进行内存共享。详细内容可以点击此处查看。
其中线程间通信使用的是 libuv Async handle 机制,详细的原理可以参考 libuv 源码分析(七)异步唤醒(Async) 。概括来说:一般我们认为 EventLoop 「最终」会「卡」在 Polling IO,那么工作线程做完的通知如何进入 EventLoop ?底层原理是模拟 IO 事件,在工作完成之后就写入 IO,从而进入 EventLoop。
源码以 nodejs/node@f217025 为基础进行分析,可自行下载比对。分析止步于 V8 和 libuv。
源码从 lib/worker_threads.js 开始,重点跟进 Worker
类的实现。Worker
来做 internal/worker 这个内部 module,对应的文件路径 lib/internal/worker.js
定位到 Worker
类的实现,继承 EventEmitter,所以可以使用 on
相关 API
class Worker extends EventEmitter {
constructor(filename, options = {}) {
// ....
// 调用内部 c++ 实现的 Worker 类
this[kHandle] = new WorkerImpl(url,
env === process.env ? null : env,
options.execArgv,
parseResourceLimits(options.resourceLimits),
!!(options.trackUnmanagedFds ?? true));
//....
// 下面一行语句,就是开启一个新的线程,运行创建 Context 和执行子线程代码,创建 EventLoop 并运行 uv_run
// Actually start the new thread now that everything is in place.
this[kHandle].startThread();
}
}
继续跟进 WorkerImpl
的实现,其来自于内部绑定的 worker,对应的文件路径,src/node_worker.cc
// 下面 2 句通过 Nodejs Register 机制将 worker 注册
NODE_MODULE_CONTEXT_AWARE_INTERNAL(worker, node::worker::InitWorker)
NODE_MODULE_EXTERNAL_REFERENCE(worker, node::worker::RegisterExternalReferences)
继续跟入 node::worker::InitWorker
void InitWorker(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
//...
{
Local<FunctionTemplate> w = env->NewFunctionTemplate(Worker::New);
// 省略初始化过程
// 在这里注册 Worker 的实现,也就是在 Nodejs 里面使用的 WorkerImpl
env->SetConstructorFunction(target, "Worker", w);
}
// 省略
// 获取通信端口
env->SetMethod(target, "getEnvMessagePort", GetEnvMessagePort);
// 设置主线程标志
target
->Set(env->context(),
FIXED_ONE_BYTE_STRING(env->isolate(), "isMainThread"),
Boolean::New(env->isolate(), env->is_main_thread()))
.Check();
// 省略
}
Worker::New
实现
void Worker::New(const FunctionCallbackInfo<Value>& args) {
// 参数解析
// ...
Worker* worker = new Worker(env,
args.This(),
url,
per_isolate_opts,
std::move(exec_argv_out),
env_vars);
// worker 初始化之后处理
}
Worker
实现,继承 AsyncWrap
Worker::Worker(Environment* env,
Local<Object> wrap,
const std::string& url,
std::shared_ptr<PerIsolateOptions> per_isolate_opts,
std::vector<std::string>&& exec_argv,
std::shared_ptr<KVStore> env_vars)
: AsyncWrap(env, wrap, AsyncWrap::PROVIDER_WORKER),
per_isolate_opts_(per_isolate_opts),
exec_argv_(exec_argv),
platform_(env->isolate_data()->platform()),
thread_id_(AllocateEnvironmentThreadId()),
env_vars_(env_vars) {
// Set up everything that needs to be set up in the parent environment.
parent_port_ = MessagePort::New(env, env->context());
// 设置 message_port_string,thread_id_string
// 设置 argv_,对应文件名或者运行的 JS 代码。argv_ 将在 Worker::Run 中调用
argv_ = std::vector<std::string>{env->argv()[0]};
}
改函数在 src/node_worker.cc 中被注册和实现
// 注册模块
NODE_MODULE_CONTEXT_AWARE_INTERNAL(worker, node::worker::InitWorker)
// 注册函数
env->SetProtoMethod(w, "startThread", Worker::StartThread);
// 实现 StartThread
void Worker::StartThread(const FunctionCallbackInfo<Value>& args) {
int ret = uv_thread_create_ex(&w->tid_, &thread_options, [](void* arg) {
Worker* w = static_cast<Worker*>(arg);
w->Run();
}, static_cast<void*>(w));
}
StartThread
的实现基于 libuv 的 uv_thread_create_ex
创建新的线程,并在子线程中执行 Run
函数,其实现如下:
void Worker::Run() {
// 调用 CreateEnvironment 创建新的环境,也就是对应 JS 执行环境实例,实现在文件 src/api/environment.cc CreateEnvironment
// 调用 LoadEnvironment 加载并执行用户编写的子线程代码,详细过程可以参考执行 Nodejs 主实例 [#33](https://github.com/creamidea/creamidea.github.com/issues/32)
// 调用 SpinEventLoop 创建新的 libuv 事件循环,实现在文件 src/api/embed_helpers.cc SpinEventLoop
}
子线程按照「正常」Nodejs 运行模式运行,通过 parentPort.postMessage
方式进行通信。
和主线程执行的区别,在 src/node.cc,这里会执行 main/worker_thread.js
文件,这个会在下面提到
if (env->worker_context() != nullptr) {
return StartExecution(env, "internal/main/worker_thread");
}
parentPort
赋值过程
综合上面的信息,也就是说子线程在运行的时候,被注入了 parentPort
,于是就可以用了。
publicPort
实现在 lib/internal/main/worker_thread.js,在 message 事件的回调中出现。这个 message 事件来自 lib/internal/worker.js
this[kPort].postMessage({
argv,
type: messageTypes.LOAD_SCRIPT,
filename,
doEval,
cwdCounter: cwdCounter || workerIo.sharedCwdCounter,
workerData: options.workerData,
environmentData,
publicPort: port2,
manifestURL: getOptionValue('--experimental-policy') ?
require('internal/process/policy').url :
null,
manifestSrc: getOptionValue('--experimental-policy') ?
require('internal/process/policy').src :
null,
hasStdin: !!options.stdin
}, transferList);
后面有时间再单开一篇文章分析 Message 机制吧(逃
When you use connect
, please don't transfer mapDispatchToProps
( the second optional param of connect
), which overwrite this.props.dispatch
in componentDidMount.
For example, the right way:
class ListContainer extends Component {
constructor (props) {
super(props)
}
componentDidMount () {
const { dispatch } = this.props
dispatch(fetchIssues(1, 25))
}
render () {
return <IssueList {...this.props} />
}
}
const VisibleIssueList = connect(
mapStateToProps
// please don't transfer the `mapDispatchToProps `
)(ListContainer)
This is a disgust problem!!!
One way of solving this problem is to watch the error.log
First, you should know which encode the system is using. You can use locale
to look at.
Second, make sure your nginx is using the same code as the system. You can put this in nginx.conf
http {
...
charset utf-8;
large_client_header_buffers 4 16k; # maybe
...
}
At last, make sure your Django(Python) using the same code as the nginx. You can put this code in your /your/path/to/lib/python2.7/sitecustomize.py
.
# encoding=utf8
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
Good luck.
PS: uwsgi ini file format
[uwsgi]
# Django-related settings
# the virtualenv (full path)
# virtualenv = /opt/weixnpy/
# the base directory (full path)
chdir = /home/www/xxxxx/wx/weixnpy
# Django's wsgi file
module = weixnpy.wsgi
#module = weixnpy.wsgi:application
env = DJANGO_SETTINGS_MODULE = weixnpy.settings
home = /opt/weixnpy/ # your virtual environment
# py-programname = /usr/local/bin/python2.7
# pythonpath = /home/www/icslg/wx/weixnpy/
# process-related settings
# master
master = true
# maximum number of worker processes
processes = 4
# the socket (use the full path to be safe
# socket = 127.0.0.1:8001
socket = /tmp/xxxxxxx.sock
# socket = /var/run/weixn.sock
# ... with appropriate permissions - may be needed
chmod-socket = 664
chown-socket = www:www
# clear environment on exit
vacuum = true
logto = /var/log/uwsgi.log
python 导入分为绝对路径导入和相对路径导入,之前一直不理解相对是怎么相对,直到看到这个:
也就是说,python xx.py
这里的 xx.py 就是主模块,假设代码如下
from .somepkg import func1
他里面的相对引用会变成 __main__.somepkg
,而这个显然是找不到的。如果要使其能够运行,需要将相对路径变成绝对路径
from somepkg import func1
或者把 xx.py 变成一个子模块,让上层变成主模块,比如变成如下结构
project
|- lib
|- __init__.py
`- xx.py
`- main.py
main.py 的代码
from lib import xx
https://github.com/zhansingsong/js-leakage-patterns
新生代中的对象主要通过 Scavenge 算法进行垃圾回收。在 Scavenge 的具体实现中,主要采用了 Cheney 算法。
晋升条件主要有两个:
老生代使用了标记-清除算法 (Mark-Sweep)进行垃圾回收,并使用标记-压缩算法 (Mark-Compact)整理内存碎片,提高内存的利用率
因为上述三种方式都是「全停顿」,所以 V8 引入增量标记、惰性清理、并发标记。
白灰黑
黑是活跃内存
灰是扫描过程中,相邻节点,等待后续处理
白是可以释放的内存
可达性分析,GC ROOTS,深度优先遍历
记录老生区指向新生区的情况
SMI, small integer
V8 预留所有的字(word,32位机器是 4 字节,64 位机器是8字节)的最后一位用于标记(tag)这个字中的内容的类型,1 表示指针,0 表示整数,这样给定一个内存中的字,它能通过查看最后一位快速地判断它包含的指针还是整数,并且可以将整数直接存储在字中,无需先通过一个指针间接引用过来,节省空间。
Rust 有极强的跨平台性,对比 c/c++,又能解决野指针的问题,还能解决类型安全的问题。
所以,使用 Rust 编译出来的程序,可以以库的形式和其它程序进行连接,是不错的未来。
如果是编写 Nodejs 扩展,https://github.com/napi-rs/node-rs 是一个不错的开始。
如果是编写 wasm 项目,https://github.com/rustwasm/wasm-pack 是一个不错的开始。
本文仅仅从较低层面,讲解如何编译动态库,并在 c 和 rust 之间调用。
示例 c-app
第一步,配置 rust Cargo.tomal,使其输出 cdylib
[lib]
name = "hello"
path = "src/lib.rs"
crate-type = ["cdylib"]
第二步,编写 rust 代码,使用 extern 声明导出的函数。使用 cargo build 编译产出库文件 libhello.so,位置在 target/debug 文件夹下。(仅用于测试,如果是用于生产,请带上 --release)
#[no_mangle]
pub extern "C" fn sum(a: i32, b: i32) -> i32 {
a + b
}
第三步,编写 c 代码,引用 rust 写的功能函数
// hello.h
#ifndef HELLO_H_
#define HELLO_H_
// 声明 sum 来自外部库
extern int sum(int, int);
#endif
// hello.c
#include <stdio.h>
#include "hello.h"
int main(int argc, char const *argv[])
{
int result = sum(1, 2);
printf("result: %d\n", result);
return 0;
}
第四步,编译 c 代码,链接 Rust 动态链接库
-l
指定链接时需要的动态库,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a或.so来确定库-L
依赖库搜索路径gcc hello.c -o hello -lhello -L../target/debug
示例 c-lib
第一步,编写 C 代码
#include <stdio.h>
typedef void (*rust_callback)(int);
rust_callback cb;
int register_callback(rust_callback callback) {
printf("register_callback...\n");
cb = callback;
return 3;
}
void trigger_callback() {
cb(7); // Will call callback(7) in Rust.
}
第二步,编译
# 动态链接
# -fPIC 和 -shared 可以编译出动态链接库
gcc -fPIC -shared -o libext.so ext.c
# 静态链接
gcc -o ext.o -c ext.c
# -c 不要编译为可执行文件,只编译为目标文件
ar -cvr libext.a ext.o
第三步,编写 Rust 代码,引用 C 函数
extern fn callback(a: i32) {
println!("I'm called from C with value {0}", a);
}
// 使用 link 宏,引用 C 函数。ext 为编译出来的库名称(一般不写 lib 前缀)
// 可以通过指定 kind,告诉 rustc 是使用 动态链接库 还是 静态链接库
// 动态链接库 #[link(name = "ext")]
// 静态链接库 #[link(name = "ext", kind = "static")]
// https://doc.rust-lang.org/nomicon/ffi.html#linking
// 这里的 extern 类似于接口,定义内部函数的接口,Rust 代码调用时,需要提供接口的实现。
#[link(name = "ext")]
extern {
fn register_callback(cb: extern fn(i32)) -> i32;
fn trigger_callback();
}
fn main() {
unsafe {
let result = register_callback(callback);
println!("result from c: {}", result);
trigger_callback(); // Triggers the callback.
}
}
第四步,编写 build.rs。需要注意的是:
对于链接动态链接库,只需要配置
println!(r"cargo:rustc-link-search=./c-lib");
对于静态链接库,需要再增加配置(或者在 rust 代码里面 link 指定 kind 为 static 即可)
https://doc.rust-lang.org/rustc/command-line-arguments.html#-l-link-the-generated-crate-to-a-native-library
println!(r"cargo:rustc-link-lib=static=ext");
fn main() {
// 制定搜索路径
println!(r"cargo:rustc-link-search=./c-lib");
// 如果是动态链接库,在 macOS 上,编译成功之后,需要配置 DYLD_LIBRARY_PATH
// export DYLD_LIBRARY_PATH=.:$DYLD_LIBRARY_PATH
// 此时执行可执行文件才能成功。否则会提示 dyld: Library not loaded
// 待确定:如果是静态链接库,可以配置如下参数。编译成功之后,可以直接执行
// println!(r"cargo:rustc-link-lib=static=ext");
// 有兴趣可以试试 crate cc https://crates.io/crates/cc
// --crate-type=cdylib --crate-name=ext ext.c
// Tell Cargo that if the given file changes, to rerun this build script.
// println!("cargo:rerun-if-changed=c-lib/ext.c");
// Use the `cc` crate to build a C file and statically link it.
// cc::Build::new()
// .file("src/hello.c")
// .compile("hello");
}
其它文章参考
第五步,执行。对于动态链接库,需要配置 DYLD_LIBRARY_PATH
(macOs) / LD_LIBRARY_PATH
(Linux) 才能正常执行。否则提示 dyld: Library not loaded
。
另外,如果是静态编译,那么需要确保库所在位置只有静态库,不能有同名的动态库
cargo run
# 或者
cargo build
target/debug/xx
如果是 rust 编译出来的库,则可以参考下面的例子。
使用 extern crate 导入。并在编译的时候,通过 --extern
参数指定依赖库的名称和地址
// rustc hello.rs --extern hello=../target/debug/libhello.rlib -o hello
extern crate hello;
fn main() {
let a = 1;
let b = 2;
let c = hello::sum(a, b);
println!("{a} + {b} = {c}");
}
前往 https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/ 在页面查找 tmux
动态链接库可以继续在 https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/ 上查找。找到之后解压,将其中的 lib 文件夹按照 /usr/local/opt/[lib-name]
的形式存放,比如
接下来使用 install_name_tool 命令进行动态链接库地址修改。
install_name_tool -change @@HOMEBREW_PREFIX@@/opt/utf8proc/lib/libutf8proc.2.dylib /usr/local/opt/utf8proc/lib/libutf8proc.2.dylib ./tumx
install_name_tool -change @@HOMEBREW_PREFIX@@/opt/ncurses/lib/libncursesw.6.dylib /usr/local/opt/ncurses/lib/libncursesw.6.dylib
install_name_tool -change @@HOMEBREW_PREFIX@@/opt/libevent/lib/libevent_core-2.1.7.dylib /usr/local/opt/libevent/lib/libevent_core-2.1.7.dylib
到这一步,tmux 程序已经可以运行,但是无法正常创建 session。出现如下错误 can't find terminfo database
最终问题通过这种方式解决:https://unix.stackexchange.com/a/677744
在 .zshrc 里面增加 export TERMINFO='/usr/share/terminfo/'
,可以顺利解决该问题
记录一次在 React 开发的过程中出现下面错误的问题:
ERROR in ./web-src/app.jsx
Module parse failed: C:\Users\xxxyyy\Repository\MemorizingWords\web-src\app.jsx Unexpected token (7:2)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected token (7:2)
at Parser.pp$4.raise (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:2221:15)
at Parser.pp.unexpected (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:603:10)
at Parser.pp$3.parseExprAtom (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1822:12)
at Parser.pp$3.parseExprSubscripts (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1715:21)
at Parser.pp$3.parseMaybeUnary (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1692:19)
at Parser.pp$3.parseExprOps (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1637:21)
at Parser.pp$3.parseMaybeConditional (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1620:21)
at Parser.pp$3.parseMaybeAssign (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1597:21)
at Parser.pp$3.parseExprList (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:2165:22)
at Parser.pp$3.parseSubscripts (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1741:35)
at Parser.pp$3.parseExprSubscripts (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1718:17)
at Parser.pp$3.parseMaybeUnary (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1692:19)
at Parser.pp$3.parseExprOps (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1637:21)
at Parser.pp$3.parseMaybeConditional (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1620:21)
at Parser.pp$3.parseMaybeAssign (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1597:21)
at Parser.pp$3.parseExpression (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:1573:21)
at Parser.pp$1.parseStatement (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:727:47)
at Parser.pp$1.parseTopLevel (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:638:25)
at Parser.parse (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:516:17)
at Object.parse (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\acorn\dist\acorn.js:3098:39)
at Parser.parse (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\webpack\lib\Parser.js:902:15)
at DependenciesBlock.<anonymous> (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\webpack\lib\NormalModule.js:104:16)
at DependenciesBlock.onModuleBuild (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\webpack-core\lib\NormalModuleMixin.js:310:10)
at nextLoader (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\webpack-core\lib\NormalModuleMixin.js:275:25)
at C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\webpack-core\lib\NormalModuleMixin.js:259:5
at Storage.finished (C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\enhanced-resolve\lib\CachedInputFileSystem.js:38:16)
at C:\Users\xxxyyy\Repository\MemorizingWords\node_modules\graceful-fs\graceful-fs.js:78:16
at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:380:3)
跟着 webpack 的代码走,慢慢排查出问题代码出现在这里 (console.log + grep
大法好):
在正则匹配的时候,因为操作系统对于路径的不同表示,导致 this.matchPart(str, obj.include)
会出现不同的结果。
于是有两种修改方式:
将业务代码写出这个样子:
module: {
loaders: [
{
test: /\.jsx$/,
exclude: /node_modules/,
include: **path.resolve(__dirname, 'web-src')**,
loader: "babel",
query: {
// plugins: ['transform-runtime', 'transform-react-jsx'],
cacheDirectory: true,
presets: ["es2015", "react"]
}
}
];
}
重点:将 include: __dirname+'/web-src'
改成 path.resolve(__dirname, 'web-src')
if(typeof test === "string") {
return regExpAsMatcher(new RegExp("^"+test.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")));
}
改成
var os = require('os')
...
if(typeof test === "string") {
test = /windows/i.test(OS.type()) ? test.split('/').join('\\') : test;
return regExpAsMatcher(new RegExp("^"+test.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&")));
}
最后,从这个错误中我们可以学习到,在写跨平台的操作系统的时候,对于路径问题的处理是需要小心谨慎的。
最近,碰到一个更新问题,让我对 JSX 是声明式的有了更深的理解。问题后续有空详细解释,现在简单说一下。
问题场景是,如下 JSX 描述的 UI
<Layout>
<Tab>
<TabItem><Table /></TabItem>
<TabItem><Table /></TabItem>
</Tab>
</Layout>
在 Layout 这个组件内部会 clone children,在 clone 前会找到 Table,并改变其属性,然后 clone。问题在于,改变属性并不能使 Table 重新渲染,「按理」来说,如果 Table 属性变化,会在 reconciler 阶段被识别为 Update,在 commit 阶段进行「渲染」。为什么?
因为这里的 Tab 组件也会处理其 children,如果内部只对第一次渲染处理,所有后续渲染全部 return 之前的 children(这个例子很极端,只是让大家更好的理解)。那么你再怎么处理 Table,也不会使 Table 被重新渲染(因为在 React 看来,Tab 后续的 children 都没有变化)。这里的 Tab JSX 只是描述了这样的 UI,也就是所谓的声明式,Tab 内部有一套自己的处理方式,处理完成的结果才会给 React 去 reconciler,而不是你在 Layout 内部处理的之后的 children。React 只会相应 Layout 第一层,比如这里的 Tab。
这里使用的 Tab 来自 fusion 组件库,这行代码,类似做了一次缓存(返回了空对象),导致如果 Tab 标签页不进行切换,那么就没办法更新其 children,也就是上面说的 Table。
Better Reading
Better Research
Better Sharing
> chcp 65001
Some Reference:
Bad:
export default function() {}
Good:
export default function Component() {}
function Component({ title, value }) {
...
}
useReducer
Bad code:
const [count, setCount] = useState(0);
const [name, setName] = useState("");
const onClick = () => {
setTimeout(() => {
setName("John");
setCount(count + 1);
}, 1000);
};
Good code:
const [state, setState] = useState({
count: 0,
name: "",
});
const onClick = () => {
setTimeout(() => {
setState((prevState) => ({
...prevState,
name: "John",
count: prevState.count + 1,
}));
}, 1000);
};
Bad code:
return (
<div>
<button
onClick={() => {
setCount(1);
// ...
}}
>
Click
</button>
</div>
);
Good code:
const onClick = useCallback(() => {
setCount(1);
// ...
}, [deps]);
return (
<div>
<button onClick={onClick}>Click</button>
</div>
);
object(Map)
替代 switch
Bad code:
switch (props.type) {
case "ADMIN":
return <Admin />;
case "USER":
return <User />;
default:
return <NotFound />;
}
Good code:
const componentMap = {
ADMIN: Admin,
USER: User,
NOT_FOUND: NotFound,
};
const Component = componentMap[props.type];
return <Component />;
Better code:
const componentMap = {
ADMIN: React.lazy(() => import("../components/Admin")),
USER: React.lazy(() => import("../components/User")),
NOT_FOUND: React.lazy(() => import("../components/NotFound")),
};
const Component = componentMap[props.type];
return <Component />;
Bad code:
return <button disabled={true}>Submit</button>;
Good code:
return <button disabled>Submit</button>;
Bad code:
return <Component></Component>;
Good code:
return <Component />;
参考文档:
react hydrate 是指,在 SSR 模式下渲染出 html 字符串发送到浏览器渲染之后,绑定监听事件以及将 react Fiber 树构建出来的过程,也就是所谓的「水合」,或者叫做「注水」。
一个简单的例子🌰
import { createElement } from "react";
import {
renderToString,
} from "react-dom/server";
import http from "http";
const hello = createElement(
"div",
{
onClick: () => {
console.log("hello");
},
},
"Hello World"
)
const server = http.createServer((req, res) => {
const html = renderToString(
hello
);
res.end(`
<!DOCTYPE html>
<html lang="en">
<head>
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
</head>
<body>
<div id="root">${html}</div>
<script>
const { createElement } = window.React;
const { hydrateRoot, createRoot } = window.ReactDOM;
const root = hydrateRoot(
document.getElementById('root'),
createElement(
"div",
{
onClick: () => {
console.log("hello");
},
},
"Hello World"
),
);
</script>
</body>
</html>`);
});
server.listen(3001);
大致过程:通过 renderToString
等方法,将 React 组件代码处理成 HTML(Fiber 树 => HTML,去掉所有绑定事件,只留 HTML 信息),返回浏览器或者搜索引擎,实现 SSR 的效果。客户端 React 也不需要再一次处理 DOM 元素,只需要绑定的事件,处理 Fiber 树等即可。一举两得。
重点就在下面的代码里面,调用 hydrateRoot 时第二个参数,也就是把 React 组件重新「执行」一下。这里的 React 组件要和服务端 SSR 时放入的组件一致。
<script>
const { createElement } = window.React;
const { hydrateRoot, createRoot } = window.ReactDOM;
const root = hydrateRoot(
document.getElementById('root'),
createElement(
"div",
{
onClick: () => {
console.log("hello");
},
},
"Hello World"
),
);
</script>
下面进入代码分析阶段,回答 2 个问题:
hydrateRoot
函数内部会调用 listenToAllSupportedEvents(container)
,这里的 container 就是 DOM 容器节点 。listenToAllSupportedEvents
函数就是处理委托事件的开始入口,具体实现可以看这篇文章。
承接上段,hydrateRoot
函数内部会执行如下创建逻辑,进入 Fiber 树构造。最大的区别在于,跳过 DOM 树的创建和增加。
createHydrationContainer
(createFiberRoot: FiberRootNode, createHostRootFiber)createFiberRoot
的第 3 个参数被赋值为 true,最终影响该数据结构const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
cache: (null: any), // not enabled yet
transitions: null,
};
uninitializedFiber.memoizedState = initialState;
scheduleInitialHydrationOnRoot
,和 scheduleUpdateOnFiber
相比,代码更加简单,标记 lanes,就直接进入 ensureRootIsScheduled
阶段export function scheduleInitialHydrationOnRoot(
root: FiberRoot,
lane: Lane,
eventTime: number,
) {
const current = root.current;
current.lanes = lane;
markRootUpdated(root, lane, eventTime);
ensureRootIsScheduled(root, eventTime);
}
开始更新时,使用默认优先级 16,React18 版本下会进入并发,但不会分片。(16 是在 BlockingLane 范围内)
在 render 阶段的 beginWork 阶段处理 HostRoot 的 updateHostRoot 函数内会对 isDehydrated 判断。如果是真值,会更新 updateQueue.baseState 和 workInProgress.memoizedState
// Flip isDehydrated to false to indicate that when this render
// finishes, the root will no longer be dehydrated.
const overrideState: RootState = {
element: nextChildren,
isDehydrated: false,
cache: nextState.cache,
transitions: nextState.transitions,
};
const updateQueue: UpdateQueue<RootState> = (workInProgress.updateQueue: any);
// `baseState` can always be the last state because the root doesn't
// have reducer functions so it doesn't need rebasing.
updateQueue.baseState = overrideState;
workInProgress.memoizedState = overrideState;
以及调用 enterHydrationState
函数:获取第一个 DOM 节点并赋值全局变量 nextHydratableInstance
。
enterHydrationState(workInProgress);
const parentInstance: Container = fiber.stateNode.containerInfo;
nextHydratableInstance = getFirstHydratableChildWithinContainer(
parentInstance,
);
在 render 阶段的 beginWork 阶段处理 HostComponent 的 updateHostComponent 函数,会将已经存在的 DOM 实例和子 fiber 进行关联,也就是赋值 fiber.stateNode,通过 tryToClaimNextHydratableInstance
完成,tryHydrate
函数完成具体的赋值 stateNode 操作。
在 render 阶段的 completeOfWork 内处理 HostComponent 分支时,下面的语句返回真值,进入「水合」处理,判断当前节点是否存在更新
var _wasHydrated = popHydrationState(workInProgress);
if (_wasHydrated) {
if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
markUpdate(workInProgress);
}
} else {
...
}
根据 HostRoot 的 memoizedState.isDehydrated
为 true,会进入 commitHydratedContainer
逻辑,该逻辑会处理 queuedDiscreteEvents。
处理所有副作用和生命周期,和正常的 Commit 阶段类似,不再赘述。
以上
有如下代码,静态编写多子组件,不会出现 key 警告
export function App({ name }) {
return (
<div>
<h1>{name}</h1>
<h1>{name}</h1>
<h1>{name}</h1>
</div>
)
}
但是,如果使用动态的形式创建多个子组件,就会出现 key 警告
export function App({ name }) {
return (
<div>
{
[1,2,3].map(id => {
return <h2>{id}</h2>
})
}
</div>
)
}
那么在分析 JSX 代码的时候,对于静态多个子组件,会对其父组件使用 jsxs
。而对于动态版本,则会使用 jsx
jsxs
和 jsx
函数定义在文件 packages/react-reconciler/src/ReactChildFiber.new.js
通过第四个参数控制,如果为 true,则会进入 validateChildKeys -> validateExplicitKey 提前设置 element._store.validated = true;
。那么在 reconcileChildrenArray 阶段,warnForMissingKey 函数会在判断 !child._store || child._store.validated || child.key != null
提前返回(child._store.validated
为 true)
validateExplicitKey (index.js:formatted:2473)
validateChildKeys (index.js:formatted:2473)
jsxWithValidation (index.js:formatted:2473) <- 这里调用时,第四个参数为 false
App (App5.js:6)
renderWithHooks (index.js:formatted:2473)
mountIndeterminateComponent (index.js:formatted:2473)
beginWork (index.js:formatted:2473)
beginWork$1 (index.js:formatted:2473)
performUnitOfWork (index.js:formatted:2473)
workLoopSync (index.js:formatted:2473)
renderRootSync (index.js:formatted:2473)
performSyncWorkOnRoot (index.js:formatted:2473)
scheduleUpdateOnFiber (index.js:formatted:2473)
updateContainer (index.js:formatted:2473)
(anonymous) (index.js:formatted:2473)
unbatchedUpdates (index.js:formatted:2473)
legacyRenderSubtreeIntoContainer (index.js:formatted:2473)
render (index.js:formatted:2473)
(anonymous) (index.js:10)
./src/index.js (index.js:24)
__webpack_require__ (bootstrap:856)
fn (bootstrap:150)
1 (reportWebVitals.js:14)
__webpack_require__ (bootstrap:856)
checkDeferredModules (bootstrap:45)
webpackJsonpCallback (bootstrap:32)
(anonymous) (main.chunk.js:1)
Google Service Sync
去设置里面选择Google帐号,输入帐户和密码。如果你开启了两步验证,需要使用Google App的密码,就是Google生成的那个密码,
Google联系人同步问题:
not enough memory to sync error code 8007000E
你看看是不是Google联系人中有些人的头像太大了,然后Windows Phone无法处理这么大的图片。删除这些人的头像,然后再试试。
安装软件
如果你不怕麻烦,可以去试试破解之后的安装方式。如果你不想折腾的话,直接去注册一个微软帐号吧
从 createRoot 函数开始
function createRoot(container) {
...
const rootContainerElement: Document | Element | DocumentFragment =
container.nodeType === COMMENT_NODE
? (container.parentNode: any)
: container;
listenToAllSupportedEvents(rootContainerElement);
...
}
listenToAllSupportedEvents 进行原生事件绑定。也会处理 nonDelegatedEvents,selectionchange 事件等
function listenToAllSupportedEvents() {
// 核心代码,listenToNativeEvent 调用 addTrappedEventListener
// eventSystemFlags |= IS_CAPTURE_PHASE;
listenToNativeEvent(domEventName, true, rootContainerElement);
}
addTrappedEventListener 函数根据不同事件,构造不同优先级的处理函数(createEventListenerWrapperWithPriority),得到该函数之后进行原生 DOM 事件绑定,绑定到根 DOM 节点。比如,click 事件,处理函数就是 dispatchDiscreteEvent,默认是 dispatchEvent。并通过 bind 的方式,固定参数:domEventName、eventSystemFlags、targetContainer(即 root dom 节点实例)
至此,绑定原生事件的过程结束,后续就是等待用户交互,触发原生事件,进入诸如 dispatchEvent 函数进行处理。
进入诸如 dispatchEvent 函数进行处理。其最总调用 dispatchEventOriginal 函数处理。
function dispatchEventOriginal() {
...
const blockedOn = findInstanceBlockingEvent(
domEventName,
eventSystemFlags,
targetContainer,
nativeEvent,
);
if (blockedOn === null) {
dispatchEventForPluginEventSystem(
domEventName,
eventSystemFlags,
nativeEvent,
return_targetInst,
targetContainer,
);
...
return;
}
...
}
function findInstanceBlockingEvent() {
// 该值会在函数中处理,有值的 2 种情况
// - targetInst NearestMountedFiber 是 Suspense
// - Hydrating 情况下,targetInst NearestMountedFiber 是 HostRoot
return_targetInst = null;
const nativeEventTarget = getEventTarget(nativeEvent);
// targetInst 是根据当前事件的 DOM 节点中存储的 internalInstanceKey 找到对应 fiber 实例
let targetInst = getClosestInstanceFromNode(nativeEventTarget);
...
return null
}
blockedOn 有值的 2 种情况,有值则会调用 queueDiscreteEvent 放入到 queuedDiscreteEvents 队列(retryIfBlockedOn 会消费)
function dispatchEventForPluginEventSystem() {
// 注意,这里的 targetInst 是👆说的 return_targetInst 这个全局变量
// 即处理 Suspense 或者 HostRoot 情况
if (targetInst !== null) {
...
}
batchedUpdates(() =>
dispatchEventsForPlugins(
domEventName,
eventSystemFlags,
nativeEvent,
ancestorInst,
targetContainer,
),
);
}
dispatchEventsForPlugins 函数就是调用注册的 Plugin 的 extract 方法,提取事件。比如 SimpleEventPlugin.extractEvents 函数内会通过 accumulateSinglePhaseListeners 函数收集绑定的事件,并构造 SyntheticEvent,一起放入到 dispatchQueue 队列
function extractEvents() {
...
const listeners = accumulateSinglePhaseListeners(
targetInst,
reactName,
nativeEvent.type,
inCapturePhase,
accumulateTargetOnly,
nativeEvent,
);
if (listeners.length > 0) {
// Intentionally create event lazily.
const event = new SyntheticEventCtor(
reactName,
reactEventType,
null,
nativeEvent,
nativeEventTarget,
);
dispatchQueue.push({event, listeners});
}
...
}
function accumulateSinglePhaseListeners() {
// Accumulate all instances and listeners via the target -> root path.
}
dispatchQueue 队列会在 processDispatchQueue 函数内被处理
function processDispatchQueueItemsInOrder(
event: ReactSyntheticEvent,
dispatchListeners: Array<DispatchListener>,
inCapturePhase: boolean,
): void {
let previousInstance;
if (inCapturePhase) {
// 处理捕获事件
for (let i = dispatchListeners.length - 1; i >= 0; i--) {
const {instance, currentTarget, listener} = dispatchListeners[i];
if (instance !== previousInstance && event.isPropagationStopped()) {
return;
}
executeDispatch(event, listener, currentTarget);
previousInstance = instance;
}
} else {
// 处理冒泡事件
for (let i = 0; i < dispatchListeners.length; i++) {
const {instance, currentTarget, listener} = dispatchListeners[i];
if (instance !== previousInstance && event.isPropagationStopped()) {
return;
}
executeDispatch(event, listener, currentTarget);
previousInstance = instance;
}
}
}
_config.yml
bucket_title:
- text: archive
url: /archive/index.html
- text: tags
url: /tags/index.html
- text: draft
url: https://github.com/creamidea/creamidea.github.com/tree/master/_draft
- text: emotion
url: http://www.douban.com/people/creamidea/notes
bucket_title.html
{% unless page.layout == 'front' %}
<nav class="bucket-sort">
{% for title in site.bucket_title %}
{% assign page_url = page.url|split: '/' %}
{% if page_url[1] == title.text or page.layout == link.layout %}
{% assign href = "#" %}
{% assign class = "no-link" %}
{% assign active = "active" %}
{% else %}
{% assign href = title.url %}
{% assign class = "" %}
{% assign active = "" %}
{% endif %}
<h5 class="bucket-title {{active}}">
<a href="{{ href }}" class="{{ class }}">
<span class="bucket-header-title">{{ title.text }}</span>
</a>
</h5>
{% endfor %}
</nav>
{% endunless %}
今天按照自己的知识体系理解了一下 Django Rest Framework。其实这就是一个 MVVM 的实现。而 MVVM 的核心:binding
请求数据和 Model 如何绑定?通过 ViewSet。那之间的数据如何进行转换?即用户通过请求提交的数据如何转成 Django 的模型(Python 内的数据)?通过 ViewSet 里声明的 Serializer。这也就是为什么在 Serializer 里面可以声明和 Model 的绑定。
Do you really know JavaScript
http://javascript-puzzlers.herokuapp.com/
On Windows:
$ git config --global credential.helper wincred
Or you can find more information about this followed the below link:
https://help.github.com/articles/working-with-ssh-key-passphrases/
At last, I still don't know how to let git-bash remember the password 'forever'.
以 FunctionComponent 和 一次 click 内 setState 为例
省略 React 处理事件过程,直接进入 onClick 处理函数调用 setState,这里的 setState 就是 dispatchSetState 函数。
fiber.lanes === NoLane && (!fiber.alternate || fiber.alternate.lanes === NoLane
),则会被设置为 true,并计算出 state 记录。如果计算的 state 和当前 state 一致,则直接退出本次更新if 分支处理
ensureRootIsScheduled
以 Root 为参数,安排调度 performSyncWorkOnRoot
if 分支处理:没有获取到调度优先级
nextLanes === NoLanes 说明没有需要做的更新,重置相关变量,退出更新
getHighestPriorityLane(nextLanes) 取出最高优先级的任务作为本次更新的任务调度优先级
if 分支处理:本次更新和当前更新(如果有)比较
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root))
supportsMicrotasks
,那么安排一下 flushSyncCallbacks
(这里面会执行 performSyncWorkOnRoot,也就是将 React 工作安排在同一个浏览器 task 里面)scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks)
,这就会被安排到下一个 task 立即执行scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root))
安排到下一个浏览器 task开始遍历 root 前的一些准备工作
flushPassiveEffects 如果存在 rootWithPendingPassiveEffects(这个会在 commitRoot 阶段被设置。如果存在说明上一次任务执行完,调度的 effect 还没有执行。那么就需要在本次更新任务执行前,执行完成。留给本次更新一个干净的环境),则执行
getNextLanes 再次获取当前任务的优先级
if 分支判断:优先级
不包含 SyncLane,则将本次更新调用 ensureRootIsScheduled 重新安排,并退出本次更新,等重新调度
包含 SyncLane,调用 renderRootSync 开始遍历
👆函数执行完成有一个退出码
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
root.callbackNode = null;
root.callbackPriority = NoLane;
let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
markRootFinished(root, remainingLanes);
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
pendingPassiveEffectsRemainingLanes = remainingLanes;
// workInProgressTransitions might be overwritten, so we want
// to store it in pendingPassiveTransitions until they get processed
// We need to pass this through as an argument to commitRoot
// because workInProgressTransitions might have changed between
// the previous render and commit if we throttle the commit
// with setTimeout
pendingPassiveTransitions = transitions;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
// This render triggered passive effects: release the root cache pool
// *after* passive effects fire to avoid freeing a cache pool that may
// be referenced by a node in the tree (HostRoot, Cache boundary etc)
return null;
});
}
}
如果有内容更新,比如 DOM 变化等。首先设置执行状态 executionContext |= CommitContext
。然后是 3 大步骤:
commitBeforeMutationEffects
getSnapshotBeforeUpdate
,方向是 child -> parentcommitMutationEffects
case HostComponent
时没有 break
也没有 return
,于是就会进入👇 HostText 的处理)commitLayoutEffects
设置 useEffect 执行需要的状态
rootDoesHavePassiveEffects = false;
// 👇这个是遍历时需要的数据
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
再次安排调度 ensureRootIsScheduled,确定没有需要更新的内容
如果本次任务有同步的优先级,那么同步执行 useEffect(针对的是离散型事件,比如点击,让其同步执行)
if (
includesSomeLane(pendingPassiveEffectsLanes, SyncLane) &&
root.tag !== LegacyRoot
) {
flushPassiveEffects();
}
此时使用 updateState
,来到 updateReducer
处理函数
笔记中梳理了
如果感兴趣,还可以深入了解一些 https://7kms.github.io/react-illustration-series/algorithm/bitfiled
我们十年后见。哦,对了,人类将在五十年内实现可控核聚变(狗头,划掉)
双拼是一种与现代汉语拼音方案几乎同时提出的一种拉丁化拼音文字方案,双拼方案在编码上不像全拼那样有长有短,而一律双码,即每个汉字的拼音都固定用两个字母表示。双拼方案的双码形式比较简短,符合韵律,可以快速输入。因此,越来越受到广大用户的青睐。
“自然码输入系统”中包含了很多种常用编码方案的输入法,“自然双拼方案”是其中最简便快捷的压缩拼音,是在CCDOS压缩拼音的基础上补充设计而成的,将声母“zh”“ch”“sh”分别定义在“V”“I”“U”键上,然后将多个拼音字母组成的韵母压缩在一个键上,使得每个汉字的拼音都简化成了两个字母。
自然双拼比其他双拼设计合理,并能与标准全拼和简拼最大程度兼容,是目前应用最多的双拼。包括微软双拼在内,都是以自然码双拼为基础的。
自然码双拼助记口诀
Zei Ying Qiu Nin Fen Ii W.ua.ia Ee
贼 应 求 您 分 吃 蛙 鸭 鹅,
T.ve Song Uu Lai Vui Ruan+ Mian Xie,
特约 松 鼠 来 追 软 棉 鞋,
C.iao (Y) Hang P.un.vn,Geng Kao B.ou J.an D.uang.iang.
此要 (外) 行 破文韵, 更 靠 笔偶 结案 得 汪 洋。
其实这个图依然是 MVC 相关的知识,还是和 binding 相关。远程数据如何和本地的视图进行交互,更一般的,用户的操作如何反馈到视图上?这一层的 binding 在 Mutations 里面声明。
查看 Mutations 相关的 API,可以发现就是和浏览器 Event 一样的概念:事件 + 实现。事件是 Mutations 里函数的名称,实现是 Mutations 里函数的实现。另外,Mutations 暴露出 commit 方法,通过该方法触发相关事件,进而修改内部的 State。为了降低概念和实现的复杂度,Mutations 里面的实现要求是同步的操作,修改 State 和渲染 View 被安排在同一个操作(tick)里面,而不是被分拆成异步。
那如何进行异步更新呢?通过 Action,所有异步的逻辑在 Action 里面实现。Action 的接口中有 Mutations 暴露的 commit 方法,当动作需要修改状态时调用 commit。定义好的 Action 通过 Dispatch 进行分发。
其它的概念
element 为第一个组件,即 HostRoot 的 children
在 render 函数里面,处理成 update.payload = { element }
在 render phase updateHostRoot 函数内,通过判读前后 children,判断 HostRoot 是否可以 bailout。所以作用就是快速路径返回的作用。
function updateHostRoot() {
...
const prevState = workInProgress.memoizedState;
const prevChildren = prevState.element;
...
if (nextChildren === prevChildren) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
...
}
在 processUpdateQueue 内将 update.payload = { element }
,计算 state 结果放到 memoizedState 里
function processUpdateQueue() {
...
// getStateFromUpdate 函数内 UpdateState 分支处理
// payload 不是函数,所以直接返回 payload
newState = getStateFromUpdate(
workInProgress,
queue,
update,
newState,
props,
instance,
);
...
workInProgress.memoizedState = newState;
}
in virtual env
export:
(env_name) pip freeze > requirements.txt
import:
$ virtualenv <env_name>
$ source <env_name>/bin/activate
(<env_name>)$ pip install -r path/to/requirements.txt
You can use this script to auto complete:
def pip_install():
with cd(env.path):
with prefix('source venv/bin/activate'):
run('pip install -r requirements.txt')
Christina G Rossetti
Who has seen the wind?
Neither I nor you.
But when the leaves hang trembling,
The wind is passing through.
Who has seen the wind?
Neither you nor I.
But when the trees bow down their heads,
The wind is passing by.
I met this problem because I had deleted my private key on my laptop in order to protect my secret.
I use gpg2 to solve this problem. Ref: link
git config --global gpg.program gpg2
And, I also change the signingkey to my key of sign. You can use gpg2 --edit-key [key-id]
to see which key is to sign.
Good luck.
BTW. If you want to sign the previous commit, you can use git --amend --signoff
有如下代码,如果此时一个 route 的 element 抛出异常,那么该异常会不断进入 ErrorBoundary
的 render
函数进行异常渲染,直至最大调用栈报错。经过一番排出,最终解决方案,只需要对 ErrorBoundary
增加 key
属性即可,我想了解 React 运行机制的小伙伴已经嗅到真相的味道了。
<Routes>
{routes.map((route, index) => {
return (
<Route
path={route.url}
element={
<Suspense fallback="loading...">
<ErrorBoundary>
{route.element}
</ErrorBoundary>
</Suspense>
}
/>
);
})}
</Routes>
// ErrorBoundary
export class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 更新 state 使下一次渲染能够显示降级后的 UI
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// 你同样可以将错误日志上报给服务器
console.log('send:', error, errorInfo);
}
render() {
if (this.state.hasError) {
if (this.state.error.code === 401) {
return <Navigate to="/login" replace={true} />;
}
if (this.state.error.code === 403) {
return <NoPermission />;
}
// 你可以自定义降级后的 UI 并渲染
return <h1>{this.state.error.message || "Something went wrong"}</h1>;
}
return this.props.children;
}
}
// 其中一个 route.element
export default function Dashboard() {
// 通过 axios 网络访问获取数据异常
throw {
success: false,
code: 401,
message: 'no permission',
};
}
在 React 的 reconcileChildFibers
阶段,本案例是进入了 reconcileSingleElement
函数逻辑:先比较 key
,再比较 elementType
,问题就出在这 2 次比较。
展开分析这里之前,我们先了解一下 react router 中 Route
和 Routes
这 2 个组件。
Route
是空组件,仅起到「标识」作用 https://github.com/remix-run/react-router/blob/334589beb3aeedb48ea2f3f05c14b48c5d439b2f/packages/react-router/lib/components.tsx#L144Routes
会扫描所有自组件 Route,并转成配置形式给 useRoutes
处理。最终返回的子组件是匹配到的组件,用 Provider 包裹之后直接返回。https://github.com/remix-run/react-router/blob/334589beb3aeedb48ea2f3f05c14b48c5d439b2f/packages/react-router/lib/hooks.tsx#L385从上面得知,虽然我们代码里面使用 map
返回了多个 ErrorBoundary
,但其实在 React 里面只有命中当前 URL 的组件(一个或多个,本案例只有一个)。
// 这里 Routes 只是渲染了命中的子组件
<Routes>
{routes.map((route, index) => {
return (
<Route
path={route.url}
element={
<Suspense fallback="loading...">
<ErrorBoundary>
{route.element}
</ErrorBoundary>
</Suspense>
}
/>
);
})}
</Routes>
现在我们回到 reconcileChildFibers
的比较情况。如果没有设置 key
(默认值为 null),null === null
表明 key
是一致的。那么就看 elementType
,从 Routes 渲染情况刻制,elementType
也是一致的。于是进入了复用 fiber(ErrorBoundary) 逻辑。这个影响就是 this.state.hasError
始终为 true
。那么就会再次进入 <Navigate to="/login" replace={true} />
逻辑,从而表现为不断循环的现象。
在前一篇文章 #32 之后,对于 Nodejs 启动有了一个大致的想法。这篇文章将概括讲述 Nodejs 的 3 种主要启动方式
源码以 https://github.com/nodejs/node/tree/f2170253b694c488f8ad2616dfc5c66b6a3c90a0 为基础进行分析,可自行下载比对。分析止步于 V8 和 libuv,仅分析在 Nodejs 层的流程。
旅程从 node.gyp
开始。(没错,整个 Nodejs 构建和写其插件的构建方式相同,都是使用 node-gyp 构建工具构建)
{
'sources': [
'src/node_main.cc'
],
}
进入 src/node_main.cc 文件
int main(int argc, char* argv[]) {
// 省略:跨平台处理
// Disable stdio buffering, it interacts poorly with printf()
// calls elsewhere in the program (e.g., any logging from V8.)
setvbuf(stdout, nullptr, _IONBF, 0);
setvbuf(stderr, nullptr, _IONBF, 0);
// Start 实现在 node.cc
return node::Start(argc, argv);
}
进入 node.cc
int Start(int argc, char** argv) {
// 省略:前置处理
{
// 省略:缓存处理
// 配置 libuv,为启动 Event Loop 作准备
uv_loop_configure(uv_default_loop(), UV_METRICS_IDLE_TIME);
// 创建 Node 主实例,并运行
NodeMainInstance main_instance(¶ms,
uv_default_loop(),
per_process::v8_platform.Platform(),
result.args,
result.exec_args,
indices);
// Run 的实现在 node_main_instance.cc 文件
result.exit_code = main_instance.Run(env_info);
}
TearDownOncePerProcess();
return result.exit_code;
}
进入 node_main_instance.cc。在此处,终于进入核心启动逻辑,重要的是在 Run 函数中调用这 2 个函数
LoadEnvironment
实现在 src/api/environment.ccSpinEventLoop
实现在 src/api/embed_helpers.ccMaybeLocal<Value> LoadEnvironment(
Environment* env,
StartExecutionCallback cb) {
env->InitializeLibuv();
env->InitializeDiagnostics();
return StartExecution(env, cb);
}
初始化 libuv 等相关组件,最重要的是调用 StartExecution
执行用户写的脚本。当然在真正执行用户脚步之前,还有许多工作要做,下面我们简单分析一下。
StartExecution
有 2 个实现,函数接口分别如下:
MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb)
MaybeLocal<Value> StartExecution(Environment* env, const char* main_script_id)
前者负责处理不同情况下以什么入口启动运行,下文要说的 worker_thread 启动也是在这个函数内判断;后者负责找到具体入口并编译和运行。让我们把目光聚焦到主模式下启动的代码
// 用户脚本启动方式
if (!first_argv.empty() && first_argv != "-") {
return StartExecution(env, "internal/main/run_main_module");
}
// REPL
if (env->options()->force_repl || uv_guess_handle(STDIN_FILENO) == UV_TTY) {
return StartExecution(env, "internal/main/repl");
}
// stdin
return StartExecution(env, "internal/main/eval_stdin");
在了解了针对不同模式的分类处理之后,我们继续分析「真正」的 StartExecution
方法实现,其内部调用链关系
StartExecution -> ExecuteBootstrapper -> LookupAndCompile
ExecuteBootstrapper
内部会调用 LookupAndCompile
,LookupAndCompile
会根据前面不同模式下的分类,找到对应入口文件并经过 V8 处理成可调用的 Function
MaybeLocal<Value> ExecuteBootstrapper(Environment* env,
const char* id,
std::vector<Local<String>>* parameters,
std::vector<Local<Value>>* arguments) {
MaybeLocal<Function> maybe_fn =
NativeModuleEnv::LookupAndCompile(env->context(), id, parameters, env);
// 此处调用编译成功的用户代码,也就是用户代码在此时开始执行
MaybeLocal<Value> result = fn->Call(env->context(),
Undefined(env->isolate()),
arguments->size(),
arguments->data());
// ...
}
下面我们简单看一下 LookupAndCompile
是如何实现。其实现在 src/node_native_module.cc 文件,调用关系
LookupAndCompile -> CompileFunctionInContext
CompileFunctionInContext
已经来到 V8 API 层面,到此已经到达笔者极限。后续如果有能力再进一步探索。
关于 Nodejs 是如何读取用户代码,并交给 V8 进行编译和执行过程就如上所示了。我们回过头看一下,以用户脚本为例,其「引导」过程是什么样子。
用户脚本的入口脚本:internal/main/run_main_module.js
require('internal/bootstrap/pre_execution');
require('internal/modules/cjs/loader').Module.runMain(process.argv[1]);
我们稍微看一下 internal/modules/cjs/loader.js,这个是以 CJS 规范的模块加载,后续还有以 ESM 规范的加载方式(internal/process/esm_loader.js),就请读者自己查看啦:)
在 loader 文件内,会构造 exports, require, module, __filename, __dirname
包裹用户代码;还有对不同文件的加载处理,.js, .node, .json
等
该部分主要是调用 libuv 函数 uv_run
Maybe<int> SpinEventLoop(Environment* env) {
{
do {
// 调用 uv_run 开启 Event Loop
uv_run(env->event_loop(), UV_RUN_DEFAULT);
more = uv_loop_alive(env->event_loop());
if (more && !env->is_stopping()) continue;
if (EmitProcessBeforeExit(env).IsNothing())
break;
// Emit `beforeExit` if the loop became alive either after emitting
// event, or after running some callbacks.
more = uv_loop_alive(env->event_loop());
} while (more == true && !env->is_stopping());
}
}
从目前的代码分析来看,V8 执行用户代码和 Event Loop 是在同一个线程内,这个也能过验证长时间运行用户代码会「饿死」Event Loop。
Event Loop 本身也只使用一个线程,Nodejs 主实例最终会「卡」在 uv_run
,直到循环结束。
child_process 的启动方式以 spawn 为线索开始搜寻。我们从 lib/child_process.js 开始
function spawn(file, args, options) {
// 删去细节,保留主干
const child = new ChildProcess();
return child;
}
ChildProcess
来自 internal/child_process.js
function ChildProcess() {
FunctionPrototypeCall(EventEmitter, this);
// 实例化 Process
this._handle = new Process();
}
ObjectSetPrototypeOf(ChildProcess.prototype, EventEmitter.prototype);
ObjectSetPrototypeOf(ChildProcess, EventEmitter);
ChildProcess.prototype.spawn = function(options) {
const err = this._handle.spawn(options);
}
Process
来自内部模块 process_wrap
。该模块实现在 src/process_wrap.cc
// 注册模块
NODE_MODULE_CONTEXT_AWARE_INTERNAL(process_wrap, node::ProcessWrap::Initialize)
// 绑定 Spawn 函数实现
env->SetProtoMethod(constructor, "spawn", Spawn);
// Spawn 函数实现
static void Spawn(const FunctionCallbackInfo<Value>& args) {
// 调用 uv_spawn 能力,创建进程执行
// process_ 是 uv_process_t,可以理解为从 libuv 获取结果的句柄
// options 是 uv_process_options_s,描述了执行的文件地址,参数等创建进程必须的信息
int err = uv_spawn(env->event_loop(), &wrap->process_, &options);
}
可以看到,最终调用的是 libuv 的 uv_spawn 能力
worker_thread 启动方式详见 #32 ,这里不再赘述。
Nodejs
libuv
现代 C++
V8
雄关漫道真如铁,而今迈步从头越。
除开本职工作,平时的一些活动和思考记录。
1月-5月 和小伙伴们做了一个类似 scrimba 的视频教育网站试水。期间也和发小讨论构建审计相关的平台和工具的可行性。
6月-10月 整一些关于 Serverless 方面的资料,想基于此做一个类似 Nextjs 的框架。目前还没有实现。
11月-12月 相关源码学习,重点研读 Reactjs 源码和 Nodejs 源码
其他值得记录的文章和笔记
年末,和同事探讨团队内搭建未来的路:从前端视角来看,搭建并不是解决前端生产力的银弹,搭建是通过赋能研发链路中其他角色,从而实现解放生产力的工具。但是有可以坦然接受的「赋能」对象吗?
Q: term_ is undefined ?
A:
you don't need to connect. simply load the console when at the connection screen. i just tried it and it worked for me.
if term_ is undefined, try loading a second window, or refreshing the current one (ctrl+r).
-mike
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.