1.nodejs 14.0.0源码分析之setTimeout
2.CesiumJS 源码杂谈 - 从光到 Uniform
3.SortableJS原理分析(源码)
4.js引擎v8源码分析之Object(基于v8 0.1.5)
5.源码学习之noConflict冲突处理机制
6.next.js 源码解析 - API 路由篇
nodejs 14.0.0源码分析之setTimeout
本文深入剖析了Node.js .0.0版中定时器模块的源码阅读实现机制。在.0.0版本中,源码阅读Node.js 对定时器模块进行了重构,源码阅读改进了其内部结构以提高性能和效率。源码阅读下面将详细介绍定时器模块的源码阅读关键组成部分及其实现细节。 首先,源码阅读最强源码指标让我们了解一下定时器模块的源码阅读组织结构。Node.js 采用了链表和优先队列(二叉堆)的源码阅读组合来管理定时器。链表用于存储具有相同超时时间的源码阅读定时器,而优先队列则用来高效地管理这些链表。源码阅读 链表通过 TimersList数据结构进行管理,源码阅读它允许将具有相同超时时间的源码阅读定时器归类到同一队列中。这样,源码阅读Node.js 能够快速定位并处理即将到期的源码阅读定时器。 为了进一步优化性能,源码阅读Node.js 使用了一个优先队列(二叉堆)来管理所有链表。在这个队列中,每个链表对应一个节点,根节点表示最快到期的定时器。在时间循环(timer阶段)时,Node.js 会从二叉堆中查找超时的节点,并执行相应的回调函数。 为了实现这一功能,Node.js 还维护了一个超时时间到链表的映射,以确保快速访问和管理定时器。 接下来,我们将从 setTimeout函数的实现开始分析。这个函数主要涉及 new Timeout和 insert两个操作。其中,new Timeout用于创建一个对象来存储定时器的上下文信息,而 insert函数则用于将定时器插入到优先队列中。 具体地,Node.js 使用了 scheduleTimer函数来封装底层计时操作。这个函数通过将定时器插入到libuv的二叉堆中,为每个定时器指定一个超时时间(即最快的到期时间)。在执行时间循环时,libuv会根据这个时间判断是否需要触发定时器。 当定时器触发时,Node.js 会调用 RunTimers函数来执行回调。回调函数是在Node.js初始化时设置的,负责处理定时器触发时的七战辅助源码具体逻辑。在回调函数中,Node.js 遍历优先队列以检查是否有其他未到期的定时器,并相应地更新libuv定时器的时间。 最后,Node.js 在初始化时通过设置 processTimers函数作为超时回调来确保定时器的正确执行。通过这种方式,Node.js 保证了定时器模块的初始化和定时器触发时的执行逻辑。 本文通过详尽的分析,展示了Node.js .0.0版中定时器模块的内部机制,包括其组织结构、数据管理和回调处理等关键方面。虽然本文未涵盖所有细节,但对于理解Node.js定时器模块的实现原理提供了深入的洞察。对于进一步探索Node.js定时器模块的实现,特别是与libuv库的交互,后续文章将提供更详细的分析。CesiumJS 源码杂谈 - 从光到 Uniform
CesiumJS 源码探索:光照与Uniform的转换之旅
CesiumJS 对光照的处理主要依赖于其底层API与WebGL着色器的交互。尽管它默认只支持一个太阳光,但通过DirectionalLight扩展,可模拟各种光照效果。光在CesiumJS中被转换为Uniform值,以统一的形式传递给着色器执行。
首先,CesiumJS的光照类型主要包括场景默认的太阳光和DirectionalLight,后者允许设定光照方向。例如,官方示例中的《Lighting》展示了如何运用DirectionalLight创建灯光效果。方向光多了一个方向属性,通常表示为单位向量。
在源码中,光照信息通过UniformState对象在每帧渲染时传递给Renderer。这个过程始于Scene.js模块的render函数,其中的uniformState会更新来自FrameState的光照参数。当Context对象执行DrawCommand时,ShaderProgram的_uniforms列表会填充来自uniformState的值,包括那些由AutomaticUniforms自动更新的,如光的属性。
光照Uniform在着色器中的应用十分广泛,如点云着色时使用czm_lightColor,冯氏着色法(Phong)材质通过czm_lightColor进行漫反射和高光计算,ai续写源码Globe.js则在GlobeFS片元着色器中使用czm_lightColor。在Model API的PBR着色法中,czm_lightColorHdr变量在光照阶段的计算中扮演重要角色。
总的来说,CesiumJS的光照系统通过Uniform的转换,确保光照信息在复杂渲染流程中的顺畅传递。然而,深入研究光照材质,特别是在自定义光照效果方面,仍需要进一步学习实时渲染(RealTimeRendering)的知识。
SortableJS原理分析(源码)
前言
SortableJS是基于H5拖拽API实现的一个轻量级JS拖拽排序库,它适用于以下一些场景:
容器项目拖动排序:容器列表内的子项目,通过拖动进行位置调换,且具有动画效果;
容器间的项目移动:将一个容器列表中的子项目,拖动到另一个容器列表中(移动/克隆)。
不论是容器内元素顺序排序,或是两个容器内的元素进行移动,本质上是在通过操作DOM来实现。
下面我们先熟悉一下SortableJS基本使用。
示例1、HTML结构:
<divclass="row"><divid="leftContainer"class="list-groupcol-6"><divclass="list-group-item">Item1</div><divclass="list-group-item">Item2</div><divclass="list-group-item">Item3</div><divclass="list-group-item">Item4</div><divclass="list-group-item">Item5</div><divclass="list-group-item">Item6</div></div><divid="rightContainer"class="list-groupcol-6"><divclass="list-group-itemtinted">Item1</div><divclass="list-group-itemtinted">Item2</div><divclass="list-group-itemtinted">Item3</div><divclass="list-group-itemtinted">Item4</div><divclass="list-group-itemtinted">Item5</div><divclass="list-group-itemtinted">Item6</div></div></div>2、为容器实例化:
newSortable(leftContainer,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});现在,就可以在容器内进行排序拖动,或者拖动左侧容器元素,添加到右侧容器中。
思路分析在看源码之前,还是需要对H5拖拽用法有一定了解,如果不熟悉,直接去看源码很容易就放弃。
若你对H5拖拽API比较熟悉,就可以根据SortableJS的视图呈现效果,想出个大概思路。
拖拽,首先要搞清楚两个词汇对象:
拖动元素:作为拖拽元素被拖起(下文叫dragEl);
目标元素:作为拖拽元素即将被放置时的参照物(下文叫target);
在SortableJS中,拖拽离不开以下几个事件:
dragstart:作为拖拽元素,按下鼠标开始拖动元素时触发(拖拽周期只触发一次);
dragend:作为拖拽元素,当鼠标松开拖放元素时触发(拖拽周期只触发一次);
dragover:作为拖拽元素,当拖动元素进行移动,会持续触发,需要在这里取消默认事件,宠物互助盘源码否则元素无法被拖动(松开时元素的预览幽灵图又回去了);
drop:作为目标元素,当鼠标松开拖放元素时触发(拖拽周期只触发一次);
下面我们一起去分析SortableJS具体实现。
源码实例构造函数从上面的示例使用上得知,SortableJS是一个构造函数,接收容器元素和配置项:
constexpando='Sortable'+(newDate).getTime();functionSortable(el,options){ this.el=el;//rootelementthis.options=options=Object.assign({ },options);el[expando]=this;constdefaults={ group:null,sort:true,//默认容器可以排序animation:0,removeCloneOnHide:true,//将一个容器元素拖动至另一个容器后,默认setData:function(dataTransfer,dragEl){ dataTransfer.setData('Text',dragEl.textContent);}};//参数合并for(varnameindefaults){ !(nameinoptions)&&(options[name]=defaults[name]);}//规范group_prepareGroup(options);//绑定原型方法为私有方法for(varfninthis){ if(fn.charAt(0)==='_'&&typeofthis[fn]==='function'){ this[fn]=this[fn].bind(this);}}//绑定指针触摸事件,类似mousedownon(el,'pointerdown',this._prepareDragStart);on(el,'dragover',this);on(el,'dragenter',this);}初始化示例做了以下几件事件:
将传入的参数与提供的默认参数进行合并;
规范传入的group格式;
将原型上的方法绑定在实例对象上,便于使用;
绑定pointerdown、dragover、dragenter事件,其中pointerdown可以看作是dragstart事件,做了一些拖拽前的准备工作。
group用于两个容器元素的相互拖拽场景,规范group核心代码如下:
function_prepareGroup(options){ functiontoFn(value,pull){ returnfunction(to,from){ letsameGroup=to.options.group.name&&from.options.group.name&&to.options.group.name===from.options.group.name;if(value==null&&(pull||sameGroup)){ returntrue;}elseif(value==null||value===false){ returnfalse;}elseif(pull&&value==='clone'){ returnvalue;}else{ returnvalue===true;}};}letgroup={ };letoriginalGroup=options.group;if(!originalGroup||typeoforiginalGroup!='object'){ originalGroup={ name:originalGroup};}group.name=originalGroup.name;group.checkPull=toFn(originalGroup.pull,true);group.checkPut=toFn(originalGroup.put);options.group=group;}_prepareDragStart拖动前的准备工作当鼠标按下触发pointerdown事件时,会保存拖动元素的信息,提供后续使用,并且注册dragstart事件:
letoldIndex,newIndex;letdragEl=null;//拖拽元素letrootEl=null;//容器元素letparentEl=null;//拖拽元素的父节点letnextEl=null;//拖拽元素下一个元素letactiveGroup=null;//options.groupSortable.prototype={ _prepareDragStart(evt){ lettarget=evt.target,el=this.el,options=this.options;oldIndex=index(target);rootEl=el;dragEl=target;parentEl=dragEl.parentNode;nextEl=dragEl.nextSibling;activeGroup=options.group;dragEl.draggable=true;//设置元素拖拽属性on(dragEl,'dragend',this);on(rootEl,'dragstart',this._onDragStart);on(document,'mouseup',this._onDrop);},}on就是addEventListener,index方法用于获取元素在父容器内的索引:
functionon(el,event,fn){ el.addEventListener(event,fn);}functionoff(el,event,fn){ el.removeEventListener(event,fn);}functionindex(el){ if(!el||!el.parentNode)return-1;letindex=0;//返回元素节点之前的兄弟元素节点(不包括文本节点、注释节点)while(el=el.previousElementSibling){ if(el!==Sortable.clone)index++;}returnindex;}_onDragStart用于处理dragstart事件逻辑,_onDrop用于处理拖拽结束逻辑,比如这里执行了dragEl.draggable=true;,那么在mouseup鼠标松开后需将draggable=false。
这里有趣的一点是dragend事件,它的处理函数绑定的是this即Sortable实例本身,我们都知道实例对象是一个对象,怎么能作为函数使用呢?
其实addEventListener第二参数可以是函数,也可以是对象,当为对象时,需要提有一个handleEvent方法来处理事件:
Sortable.prototype={ handleEvent:function(evt){ switch(evt.type){ case'dragend':this._onDrop(evt);break;case'dragover':evt.stopPropagation();evt.preventDefault();break;case'dragenter':if(dragEl){ this._onDragOver(evt);}break;}},}到这里,整个拖拽流程功能函数都暴露在了眼前:
_onDragStart处理dragstart拖拽开始工作;
_onDragOver处理拖拽移动到别的元素时工作;
_onDrop处理鼠标拖动结束的收尾工作。
dragstart这里做了两件事情:
clone一个dragEl元素副本,用于两个容器项目移动时使用;
触发外部传入的clone和dragstart事件;
letcloneEl=null,cloneHidden=null;//clone元素_onDragStart(evt){ letdataTransfer=evt.dataTransfer;letoptions=this.options;cloneEl=clone(dragEl);cloneEl.removeAttribute("id");cloneEl.draggable=false;//设置拖拽数据if(dataTransfer){ dataTransfer.effectAllowed='move';options.setData&&options.setData.call(this,dataTransfer,dragEl);}Sortable.active=this;Sortable.clone=cloneEl;_dispatchEvent({ sortable:this,name:'clone'});_dispatchEvent({ sortable:this,name:'start',originalEvent:evt});},functionclone(el){ returnel.cloneNode(true);}_dispatchEvent会通过newwindow.CustomEvent构造一个事件对象,将拖拽元素的信息添加到自定义事件对象上,传递给外部的注册事件函数,大体代码如下:
functiondispatchEvent(...params){ //sortable没有传,就根据rootEl获取sortable。sortable=(sortable||(rootEl&&rootEl[expando]));if(!sortable)return;letevt,options=sortable.options,onName='on'+name.charAt(0).toUpperCase()+name.substr(1);//自定义事件,拿到事件对象,满足外部用户传入的事件正常使用if(window.CustomEvent){ evt=newCustomEvent(name,{ bubbles:true,cancelable:true});}else{ evt=document.createEvent('Event');evt.initEvent(name,true,true);}evt.to=toEl||rootEl;evt.from=fromEl||rootEl;evt.item=targetEl||rootEl;evt.clone=cloneEl;evt.oldIndex=oldIndex;evt.newIndex=newIndex;//执行外部传入的事件if(options[onName]){ options[onName].call(sortable,evt);}}可见,拖拽的商城互助盘源码核心逻辑不在dragstart中,下面我们去看dragenter的处理函数_onDragOver。
dragenterSortableJS的核心逻辑在_onDragOver中,拿容器内项目排序为例:当拖动dragEl元素,移动到另一个元素上时,会发生两者的位置交换,可见,Sort的逻辑在这里。
首先,在实例化对象时绑定了dragover和dragenter事件,并且通过handleEvent将事件逻辑交由_onDragOver来处理:
on(el,'dragover',this);on(el,'dragenter',this);handleEvent:function(evt){ switch(evt.type){ case'dragover':evt.stopPropagation();evt.preventDefault();break;case'dragenter':if(dragEl){ this._onDragOver(evt);}break;}},在_onDragOver中,需要注意一点是:假如有两个容器,那就有两个newSortable实例对象,isOwner将为false,这是就需要判断拖动容器的activeGroup.pull(是否允许被移动)和group.put(是否允许添加拖动过来的元素)。
newSortable(leftContainer,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});0上面的核心在于下面这一行代码:
newSortable(leftContainer,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});1如果拖拽元素的位置小于目标元素的位置,说明是从上往下拖动,那么将dragEl移动到target.nextSibling之前;
如果拖拽元素的位置大于目标元素的位置,说明是从下往上拖动,那么只需将dragEl移动到target之前即可;
整个移动过程均采用DOM操作insertBefore来实现。
另外如果是两个容器的场景(isOwner=false),并且拖动元素的容器activeGroup.pull=clone,需要将dragstart创建的clone元素渲染到容器中:
newSortable(leftContainer,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});2dropdrop主要做一些收尾工作,如将dragEl.draggable=false,移除绑定的mouseup、dragstart、dragend事件,触发用户传入的sort、end事件等。
不过注意,虽然起名叫drop,触发的事件确是dragend。
newSortable(leftContainer,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});3动画如果想在拖动排序中有一定的animation动画效果,可以配置动画属性,属性值是动画持续时长:
newSortable(leftContainer,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});4动画的时机也是在dragenter中,大致的思路如下:
1、记录:记录容器子项位置信息
在操作DOM移动dragEl之前,记录容器内所有子项的位置;
进行DOM操作进行位置交换,DOM操作本身没有动画;
这时再去记录一次移动后的容器内所有子项的位置;
2、执行:有了上面几步的操作,接下来就可以根据移动前后的位置进行动画操作
通过translate先让元素立刻回到移动前的位置;
此时给元素自身设置过度效果transform;
这时候就可以通过translate让元素回到移动之后的位置。
大致实现如下:
newSortable(leftContainer,{ group:{ name:'group',pull:'clone',put:true},});newSortable(rightContainer,{ group:'group',});5最后本文以探索SortableJS拖拽思路为主线,去了解业界开源拖拽库的设计与思路。感谢阅读。
原文:/post/js引擎v8源码分析之Object(基于v8 0.1.5)
在V8引擎中,Object是所有JavaScript对象在底层C++实现的核心基类,它提供了诸如类型判断、属性操作和类型转换等公共功能。
V8的对象采用4字节对齐,通过地址的低两位来识别对象的类型。作为Object的子类,堆对象(HeapObject)有其独特的属性,如map,它记录了对象的类型(type)和大小(size)。type字段用于识别C++对象类型,低位8位用于区分字符串类型,高位1位标识非字符串,低7位则存储字符串的子类型信息。
对于C++对象类型的判断,V8引擎定义了一系列宏。这些宏包括isType函数,用于确定对象的具体类型。此外,还有其他函数,如解包数字、转换为smi对象、检查索引的有效性、实现JavaScript的IsInstanceOf逻辑,以及将非对象类型转换为对象(ToObject)等。
对于数字处理,smi(Small Integers)在V8中用于表示整数,其长度为位。ToBoolean函数用于判断变量的真假,而属性查找则通过依赖子类的特定查找函数来实现,包括查找原型对象。
由于后续分析将深入探讨Object的子类和这些函数的详细实现,这里只是概述了Object类及其关键功能的概览。
源码学习之noConflict冲突处理机制
在早期项目中,我有机会深入了解Backbone.js的源码,特别是其noConflict冲突处理机制。这个机制其实非常直观,核心是一个简单的函数,代码量虽小,但作用显著。
noConflict的原理非常巧妙,每次调用这个函数,框架就回退到之前的一个版本。例如,如果你先引入了v1.4.0,接着引入v1.0.0,那么默认情况下,Backbone会指向最新版本v1.0.0。执行Backbone.noConflict()后,会回退到v1.4.0,再次调用则会回退到未被覆盖的原始状态,Backbone变成undefined。
让我们通过一个例子来说明:首先引入v1.4.0和v1.0.0的Backbone,输出的Backbone版本为1.0.0。执行noConflict后,版本会回退到1.4.0,再次执行noConflict则会释放Backbone,使其变为undefined。
源码中,Backbone的noConflict函数十分注释详尽,帮助开发者理解其工作原理。官方文档解释,这个方法可以防止第三方库对现有Backbone的覆盖,非常实用。
Backbone的冲突处理机制源自jQuery,很多框架都借鉴了这一设计。jQuery的noConflict方法也类似,除了版本回退,还有一个deep参数,当deep为true时,不仅$变量会回退,jQuery本身也会。
举个jQuery的例子:引入3.5.1和3.4.1版本,noConflict调用后,无论deep值如何,jQuery和$都会回退到之前的版本。
总的来说,noConflict冲突处理机制是开发过程中处理版本冲突的有力工具,它通过版本回退确保了代码的稳定性。
next.js 源码解析 - API 路由篇
本文深入解析 next.js 的 API 路由实现细节,以清晰的步骤指引,帮助开发者更好地理解此框架如何管理与处理 API 请求。首先,我们确认了源码的位置位于 next.js 的 packages 文件夹中,重点关注与 API 路由相关的组件。
在排查 CLI 源码的过程中,我们注意到启动 API 路由的命令,如 `start` 和 `dev`,其实际操作逻辑位于 `next/dist/bin/next` 文件中。通过分析这一文件,我们得知这些命令最终调用的是 `lib/commands.ts` 文件中的 `start` 和 `dev` 函数。
深入 `lib/commands.ts` 文件,我们发现 `start` 和 `dev` 函数通过 `lib/start-server` 中的 `startServer` 方法实现。在 `startServer` 方法中,`http` 模块被用来创建服务器,并将请求处理逻辑委托给 `next` 函数生成的应用程序,通过 `getRequestHandler` 方法获取处理逻辑。
`getRequestHandler` 方法的最终执行路径指向了 `server/next.ts` 文件中的 `createServer` 方法。这里根据 `dev` 参数的不同,分别调用 `server/dev/next-dev-server` 中的 `DevServer` 或 `server/next-server` 中的 `NextNodeServer`。`DevServer` 类继承自 `NextNodeServer`,而 `NextNodeServer` 又继承了 `server/base-server` 中的 `Server` 类。
至此,我们找到了核心处理逻辑所在,即 `handleApiRequest` 方法。此方法首先进行路由匹配和校验,然后调用 `runApi` 进行 API 请求处理。API 请求处理的路径通常位于 `/api/` 目录下的指定文件中,通过 `require` 函数引入。
`apiResolver` 方法进一步处理请求,包括检查代码模块、获取配置参数、处理 cookie、查询、预览数据、预览、bodyParser 等。其中 `setLazyProp` 方法用于优化性能,仅在访问属性时触发函数执行,实现懒加载。
最后,本文总结了 next.js API 路由处理的完整流程,并强调了源码中的关键点,为开发者提供了全面的解读。通过本文解析,开发者能够深入理解 next.js 如何高效地管理和响应 API 请求。
图文剖析 big.js 四则运算源码
big.js是一个小型且高效的JavaScript库,专门用于处理任意精度的十进制算术。
在常规项目中,算术运算可能会导致精度丢失,从而影响结果的准确性。big.js正是为了解决这一问题而设计的。与big.js类似的库还有bignumber.js和decimal.js,它们同样由MikeMcl创建。
作者在这里详细阐述了这三个库之间的区别。big.js是最小、最简单的任意精度计算库,它的方法数量和体积都是最小的。bignumber.js和decimal.js存储值的进制更高,因此在处理大量数字时,它们的速度会更快。对于金融类应用,bignumber.js可能更为合适,因为它能确保精度,除非涉及到除法操作。
本文将剖析big.js的解析函数和加减乘除运算的源码,以了解作者的设计思路。在四则运算中,除法运算最为复杂。
创建Big对象时,new操作符是可选的。构造函数中的关键代码如下,使用构造函数时可以不带new关键字。如果传入的参数已经是Big的实例对象,则复制其属性,否则使用parse函数创建属性。
parse函数为实例对象添加三个属性,这种表示与IEEE 双精度浮点数的存储方式类似。JavaScript的Number类型就是使用位二进制格式IEEE 值来表示的,其中位用于表示3个部分。
以下分析parse函数转化的详细过程,以Big('')、Big('0.')、Big('e2')为例。注意:Big('e2')中e2以字符串形式传入才能检测到e,Number形式的Big(e2)在执行parse前会被转化为Big()。
最后,Big('')、Big('-0.')、Big('e2')将转换为...
至此,parse函数逻辑结束。接下来分别剖析加减乘除运算。
加法运算的源码中,k用于保存进位的值。上面的过程可以用图例表示...
减法运算的源码与加法类似,这里不再赘述。减法的核心逻辑如下...
减法的过程可以用图例表示,其中xc表示被减数,yc表示减数...
乘法运算的源码中,主要逻辑如下...
描述的是我们以前在纸上进行乘法运算的过程。以*为例...
除法运算中,对于a/b,a是被除数,b是除数...
注意事项:big.js使用数组存储值,类似于高精度计算,但它是在数组中每个位置存储一个值,然后对每个位置进行运算。对于超级大的数字,big.js的算术运算可能不如bignumber.js快...
在使用big.js进行运算时,有时没有设置足够大的精度会导致结果不准确...
总结:本文剖析了big.js的解析函数和四则运算源码,用图文详细描述了运算过程,逐步还原了作者的设计思路。如有不正确之处或不同见解,欢迎各位提出。