vue源码createPatchFunction
export function createPatchFunction (backend) {
// ... 一些辅助方法
return function patch (oldVnode, vnode, hydrating, removeOnly) {
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
}
let isInitialPatch = false
const insertedVnodeQueue = []
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
} else {
const isRealElement = isDef(oldVnode.nodeType)
if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
} else {
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
if (isTrue(hydrating)) {
if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
invokeInsertHook(vnode, insertedVnodeQueue, true)
return oldVnode
} else if (process.env.NODE_ENV !== 'production') {
warn(
'The client-side rendered virtual DOM tree is not matching ' +
'server-rendered content. This is likely caused by incorrect ' +
'HTML markup, for example nesting block-level elements inside ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
)
}
}
// either not server-rendered, or hydration failed.
// create an empty node and replace it
oldVnode = emptyNodeAt(oldVnode)
}
// replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm)
// create new node
createElm(
vnode,
insertedVnodeQueue,
// extremely rare edge case: do not insert if old element is in a
// leaving transition. Only happens when combining transition +
// keep-alive + HOCs. (#4590)
oldElm._leaveCb ? null : parentElm,
nodeOps.nextSibling(oldElm)
)
// update parent placeholder node element, recursively
if (isDef(vnode.parent)) {
let ancestor = vnode.parent
const patchable = isPatchable(vnode)
while (ancestor) {
for (let i = 0; i < cbs.destroy.length; ++i) {
cbs.destroy[i](ancestor)
}
ancestor.elm = vnode.elm
if (patchable) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, ancestor)
}
// #6513
// invoke insert hooks that may have been merged by create hooks.
// e.g. for directives that uses the "inserted" hook.
const insert = ancestor.data.hook.insert
if (insert.merged) {
// start at index 1 to avoid re-invoking component mounted hook
for (let i = 1; i < insert.fns.length; i++) {
insert.fns[i]()
}
}
} else {
registerRef(ancestor)
}
ancestor = ancestor.parent
}
}
// destroy old node
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
return vnode.elm
}
}
上面代码中,在函数createPatchFunction
中返回了patch
方法,这种编程方式属于函数柯里化,也属于高阶函数的一种用法
什么是函数柯里化
柯里化(curring)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且接受余下的参数且返回结果的新函数的技术。
上面的解释来自于百度百科,这样对于理解可能有些抽象,结合下面的代码会更好理解
// 普通的add函数
function add (x,y){
return x + y
}
// curring之后
function curringAdd (x) {
return function (y) {
return x + y
}
}
add(1,2) // 3
curringAdd(1)(2) // 3
实际上就是把add
函数的x
和y
两个参数变成了先用一个函数接收x
然后返回一个函数去处理y
参数。现在思路应该就比较清晰了,就是值传递给函数一部分参数来调用它,让它返回一个函数去处理剩下的参数。
柯里化有什么好处
- 参数复用
// 正常正则验证字符串reg.test(txt)
// 函数封装后
function check(reg, txt) {
return reg.test(txt)
}
check(/\d+/g, 'test')// false
check(/[a-z]+/g, 'test') // true
// curring后
function curringCheck(reg) {
return function(txt) {
return reg.test(txt)
}
}
var hasNumber = curringCheck(/\d+/g)
var hasLetter = curringCheck(/[a-z]+/g)
hasNumber('test1') // true
hasNumber('testttt') // false
hasLetter('234')// false
上面的示例是一个正则的校验,正常来说直接调用check函数就可以了,但是如果我有很多地方都要校验是否有数字,起始就是需要将第一个参数reg进行复用,这样别的笛梵干就能够直接调用hasNumber,hasLetter等函数,让参数能够复用,调用起来也更方便。
2. 提前确认
var on = function(element, event, handler) {
if (document.addEventListener) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
} else {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
}
}
var on = (function() {
if (document.addEventListener) {
return function(element, event, handler) {
if (element && event && handler) {
element.addEventListener(event, handler, false);
}
};
} else {
return function(element, event, handler) {
if (element && event && handler) {
element.attachEvent('on' + event, handler);
}
};
}
})();
//换一种写法可能比较好理解一点,上面就是把isSupport这个参数给先确定下来了
var on = function(isSupport, element, event, handler) {
isSupport = isSupport || document.addEventListener;
if (isSupport) {
return element.addEventListener(event, handler, false);
} else {
return element.attachEvent('on' + event, handler);
}
}
我们在做项目的过程中,封装一些dom操作可以说再常见不过,上面第一种写法也是比较常见,但是我们看看第二种写法,它相对一第一种写法就是自执行然后返回一个新的函数,这样其实就是提前确定了会走哪一个方法,避免每次都进行判断。
3. 延迟运行
Function.protorype.bind = function(context) {
const _this = this
const args = Array.prototype.slice.call(arguments,1)
return function(){
return _this.apply(context, args)
}
}
像我们js中经常使用的bind,实现的机制就死Curring
柯里化性能
- 存取arguments对象通常要比存取命名参数要慢一点
- 一些老版本的浏览器在arguments.length的实现上是相当慢的
- 使用
fn.apply(...)
和fn.call(...)
通常比直接调用fn(...)
稍微慢点 - 创建大量嵌套作用域和闭包函数会带来花销,无论是在内存上还是速度上
起始大部分应用中,主要的性能瓶颈是在操作DOM节点上,这js的性能损耗基本是可以忽略不计的,所以currying是可以直接放心使用的
经典面试题
实现一个add方法,使计算结果能够满足如下预期
add(1)(2)(3) = 6
add(1,2,3)(4) = 10
add(1)(2)(3)(4)(5) = 15
function add(){
const _args = Array.prototype.slice.call(arguments)
const _adder = function () {
_args.push(...arguments)
return _adder
}
_adder.toSting = function(){
return _args.reduce(function(a,b){
return a + b
})
}
return _adder
}