Open echodis opened 6 years ago
src/util/next-tick.js
在下一个时间周期运行任务。
引入了bind
和each
。定义了下一个周期要执行任务列表 nextTasks
和执行下一个周期任务的函数nextHandler
。isNativePromise
判断是否支持原生Promise
。主体是nextTick
函数,即在下一个时间周期运行任务。
nextTick
入参有fn
(要运行的任务函数)和thisArg
(this指向的对象)。首先重新bind
了一下this
,然后把新的fn
存到了nextTasks
中。如果没有nextHandler
,就对nextHandler
进行初始化。nextHandler
函数内容是:首先深拷贝一份nextTasks
到tasks
,并将nextTasks
和nextHandler
分别清空。之后遍历tasks
,执行其每一个item。
最后是执行nextHandler
函数。在有setImmediate
的情况下,直接setImmediate
执行。否则,支持MessageChannel的话,用MessageChannel
去做的setImmediate
的polyfill。再否则,若支持原生Promise,用Promise.resolve().then(nextHandler)
。最后,用setTimeout(nextHandler, 0)
。
不太熟悉下面二者,mdn的解释很详细。
src/util/bind.js
bind.js
是这么用的:fn = bind(fn, thisArg);
,可以猜测用法就是改变函数内部的上下文this
。代码很简单,在支持es5 bind
的浏览器中,直接将thisArg
绑定到函数fn
中。在不支持bind
的浏览器中,用apply
将fn
绑定到thisArg
中。二者传参方式不同:bind
直接bind(arrayArguments.slice(1))
,apply
则是func.apply(thisArg, slice.call(arguments, 2));
(和源码不同,因为源码在参数绑定上 貌似有冗余)。
经过以上操作,bind
返回一个绑定了this
的函数,后续直接调用即可。
在codepen上写了个例子
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
。当result
为true
值时,each返回false
,此时遍历就终止了。
src/util/indexed-list.js
用法就是索引了一个列表。有一个IndexedList
class:
function IndexedList() {
this.raw = [];
this.index = {};
}
它有row:array
和index:object
两个属性。
在IndexedList的prototype上定义了一些增删改查连接的方法,以便实例化之后访问。
从push
方法可以看到:row保存了原数组,index是以某个item的name为key,item为value的对象。
each
需要注意的是传入了this。
remove
是同时移除了row
和index
中的内容。
src/util/empty.js
一个空函数
src/util/extend.js
对象属性拷贝。传入两个参数:目标对象target
和源对象source
。for
遍历source
,将其自身的属性(非原型链上的)拷贝到目标对象。
用hasOwnProperty()
判断是否自身的属性。
util基本看完了,由于内容太多且结构类似,粗略看了.src/parse/
,主要是对template内容的解析。正则表达式是关键,从中也能看到san支持的属性和表达式。内容太多且繁琐,所以不一一细看了。
src/view
中内容较多,且处理工作和parse
相关/相反,所以重点关注了compent.js
。其中定义了生命周期和方法,以及视图更新的方式。这也是平时使用过程中疑惑较多的地方。
`Component.prototype._update`即为视图更新函数,接受数据变化信息为参数。变化有isDataChangeByElement
和changeExprCompare
两个判断维度。第一个是判断数据变更是否来源于元素,来源于元素时,视图更新需要阻断(返回true)。第二则判断变更表达式和目标表达式之间的关系,根据关系判断视图是否需要更新。目前只区分了四种关系:无关系、父子、相同、子父。只有第一种:无关系不会改变视图,后三种均会重新更新视图。当判断视图需要更新后,进行slot解析并repaint。否则对其slotChildren进行迭代判断。当props有变化时,用get-prop-handler.js
处理属性变化。最后如果有owner,对owner数据变更进行判断。
这种视图更新方法,能解决之前遇到的当object数据某字段改变,object的不变的情况下,页面不会同步变化的问题。不过在业务中还未升级至新版本,还没有实践。
San源码真是不错,准备详细看看。一方面想了解 San 的底层原理,另一方面想借此温故 js 的基础知识:)
git clone
San,tree
一下就能看见大概的目录结构,不贴了。我首先要看的是src
目录。看webpack配置可以看到,
main.js
是入口文件。很简单,只有一个自执行函数,this
是唯一参数。函数中引入了相关依赖,并将一系列内容绑定到san Object
中。根据不同引用场景,定义了不同规范下san
的引用方式,如CommonJs
,AMD
和src引用
,并将san
绑到this
中。最后开启了dev
模式,这个先不管。按引用顺序/文件引用的深度遍历规则,在当前文件内不对依赖文件深究,对应依赖后续讨论。
那么,下一个是
next-tick.js