Blog icon indicating copy to clipboard operation
Blog copied to clipboard

Comments, Thoughts, Conclusions, Ideas, and the progress.

Results 42 Blog issues
Sort by recently updated
recently updated
newest added

**这篇文章会帮您解答js中什么是同步什么是异步。** 通常来说,在js中,我要按顺序干两件事,这样写就行: ```javascript 吃饭(); 睡觉(); ``` 这样就可以做到先吃饭然后睡觉了。 现在假如吃饭要花费两秒,**我想要在吃完饭后睡觉**,我这样写: ```javascript const eat = function() { console.log('start to eat.'); setTimeout(function() { console.log('finish.'); }, 2000); }; const sleep = function() { console.log('start to...

Javascript

## 一些感慨 从开始接触编程到现在,我有一个很强烈的感受就是:当初开始学习的技术,遇到不懂的地方,只要百度一下就能出来一大堆资料,文章和教程,能很快速地解决问题。但是之后发现若向着某个方向越往深走,你能找到的资料就越少,可能要解决的问题只在某个英文文章提了一点,又在某个中文论坛涉及到一两句,你想要窥探整个真相的全貌,就只能靠自己去东拼西凑收集整理理解,但是往往还是触碰不到你想要知道的那个“点”。到最后甚至有些问题只出现在英文论文里,如果不想看英文论文,就只能去GitHub啃源码了,但是说实话啃源码是最痛苦的。 所以说为什么入门是最简单的,因为前人已经帮你开好路填好坑了,你只要照着走就行。到了鲜有人涉及的地方,就要靠自己去摸索了。所以先驱者总是牛逼的。 ## Timestepping是什么 物理引擎是以离散的间隔时间显示的,简单来说物理引擎就是一个大型循环器,每次循环进行一次物理模拟,每两次循环的时间间隔就是离散的时间间隔。 每次进行一次循环,我们称之为一次**Timestepping**(有些物理引擎也叫runner或者timer),每进行一次Timestepping称为一帧。这里不是我装逼非要用英文,是因为他这个词我找不到对应的中文翻译,“时间步”?但总感觉怪怪的,像失去了灵魂。下面我一律用Timestepping。 Timestepping之于物理引擎就像血液之于人类一样重要,物理引擎能跑起来就是因为它。通常一次Timestepping只干两件事:**更新物理数据(updatePhysics)**和**渲染(render)**。其中updatePhysics就是常说的**模拟**,是纯计算的阶段,也是最重要的阶段。而render便是将结果可视化的过程。render阶段往往不太重要,很多纯计算的物理引擎都不包含render部分,需要用户自己编写render器。render阶段往往比updatePhysics更耗时。 设计好Timestepping里面的时间分配和调度十分重要,这关系到物理引擎的性能,精确性和稳定性。 ## 设计Timestepping 接下来我会一步步示范如何设计一个稳定的Timestepping。 首先,我们需要先设定好物理引擎的**步长(dt)**,通常是1/60。 ```typescript const dt = 1/60; ``` 我们需要一种方法来确保我们的物理引擎只在经过特定时间后才运行一次,使用固定的dt值实际上会使物理引擎具有确定性,这被称为**固定时间步长(fixed dt)**。确定性物理引擎总是在每次运行时做完全相同的事情,前提是给出相同的输入。这点非常重要,因为物理引擎本质也是一个纯函数,给定一个固定输入必定要得固定输出,同样这对于调试物理引擎也很重要,为了精确定位错误,物理引擎的行为需要保持一致。 之后我们就可以写出我们的最简单的Timestepping了: ```typescript function timeStepping() { // 更新物理 updatePhysics(dt);...

物理引擎

## 前言 最近一段时间一直在专研物理引擎,这真是一个集代数,平面几何,物理和计算机于一身的交叉领域,要一层层拨开里面的迷雾真的不容易。 前段时间我阅读了某个仿box2d的c++物理引擎clib的源码,虽说里面的代码写得乱七八糟的,但是也有一点收获。后来就转去看matter.js的源码了,感叹没有早点发现matter.js,里面的代码真的堪称工程典范,条理清晰,井井有条。 跑题了,说回今天要介绍的内容。在看完clib的碰撞检测部分后,我意识到我之前有一个地方说错了: > 最近在看box2d的源码,看得好累。发现box2d的碰撞检测不止用到了SAT(分离轴)算法,还有GJK算法和V-clip算法(连google都找不到的冷门算法,不知道具体原理)。box2d貌似把3种算法糅合起来了,根本看不懂。 额,其实并没有V-clip这个算法(怪不得google搜不到),box2d里面的V-clip函数其实是指Vertex Clip,即**多边形裁剪**,而该多边形裁剪算法的真正名字叫**Sutherland Hodgman算法**。 ## Sutherland Hodgman算法 Sutherland Hodgman是一个用于在指定区域裁剪多边形的算法,注意,裁剪不是分割,这是两回事。sh的基本思想其实很简单,核心就是根据上个顶点和当前顶点于裁剪区域的关系进行一步步迭代裁剪。Sutherland Hodgman把上个顶点和当前顶点于裁剪区域的关系分成四种情况: - **上一个顶点在裁剪区域外,当前顶点在裁剪区域内** - **上一个顶点在裁剪区域内,当前顶点在裁剪区域外** - **上一个和当前顶点都在顶点在裁剪区域外** - **上一个和当前顶点都在顶点在裁剪区域内** ![](https://github.com/phenomLi/Blog/raw/master/photos/2019-7-3/微信截图_20190703223746.png) 而根据上面的四种情况,Sutherland Hodgman对多边形所有顶点进行遍历,选择哪些顶点需要保留,哪些顶点需要遗弃: - **情况1:保留current和last连线与裁剪线的交点和current** -...

Typescript
物理引擎

在拥有了四叉树之后,我们就可以利用四叉树这个工具计算物体的N体力了,不过在计算N体力之前,我们首先要完成计算连接物体间的作用力。 ## 胡克弹力 连接物体间受到的力是**胡克弹力**,用作模拟某种弹性杆连接。胡克弹力的公式为: ![](https://github.com/phenomLi/Blog/raw/master/photos/2019-6-9/微信截图_20190609181209.png) 其中**k**为弹簧系数,**x0**为弹簧原长,**x**为弹簧发生形变后的长度。 在对相互连接的物体添加弹力之前,我们首先要对`ItemNode`进行一些改造,使其支持物体连接: ```typescript // 最小区块节点(即叶节点,物体节点) export class ItemNode { //......省略 //连接的物体列表 public link: ItemNode[]; constructor(item) { //......省略 this.link = []; } /** * 计算弹力 *...

Typescript
可视化

最近由于项目需要,研究了下力导向图的实现,发现了不少有意思的东西。如力导向图的弹性布局效果的实现是将节点看成带电粒子,粒子因为受到其他粒子的引力和斥力发生运动,最终受力平衡而稳定下来。 要计算连接粒子间的受力不算难,根据距离套用公式即可。但是单个粒子不只是受到与其连接的粒子的力,而是受到来自除该粒子外所有粒子的力,即任何两个粒子之间都将受到力的影响。这种力就叫**N体力(Many-Body)**。 ## 基本思想 假如一个力导向图有100个节点,要计算每个粒子的N体力,就要进行100*100 = 10000次计算,这是极其耗时的。所以要快速地计算N体力,需要减少问题的规模,提升N体问题模拟算法的速度,一种非常重要的思想就是**把相互接近的一组物体近似看成单独的一个物体**。对于一组距离足够远的物体,我们可以将它的力作用近似看成是来自其质心的作用力。一组物体的质心是这种物体经过质量加权后的平均位置。例如,如果两个物体的位置分别是(x1,y1)和(x2,y2),质量分别为m1和m2,那么它们的总质量和总质心(x,y)分别为: ![](https://github.com/phenomLi/Blog/raw/master/photos/2019-6-1/微信截图_20190601161718.png) 将平面进行等分划分是一种将物体分组的好方法,在同一区块的物体被视作一组。而通常情况下,四等分平面便可以解决大部分问题,为了更细致地分组,我们可以进行递归四等分,如下图所示: ![](https://github.com/phenomLi/Blog/raw/master/photos/2019-6-1/微信截图_20190601162406.png) 具体的划分规则是:每个物体都被划分到各自的 **区块(block)** 里,同样地,每个最小区块(即没有子区块的区块)最多只能有一个物体,若一个最小区块有了一个以上的物体,则这个区块要被再次四等分。 我们需要对这种理论操作找对对应的数据结构,显然这种结构就是**四叉树(quad-tree)**。有了上面的划分规则,我们很容易想到如何构建该四叉树:首先,若一个区块是最小区块,那么该区块里面的物体单独为一个四叉树叶子节点,否则,该区块就是一个非叶子节点。这种子区块的划分最终将建造一棵非完全的树。上图平面的划分对应的四叉树如下图所示: ![](https://github.com/phenomLi/Blog/raw/master/photos/2019-6-1/微信截图_20190601162429.png) 如果要计算某个单独的物体所受到的合力,那么就从根开始遍历树中的节点。如果一个非叶子节点的质心离某个物体足够远,那么就将树中那个部分所包含的物体近似看成一个整体,其位置就是整组物体的质心,其质量就是整组物体的总质量。这个算法相当高效,因为我们无需逐一地单独检验某一组中的个别物体。关于利用四叉树计算N体力的具体方法这里先不细说。 解决N体力问题只是四叉树的一种应用,少量动态物体与大量静态物体的碰撞检测也可以用四叉树分块做粗检查阶段。 ## 四叉树的构建 为了构建一棵四叉树,我们将一个一个地向树中插入节点。具体来说,当向一个由(根)节点**Root** 所表示的树中插入一个节点**A**时,我们就递归地执行如下步骤(注意这里我们给“根”字加了一个括号的意思是Root不一定是整棵树的根,它也可能是其中某个子树的根): 1. **如果Root为空节点(即没有任何物体),则把新的物体A作为Root。** 2. **如果Root是一个非叶子节点(即非最小区块),就更新Root的总质量和质心。递归地将物体A插入到Root的子区块中的某个,即A成为Root的四个孩子节点中的某个。** 3. **如果Root是一个叶子节点(即最小区块),它包含有一个物体B,那么也就是说在一个最小区块里包含有两个物体Root和B。那么便需要将该区块划分为四个子区块:新建一个非叶子节点C,把Root保存为A,将C作为Root,然后递归地插入物体A和B到Root中。最终更新Root的质心和总质量。** 下面这个例子演示了构建一棵包含5个节点的四叉树的过程,我们按A,B,C,D,E的顺序插入节点。 ![](https://github.com/phenomLi/Blog/raw/master/photos/2019-6-1/20161104233245523.png) 其中,根节点中储存了全部5个物体的质心和总质量。另外一个非叶子节点则包含有B、C和D三个物体所构成的整体的总质量和质心。...

Typescript
可视化

有时候我们想要在一个`ActivityA`中,去调用另一个`ActivityB`的方法,那么我们可以在`ActivityB`中,将这个方法设置为静态方法: **ActivityB:** ```java //ActivityB public class ActivityB { public static void method() { Log.i("", "我是ActivityB的方法。"); } } ``` **在ActivityA中调用:** ```java //ActivityA ActivityB.method(); ``` **但是,假如`ActivityB`中的`method`方法不是一个静态方法,那么`ActivityA`想要调用它,又该怎么办呢?** 有人也许会说,把`method`声明为`static`或者把方法抽象成公共类不就行了吗?说是这样说,但是有些条件下,并不允许这样做: - 受限于业务,一个方法里面的逻辑可能会非常复杂,引用了许多的临时变量或者非静态变量,为了这样一个要求而把里面的所有变量都设置为静态变量,这显然是一种得不偿失的做法,首先注册为静态变量,意味着不能被GC(Garbage Clean)。第二,可能不止这一个方法引用了这些变量,一个变量改了,可能其他方法也要改,会造成牵一发而动全身的情况。 - 有时候方法里面的变量不是临时变量,而是该`Activity`类的属性(或者依赖于该`Activity`类),那么这个方法就无法被抽象成公共类。...

Android

# 一个场景 假设现在有这么一个场景,在一个``里面有10个`` ```html ``` 然后要求每一个``点击之后都会填入该``的`index`。 看起来是十分简单的需求,很多经验不足的人里面就洋洋洒洒写下一段(比如以前的我): ```javascript const li = document.querySelectorAll('ul>li'); for(var i = 0; i < li.length; i++) { li[i].addEventListener('click', function(e) { this.innerHTML = i; }); } ```...

Javascript

# 先简单说说Promise是什么 在传统的js异步处理中,嵌套回调函数是最常规的做法,比如延迟一秒后执行`fn`: ```javascript setTimeout(() => { fn(); }, 1000); ``` 在浏览器环境的js中,可能异步处理场景较少,嵌套回调的弊端还不会十分明显,但是对于node服务器这种I/O密集的场景下,当回调嵌套得足够多时,代码就很恶心了。比如按顺序异步读取4个文件后再调用`fn`处理文件数据: ```javascript fs.readFile(path, (err, data) => { fs.readFile(path, (err, data) => { fs.readFile(path, (err, data) => { fs.readFile(path, (err,...

Javascript

相信玩过vue的都知道,vue的数据和视图都是双向绑定的,也就是说当数据(data)发生更改时,vue会自动将更改diff到视图层上。那么vue是怎么自动检测到他的数据变动的呢?在这个问题上,angluar用的是脏检查(dirty check),也就是轮询检测,性能较低,而knockout用的是`ko.observable`函数(兼容IE6还要什么自行车),而vue则用的是`Object.defineProperty`。 其实在很久之前就听说过`Object.defineProperty`这个属性,但是只知道是个es5新属性(这也就是为什么vue不兼容IE9的原因之一),具体能干什么没有深究。直到上个学期末,考完试后有两个星期的空余时间,于是打算造个mvvm轮子(也就是后来的Zeta),当时深挖vue双向绑定原理的时候也好好研究了一番这个`Object.defineProperty`。 # Object.defineProperty是什么 正如他的名字一样,`Object.defineProperty`是为对象设置一些默认的属性,如`writeable`(可写)和`getter`(访问器)等,也就是说,`Object.defineProperty`是用作扩展原生对象的一种方法。 ### 使用方法 ```javascript Object.defineProperty(obj, prop, descriptor); ``` - `obj`需要定义属性的对象。 - `prop`需被定义或修改的属性名。 - `descriptor`需被定义或修改的属性的描述符。 问题来了,`描述符`是个什么东西,有什么用?我们先看看官方定义: > configurable: 仅当该属性的 configurable 为 true 时,该属性才能够被改变,也能够被删除。默认为 false enumerable: 仅当该属性的...

Javascript

在进行响应式网页开发时,利用`@media screen`设置屏幕断点是十分常用的做法: ```css @media screen and (max-width: 768px) { /* your style */ } ``` `@media screen`可以判断当前设备的宽度然后使用定义的样式。 但是`@media screen`只能控制样式,不能控制逻辑。 假如现在有这么一个需求:在屏幕宽度小于768px时,出现弹框。 最常规的做法是用js检测屏幕: ```javascript window.onresize = function() { if(window.document.documentElement.clientWidth -1) { //小屏幕...

CSS
Javascript