anathanpham / blog Goto Github PK
View Code? Open in Web Editor NEW随便写写
随便写写
浏览器端的模块化。
你需要在模块内部使用导入导出,意味着你需要在HTML中写<script type="module" src="main.mjs"></script>
。
它是顶级模块,也是入口模块。模块导入其他模块,可以根据目录结构发起请求。
可以将import
作为函数来使用。函数调用的返回值为promise。
最近在读TypeScript的文档,在这里记录自己的思考。
TS无法对解构的变量进行类型声明。因为这在ES中被视为变量的别名。
TS无视类型字段的readonly,进行兼容。
interface ReadonlyPerson {
readonly name: string;
readonly age: number;
}
let writablePerson: Person = {
name: "Person McPersonface",
age: 42,
};
// works
let readonlyPerson: ReadonlyPerson = writablePerson;
console.log(readonlyPerson.age); // prints '42'
writablePerson.age++;
console.log(readonlyPerson.age); // prints '43'
readonly的陷阱:
let x: readonly string[] = [];
let y: string[] = [];
x = y;
y = x;
The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.
// 泛型 组合
type OrNull<Type> = Type | null;
TS一般是类型模型,泛型是对类型模型的抽象(或类型模型的模型)。
因为实际上没有对第二次抽象单独进行具象,所以泛型会导致类型推断丢失。
function loggingIdentity<Type>(arg: Type): Type {
console.log(arg.length);
Property 'length' does not exist on type 'Type'.
return arg;
}
//You can only use types when indexing, meaning you can’t use a const to make a variable reference:
const key = "age";
type Age = Person[key];
//Type 'any' cannot be used as an index type.
//'key' refers to a value, but is being used as a type here. Did you mean 'typeof key'?
//Try
//However, you can use a type alias for a similar style of refactor:
type key = "age";
type Age = Person[key];
浏览器的event loop遵循下面的顺序(都是可选的,意味着每个步骤不一定执行):
this指向class本身
本文只关注更新阶段(Render)的细节,不关注Scheduler(驱动React进行更新是Scheduler的工作)。
为了直观的描述更新的过程,我提供一个具体的场景。
class App extends React.Component {
state = {
name: "ana",
};
render() {
const { name } = this.state;
return React.createElement(
"h1",
{
onClick: () => {
this.setState({
name: name + " OG",
});
},
},
name
);
}
}
给原生组件h1绑定点击事件,当点击h1后,看看React会做出什么响应。
通过合成事件,React执行了onClick回调。进而执行了this.setState
。setState调用了更新器(注1)。
更新器通过this
(App组件实例)获取到App fiber(注2)。
创建更新对象
计算过期时间(注3),根据过期时间和更新payload(payload指更新的实际数据,在这里是{name: name + " OG"}
),加上setState第二个参数callback(如果有的话),一起组成了更新对象update。
update会被添加进App fiber的更新队列(updateQueue)(注4)(注5)属性中。
安排更新
React首先会将触发更新的fiber节点自身的过期时间更新(注6),再更新父节点标记(子代过期时间)(注7),表示子孙节点有更新。并且一直向上进行标记,直到所有祖先都被标记。(这是为了避免更新fiber树种未发生变动的其他树枝。
)
将fiber标记好后,就开始安排更新了。
安排更新前会进行检查,如果更新已经安排了并且新的更新优先级不高于现有优先级,则不安排更新。否则,取消当前task回调,安排新的task。
React通过scheduleCallback
安排更新,它根据优先级和回调创建了一个任务(task)。task模拟了浏览器事件循环的宏任务。(对于延迟任务使用setTimeout,对于过期/立即执行任务使用postMessage【注8】)
在这里task的任务的内容是更新fiber树。
对于同步任务,React做了特别的处理。React将同步任务的回调收集起来,放入同步队列。
TODO:生产中...
弱声明式,我取得名字。指的是介于声明式和命令式之间的一种代码形态。
实现上按照声明式去做,但是技术局限,包含了很多命令式的代码。
换句话说,代码没规划好。
React中调用setState如果当前没有上下文,那么在安排更新函数中,会同步刷新任务队列,即会立即更新state,组件树重新渲染。如果多次调用setState,则会导致组件树多次渲染。
使用unstable_batchedUpdates,为setState调用栈外层包上一层上下文。则多次setState调用只会使组件树渲染一次。
简单的看了文档之后,
我理解的是 包含了react和简化版reactDom的集合。
但是它不是React,我想知道差异在哪里、体现到实际使用的差异?
通过设置Access-Control
系列的响应头,主要是Access-Control-Allow-Origin
。
服务器返回的数据形如
doSomeThing({name:'ana',age:24})
,实际上服务器向提供给客户端的数据是{name:'ana',age:24}
。
客户端需要定义doSomeThing
方法。
本地运行前后端分离项目时使用。
向指定域名传递消息
我的精力是有限的,我用一些流行的工具,它们总是可靠的。(如果不可靠,我会去了解)
我把精力放在值得关注的地方。
https://www.typescriptlang.org/docs/handbook/2/narrowing.html#typeof-type-guards
按理说,typeof strs === "object"
会让ts的类型范围缩小到string[]
和null
。事实上却类型范围中没有包含null
(tsc命令没有报错)。
function printAll(strs: string | string[] | null) {
if (typeof strs === "object") {
for (const s of strs) {
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
} else {
// do nothing
}
}
可以快捷的改变函数this的指向
在js中,为什么(0,fn)()这样就可在全局中执行fn这个函数?
git无法追踪一个空的文件夹,当用户需要追踪(track)一个空的文件夹的时候,按照惯例,大家会把一个称为.gitkeep的文件放在这些文件夹里。
在React写class组件时,有这样一种写法
class Button extends React.component {
handleClick = ()=>{
this.setState()
}
handleClick2(){
// do something
}
}
对于handleClick2
,它是Button的原型(prototype)上的方法。
当使用这个方法的时候存在一个陷阱,如果调用该方法时,不是通过当前组件实例调用,可能出现this的指向问题。
针对上面的陷阱,我们会使用handleClick
这种写法。
那它是怎么做到的呢?在JavaScript的世界,没有魔法。这种写法是一种语法糖,他会被babel转换。
如果你来做,你会怎么实现呢?
我设想将handleClick
也作为prototype
上的方法行吗?
显然,这样this会指向调用者。没有将this的指向固定。所以不可行。
将this固定很容易的想到的是bind
、call
、apply
。
固定this的工作在constructor中完成。
还有另一种办法,它没有改变this的指向,而是利用闭包,将this作为私有变量。同样的也在constructor中完成。
constructor(){
const that = this
this.handleClick = function(){
// ... handleClick的原始代码 将this替换为that
}
}
babel采用的是第二种办法。
在React中,我们会利用@babel/plugin-proposal-class-properties来使用这种语法。
另外,class语法本身支持箭头函数实例属性的语法。
它和普通对象字面量行为不一致。
待补充
迭代协议分为两种,可迭代协议和迭代器协议。
可迭代协议用于for of
,一个对象或原型上有[Symbol.iterator]
,此时它是可迭代对象。[Symbol.iterator]
的值为返回值为迭代器的函数(它可以是生成器也可以是普通函数)。for of
迭代的项是生成器.next
调用的返回值.value
迭代器协议
通常迭代器也实现了可迭代协议。
var myIterator = {
next: function() {
// ...
},
[Symbol.iterator]: function() { return this }
}
new Map([iterable])
new WeakMap([iterable])
new Set([iterable])
new WeakSet([iterable])
Promise.all(iterable)
Promise.race(iterable)
Array.from(iterable)
展开语法
String、Array、TypedArray、Map 和 Set,它们的原型对象都实现了 @@iterator 方法。
用户访问带有script的参数的链接,页面将script直接innerHTML页面,执行恶意代码
https://www.cnblogs.com/chyingp/archive/2013/06/06/zcool-xss.html
ReactDOM.render将React元素挂载到DOM容器上。
ReactDOM.render挂载React元素中,最核心是构建虚拟DOM树。
如果当前的DOM容器不是React控制(注1),且不是服务端渲染(注2)。React会清空容器DOM(注3)。
然后,React创建了虚拟DOM的全局状态存储对象--FiberRootNode,和顶级(根)Fiber节点--current FiberNode(注4)。
此时,有了根fiber。下一步就从根fiber开始构建fiber树。React构建fiber树的逻辑,沿用了fiber树更新的逻辑。
React巧妙的将React元素(注5)作为更新对象添加到current更新队列,将ReactDOM.render作为更新对象的回调(注6)。
然后安排更新(注7),同步(注8)执行更新fiber树。
多个本地仓库拉取了远程仓库包含tag的git记录。
此时远程仓库删除tag如何同步给所有本地仓库?
我这里好像本地仓库推送代码会将tag重新添加到远程仓库。
tag有变更记录吗?
const process = require('child_process');
process.exec('shutdown -h now',function (error, stdout, stderr) {
console.log(123)
})
对数组的左右两端用指针标记,取出数组最左侧的元素作为参照数,将数组中小于参照数的置于左侧、大于参照数的置于右侧,此时数组被分为左右两个子数组,再分别对两个子数组进行上述排序,直到子数组长度为1或0。
如何处理元素与参照数相等的情况
空槽和当前方向
一轮排序结束后,参照数是否分组
下面的排序函数只是按照最初的思路进行实现,后面会进行优化。
function quickSort(arr) {
if (arr < 2) {
return arr;
}
const length = arr.length;
let left = 0;
let right = length - 1;
const reference = arr[0];
let slotInLeft = true;
// 假设排序结果为递增数组
// 5. 最后的情况是,两个指针距离为1,一个指向待比较元素,一个指向空槽。
// 5.1 待比较元素较大,放入空槽,原始空槽指针移动到原始待比较元素位置(新空槽),两指针相遇。
// 5.2 待比较元素较小,它的指针移动到空槽,两指针相遇。
// 所以一轮排序的终止条件为,两指针相遇(即left===right)(排序中的状态:left的值应该小于right)
while (left !== right) {
// 6. 对空槽位置不同情况的判断
if (slotInLeft) {
// 1. 比较参考数和待排序元素
if (reference > arr[right]) {
// 2. 对待排序元素排序,放入空槽
arr[left] = arr[right];
// 3. 更新空槽标记,空槽从左侧变为右侧 (一开始默认空槽在左侧,但是漏掉了对空槽位置不同情况的判断[6])
slotInLeft = false;
// 4. 开始下一次比较,左侧索引left已经排好序,继续从左向右扫描(此时发现需要添加**扫描的终止条件**)
left++;
} else {
// 右侧值大,保持不动,继续从右向左扫描
right--;
}
} else {
// 7. 当前空槽在右侧,待排序元素则在左侧。此时需要特别注意,在空槽在左侧的情况没有对“与参考数相等”的情况做处理(相当于默认相等相等时,右侧元素保持不动),所以在此必须处理这种情况,左侧元素相等时将移动到右侧
if (reference <= arr[left]) {
arr[right] = arr[left];
slotInLeft = true;
right--;
} else {
left++;
}
}
}
// 8. 一轮排序结束后,left和right相遇。当前left指针指向空槽,将参照数放入空槽。
arr[left] = reference;
// 9. 以left为界限,划分两个子数组。特别注意,右子数组可能存在数组越界的问题(当left指向arr最右侧时,left+1会越界,越界长度为1)
const leftChildArr = arr.slice(0, left);
const rightChildArr = left === length - 1 ? [] : arr.slice(left + 1, length);
// 10. 分别对左子数组和右子数组排序,然后组合起来,整个数组就排好序了
return [...quickSort(leftChildArr), reference, ...quickSort(rightChildArr)];
}
saga作为中间件,原理是对dispatch的增强。
saga拦截了action,现在saga中进行处理,之后再抛给redux。
在saga,yield的作用是让saga来处理任务。如果没有yield,saga不会去处理任务。
call不光可以调用请求,还可以调用saga。他会阻塞saga任务直到它本身完成且它其中的异步saga也完成。其中的异步其实没说清楚,异步中的异步也会阻塞,下面用代码来表示。可以方向的确保被call的saga任务完全完成,包括嵌套的异步saga任务。
export function* asyncSage1() {
console.log("asyncSage1");
yield fork(request1);
yield fork(asyncSage2);
yield delay(1000);
}
export function* asyncSage2() {
yield fork(request2);
console.log("asyncSage2 end");
}
export function request1() {
return new Promise((res, rej) => {
setTimeout(() => {
res();
console.log("request 1");
}, 3000);
});
}
export function request2() {
return new Promise((res, rej) => {
setTimeout(() => {
res();
console.log("request 2");
}, 6000);
});
}
export function* watchIncrementAsync() {
yield takeEvery("asyncSage1", asyncSage1);
}
react合成事件指的是react用js模拟了一个Dom事件流。(fiber树模拟Dom树结构)
合成事件的事件流在fiber树中发生捕获和冒泡。
当你点击input输入框,react在根节点(注1)监听到focus事件(注2)(注3)。
如何从原生事件找到对应的虚拟Dom?
此时,react得到的信息只有原生事件对象(nativeEvent)。react通过nativeEvent对应的Dom(eventTarget),沿着Dom树向上找到距离该eventTarget最近的被react管理的Dom节点(注4)(注5),并获得对应的fiber A。
接着通过事件插件(注6),创建合成事件(注7)A。合成事件A被react视为模拟事件流中的事件源,fiber A被react视为事件目标。
合成事件流?
从fiber A出发向上(直到顶层fiber HostComponent为止)收集所有的host类型的fiber。
然后将收集到的fiber数组,从后向前(捕获),再从前向后(冒泡)遍历。每次遍历,会收集(注8)当前遍历项fiber节点的绑定的focus事件。之后(事件插件完成后,即合成事件生成好了)会按照收集的顺序执行foucs回调。
react就是这样模拟了事件流。
除了focus外,也触发了其他的事件--click等。react在根节点对不同类型的事件进行了监听,每监听到一种事件就会派发一次,多种类型的事件,会派发多次。
点击输入框,会先后触发focus和click。当focus事件的派发完后,就会派发click事件。
每次某个事件派发结束,会处理待处理的同步任务队列(flushSyncCallBackQueue)。
意料之外的render?
在NoMode模式下,多次派发事件且每个事件都改变了状态(如调用setState),则对应组件会被render多次。
在本例中,点击input输入框,如果给input绑定了focus事件和click事件并且事件回调都调用setState,input将会render两次。
react中,在input设置了value属性的条件下,无论在输入框中输入什么,输入框的值都不会改变。除非你改变了input组件的state。
react在处理完模拟事件流后,会调用方法将一些意外的效果重置。
例如该场景,在input中输入了一个值,input输入框会出现你输入的值,但马上input的值会被对应的fiber的value属性更新(finishEventHander 重置受控组件)。
如果没有给input设置value则会忽略。
因为react在事件流(捕获到冒泡)完后就将合成事件对象释放(SyntheticEvent.prototype.destructor 将合成事件对象的属性重置)。
react按照事件流顺序执行回调,在执行前会检查当前合成事件对象是否处于阻止冒泡的状态,如果是,则终止事件流。
react的合成事件对象原型对原生函数进行增强。对原生事件方法的阻止冒泡、阻止默认行为进行了封装(内部也调用原生事件的方法)。
注1:根节点,在 react-v16 为 document ,在 react-v17 为挂载容器Dom
注2:focus事件并不是冒泡事件,react对非冒泡事件在捕获阶段监听
注3:根节点对所有事件进行了监听,除了特别例外submit、reset、invalid和媒体事件等
注4:react将fiber树挂载到Dom树上时,每个宿主(host)类型fiber节点与Dom节点一一对应,并链接
注5:向上查找是因为可能子Dom节点并不是被react管理的,如第三库滚动插件等
注6:为了模拟Dom事件,react进行的补充
注7:react内部一个构造函数的实例,合成事件的部分属性来源于nativeEvent。合成事件与fiber关联
注8:收集而不是执行。因为react针对某一类型事件的做了批处理
控制反转是一种在软件工程中解耦合的**,调用类只依赖接口,而不依赖具体的实现类,减少了耦合。 控制权交给了容器,在运行的时候才由容器决定将具体的实现动态的“注入”到调用类的对象中。
依赖注入是一种设计模式,可以作为控制反转的一种实现方式。
从 npm包而来,是一个快捷方式,本体在某个npm包中(它可能被在文件夹内)
step into next function call
进入的时候会闪一下,这时是 webpack 加载目标模块的依赖。
相较于普通的发布订阅模式,promise可以保存状态。
这意味着promise不需要依赖加载顺序。
如果是发布订阅模式,订阅者还没有加载(添加订阅回调),当发布者发布该订阅者会错过这是执行订阅回调的机会。
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
原则:可平稳退化、可接受的错误,不应该导致程序中断
期望缓存请求数据,通过sessionStorage缓存。
可能存在缓存溢出导致报错,这是可接受的错误,不应该导致程序中断。
在盘古开天地的时候,只有一个fiber。这个fiber就是current的顶级fiber。
此后,当安排更新,便向从current顶级fiber复制(TODO:?)了一个fiber,称为workInProgress fiber。
然后就开始了父亲带着孩子的更新之旅。
第一次对比这两个顶级fiber,他们一模一样,没有什么可比较的。对于父亲,只更新。
过程是获取fiber上的 update对象,把update应用到workInProgress fiber上。这时,workInProgress fiber是最新的。
最新的父亲,生的孩子也是最新的。所以新父亲生下来新孩子,而旧父亲,只能眼巴巴的看着,但旧父亲有旧孩子。
此时,差异出现了。新孩子(react元素)和旧孩子(fiber)。(那父亲们呢?父亲已经更新了。对比的目的是什么?是为了得到最新的fiber树,同时尽可能利用旧的fiber节点。对于两个顶级父亲,他们是相同类型的,所以复用并更新。)
如果新孩子和旧孩子类型相同,那么就复用。
对于类型相同的孩子,新的和旧的,区别在于props。(createElement(类型,props,...children),children实际是props的子属性)
怎么复用呢?新孩子复制旧孩子的属性,只更新props(props在新父亲生下新孩子的时候已经确定)。
此后,在下一轮。新孩子和旧孩子,摇身一变,都成了父亲。(除了pendingProps几乎一样,他们都是旧的。更新对象update还挂在身上)
如果新孩子和旧孩子类型不同。
新孩子和旧孩子不是一类人了,旧孩子和新孩子不在一家(同一个节点)住。旧孩子的亲人(sibling兄弟节点、child叶子节点),家具(state或hook、update对象)和新孩子没有任何关系了。
那么就把旧孩子删掉(标记删除,此时属于render阶段。最终的删除操作在commit阶段执行)。
新孩子,转化成的fiber的alternate属性为null。
下一轮,新孩子变身成workInProgress父亲,null代表current父亲。
先点赞,点赞的2s后响应,点踩,1s后响应。
最后显示赞?
setting.json
:
{
"[markdown]": {
"editor.quickSuggestions": true
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<!-- 测试DOM -->
<button id="button">begin render by 100 elements per frame!</button>
<button id="button2">begin render by 1000 elements per frame !</button>
<button id="button3">begin render by 10000 elements per frame !</button>
<button id="button4">begin render by sync !</button>
<!-- 容器DOM -->
<div id="container">container</div>
<!-- 核心代码 -->
<script>
const waitRenderData = Array.from({ length: 10 * 10000 })
const container = document.querySelector("#container")
// 部分渲染 这不是一个幂等函数
function renderPart(
container,
waitRenderData,
startIndex,
option = {
size: 100,
}
) {
const SIZE = option.size
const endIndex = Math.min(startIndex + SIZE, waitRenderData.length - 1) //指向最后一个待渲染的数据
const fragmentEl = document.createDocumentFragment()
while (startIndex <= endIndex) {
// 创造节点,初始化节点,装载节点
const textEl = document.createElement("text")
textEl.innerText = "" + startIndex + "-"
fragmentEl.appendChild(textEl)
// 更新循环条件
startIndex++
}
// 最终 startIndex 指向第一个**待渲染**的数据(下一轮)
container.appendChild(fragmentEl)
return startIndex
}
// 渲染 启动
function renderBigData(container, waitRenderData, option) {
let indexPoint = 0 //指向第一个**待渲染**的数据
let startTime = Date.now()
let endTime = startTime
indexPoint = renderPart(container, waitRenderData, indexPoint, option)
// 自动渲染
autoRenderPart = () => {
indexPoint = renderPart(container, waitRenderData, indexPoint, option)
if (indexPoint > waitRenderData.length - 1) {
endTime = Date.now()
console.log("渲染耗时:" + (endTime - startTime) / 1000 + "秒")
return
}
requestAnimationFrame(autoRenderPart)
}
requestAnimationFrame(autoRenderPart)
}
</script>
<!-- 测试代码 -->
<script>
const button = document.querySelector("#button")
const button2 = document.querySelector("#button2")
const button3 = document.querySelector("#button3")
const button4 = document.querySelector("#button4")
button.addEventListener("click", runTestCode.bind(null, 100))
button2.addEventListener("click", runTestCode.bind(null, 1000))
button3.addEventListener("click", runTestCode.bind(null, 10000))
button4.addEventListener(
"click",
runTestCode.bind(null, waitRenderData.length)
)
function runTestCode(size) {
container.innerHTML = ""
renderBigData(container, waitRenderData, { size })
}
</script>
</body>
</html>
Context是通过React.createContext创建的特殊对象,它的两个属性Provider、Consumer都是对象类型的React组件。
什么是Provider组件 ?
Provider组件可将props.value传递给所有它的子节点。
子节点可以通过静态属性接收(class组件)、Consumer组件的render children接收。
接收的原理是什么?
class组件在constructor执行构造实例之前,已经获得了context,并且构造时将context作为第二个参数传入constructor。
class组件父类对实例属性进行赋值,其中包含this.context。
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
补充一句,在获得context时顺便将当前fiber和context关联起来(作为依赖),目的是为了接收祖先Provider组件改变context发出的通知。
Consumer组件的组件类型的一个属性就是context的引用,所以可以直接获取context。把context作为参数传给render child。
Provider的value改变会发生什么?
Provider的value如果直接修改其属性,是不会有反应的。它遵循react的更新机制,需要通过react的更新(如setState)来触发引起Provider的更新。
render阶段,更新Provider中如果value发生改变(Object.is),Provider会遍历所有子节点,找到依赖该上下文的节点并标记这些fiber的更新时间。这相当于这些节点内部发生了更新(类似于调用setState(null))。
补充一句,为class组件额外生成并添加一个强制更新的更新对象,它将覆盖shouldUpdate的效果。
value没有改变发生什么?
Provider会检查props.children是否相同,如果相同则跳过Provider更新返回旧的子fiber。否则,对比孩子(新的props.children和旧子fiber)。
vite?
一个封装的模块,内部各处和其他模块通信,通信产生副作用。
单独处理显然是不合理。
需要将离散的逻辑,收集起来。
在哪里收集?
在哪通信就在哪收集。
在模块销毁等时候,集中处理。
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.