echodis / mona-s-Notes

notes and summaries for books since 20.
0 stars 0 forks source link

San源码 #2

Open echodis opened 6 years ago

echodis commented 6 years ago

San源码真是不错,准备详细看看。一方面想了解 San 的底层原理,另一方面想借此温故 js 的基础知识:)

git clone Santree一下就能看见大概的目录结构,不贴了。我首先要看的是src目录。

src/main.js

看webpack配置可以看到,main.js是入口文件。很简单,只有一个自执行函数,this是唯一参数。函数中引入了相关依赖,并将一系列内容绑定到san Object中。根据不同引用场景,定义了不同规范下san的引用方式,如CommonJs, AMDsrc引用,并将san绑到this中。最后开启了dev模式,这个先不管。

按引用顺序/文件引用的深度遍历规则,在当前文件内不对依赖文件深究,对应依赖后续讨论。

那么,下一个是next-tick.js

echodis commented 6 years ago

src/util/next-tick.js

在下一个时间周期运行任务。

引入了bindeach。定义了下一个周期要执行任务列表 nextTasks和执行下一个周期任务的函数nextHandlerisNativePromise判断是否支持原生Promise。主体是nextTick函数,即在下一个时间周期运行任务。

nextTick入参有fn(要运行的任务函数)和thisArg(this指向的对象)。首先重新bind了一下this,然后把新的fn存到了nextTasks中。如果没有nextHandler,就对nextHandler进行初始化。nextHandler函数内容是:首先深拷贝一份nextTaskstasks,并将nextTasksnextHandler分别清空。之后遍历tasks,执行其每一个item。

最后是执行nextHandler函数。在有setImmediate的情况下,直接setImmediate执行。否则,支持MessageChannel的话,用MessageChannel去做的setImmediate的polyfill。再否则,若支持原生Promise,用Promise.resolve().then(nextHandler)。最后,用setTimeout(nextHandler, 0)

不太熟悉下面二者,mdn的解释很详细。

setImmediate

MessageChannel

echodis commented 6 years ago

src/util/bind.js

bind.js是这么用的:fn = bind(fn, thisArg);,可以猜测用法就是改变函数内部的上下文this。代码很简单,在支持es5 bind的浏览器中,直接将thisArg绑定到函数fn中。在不支持bind的浏览器中,用applyfn绑定到thisArg中。二者传参方式不同:bind直接bind(arrayArguments.slice(1))apply则是func.apply(thisArg, slice.call(arguments, 2));(和源码不同,因为源码在参数绑定上 貌似有冗余)。 经过以上操作,bind返回一个绑定了this的函数,后续直接调用即可。

在codepen上写了个例子

echodis commented 6 years ago

src/util/each.js

each.js这么用:

each(tasks, function (task) {
     task();
});

bind.js一样,也是处理兼容性的函数。each接受3个参数:array数组,iterator遍历函数和thisArg上下文this。当传入this时,直接将this绑定到遍历函数。否则,遍历array数组,在每一次循环中都执行遍历函数,并传入对应循环item和index。若有一项函数执行结果为false时,break掉本次循环。这里的逻辑需要结合后续task()看。

codepen上和this上下文相关的例子

src/util/contains.js

判断数组是否包含某项。主要用each实现,result记录遍历项是否和传入项相等,返回!result。当resulttrue值时,each返回false,此时遍历就终止了。

echodis commented 6 years ago

src/util/indexed-list.js

用法就是索引了一个列表。有一个IndexedListclass:

function IndexedList() {
    this.raw = [];
    this.index = {};
}

它有row:arrayindex:object两个属性。

在IndexedList的prototype上定义了一些增删改查连接的方法,以便实例化之后访问。

push方法可以看到:row保存了原数组,index是以某个item的name为key,item为value的对象。

each需要注意的是传入了this。

remove是同时移除了rowindex中的内容。

echodis commented 6 years ago

src/util/empty.js

一个空函数

src/util/extend.js

对象属性拷贝。传入两个参数:目标对象target和源对象sourcefor遍历source,将其自身的属性(非原型链上的)拷贝到目标对象。

hasOwnProperty()判断是否自身的属性。

echodis commented 6 years ago

util基本看完了,由于内容太多且结构类似,粗略看了.src/parse/,主要是对template内容的解析。正则表达式是关键,从中也能看到san支持的属性和表达式。内容太多且繁琐,所以不一一细看了。

src/view中内容较多,且处理工作和parse相关/相反,所以重点关注了compent.js。其中定义了生命周期和方法,以及视图更新的方式。这也是平时使用过程中疑惑较多的地方。

`Component.prototype._update`即为视图更新函数,接受数据变化信息为参数。变化有isDataChangeByElementchangeExprCompare两个判断维度。第一个是判断数据变更是否来源于元素,来源于元素时,视图更新需要阻断(返回true)。第二则判断变更表达式和目标表达式之间的关系,根据关系判断视图是否需要更新。目前只区分了四种关系:无关系、父子、相同、子父。只有第一种:无关系不会改变视图,后三种均会重新更新视图。当判断视图需要更新后,进行slot解析并repaint。否则对其slotChildren进行迭代判断。当props有变化时,用get-prop-handler.js处理属性变化。最后如果有owner,对owner数据变更进行判断。

这种视图更新方法,能解决之前遇到的当object数据某字段改变,object的不变的情况下,页面不会同步变化的问题。不过在业务中还未升级至新版本,还没有实践。