先学习一波基础知识
柯里化概念:在计算机科学中,柯里化(英語:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
通俗的解释:柯里化就是一个函数原本有多个参数,只传入一个参数,生成一个新函数,由新函数接收剩下的参数来运行得到结果,这里重点是一个参数。
柯里化有什么作用?
参数复用,或者说是固定参数,避免重复传参;
提前返回,或者说是提前确认,避免重复判断;
延迟执行。
而这三点怎么通俗的解释呢,只能通过例子。
1.假如我们有个需求,如何判断一个标签是否为原生的html标签,第一反应就是暴力循环一波,可以写出以下代码
var str = 'html,body,base,head,link,meta,style,title,' +
'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +
'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' +
'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +
's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +
'embed,object,param,source,canvas,script,noscript,del,ins,' +
'caption,col,colgroup,table,thead,tbody,td,th,tr,' +
'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +
'output,progress,select,textarea,' +
'details,dialog,menu,menuitem,summary,' +
'content,element,shadow,template,blockquote,iframe,tfoot';
function isHTMLTag ( tag, str ) {
var list = str.split(',');
var flag =false;
for (var i = 0; i < list.length; i++) {
if(list[i] == tag){
flag = true;
break;
}
}
return flag;
}
var isHTMLTag = isHTMLTag('div',str);
这样写本来没有问题,但是假如我们有100个标签需要判断,那这个函数就要调100次,本来函数里面都是for循环,我们循环就是(100*原生标签的个数)次
接下来我们看vue中是如果巧妙的实现这个功能的
/**
* Make a map and return a function for checking if a key
* is in that map.
*/
function makeMap ( str, expectsLowerCase ) {
var map = Object.create(null);
var list = str.split(',');
for (var i = 0; i < list.length; i++) {
map[list[i]] = true;
}
return expectsLowerCase
? function (val) { return map[val.toLowerCase()]; }
: function (val) { return map[val]; }
}
var isHTMLTag = makeMap(
'html,body,base,head,link,meta,style,title,' +
'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' +
'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' +
'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' +
's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' +
'embed,object,param,source,canvas,script,noscript,del,ins,' +
'caption,col,colgroup,table,thead,tbody,td,th,tr,' +
'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' +
'output,progress,select,textarea,' +
'details,dialog,menu,menuitem,summary,' +
'content,element,shadow,template,blockquote,iframe,tfoot'
);
var isHTMLTag = isHTMLTag('div');
这样做的好处是这下我们假如要判断100个标签,只需要执行makemap时循环执行一次,后面再执行isHTMLTag就不会去再走循环,这里map中存了我们需要的结果,我们只需要根据map的键去检索,多余的开销就是创建了map这一个对象,典型的空间换时间理念。 这里就是提前确认,避免重复判断这个作用
2.vue在生命周期的声明为如下代码
const createHook = function (lifecycle) {
return function (hook, target = currentInstance) {
injectHook(lifecycle, hook, target)
}
}
export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT)
export const onMounted = createHook(LifecycleHooks.MOUNTED)
export const onBeforeUpdate = createHook(LifecycleHooks.BEFORE_UPDATE)
export const onUpdated = createHook(LifecycleHooks.UPDATED)
export const onBeforeUnmount = createHook(LifecycleHooks.BEFORE_UNMOUNT)
export const onUnmounted = createHook(LifecycleHooks.UNMOUNTED)
export const onServerPrefetch = createHook(LifecycleHooks.SERVER_PREFETCH)
这里的createhook函数无非就是把injecthook函数返回出来,为什么要多此一举呢?那我们可以像下面这么写吗?
export const onBeforeMount = function (hook, target = currentInstance) {
injectHook(LifecycleHooks.BEFORE_MOUNT, hook, target)
}
export const onMounted = function (hook, target = currentInstance) {
injectHook(LifecycleHooks.MOUNTED, hook, target)
}
当然可以,但是这时我们这时发现我们这样定义的函数只是injectHook的第一个参数不同,我们这么写每次都要要多写 hook, target这两个重复参数,就显得很冗余,而createHook函数就巧妙的解决了我们的问题。这里就是参数复用,或者说是固定参数,避免重复传参的作用。
3.如果写过vue3的话都应该写过以下代码
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
这结构一看,我立马盲猜createApp函数的源码
const createApp = function(root){
return function mount(container){
////balabla
}
}
然后跑去扒源码
const createApp = ((...args) => {
// 创建 app 对象
const app = ensureRenderer().createApp(...args)
const { mount } = app
// 重写 mount 方法,为了跨平台
app.mount = (containerOrSelector) => {
// ...
}
return app
})
这,这不太像我想的啊,别急继续往下看,那我继续把想的我的代码改改
const createApp = function(root){
const app={
mount(){//}
}
app.mount=(containerOrSelector) => {
// ...
}
return app
}
只不过源码这里的app不是这么一个简单只有一个方法的对象
// 渲染相关的一些配置,比如更新属性的方法,操作 DOM 的方法
const rendererOptions = {
patchProp,
...nodeOps
}
let renderer
// lazy create the renderer - this makes core renderer logic tree-shakable
// in case the user only imports reactivity utilities from Vue.
function ensureRenderer() {
return renderer || (renderer = createRenderer(rendererOptions))
}
function createRenderer(options) {
return baseCreateRenderer(options)
}
function baseCreateRenderer(options) {
function render(vnode, container) {
// 组件渲染的核心逻辑
}
return {
render,
createApp: createAppAPI(render)
}
}
function createAppAPI(render) {
// createApp createApp 方法接受的两个参数:根组件的对象和 prop
return function createApp(rootComponent, rootProps = null) {
const app = {
_component: rootComponent,
_props: rootProps,
//基础的mount方法
mount(rootContainer) {
// 创建根组件的 vnode
const vnode = createVNode(rootComponent, rootProps)
// 利用渲染器渲染 vnode
render(vnode, rootContainer)
app._container = rootContainer
return vnode.component.proxy
}
}
return app
}
}
这一通操作下来其实就是我们开始想的 ensureRenderer().createApp(...args)返回一个对象,对象中有mount方法,就是最后这段代码
const app = {
_component: rootComponent,
_props: rootProps,
mount(rootContainer) {
// 创建根组件的 vnode
const vnode = createVNode(rootComponent, rootProps)
// 利用渲染器渲染 vnode
render(vnode, rootContainer)
app._container = rootContainer
return vnode.component.proxy
}
}
这段代码可谓是把柯里化用到了极致,函数套函数,return一层又一层,目的无非是为了tree-shaking,有的为了跨平台,有的是为了参数复用。做到不同的判断逐渐传入参数,而不是一次性把参数传完,又不断地if else.