GithubHelp home page GithubHelp logo

code-comments's People

Contributors

unproductive-wanyicheng avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar

code-comments's Issues

Vue(v3.0.11)源码简析之模板编译ast的相关实现

看下vue如何把一段我们写的html模板代码解析成一颗包含原始字符信息的AST树:)

// AST Utilities ---------------------------------------------------------------
// Some expressions, e.g. sequence and conditional expressions, are never
// associated with template nodes, so their source locations are just a stub.
// Container types like CompoundExpression also don't need a real location.
// 位置信息对象接口
const locStub = {
    // 起始结束位置段的内容
    source: '',
    start: { line: 1, column: 1, offset: 0 },
    end: { line: 1, column: 1, offset: 0 }
};
// 逻辑根节点 汇总ast树的信息的作用
function createRoot(children, loc = locStub) {
    return {
        // 一颗ast的根节点总是类型0
        type: 0 /* ROOT */,
        // 实际产生的vnode都是在子节点数组中其实
        children,
        // 整颗ast树所有节点用到的helper对应的名字字符串存储这里
        helpers: [],
        // 整颗ast树所有节点用到的用户自定义组件的名字都存储在这里
        components: [],
        // 整颗ast树所有节点用到的用户自定义指令的名字都存储在这里
        directives: [],
        // 树中静态内容 包含 不会改变的vnode以及prop
        hoists: [],
        // TODO
        imports: [],
        // 被缓存起来的vnode数量
        cached: 0,
        // TODO
        temps: 0,
        // root节点的 codegenNode transform阶段生成 codegenNode也是一种node 不过包含了用于genenrate第三个阶段所需要的信息 不是原始的简单ast节点了
        codegenNode: undefined,
        // 位置信息
        loc
    };
}
// 下面的节点类型都是生成render过程中用于包裹目标节点的一些属性的包装形式 保留着原始信息以一定方式组织起来 等到生成代码的阶段再逐一解析提取出来 按照指定逻辑生成代码
// 需要创建一个vnode的节点类型的代码片段信息 注意这些参数 都是从模板html中解析构建出来的属性 最终生成的时候会调用createVnode来得到实际的vnode节点对象
function createVNodeCall(context, tag, props, children, patchFlag, dynamicProps, directives, isBlock = false, disableTracking = false, loc = locStub) {
    if (context) {
        // 需要另起block的vnode
        if (isBlock) {
            context.helper(OPEN_BLOCK);
            context.helper(CREATE_BLOCK);
        }
        else {
            context.helper(CREATE_VNODE);
        }
        // 带有运行时指令
        if (directives) {
            context.helper(WITH_DIRECTIVES);
        }
    }
    return {
        type: 13 /* VNODE_CALL */,
        tag,
        props,
        children,
        patchFlag,
        dynamicProps,
        directives,
        isBlock,
        disableTracking,
        loc
    };
}
// 数组存储一些 表达式 等到使用的时候直接遍历数组进行相应的处理即可
function createArrayExpression(elements, loc = locStub) {
    return {
        type: 17 /* JS_ARRAY_EXPRESSION */,
        loc,
        elements
    };
}
// 直接就是js 对象表达式 properties 可以是任意js对象类型
function createObjectExpression(properties, loc = locStub) {
    return {
        type: 15 /* JS_OBJECT_EXPRESSION */,
        loc,
        properties
    };
}
// 创建一个js 属性对象 主要是存储它的key和value 方便提取
function createObjectProperty(key, value) {
    return {
        type: 16 /* JS_PROPERTY */,
        loc: locStub,
        // key始终是对象
        key: isString(key) ? createSimpleExpression(key, true) : key,
        value
    };
}
// 值是一个简单的表达式 在render函数中可以直接使用它的content 包裹体对象 作为代码使用
function createSimpleExpression(content, isStatic, loc = locStub, constType = 0 /* NOT_CONSTANT */) {
    return {
        type: 4 /* SIMPLE_EXPRESSION */,
        loc,
        content,
        // 是否是静态不会改变的内容
        isStatic,
        constType: isStatic ? 3 /* CAN_STRINGIFY */ : constType
    };
}
// 组合表达式 由children数组内的字符串形式的内容拼接相加而成 往往是一段代码分成几部分存储 最终合并起来成一段代码字符串 作为代码使用
function createCompoundExpression(children, loc = locStub) {
    return {
        type: 8 /* COMPOUND_EXPRESSION */,
        loc,
        children
    };
}
// JS 函数表达式 函数名(callee) + 参数(args) 提取的时候执行 callee 函数一次得到结果给调用者作为代码使用
function createCallExpression(callee, args = [], loc = locStub) {
    return {
        type: 14 /* JS_CALL_EXPRESSION */,
        loc,
        callee,
        arguments: args
    };
}
// 生成函数的表达式对象包裹体 一段需要写进render函数字符串的函数代码字符串 例如 '() => ...' 这样的 在slots中 事件中等等都需要用到这种 所以现在提前存储所需的关键信息 如 参数 返回值等
function createFunctionExpression(params, returns = undefined, newline = false, isSlot = false, loc = locStub) {
    return {
        type: 18 /* JS_FUNCTION_EXPRESSION */,
        params,
        returns,
        newline,
        isSlot,
        loc
    };
}
// 三元条件表达式对象包裹体 生成三元表达式代码 需要3个关键信息 
function createConditionalExpression(test, consequent, alternate, newline = true) {
    return {
        type: 19 /* JS_CONDITIONAL_EXPRESSION */,
        // 测试条件
        test,
        // 正常结果
        consequent,
        // 回退结果
        alternate,
        newline,
        loc: locStub
    };
}
// 缓存表达式 包裹体
function createCacheExpression(index, value, isVNode = false) {
    return {
        type: 20 /* JS_CACHE_EXPRESSION */,
        index,
        value,
        isVNode,
        loc: locStub
    };
}

// 表达式值是 静态已确定的
const isStaticExp = (p) => p.type === 4 /* SIMPLE_EXPRESSION */ && p.isStatic;
// 名称相等 或者 小写连字符相等
const isBuiltInType = (tag, expected) => tag === expected || tag === hyphenate(expected);
// 内置的几种组件
function isCoreComponent(tag) {
    if (isBuiltInType(tag, 'Teleport')) {
        return TELEPORT;
    }
    else if (isBuiltInType(tag, 'Suspense')) {
        return SUSPENSE;
    }
    else if (isBuiltInType(tag, 'KeepAlive')) {
        return KEEP_ALIVE;
    }
    else if (isBuiltInType(tag, 'BaseTransition')) {
        return BASE_TRANSITION;
    }
}
// 数字+非$以及非单词字符的组合
const nonIdentifierRE = /^\d|[^\$\w]/;
// 不属于以上情况才可以
const isSimpleIdentifier = (name) => !nonIdentifierRE.test(name);
// 这个正则..en 比较复杂
// 主要是 ac || ab . cd  || ab[cd] 这样的从this的成员取值的情况 path参数也透露出它是对参数路径做判断
const memberExpRE = /^[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*(?:\s*\.\s*[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*|\[[^\]]+\])*$/;
const isMemberExpression = (path) => {
    if (!path)
        return false;
    return memberExpRE.test(path.trim());
};
// 获取 prop片段 name = ... 这样的 name片段 的位置信息
function getInnerRange(loc, offset, length) {
    const source = loc.source.substr(offset, length);
    const newLoc = {
        source,
        start: advancePositionWithClone(loc.start, loc.source, offset),
        end: loc.end
    };
    if (length != null) {
        newLoc.end = advancePositionWithClone(loc.start, loc.source, offset + length);
    }
    return newLoc;
}
// 克隆一个位置新对象 避免影响原对象
function advancePositionWithClone(pos, source, numberOfCharacters = source.length) {
    return advancePositionWithMutation(extend({}, pos), source, numberOfCharacters);
}
// advance by mutation without cloning (for performance reasons), since this
// gets called a lot in the parser
// 更新位置信息中的行列信息
function advancePositionWithMutation(pos, source, numberOfCharacters = source.length) {
    let linesCount = 0;
    let lastNewLinePos = -1;
    for (let i = 0; i < numberOfCharacters; i++) {
        // 新一行
        if (source.charCodeAt(i) === 10 /* newline char code */) {
            linesCount++;
            lastNewLinePos = i;
        }
    }
    // 更新信息
    pos.offset += numberOfCharacters;
    pos.line += linesCount;
    // 在最新一行中所处的列数
    pos.column =
        lastNewLinePos === -1
            ? pos.column + numberOfCharacters
            : numberOfCharacters - lastNewLinePos;
    return pos;
}
// 断言 condition 不为真 则触发
function assert(condition, msg) {
    /* istanbul ignore if */
    if (!condition) {
        throw new Error(msg || `unexpected compiler condition`);
    }
}
// props数组中查找指令 允许无value的情况
function findDir(node, name, allowEmpty = false) {
    for (let i = 0; i < node.props.length; i++) {
        const p = node.props[i];
        if (p.type === 7 /* DIRECTIVE */ &&
            (allowEmpty || p.exp) &&
            (isString(name) ? p.name === name : name.test(p.name))) {
            return p;
        }
    }
}
// props数组中查找属性或者特性
function findProp(node, name, dynamicOnly = false, allowEmpty = false) {
    for (let i = 0; i < node.props.length; i++) {
        const p = node.props[i];
        if (p.type === 6 /* ATTRIBUTE */) {
            if (dynamicOnly)
                // 只找动态属性prop
                continue;
            if (p.name === name && (p.value || allowEmpty)) {
                return p;
            }
        }
        // v-bind:someProp = 'somevalue'
        else if (p.name === 'bind' &&
            (p.exp || allowEmpty) &&
            // 注意key确定和不确定的场景
            isBindKey(p.arg, name)) {
            return p;
        }
    }
}
// 匹配key和name 对于bind的arg参数需要是静态的
function isBindKey(arg, name) {
    return !!(arg && isStaticExp(arg) && arg.content === name);
}
// 4种有可能动态prop的key绑定的场景 注释有描述呢
function hasDynamicKeyVBind(node) {
    return node.props.some(p => p.type === 7 /* DIRECTIVE */ &&
        p.name === 'bind' &&
        (!p.arg || // v-bind="obj"
            p.arg.type !== 4 /* SIMPLE_EXPRESSION */ || // v-bind:[_ctx.foo]
            !p.arg.isStatic) // v-bind:[foo]
    );
}
// text node 2种场景 第一种是插槽节点 第二种直接就是静态text节点
function isText(node) {
    return node.type === 5 /* INTERPOLATION */ || node.type === 2 /* TEXT */;
}
// v-slot prop
function isVSlot(p) {
    return p.type === 7 /* DIRECTIVE */ && p.name === 'slot';
}
// template 标签 算一种原始html标签
function isTemplateNode(node) {
    return (node.type === 1 /* ELEMENT */ && node.tagType === 3 /* TEMPLATE */);
}
// slot 标签 自定义组件
function isSlotOutlet(node) {
    return node.type === 1 /* ELEMENT */ && node.tagType === 2 /* SLOT */;
}
// 针对不同的表达式对象完成 对目标node节点 添加一个prop的操作 因为之前的props的值类型情况比较多 所以要处理的情况也很多
function injectProp(node, prop, context) {
    let propsWithInjection;
    // node参数有2种格式 一个 vnodecall 一个是 函数表达式 2种情况 props对象的位置不同
    const props = node.type === 13 /* VNODE_CALL */ ? node.props : node.arguments[2];
    if (props == null || isString(props)) {
        // 最简单的情况 原本值不存在 把插入值构成结果即可
        propsWithInjection = createObjectExpression([prop]);
    }
    else if (props.type === 14 /* JS_CALL_EXPRESSION */) {
        // merged props... add ours
        // only inject key to object literal if it's the first argument so that
        // if doesn't override user provided keys
        // 已经是之前呗按照下面的 MERGE_PROPS 作为called处理过的 js call表达式了 意味着之前插入过了
        const first = props.arguments[0];
        if (!isString(first) && first.type === 15 /* JS_OBJECT_EXPRESSION */) {
            // 前面插入 后面的用户key可以覆盖此时插入的
            first.properties.unshift(prop);
        }
        else {
            if (props.callee === TO_HANDLERS) {
                // #2366
                // 也是保持新插入的在前面
                propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [
                    createObjectExpression([prop]),
                    props
                ]);
            }
            else {
                props.arguments.unshift(createObjectExpression([prop]));
            }
        }
        !propsWithInjection && (propsWithInjection = props);
    }
    // props是object表达式
    else if (props.type === 15 /* JS_OBJECT_EXPRESSION */) {
        let alreadyExists = false;
        // check existing key to avoid overriding user provided keys
        if (prop.key.type === 4 /* SIMPLE_EXPRESSION */) {
            const propKeyName = prop.key.content;
            alreadyExists = props.properties.some(p => p.key.type === 4 /* SIMPLE_EXPRESSION */ &&
                p.key.content === propKeyName);
        }
        // 确认不存在 就往前添加即可
        if (!alreadyExists) {
            props.properties.unshift(prop);
        }
        propsWithInjection = props;
    }
    else {
        // single v-bind with expression, return a merged replacement
        // 合并已有的 构造MERGE_PROPS函数调用表达式
        propsWithInjection = createCallExpression(context.helper(MERGE_PROPS), [
            createObjectExpression([prop]),
            props
        ]);
    }
    // 更新引用
    if (node.type === 13 /* VNODE_CALL */) {
        node.props = propsWithInjection;
    }
    else {
        node.arguments[2] = propsWithInjection;
    }
}
// 统一type对应的资产类型的对应规范名字 无效字符用_代替
function toValidAssetId(name, type) {
    return `_${type}_${name.replace(/[^\w]/g, '_')}`;
}

// The default decoder only provides escapes for characters reserved as part of
// the template syntax, and is only used if the custom renderer did not provide
// a platform-specific decoder.
// html解码
const decodeRE = /&(gt|lt|amp|apos|quot);/g;
const decodeMap = {
    gt: '>',
    lt: '<',
    amp: '&',
    apos: "'",
    quot: '"'
};
// 一些默认ast解析辅助方法
const defaultParserOptions = {
    // 插槽分隔符
    delimiters: [`{{`, `}}`],
    // html namespace
    getNamespace: () => 0 /* HTML */,
    // 获取元素 mode
    getTextMode: () => 0 /* DATA */,
    // 自闭合元素tag
    isVoidTag: NO,
    // <pre>
    isPreTag: NO,
    // 自定义元素
    isCustomElement: NO,
    // 替换成预定义的内容即可
    decodeEntities: (rawText) => rawText.replace(decodeRE, (_, p1) => decodeMap[p1]),
    // 错误handler
    onError: defaultOnError,
    comments: false
};
// ast入口函数 接受一段html文本串 得到一个对应ast
function baseParse(content, options = {}) {
    // ast上下文
    const context = createParserContext(content, options);
    // 起始位置
    const start = getCursor(context);
    // 根节点:children子数组 + (起始、终止位置信息)
    // getSelection 在 parseChildren 之后执行 此时上下文的信息已经更新了 得到的是最终的起止位置信息
    return createRoot(parseChildren(context, 0 /* DATA */, []), getSelection(context, start));
}
function createParserContext(content, rawOptions) {
    // 合并用户自定义的方法和默认方法
    const options = extend({}, defaultParserOptions);
    for (const key in rawOptions) {
        // @ts-ignore
        // 用户设置的解析辅助函数优先级更高哦
        options[key] = rawOptions[key] || defaultParserOptions[key];
    }
    return {
        // 辅助方法集
        options,
        // 待解析的字符所处的某一行的列数
        column: 1,
        // 待解析的字符所处第几行行
        line: 1,
        // 已经解析过的字符数量 也是待解析字符的索引位置 如 解析过了5个字符 0-4 那最新待解析的字符索引就是5
        offset: 0,
        // 最初的待解析原始字符串
        originalSource: content,
        // 解析过程中会不断被修改 越来越少的待处理字符串
        source: content,
        // 当前正在解析的元素是pre
        inPre: false,
        // 当前解析的元素上的prop存在v-pre
        // 文档:跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
        inVPre: false
    };
}
// 解析子节点内容
// 由于该方法会被递归调用 所以 ancestors 存放的是一个解析出来的dom 元素 栈 mode 标识正在解析的内容类型
// 可以看到对应mode类型下可以包含的字符类型
/**
 * export const enum TextModes {
        //          | Elements | Entities | End sign              | Inside of
        DATA, //    | ✔        | ✔        | End tags of ancestors |
        RCDATA, //  | ✘        | ✔        | End tag of the parent | <textarea>
        RAWTEXT, // | ✘        | ✘        | End tag of the parent | <style>,<script>
        CDATA,
        ATTRIBUTE_VALUE
    }
 * 
 */
function parseChildren(context, mode, ancestors) {
    // 取出最近的组件元素 如果为空代表正在解析第一个元素
    const parent = last(ancestors);
    // 取得父元素的命名空间 作用 TODO 我们一般关注解析的是html的情况就可以了
    const ns = parent ? parent.ns : 0 /* HTML */;
    // 解析一个html片段的是会有多个元素 所以是nodes
    const nodes = [];
    // 根据mode判断是否算解析完成
    while (!isEnd(context, mode, ancestors)) {
        // 取出待解析的剩余字符串
        const s = context.source;
        let node = undefined;
        if (mode === 0 /* DATA */ || mode === 1 /* RCDATA */) {
            // 当前元素属性上没有v-pre指令的情况下 此时待解析字符以 {{ 插槽形式开头 就解析插槽内容即可
            if (!context.inVPre && startsWith(s, context.options.delimiters[0])) {
                // '{{'
                // 得到节点
                node = parseInterpolation(context, mode);
            }
            // 当我们遇到 < 开头的时候
            else if (mode === 0 /* DATA */ && s[0] === '<') {
                // https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
                // 错误情况
                if (s.length === 1) {
                    emitError(context, 5 /* EOF_BEFORE_TAG_NAME */, 1);
                }
                // 情况以 <! 开头
                else if (s[1] === '!') {
                    // https://html.spec.whatwg.org/multipage/parsing.html#markup-declaration-open-state
                    if (startsWith(s, '<!--')) {
                        // 得到注释节点
                        node = parseComment(context);
                    }
                    else if (startsWith(s, '<!DOCTYPE')) {
                        // Ignore DOCTYPE by a limitation.
                        // 用注释节点替换即可
                        node = parseBogusComment(context);
                    }
                    else if (startsWith(s, '<![CDATA[')) {
                        if (ns !== 0 /* HTML */) {
                            // 解析 CDATA
                            // 有可能返回的是数组
                            node = parseCDATA(context, ancestors);
                        }
                        else {
                            // html不出现 <![CDATA[ xml才用
                            emitError(context, 1 /* CDATA_IN_HTML_CONTENT */);
                            // 注释替换即可
                            node = parseBogusComment(context);
                        }
                    }
                    else {
                        // 非法情况 注释替换
                        emitError(context, 11 /* INCORRECTLY_OPENED_COMMENT */);
                        node = parseBogusComment(context);
                    }
                }
                // 情况如 </ 开头
                else if (s[1] === '/') {
                    // https://html.spec.whatwg.org/multipage/parsing.html#end-tag-open-state
                    if (s.length === 2) {
                        // 错误场景
                        emitError(context, 5 /* EOF_BEFORE_TAG_NAME */, 2);
                    }
                    // </>
                    else if (s[2] === '>') {
                        emitError(context, 14 /* MISSING_END_TAG_NAME */, 2);
                        // 前进 忽略
                        advanceBy(context, 3);
                        continue;
                    }
                    // 正常情况 parseElement 会把元素的结束符号 </对应元素> 处理掉直接 所以不应该再次遇到结束符号了
                    // 虽然是错误情况 但是还是以 parseTag(context, 1 /* End */, parent) 方式兼容处理
                    else if (/[a-z]/i.test(s[2])) {
                        emitError(context, 23 /* X_INVALID_END_TAG */);
                        // 兼容
                        parseTag(context, 1 /* End */, parent);
                        continue;
                    }
                    else {
                        // 非法情况 注释化
                        emitError(context, 12 /* INVALID_FIRST_CHARACTER_OF_TAG_NAME */, 2);
                        node = parseBogusComment(context);
                    }
                }
                // <div 这样的元素开头
                else if (/[a-z]/i.test(s[1])) {
                    // 重点看 解析元素
                    // 递归完成后回到调用处
                    node = parseElement(context, ancestors);
                }
                // 非法 <?
                else if (s[1] === '?') {
                    emitError(context, 21 /* UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME */, 1);
                    node = parseBogusComment(context);
                }
                else {
                    emitError(context, 12 /* INVALID_FIRST_CHARACTER_OF_TAG_NAME */, 1);
                }
            }
        }
        // 以上情况如果没有解析得到node 如一段空白字符开头或者非法字符 用 parseText 兼容处理
        if (!node) {
            node = parseText(context, mode);
        }
        // 放入nodes数组中
        if (isArray(node)) {
            for (let i = 0; i < node.length; i++) {
                pushNode(nodes, node[i]);
            }
        }
        else {
            pushNode(nodes, node);
        }
    }
    // Whitespace management for more efficient output
    // (same as v2 whitespace: 'condense')
    let removedWhitespace = false;
    // 解析完成当前元素后 对它的子节点们做一次 text节点的优化
    if (mode !== 2 /* RAWTEXT */ && mode !== 1 /* RCDATA */) {
        for (let i = 0; i < nodes.length; i++) {
            const node = nodes[i];
            if (!context.inPre && node.type === 2 /* TEXT */) {
                // 当前node没有文字内容
                if (!/[^\t\r\n\f ]/.test(node.content)) {
                    // 前节点
                    const prev = nodes[i - 1];
                    // 后节点
                    const next = nodes[i + 1];
                    // If:
                    // - the whitespace is the first or last node, or:
                    // - the whitespace is adjacent to a comment, or:
                    // - the whitespace is between two elements AND contains newline
                    // Then the whitespace is ignored.
                    // 三种忽略空格的场景 如注释所言
                    if (!prev ||
                        !next ||
                        prev.type === 3 /* COMMENT */ ||
                        next.type === 3 /* COMMENT */ ||
                        (prev.type === 1 /* ELEMENT */ &&
                            next.type === 1 /* ELEMENT */ &&
                            /[\r\n]/.test(node.content))) {
                        removedWhitespace = true;
                        nodes[i] = null;
                    }
                    else {
                        // Otherwise, condensed consecutive whitespace inside the text
                        // down to a single space
                        node.content = ' ';
                    }
                }
                // 有文字内容 把中间的换行符什么的全部替换为一个 空格符
                else {
                    node.content = node.content.replace(/[\t\r\n\f ]+/g, ' ');
                }
            }
        }
        // pre的子节点第一行的换行符忽略 MDN有描述此定义
        if (context.inPre && parent && context.options.isPreTag(parent.tag)) {
            // remove leading newline per html spec
            // https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
            const first = nodes[0];
            if (first && first.type === 2 /* TEXT */) {
                first.content = first.content.replace(/^\r?\n/, '');
            }
        }
    }
    // 清除空白内容的节点
    return removedWhitespace ? nodes.filter(Boolean) : nodes;
}
// 推入节点的同时尝试合并text node
function pushNode(nodes, node) {
    if (node.type === 2 /* TEXT */) {
        const prev = last(nodes);
        // Merge if both this and the previous node are text and those are
        // consecutive. This happens for cases like "a < b".
        if (prev &&
            prev.type === 2 /* TEXT */ &&
            prev.loc.end.offset === node.loc.start.offset) {
            prev.content += node.content;
            prev.loc.end = node.loc.end;
            prev.loc.source += node.loc.source;
            return;
        }
    }
    nodes.push(node);
}
// 解析CDATA内部的内容
function parseCDATA(context, ancestors) {
    // 越过开头
    advanceBy(context, 9);
    // 递归调用得到子树
    const nodes = parseChildren(context, 3 /* CDATA */, ancestors);
    if (context.source.length === 0) {
        emitError(context, 6 /* EOF_IN_CDATA */);
    }
    else {
        // 越过结尾
        advanceBy(context, 3);
    }
    // 有可能返回的是数组
    return nodes;
}
// 解析注释
function parseComment(context) {
    // 起始位置
    const start = getCursor(context);
    let content;
    // Regular comment.
    const match = /--(\!)?>/.exec(context.source);
    if (!match) {
        // 没有发现注释的结束标记 --> 错误情况
        content = context.source.slice(4);
        advanceBy(context, context.source.length);
        emitError(context, 7 /* EOF_IN_COMMENT */);
    }
    else {
        // 正确的如 <!--xxx(可选)--> index肯定不会小于3的 
        if (match.index <= 3) {
            emitError(context, 0 /* ABRUPT_CLOSING_OF_EMPTY_COMMENT */);
        }
        // 只允许出现一次 -->
        if (match[1]) {
            emitError(context, 10 /* INCORRECTLY_CLOSED_COMMENT */);
        }
        // 取出注释内部的内容
        content = context.source.slice(4, match.index);
        // Advancing with reporting nested comments.
        const s = context.source.slice(0, match.index);
        let prevIndex = 1, nestedIndex = 0;
        // nestedIndex 嵌套的 <!-- 出现的索引位置
        while ((nestedIndex = s.indexOf('<!--', prevIndex)) !== -1) {
            // 越过前面嵌套的注释内容
            advanceBy(context, nestedIndex - prevIndex + 1);
            if (nestedIndex + 4 < s.length) {
                // 嵌套的注释
                emitError(context, 16 /* NESTED_COMMENT */);
            }
            prevIndex = nestedIndex + 1;
        }
        // 越过 --> 之前的内容
        advanceBy(context, match.index + match[0].length - prevIndex + 1);
    }
    return {
        // ast 注释节点
        type: 3 /* COMMENT */,
        content,
        loc: getSelection(context, start)
    };
}
// 用注释代替忽略或者不符合要求的内容 
function parseBogusComment(context) {
    const start = getCursor(context);
    const contentStart = context.source[1] === '?' ? 1 : 2;
    let content;
    const closeIndex = context.source.indexOf('>');
    if (closeIndex === -1) {
        content = context.source.slice(contentStart);
        advanceBy(context, context.source.length);
    }
    else {
        content = context.source.slice(contentStart, closeIndex);
        advanceBy(context, closeIndex + 1);
    }
    // 简单返回注释节点即可
    return {
        type: 3 /* COMMENT */,
        content,
        loc: getSelection(context, start)
    };
}
// 解析元素 <div 之类的
function parseElement(context, ancestors) {
    // Start tag.

    // 在处理当前元素之前上下文的状态

    // 是否处于解析 v-pre 里面的内容
    const wasInPre = context.inPre;
    // 之前组件元素上是否有v-pre的指令出现
    const wasInVPre = context.inVPre;
    // 父元素
    const parent = last(ancestors);
    // 解析元素的开头整体部分 <div :id='xx' ... > ... </div>的前面内容 <div :id='xx' ... >
    const element = parseTag(context, 0 /* Start */, parent);
    // 现在的状态
    const isPreBoundary = context.inPre && !wasInPre;
    const isVPreBoundary = context.inVPre && !wasInVPre;
    // 无结束标签的元素
    if (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
        return element;
    }
    // Children.
    // 入栈
    ancestors.push(element);
    // 以当前mode传入 parseChildren
    const mode = context.options.getTextMode(element, parent);
    // 递归调用 parseChildren 解析 <div :id='xx' ... > ... </div> 中间部分的内容
    // 递归结束得到的就是当前节点的子节点数组了
    const children = parseChildren(context, mode, ancestors);
    // 出栈
    ancestors.pop();
    // 建立父子引用
    element.children = children;
    // End tag.
    // 解析元素的开头整体部分 <div :id='xx' ... > ... </div>的结束内容
    if (startsWithEndTagOpen(context.source, element.tag)) {
        parseTag(context, 1 /* End */, parent);
    }
    else {
        // 错误结束标签
        emitError(context, 24 /* X_MISSING_END_TAG */, 0, element.loc.start);
        if (context.source.length === 0 && element.tag.toLowerCase() === 'script') {
            const first = children[0];
            // script 下 有 <!-- 注释内容
            if (first && startsWith(first.loc.source, '<!--')) {
                emitError(context, 8 /* EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT */);
            }
        }
    }
    // 设置元素的整体起始-结束位置信息
    element.loc = getSelection(context, element.loc.start);
    // 只有之前不在pre状态下 才有重置旧值的意义 否则一直都属于pre状态而已
    if (isPreBoundary) {
        context.inPre = false;
    }
    if (isVPreBoundary) {
        context.inVPre = false;
    }
    // 返回节点
    return element;
}
const isSpecialTemplateDirective = /*#__PURE__*/ makeMap(`if,else,else-if,for,slot`);
/**
 * Parse a tag (E.g. `<div id=a>`) with that type (start tag or end tag).
 */
// 如注释所言
function parseTag(context, type, parent) {
    // Tag open.
    // 起始位置
    const start = getCursor(context);
    // 捕获起始或者结束位置的tag部分内容
    const match = /^<\/?([a-z][^\t\r\n\f />]*)/i.exec(context.source);
    // dom标签tag
    const tag = match[1];
    // namespace
    const ns = context.options.getNamespace(tag, parent);
    // 越过tag的内容
    advanceBy(context, match[0].length);
    // 越过空格
    advanceSpaces(context);
    // save current state in case we need to re-parse attributes with v-pre
    // 如注释所言 暂存此时的位置状态和文本内容
    const cursor = getCursor(context);
    const currentSource = context.source;
    // Attributes.
    // 把所有的prop属性一次性都解析出来
    let props = parseAttributes(context, type);
    // check <pre> tag
    // pre标签tag
    if (context.options.isPreTag(tag)) {
        // 置位
        context.inPre = true;
    }
    // check v-pre
    if (!context.inVPre &&
        props.some(p => p.type === 7 /* DIRECTIVE */ && p.name === 'pre')) {
        // 置位
        context.inVPre = true;
        // reset context
        // v-pre不做任何处理 返回原html内容
        // 重置已被解析确认的source内容
        extend(context, cursor);
        context.source = currentSource;
        // re-parse attrs and filter out v-pre itself
        // 去掉v-pre属性
        props = parseAttributes(context, type).filter(p => p.name !== 'v-pre');
    }
    // Tag close.
    let isSelfClosing = false;
    // 错误情况
    if (context.source.length === 0) {
        emitError(context, 9 /* EOF_IN_TAG */);
    }
    else {
        // 自关闭标签
        isSelfClosing = startsWith(context.source, '/>');
        // 自关闭情况不会有type == 1 调用的场景
        if (type === 1 /* End */ && isSelfClosing) {
            emitError(context, 4 /* END_TAG_WITH_TRAILING_SOLIDUS */);
        }
        // 越过 > 或者 />
        advanceBy(context, isSelfClosing ? 2 : 1);
    }
    let tagType = 0 /* ELEMENT */;
    const options = context.options;
    // 非自定义元素
    if (!context.inVPre && !options.isCustomElement(tag)) {
        // v-is 原生html元素才能设置这个is prop
        const hasVIs = props.some(p => p.type === 7 /* DIRECTIVE */ && p.name === 'is');
        if (options.isNativeTag && !hasVIs) {
            // 普通用户自定义组件 没有is prop
            if (!options.isNativeTag(tag))
                tagType = 1 /* COMPONENT */;
        }
        // 几种内置组件 tagType 也是 1 
        else if (hasVIs ||
            isCoreComponent(tag) ||
            (options.isBuiltInComponent && options.isBuiltInComponent(tag)) ||
            /^[A-Z]/.test(tag) ||
            tag === 'component') {
            tagType = 1 /* COMPONENT */;
        }
        // slot的标签内容 2
        if (tag === 'slot') {
            tagType = 2 /* SLOT */;
        }
        // 带有 if,else,else-if,for,slot 指令的 template 3 否则不是 视为原生html标签来处理
        else if (tag === 'template' &&
            props.some(p => {
                return (p.type === 7 /* DIRECTIVE */ && isSpecialTemplateDirective(p.name));
            })) {
            tagType = 3 /* TEMPLATE */;
        }
    }
    return {
        type: 1 /* ELEMENT */,
        ns,
        tag,
        tagType,
        props,
        isSelfClosing,
        children: [],
        loc: getSelection(context, start),
        // 每个元素节点必有 codegenNode
        codegenNode: undefined // to be created during transform phase
    };
}
// 解析所有属性
function parseAttributes(context, type) {
    // 数组
    const props = [];
    // 属性name集合
    const attributeNames = new Set();
    // 没有遇到终止符 > /> 之前遍历解析
    while (context.source.length > 0 &&
        !startsWith(context.source, '>') &&
        !startsWith(context.source, '/>')) {
        // 错误情况 忽略 尝试继续处理
        if (startsWith(context.source, '/')) {
            emitError(context, 22 /* UNEXPECTED_SOLIDUS_IN_TAG */);
            advanceBy(context, 1);
            advanceSpaces(context);
            continue;
        }
        // 只有开始情况属性才有效
        if (type === 1 /* End */) {
            emitError(context, 3 /* END_TAG_WITH_ATTRIBUTES */);
        }
        // 解析出来一个 attr 属性
        const attr = parseAttribute(context, attributeNames);
        if (type === 0 /* Start */) {
            props.push(attr);
        }
        // 属性之间用空格隔开
        if (/^[^\t\r\n\f />]/.test(context.source)) {
            emitError(context, 15 /* MISSING_WHITESPACE_BETWEEN_ATTRIBUTES */);
        }
        // 越过空格
        advanceSpaces(context);
    }
    return props;
}
// 解析单个attr属性
function parseAttribute(context, nameSet) {
    // Name.
    // 起始位置
    const start = getCursor(context);
    // 先处理属性的名字 id = 'xxx'
    const match = /^[^\t\r\n\f />][^\t\r\n\f />=]*/.exec(context.source);
    const name = match[0];
    if (nameSet.has(name)) {
        emitError(context, 2 /* DUPLICATE_ATTRIBUTE */);
    }
    // 添加一个name
    nameSet.add(name);
    // 空白名字不符合要求
    if (name[0] === '=') {
        emitError(context, 19 /* UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME */);
    }
    {
        // name中不得包含的字符
        const pattern = /["'<]/g;
        let m;
        while ((m = pattern.exec(name))) {
            emitError(context, 17 /* UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME */, m.index);
        }
    }
    // 越过name
    advanceBy(context, name.length);
    // Value
    let value = undefined;
    // 带有 = value 的情况
    if (/^[\t\r\n\f ]*=/.test(context.source)) {
        // 越过空格
        advanceSpaces(context);
        // 越过 = 
        advanceBy(context, 1);
        // 越过空格
        advanceSpaces(context);
        // 解析 value 部分
        value = parseAttributeValue(context);
        if (!value) {
            emitError(context, 13 /* MISSING_ATTRIBUTE_VALUE */);
        }
    }
    // 得到这个属性的 起始-终止位置部分
    const loc = getSelection(context, start);
    if (!context.inVPre && /^(v-|:|@|#)/.test(name)) {
        const match = /(?:^v-([a-z0-9-]+))?(?:(?::|^@|^#)(\[[^\]]+\]|[^\.]+))?(.+)?$/i.exec(name);
        // 为简写的指令添加完整的名字
        const dirName = match[1] ||
            (startsWith(name, ':') ? 'bind' : startsWith(name, '@') ? 'on' : 'slot');
        let arg;
        if (match[2]) {
            const isSlot = dirName === 'slot';
            // name起始位置的偏移量
            const startOffset = name.lastIndexOf(match[2]);
            // 得到整个属性字符串的 起始-终止 信息
            const loc = getSelection(context, getNewPosition(context, start, startOffset), getNewPosition(context, start, startOffset + match[2].length + ((isSlot && match[3]) || '').length));
            // 内容
            let content = match[2];
            let isStatic = true;
            // 动态属性名字 isStatic 就不是true了
            if (content.startsWith('[')) {
                isStatic = false;
                if (!content.endsWith(']')) {
                    emitError(context, 26 /* X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END */);
                }
                // 取出表达式
                content = content.substr(1, content.length - 2);
            }
            // 兼容写法
            else if (isSlot) {
                // #1241 special case for v-slot: vuetify relies extensively on slot
                // names containing dots. v-slot doesn't have any modifiers and Vue 2.x
                // supports such usage so we are keeping it consistent with 2.x.
                content += match[3] || '';
            }
            arg = {
                type: 4 /* SIMPLE_EXPRESSION */,
                content,
                isStatic,
                // 非固定属性name
                constType: isStatic
                    ? 3 /* CAN_STRINGIFY */
                    : 0 /* NOT_CONSTANT */,
                loc
            };
        }
        if (value && value.isQuoted) {
            //更新有引号的位置状态
            const valueLoc = value.loc;
            valueLoc.start.offset++;
            valueLoc.start.column++;
            valueLoc.end = advancePositionWithClone(valueLoc.start, value.content);
            valueLoc.source = valueLoc.source.slice(1, -1);
        }
        // 得到一个指令
        return {
            type: 7 /* DIRECTIVE */,
            name: dirName,
            exp: value && {
                type: 4 /* SIMPLE_EXPRESSION */,
                content: value.content,
                isStatic: false,
                // Treat as non-constant by default. This can be potentially set to
                // other values by `transformExpression` to make it eligible for hoisting.
                constType: 0 /* NOT_CONSTANT */,
                loc: value.loc
            },
            arg,
            // 指令参数
            modifiers: match[3] ? match[3].substr(1).split('.') : [],
            loc
        };
    }
    // html attr 普通文本内容
    return {
        type: 6 /* ATTRIBUTE */,
        name,
        value: value && {
            type: 2 /* TEXT */,
            content: value.content,
            loc: value.loc
        },
        loc
    };
}
// 解析属性的值 内部部分
function parseAttributeValue(context) {
    // 起始位置
    const start = getCursor(context);
    let content;
    const quote = context.source[0];
    // 开头起始引号
    const isQuoted = quote === `"` || quote === `'`;
    if (isQuoted) {
        // Quoted value.
        // 越过引号
        advanceBy(context, 1);
        const endIndex = context.source.indexOf(quote);
        // 缺失最后的引号
        if (endIndex === -1) {
            content = parseTextData(context, context.source.length, 4 /* ATTRIBUTE_VALUE */);
        }
        else {
            content = parseTextData(context, endIndex, 4 /* ATTRIBUTE_VALUE */);
            advanceBy(context, 1);
        }
    }
    else {
        // Unquoted
        // 无引号 
        // 匹配普通值的格式
        const match = /^[^\t\r\n\f >]+/.exec(context.source);
        if (!match) {
            return undefined;
        }
        // 不应该遇到的符号
        const unexpectedChars = /["'<=`]/g;
        let m;
        while ((m = unexpectedChars.exec(match[0]))) {
            emitError(context, 18 /* UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE */, m.index);
        }
        // value出来了
        content = parseTextData(context, match[0].length, 4 /* ATTRIBUTE_VALUE */);
    }
    // 返回value 也是一个对象
    return { content, isQuoted, loc: getSelection(context, start) };
}
// 解析插槽
function parseInterpolation(context, mode) {
    // 分别为 {{ 和 }} 或者用户自定义的
    const [open, close] = context.options.delimiters;
    // }} 第一个} 所在待解析字符的索引
    const closeIndex = context.source.indexOf(close, open.length);
    // 格式不符要求
    if (closeIndex === -1) {
        emitError(context, 25 /* X_MISSING_INTERPOLATION_END */);
        return undefined;
    }
    // 含{{的起始位置
    const start = getCursor(context);
    // 越过 {{长度 2个字符
    advanceBy(context, open.length);
    // 不含 {{ 的起始位置
    const innerStart = getCursor(context);
    const innerEnd = getCursor(context);
    // 不含{{ }}的原始插槽内容长度
    const rawContentLength = closeIndex - open.length;
    // 取出原始内容
    const rawContent = context.source.slice(0, rawContentLength);
    // 解析text内容完成
    const preTrimContent = parseTextData(context, rawContentLength, mode);
    // 去除前后空格
    const content = preTrimContent.trim();
    // 取得前空格的长度
    const startOffset = preTrimContent.indexOf(content);
    if (startOffset > 0) {
        // 更新 innerStart 的位置和偏移量信息 因为去除了空格
        advancePositionWithMutation(innerStart, rawContent, startOffset);
    }
    // preTrimContent.length - content.length - startOffset = 后空格的长度
    // rawContentLength - 后空格长度 就是后空格的偏移量
    const endOffset = rawContentLength - (preTrimContent.length - content.length - startOffset);
    // 更新 innerEnd 的位置信息
    advancePositionWithMutation(innerEnd, rawContent, endOffset);
    // 越过 }}
    advanceBy(context, close.length);
    // 返回插槽ast node 类型 5 它的content 是另外一种 ast node 类型 4 代表插件的内部表达式 它的content 就是 去除空格后的表达式了
    return {
        type: 5 /* INTERPOLATION */,
        content: {
            type: 4 /* SIMPLE_EXPRESSION */,
            isStatic: false,
            // Set `isConstant` to false by default and will decide in transformExpression
            constType: 0 /* NOT_CONSTANT */,
            content,
            // end位置以 innerEnd 为准
            loc: getSelection(context, innerStart, innerEnd)
        },
        // end 以  }}后面一个字符为准
        loc: getSelection(context, start)
    };
}
// 解析目前能得到的最长的直接字符串 some chars {{ xx }}
function parseText(context, mode) {
    // < 和 {{
    const endTokens = ['<', context.options.delimiters[0]];
    if (mode === 3 /* CDATA */) {
        endTokens.push(']]>');
    }
    let endIndex = context.source.length;
    // 确定文本内容的结束位置
    for (let i = 0; i < endTokens.length; i++) {
        const index = context.source.indexOf(endTokens[i], 1);
        if (index !== -1 && endIndex > index) {
            endIndex = index;
        }
    }
    // 起始位置
    const start = getCursor(context);
    // 得到直接text文本内容 some chars 
    const content = parseTextData(context, endIndex, mode);
    // 返回ast text node
    return {
        type: 2 /* TEXT */,
        content,
        loc: getSelection(context, start)
    };
}
/**
 * Get text data with a given length from the current location.
 * This translates HTML entities in the text data.
 */
// 尝试解析一段文本字符串
function parseTextData(context, length, mode) {
    // 复制一份
    const rawText = context.source.slice(0, length);
    // 前进 代表已解析
    advanceBy(context, length);
    if (mode === 2 /* RAWTEXT */ ||
        mode === 3 /* CDATA */ ||
        rawText.indexOf('&') === -1) {
        // 无需额外的处理 直接返回
        return rawText;
    }
    else {
        // DATA or RCDATA containing "&"". Entity decoding required.
        // html中带 &nbsp;之类的情况 需要解码一下 
        // 出现了 mode === 4 这种情况
        return context.options.decodeEntities(rawText, mode === 4 /* ATTRIBUTE_VALUE */);
    }
}
// 获取位置对象 一般在解析开始前调用一次得到 起始位置 然后解析完成后再调用一次得到 结束位置
function getCursor(context) {
    const { column, line, offset } = context;
    return { column, line, offset };
}
// 得到完整的起始-结束位置对象
function getSelection(context, start, end) {
    end = end || getCursor(context);
    return {
        start,
        end,
        // 截取这段位置区间内的原始字符内容
        source: context.originalSource.slice(start.offset, end.offset)
    };
}
// 取dom 元素栈 栈顶元素
function last(xs) {
    return xs[xs.length - 1];
}
// 字符串开头内容匹配
function startsWith(source, searchString) {
    return source.startsWith(searchString);
}
// 前进 numberOfCharacters 个字符数
function advanceBy(context, numberOfCharacters) {
    const { source } = context;
    // 更新位置信息中的行列信息
    advancePositionWithMutation(context, source, numberOfCharacters);
    // 更新上下文的待解析内容 source
    context.source = source.slice(numberOfCharacters);
}
// 越过空白内容部分
function advanceSpaces(context) {
    const match = /^[\t\r\n\f ]+/.exec(context.source);
    if (match) {
        advanceBy(context, match[0].length);
    }
}
// 根据start得到克隆后新的位置对象
function getNewPosition(context, start, numberOfCharacters) {
    return advancePositionWithClone(start, context.originalSource.slice(start.offset, numberOfCharacters), numberOfCharacters);
}
// parser过程中统一错误处理函数
function emitError(context, code, offset, loc = getCursor(context)) {
    if (offset) {
        loc.offset += offset;
        loc.column += offset;
    }
    context.options.onError(createCompilerError(code, {
        start: loc,
        end: loc,
        source: ''
    }));
}
// 当前节点解析完成
function isEnd(context, mode, ancestors) {
    const s = context.source;
    switch (mode) {
        // 对于 DATA类型 只要 父元素中存在相同的标签就视为当前元素解析完成
        case 0 /* DATA */:
            if (startsWith(s, '</')) {
                // TODO: probably bad performance
                for (let i = ancestors.length - 1; i >= 0; --i) {
                    if (startsWithEndTagOpen(s, ancestors[i].tag)) {
                        return true;
                    }
                }
            }
            break;
        case 1 /* RCDATA */:
        case 2 /* RAWTEXT */: {
            // 对于上面2种 只有父元素标签严格匹配当前元素结束标签才可以
            const parent = last(ancestors);
            if (parent && startsWithEndTagOpen(s, parent.tag)) {
                return true;
            }
            break;
        }
        case 3 /* CDATA */:
            // 直接校验
            if (startsWith(s, ']]>')) {
                return true;
            }
            break;
    }
    // 取决于是否还有内容要解析
    return !s;
}
// 元素的开头和结尾标签是否一致
function startsWithEndTagOpen(source, tag) {
    return (startsWith(source, '</') &&
        source.substr(2, tag.length).toLowerCase() === tag.toLowerCase() &&
        /[\t\r\n\f />]/.test(source[2 + tag.length] || '>'));
}

简单聊一聊generator和async函数的模拟实现吧(3)

// 这次我们在上文的基础上加上 throw 接口的实现

// 先看一个例子:

/*
    var gen = function* gen(){
        try {
            yield console.log('a');
        } catch (e) {
            // ...
        }
        yield console.log('b');
        yield console.log('c');
    }
    
    var g = gen();
    g.next() // a
    g.throw() // 错误
    // b
    g.next() // c
*/

var regeneratorRuntime = (function (exports) {
    // 一些辅助函数
    var Op = Object.prototype;
    var hasOwn = Op.hasOwnProperty;
    var undefined; // More compressible than void 0.
    var $Symbol = typeof Symbol === "function" ? Symbol : {};
    var iteratorSymbol = $Symbol.iterator || "@@iterator";
    var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator";
    var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag";

    function define(obj, key, value) {
        Object.defineProperty(obj, key, {
            value: value,
            enumerable: true,
            configurable: true,
            writable: true
        });
        return obj[key];
    }

    // 生成器 当做函数看待 只从它身上取 prototype
    function Generator() { }
    // 生成器函数 纯函数看待
    function GeneratorFunction() { }
    // 生成器函数原型对象 我们对它同时看做 函数 和 对象 并赋予各自的属性
    function GeneratorFunctionPrototype() { }

    var IteratorPrototype = {};
    IteratorPrototype[iteratorSymbol] = function () {
        return this;
    };

    var getProto = Object.getPrototypeOf;
    // 取出数组上存在的原生迭代器对象实例 就是下面的Array Iterator {}  然后取出它继承的原型对象的原型对象 也就是 仅仅次于Object用于继承的原型对象那个拥有 Symbol(Symbol.iterator): ƒ [Symbol.iterator]() 属性的原型对象
    /*
        Array Iterator {}
        __proto__: Array Iterator
            next: ƒ next()
            Symbol(Symbol.toStringTag): "Array Iterator"
            __proto__:
                Symbol(Symbol.iterator): ƒ [Symbol.iterator]()
                __proto__: Object
    */
    // 原生迭代器原型对象
    var NativeIteratorPrototype = getProto && getProto(getProto(values([])));
    if (NativeIteratorPrototype &&
        NativeIteratorPrototype !== Op &&
        hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {
        // This environment has a native %IteratorPrototype%; use it instead
        // of the polyfill.
        IteratorPrototype = NativeIteratorPrototype;
    }

    // GeneratorFunctionPrototype 当做函数看待
    // Gp: 生成器函数原型对象 Object.create(IteratorPrototype) 以原生迭代器原型继承构造一个新对象
    var Gp = GeneratorFunctionPrototype.prototype = Object.create(IteratorPrototype);
    // 补充原型对象的构造函数属性
    Gp.constructor = GeneratorFunctionPrototype;

    // GeneratorFunctionPrototype 当做对象再看待
    GeneratorFunction.prototype = GeneratorFunctionPrototype;
    GeneratorFunctionPrototype.constructor = GeneratorFunction;

    // 可能大家不理解上面这几行代码设置原型有啥用 我们写完 mark 和 wrap函数之后再回来分析这几行代码 的目标效果

    Generator.prototype = GeneratorFunctionPrototype.prototype;

    // 再给它加个名字吧
    GeneratorFunction.displayName = define(
        GeneratorFunctionPrototype,
        toStringTagSymbol,
        "GeneratorFunction"
    );

    // 先来实现mark 看它标记了啥:
    exports.mark = function (genFun) {
        if (Object.setPrototypeOf) {
            // genFun.__proto__ = GeneratorFunctionPrototype;
            Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);
        } else {
            genFun.__proto__ = GeneratorFunctionPrototype;
            define(genFun, toStringTagSymbol, "GeneratorFunction");
        }
        // 设置 genFun.prototype = 一个新对象 (new obj, obj.__proto__ = Gp)
        genFun.prototype = Object.create(Gp);


        return genFun;
    };

    // 再看 wrap 包装了啥:
    // innerFn 执行业务代码 outerFn 就是被mark过的函数
    function wrap(innerFn, outerFn, self, tryLocsList) {
        // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.
        // outerFn.prototype 也就是 genFun.prototype 是 Object.create(Gp)得到 也就是 Gp的实例 而 Generator.prototype 就是 Gp 所以满足条件
        var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator;
        // 好了 hw 就是来自于 Object.create(protoGenerator.prototype) 构造的实例 hw.__proto__ === protoGenerator.prototype; hw.__proto__.__proto__ === Gp
        var generator = Object.create(protoGenerator.prototype);
        // 原来 上下文 对象来自于这里构造的实例
        var context = new Context(tryLocsList || []);

        // The ._invoke method unifies the implementations of the .next,
        // .throw, and .return methods.
        // 实例上还有一个方法 
        generator._invoke = makeInvokeMethod(innerFn, self, context);

        // 这就是 hw 迭代器实例对象
        return generator;
    }
    exports.wrap = wrap;

    /*
        现在我们回头看下那几行原型设置的效果:
        规范约定如下: hw.__proto__ === helloWorldGenerator.prototype; hw.__proto__.__proto__ === helloWorldGenerator.__proto__.prototype
        这意味着 我们通过 hw = genFun() 得到的迭代器对象 需要 满足类似 new genFun() 方式得到的原型继承关系,我们来看下hw是什么:
        hw = Object.create(helloWorldGenerator.prototype);
        所以 hw.__proto__ === helloWorldGenerator.prototype 第一个条件成立;
        而 hw.__proto__.__proto__ 在上面的 wrap 分析了 其实 就是 Gp对象,再看 helloWorldGenerator.__proto__ 其实是 GeneratorFunctionPrototype 而 
        GeneratorFunctionPrototype.prototype 就是 Gp,好吧 第二个条件成立 所以满足规范要求了

        facebook人写的代码真是 666...
    */

    // return版本新增:
    function pushTryEntry(locs) {
        // 依次保存4个关键的位置信息 [try, catch, finally, finally after]
        var entry = { tryLoc: locs[0] };

        if (1 in locs) {
            entry.catchLoc = locs[1];
        }

        if (2 in locs) {
            entry.finallyLoc = locs[2];
            entry.afterLoc = locs[3];
        }

        this.tryEntries.push(entry);
    }

    // return版本新增:
    function resetTryEntry(entry) {
        var record = entry.completion || {};
        record.type = "normal";
        delete record.arg;
        entry.completion = record;
    }

    // 好了 我们先构建一个简单的 Context:

    function Context(tryLocsList) {
        // return版本新增: 补充对 tryLocsList 的处理 这个变量的名字透露出它的功能了。。try代码块的位置列表。。。
        this.tryEntries = [{ tryLoc: "root" }];
        tryLocsList.forEach(pushTryEntry, this);

        this.reset(true);
    }

    // 简单的放一些方法到原型对象上去
    // 从转码后的业务代码中可以看到 我们需要的实现有下面几个方法:
    Context.prototype = {
        constructor: Context,

        reset: function (skipTempReset) {
            this.prev = 0;
            this.next = 0;

            this.sent = this._sent = undefined;
            this.done = false;

            this.method = "next";
            this.arg = undefined;

            // return版本新增:
            this.tryEntries.forEach(resetTryEntry);
        },

        // 这个最后执行 _context.stop();
        stop: function () {
            this.done = true;

            // return版本新增:
            var rootEntry = this.tryEntries[0];
            var rootRecord = rootEntry.completion;
            if (rootRecord.type === "throw") {
                throw rootRecord.arg;
            }

            return this.rval;
        },

        dispatchException: function (exception) {
            // 停止态的无法再throw了
            if (this.done) {
                throw exception;
            }

            // context实例
            var context = this;
            // 处理错误的最终方法
            function handle(loc, caught) {
                // 默认抛出错误
                record.type = "throw";
                record.arg = exception;
                context.next = loc;

                // 如果部署了catch代码就可以捕获
                if (caught) {
                    // If the dispatched exception was caught by a catch block,
                    // then let that catch block handle the exception normally.
                    // 继续正常执行next的代码 不会报错
                    context.method = "next";
                    context.arg = undefined;
                }

                return !!caught;
            }

            // 继续从后往前找catch代码块
            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
                var entry = this.tryEntries[i];
                var record = entry.completion;

                // 无人捕获错误 
                if (entry.tryLoc === "root") {
                    // Exception thrown outside of any try block that could handle
                    // it, so set the completion value of the entire function to
                    // throw the exception.
                    return handle("end");
                }

                // 根据是否部署了catch来处理 注意 handle的第一个参数 下一步代码执行位置 第二个参数 是否捕获错误
                if (entry.tryLoc <= this.prev) {
                    var hasCatch = hasOwn.call(entry, "catchLoc");
                    var hasFinally = hasOwn.call(entry, "finallyLoc");

                    if (hasCatch && hasFinally) {
                        if (this.prev < entry.catchLoc) {
                            return handle(entry.catchLoc, true);
                        } else if (this.prev < entry.finallyLoc) {
                            return handle(entry.finallyLoc);
                        }

                    } else if (hasCatch) {
                        if (this.prev < entry.catchLoc) {
                            return handle(entry.catchLoc, true);
                        }

                    } else if (hasFinally) {
                        if (this.prev < entry.finallyLoc) {
                            return handle(entry.finallyLoc);
                        }

                    } else {
                        throw new Error("try statement without catch or finally");
                    }
                }
            }
        },

        // 这个 在最后一个yield语句之后执行 _context.abrupt("return", 'ending');
        // 任意 .return() 语句也是走的下面这个方法
        abrupt: function (type, arg) {
            // return版本重写:
            // 我们看待 return(7) 导致发生了什么: 它调用了 abrupt('return ', 7);
            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
                var entry = this.tryEntries[i];
                // 仔细看下面几个判断 分别确定了这个return语句触发的距离它最近的那个try语句块 且 保证了这个try语句块执行了 且 拥有finally块 这时候我们就需要修改一下原本的执行顺序了
                if (entry.tryLoc <= this.prev &&
                    hasOwn.call(entry, "finallyLoc") &&
                    this.prev < entry.finallyLoc) {
                    var finallyEntry = entry;
                    break;
                }
            }

            // break和continue的场景 现在还不涉及 先忽略吧
            if (finallyEntry &&
                (type === "break" ||
                    type === "continue") &&
                finallyEntry.tryLoc <= arg &&
                arg <= finallyEntry.finallyLoc) {
                // Ignore the finally entry if control is not jumping to a
                // location outside the try/catch block.
                finallyEntry = null;
            }

            var record = finallyEntry ? finallyEntry.completion : {};
            record.type = type;
            record.arg = arg;

            // 注意这个哈 再次自动调用了next...且设置了下一步执行哪一句代码 即我们看到的:先执行finally语句中的内容
            if (finallyEntry) {
                this.method = "next";
                this.next = finallyEntry.finallyLoc;
                // 迭代器再迭代一次
                return ContinueSentinel;
            }

            return this.complete(record);
        },

        // 好吧 这个也要实现。。。
        complete: function (record, afterLoc) {
            if (record.type === "throw") {
                throw record.arg;
            }

            if (record.type === "break" ||
                record.type === "continue") {
                this.next = record.arg;
                // 现在我们只关注 return 和 normal 
            } else if (record.type === "return") {
                this.rval = this.arg = record.arg;
                this.method = "return";
                // 业务代码执行最后的stop操作
                this.next = "end";
            } else if (record.type === "normal" && afterLoc) {
                this.next = afterLoc;
            }

            // 完成态自动切换到结束态 需要返回一个特殊的continue类型的返回值
            return ContinueSentinel;
        },
        // return版本新增:
        finish: function (finallyLoc) {
            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
                // 依旧同上 找到最近的一个try语句块
                var entry = this.tryEntries[i];
                // 2者匹配 意味着 finally中的语句执行完 可以执行return原本的功能了 即 终止迭代器 返回 return的值
                if (entry.finallyLoc === finallyLoc) {
                    // 终止
                    this.complete(entry.completion, entry.afterLoc);
                    // 清除这条entry的状态 作用已完成
                    resetTryEntry(entry);
                    // 继续迭代 朝着stop状态出发
                    return ContinueSentinel;
                }
            }
        },

        // catch新增
        "catch": function (tryLoc) {
            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
                var entry = this.tryEntries[i];
                // 找到对应的catch entry
                if (entry.tryLoc === tryLoc) {
                    var record = entry.completion;
                    if (record.type === "throw") {
                        // 取出我们传入的参数
                        var thrown = record.arg;
                        resetTryEntry(entry);
                    }
                    return thrown;
                }
            }

            // The context.catch method must only be called with a location
            // argument that corresponds to a known catch block.
            throw new Error("illegal catch attempt");
        },

        delegateYield: function (iterable, resultName, nextLoc) {

        }
    };

    // 继续实现 makeInvokeMethod:

    // 迭代器 状态机 的几种状态
    var GenStateSuspendedStart = "suspendedStart";
    var GenStateSuspendedYield = "suspendedYield";
    var GenStateExecuting = "executing";
    var GenStateCompleted = "completed";

    // 状态机继续迭代的特殊返回值
    var ContinueSentinel = {};

    // 返回一个 invoke 内部闭包函数 持有 innerFn context 等自由变量
    function makeInvokeMethod(innerFn, self, context) {
        // 状态机初始化起始状态
        var state = GenStateSuspendedStart;

        return function invoke(method, arg) {
            // todo
        };
    }

    // 然后我们调用的时候从 hw上调用next(),我们在直接原型上加:

    function defineIteratorMethods(prototype) {
        ["next", "throw", "return"].forEach(function (method) {
            define(prototype, method, function (arg) {
                // 其实都是执行实例的 _invoke y也就是上面的 内部函数invoke
                return this._invoke(method, arg);
            });
        });
    }

    defineIteratorMethods(Gp);

    // 所以重点就是 invoke 的实现:

    function makeInvokeMethod(innerFn, self, context) {
        // 状态机初始化起始状态
        var state = GenStateSuspendedStart;

        return function invoke(method, arg) {

            // 初次调用处于 suspendedStart 执行到某个yield语句暂停 处于 suspendedYield 结束后处于 completed
            // 而 executing 指的是 业务代码执行的过程中处于的状态 之后便会过度到 suspendedYield || completed 
            // 不应该在再次invoke的时候遇到的
            if (state === GenStateExecuting) {
                throw new Error("Generator is already running");
            }

            // 结束态后 再次调用迭代器
            if (state === GenStateCompleted) {
                if (method === "throw") {
                    throw arg;
                }

                // Be forgiving, per 25.3.3.3.3 of the spec:
                // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume
                return doneResult();
            }

            // 设置上下文的数据 用户调用的方法 以及 参数
            context.method = method;
            context.arg = arg;

            // 状态机迭代开始啦
            while (true) {

                // 先只实现next吧
                if (context.method === "next") {
                    // Setting context._sent for legacy support of Babel's
                    // function.sent implementation.
                    // next方法是可以接受用户传入的参数噢
                    context.sent = context._sent = context.arg;

                } else if (context.method === "throw") {
                    // throw新增:
                    // 实例调用throw之前必须next一次,不然状态无法变成 暂停态或者停止态
                    if (state === GenStateSuspendedStart) {
                        state = GenStateCompleted;
                        throw context.arg;
                    }

                    // 触发错误捕获事件
                    context.dispatchException(context.arg);
                } else if (context.method === "return") {
                    // 存在try语句块的情况下 调用return语句会导致代码块的执行顺序发生改变 具体变化分析见 abrupt 代码的变化
                    context.abrupt("return", context.arg);
                }

                // 进入业务代码执行的状态啦
                state = GenStateExecuting;

                // 一次执行结果的记录 context 就是最开始我们看到的上下文了 就是在这里调用我们的业务代码函数了
                // 在完成态自动过滤到结束态的时候会自动化迭代2次 注意最后次的业务代码中调用了 _context.stop();
                var record = tryCatch(innerFn, self, context);
                // 正常返回值
                if (record.type === "normal") {
                    // If an exception is thrown from innerFn, we leave state ===
                    // GenStateExecuting and loop back for another invocation.
                    // 更新状态 完成态 || 暂停态
                    state = context.done
                        ? GenStateCompleted
                        : GenStateSuspendedYield;

                    // 完成态切换到结束态 需要继续迭代状态机
                    if (record.arg === ContinueSentinel) {
                        continue;
                    }

                    // 这就是我们预期的返回值拉
                    return {
                        value: record.arg,
                        done: context.done
                    };

                    // 错误返回值 状态机继续执行 通过上面的 throw分支代码来实现
                } else if (record.type === "throw") {
                    // 需要抛出错误 迭代终止
                    state = GenStateCompleted;
                    // Dispatch the exception by looping back around to the
                    // context.dispatchException(context.arg) call above.
                    context.method = "throw";
                    context.arg = record.arg;
                }
            }
        };
    }

    // 执行业务代码 对返回结果做了包装处理
    function tryCatch(fn, obj, arg) {
        try {
            return { type: "normal", arg: fn.call(obj, arg) };
        } catch (err) {
            return { type: "throw", arg: err };
        }
    }

    // 返回一个迭代器对象
    function values(iterable) {
        if (iterable) {
            var iteratorMethod = iterable[iteratorSymbol];
            // 原生部署了迭代器生成函数 调用得到一个迭代器即可
            if (iteratorMethod) {
                return iteratorMethod.call(iterable);
            }

            // next是个函数的对象 也算吧
            if (typeof iterable.next === "function") {
                return iterable;
            }

            // 具有长度属性的类数组对象 也算吧
            if (!isNaN(iterable.length)) {
                var i = -1, next = function next() {
                    while (++i < iterable.length) {
                        if (hasOwn.call(iterable, i)) {
                            next.value = iterable[i];
                            next.done = false;
                            return next;
                        }
                    }

                    next.value = undefined;
                    next.done = true;

                    return next;
                };

                // 注意返回的这个next 函数 next.next 才是执行的函数 且持有自由变量 i 用来迭代状态
                return next.next = next;
            }
        }

        // Return an iterator with no values.
        return { next: doneResult };
    }

    exports.values = values;

    function doneResult() {
        return { value: undefined, done: true };
    }

    // 返回一个对象
    return exports;
})({});

// 转化后的代码如下:

var gen = /*#__PURE__*/regeneratorRuntime.mark(function gen() {
    return regeneratorRuntime.wrap(function gen$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.prev = 0;
            _context.next = 3;
            return console.log('a');
  
          case 3:
            _context.next = 8;
            break;
  
          case 5:
            _context.prev = 5;
            _context.t0 = _context["catch"](0);
            console.log(_context.t0);
            // 注意这里 没有break catch捕获之后既可以继续执行一次迭代
  
          case 8:
            _context.next = 10;
            return console.log('b');
  
          case 10:
            _context.next = 12;
            return console.log('c');
  
          case 12:
          case "end":
            return _context.stop();
        }
      }
    }, gen, null, [[0, 5]]);
  });
  var g = gen();
  
  g.next(); // a
  
  g.throw(new Error('准备打印b的时候报错了')); // 错误文案
  // b
  
  g.next(); // c

// 整体同之前的return情况一样 参数什么的;重点在于 catch语句的变化: _context.t0 = _context["catch"](0); 注意个参数 0 代表的是 try 起始位置; 同时我们写的catch代码被放到了它的后面;
// 终于要实现catch部分代码咯 :)

Vue(v3.0.11)源码简析之模板编译transform的相关实现(1)

transform阶段最ast节点做了一些处理,对v-if v-for这样的都在这个时刻完成了自己的实名,来看实际的过程是怎样的吧:

// 静态提升
function hoistStatic(root, context) {
    // 深度遍历一次
    walk(root, context, 
    // Root node is unfortunately non-hoistable due to potential parent
    // fallthrough attributes.
    isSingleElementRoot(root, root.children[0]));
}
// 只有一个子元素节点场景不做静态提升 原因见上文注释 用于继承 fallthrough attributes
function isSingleElementRoot(root, child) {
    const { children } = root;
    return (children.length === 1 &&
        child.type === 1 /* ELEMENT */ &&
        !isSlotOutlet(child));
}
// 深度遍历
function walk(node, context, doNotHoistNode = false) {
    let hasHoistedNode = false;
    // Some transforms, e.g. transformAssetUrls from @vue/compiler-sfc, replaces
    // static bindings with expressions. These expressions are guaranteed to be
    // constant so they are still eligible for hoisting, but they are only
    // available at runtime and therefore cannot be evaluated ahead of time.
    // This is only a concern for pre-stringification (via transformHoist by
    // @vue/compiler-dom), but doing it here allows us to perform only one full
    // walk of the AST and allow `stringifyStatic` to stop walking as soon as its
    // stringficiation threshold is met.
    let canStringify = true;
    const { children } = node;
    // 遍历跟节点的子节点即可
    for (let i = 0; i < children.length; i++) {
        const child = children[i];
        // only plain elements & text calls are eligible for hoisting.
        // 递归出口 元素节点类型中只有 真实的元素节点或者text节点 才可以被静态提升
        if (child.type === 1 /* ELEMENT */ &&
            child.tagType === 0 /* ELEMENT */) {
            // 获取内容类型 单元素节点总是不做静态提升
            const constantType = doNotHoistNode
                ? 0 /* NOT_CONSTANT */
                : getConstantType(child, context);
            if (constantType > 0 /* NOT_CONSTANT */) {
                if (constantType < 3 /* CAN_STRINGIFY */) {
                    canStringify = false;
                }
                if (constantType >= 2 /* CAN_HOIST */) {
                    // 添加patchflag
                    child.codegenNode.patchFlag =
                        -1 /* HOISTED */ + (` /* HOISTED */` );
                    // codegenNode 替换成缓存表达式
                    child.codegenNode = context.hoist(child.codegenNode);
                    hasHoistedNode = true;
                    continue;
                }
            }
            else {
                // node may contain dynamic children, but its props may be eligible for
                // hoisting.
                // 如果它的子节点中含有动态内容 那么节点本身不能静态提升 但是如果当前节点上的props是纯静态prop也是可以把props对象做静态提升的
                const codegenNode = child.codegenNode;
                if (codegenNode.type === 13 /* VNODE_CALL */) {
                    const flag = getPatchFlag(codegenNode);
                    // 纯静态对象不含动态prop的props对象才可以静态提升哦
                    if ((!flag ||
                        flag === 512 /* NEED_PATCH */ ||
                        flag === 1 /* TEXT */) &&
                        getGeneratedPropsConstantType(child, context) >=
                            2 /* CAN_HOIST */) {
                        const props = getNodeProps(child);
                        if (props) {
                            // 静态提升props
                            codegenNode.props = context.hoist(props);
                        }
                    }
                }
            }
        }
        else if (child.type === 12 /* TEXT_CALL */) {
            const contentType = getConstantType(child.content, context);
            // 根据节点的内容类型做对应的处理
            if (contentType > 0) {
                if (contentType < 3 /* CAN_STRINGIFY */) {
                    canStringify = false;
                }
                if (contentType >= 2 /* CAN_HOIST */) {
                    child.codegenNode = context.hoist(child.codegenNode);
                    hasHoistedNode = true;
                }
            }
        }
        // walk further
        // 其他逻辑形式的节点 继续遍历它的子节点
        if (child.type === 1 /* ELEMENT */) {
            const isComponent = child.tagType === 1 /* COMPONENT */;
            // 标识递归深度
            if (isComponent) {
                context.scopes.vSlot++;
            }
            // 递归遍历
            walk(child, context);
            if (isComponent) {
                context.scopes.vSlot--;
            }
        }
        // v-for节点 只有单个子节点也不做静态提升 因为它自成一个block 动态的
        else if (child.type === 11 /* FOR */) {
            // Do not hoist v-for single child because it has to be a block
            walk(child, context, child.children.length === 1);
        }
        // v-if节点同上
        else if (child.type === 9 /* IF */) {
            for (let i = 0; i < child.branches.length; i++) {
                // Do not hoist v-if single child because it has to be a block
                walk(child.branches[i], context, child.branches[i].children.length === 1);
            }
        }
    }
    // 静态提升递归结束之后 如果有自定义的 transformHoist 方法的话 执行它
    if (canStringify && hasHoistedNode && context.transformHoist) {
        context.transformHoist(children, context, node);
    }
}
// 获取节点的类型 用于判断是否含有动态内容的节点
function getConstantType(node, context) {
    const { constantCache } = context;
    switch (node.type) {
        case 1 /* ELEMENT */:
            // 其他都是某种形式的组件了
            if (node.tagType !== 0 /* ELEMENT */) {
                return 0 /* NOT_CONSTANT */;
            }
            // 缓存遍历过的node
            const cached = constantCache.get(node);
            if (cached !== undefined) {
                return cached;
            }
            const codegenNode = node.codegenNode;
            // 不是vnode call 说明这个节点是其他控制逻辑生成的 像 函数表达式生成节点 这样
            if (codegenNode.type !== 13 /* VNODE_CALL */) {
                return 0 /* NOT_CONSTANT */;
            }
            const flag = getPatchFlag(codegenNode);
            // flag等于0的时候
            if (!flag) {
                let returnType = 3 /* CAN_STRINGIFY */;
                // Element itself has no patch flag. However we still need to check:
                // 1. Even for a node with no patch flag, it is possible for it to contain
                // non-hoistable expressions that refers to scope variables, e.g. compiler
                // injected keys or cached event handlers. Therefore we need to always
                // check the codegenNode's props to be sure.
                // 判断当前节点的prop的类型
                const generatedPropsType = getGeneratedPropsConstantType(node, context);
                // props出现动态的 node就直接算动态节点了
                if (generatedPropsType === 0 /* NOT_CONSTANT */) {
                    constantCache.set(node, 0 /* NOT_CONSTANT */);
                    return 0 /* NOT_CONSTANT */;
                }
                if (generatedPropsType < returnType) {
                    returnType = generatedPropsType;
                }
                // 子节点 递归处理 以子节点的为准 只要有一个动态节点 就不行
                // 2. its children.
                for (let i = 0; i < node.children.length; i++) {
                    const childType = getConstantType(node.children[i], context);
                    if (childType === 0 /* NOT_CONSTANT */) {
                        constantCache.set(node, 0 /* NOT_CONSTANT */);
                        return 0 /* NOT_CONSTANT */;
                    }
                    if (childType < returnType) {
                        returnType = childType;
                    }
                }
                // 3. if the type is not already CAN_SKIP_PATCH which is the lowest non-0
                // type, check if any of the props can cause the type to be lowered
                // we can skip can_patch because it's guaranteed by the absence of a
                // patchFlag.
                if (returnType > 1 /* CAN_SKIP_PATCH */) {
                    for (let i = 0; i < node.props.length; i++) {
                        const p = node.props[i];
                        // 再从指令的值表达式中确认是否是动态节点
                        if (p.type === 7 /* DIRECTIVE */ && p.name === 'bind' && p.exp) {
                            const expType = getConstantType(p.exp, context);
                            if (expType === 0 /* NOT_CONSTANT */) {
                                constantCache.set(node, 0 /* NOT_CONSTANT */);
                                return 0 /* NOT_CONSTANT */;
                            }
                            if (expType < returnType) {
                                returnType = expType;
                            }
                        }
                    }
                }
                // only svg/foreignObject could be block here, however if they are
                // static then they don't need to be blocks since there will be no
                // nested updates.
                // 移除不需要的block设置了 它是静态的
                if (codegenNode.isBlock) {
                    context.removeHelper(OPEN_BLOCK);
                    context.removeHelper(CREATE_BLOCK);
                    codegenNode.isBlock = false;
                    context.helper(CREATE_VNODE);
                }
                constantCache.set(node, returnType);
                return returnType;
            }
            else {
                constantCache.set(node, 0 /* NOT_CONSTANT */);
                return 0 /* NOT_CONSTANT */;
            }
        // 静态字符串的2类
        case 2 /* TEXT */:
        case 3 /* COMMENT */:
            return 3 /* CAN_STRINGIFY */;
        // 这3个总是动态的
        case 9 /* IF */:
        case 11 /* FOR */:
        case 10 /* IF_BRANCH */:
            return 0 /* NOT_CONSTANT */;
        // 插槽和 text生成式 取决于 它内部的content
        case 5 /* INTERPOLATION */:
        case 12 /* TEXT_CALL */:
            return getConstantType(node.content, context);
        // 简单表达式 已解析确定
        case 4 /* SIMPLE_EXPRESSION */:
            return node.constType;
        case 8 /* COMPOUND_EXPRESSION */:
            let returnType = 3 /* CAN_STRINGIFY */;
            for (let i = 0; i < node.children.length; i++) {
                const child = node.children[i];
                if (isString(child) || isSymbol(child)) {
                    continue;
                }
                // 递归处理 由子节点的类型决定
                const childType = getConstantType(child, context);
                // 子节点中出现一个动态的就不行了
                if (childType === 0 /* NOT_CONSTANT */) {
                    return 0 /* NOT_CONSTANT */;
                }
                else if (childType < returnType) {
                    returnType = childType;
                }
            }
            return returnType;
        default:
            return 0 /* NOT_CONSTANT */;
    }
}
// 判断props中key和value的类型 返回当前prop集合的类型
function getGeneratedPropsConstantType(node, context) {
    let returnType = 3 /* CAN_STRINGIFY */;
    const props = getNodeProps(node);
    if (props && props.type === 15 /* JS_OBJECT_EXPRESSION */) {
        const { properties } = props;
        // prop都是key value这样的被提取出来的对象格式
        for (let i = 0; i < properties.length; i++) {
            const { key, value } = properties[i];
            const keyType = getConstantType(key, context);
            if (keyType === 0 /* NOT_CONSTANT */) {
                return keyType;
            }
            if (keyType < returnType) {
                returnType = keyType;
            }
            if (value.type !== 4 /* SIMPLE_EXPRESSION */) {
                return 0 /* NOT_CONSTANT */;
            }
            const valueType = getConstantType(value, context);
            if (valueType === 0 /* NOT_CONSTANT */) {
                return valueType;
            }
            if (valueType < returnType) {
                returnType = valueType;
            }
        }
    }
    return returnType;
}
// 获取vnode的props
function getNodeProps(node) {
    const codegenNode = node.codegenNode;
    if (codegenNode.type === 13 /* VNODE_CALL */) {
        return codegenNode.props;
    }
}
// 获取vnode的patchflag 也就是prop的组合类型
function getPatchFlag(node) {
    const flag = node.patchFlag;
    return flag ? parseInt(flag, 10) : undefined;
}

// options参数很多 总计如下:
/**
 * {
 *  isCustomElement: instance.appContext.config.isCustomElement,
    delimiters: Component.delimiters,
    hoistStatic: true,
    onError(err) {
        {
           ...
        }
    },
    isVoidTag,
    isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
    isPreTag: tag => tag === 'pre',
    decodeEntities: decodeHtmlBrowser ,
    isBuiltInComponent: (tag) => {
      ...
    },
    getNamespace(tag, parent) {
      ...
    },
    getTextMode({ tag, ns }) {
      ...  
    },
    nodeTransforms: [
      transformOnce,
      transformIf,
      transformFor,
      transformExpression,
      transformSlotOutlet,
      transformElement,
      trackSlotScopes,
      transformText,
      ignoreSideEffectTags,
      transformStyle,
      warnTransitionChildren,
    ],
    directiveTransforms: {
      on: transformOn,
      bind: transformBind,
      model: transformModel,
      cloak: noopDirectiveTransform,
      html: transformVHtml,
      text: transformVText,
      show: transformShow
    },
    transformHoist: null,
    prefixIdentifiers: false,

 * } 
 */
function createTransformContext(root, { filename = '', prefixIdentifiers = false, hoistStatic = false, cacheHandlers = false, nodeTransforms = [], directiveTransforms = {}, transformHoist = null, isBuiltInComponent = NOOP, isCustomElement = NOOP, expressionPlugins = [], scopeId = null, slotted = true, ssr = false, ssrCssVars = ``, bindingMetadata = EMPTY_OBJ, inline = false, isTS = false, onError = defaultOnError }) {
    const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/);
    const context = {
        // options
        // 如果有name的话 格式化一下 驼峰
        selfName: nameMatch && capitalize(camelize(nameMatch[1])),
        // slot 有关 dev环境一直为false 具体作用留待研究
        prefixIdentifiers,
        // 开启静态提升功能 默认是true 开启的
        hoistStatic,
        // 开启缓存事件 同prefixIdentifiers一起开启才支持 dev下也是false 不支持 先忽略
        cacheHandlers,
        // 节点转化方法集
        nodeTransforms,
        // 指令转化方法集
        directiveTransforms,
        // 自定义的或者默认的 静态内容转换函数
        transformHoist,
        // 2个元素检测方法
        isBuiltInComponent,
        isCustomElement,
        // 表达式插件 TODO
        expressionPlugins,
        // dev环境代码一直为null 具体作用留待研究 TODO
        scopeId,
        // TODO
        slotted,
        // 服务器渲染 忽略
        ssr,
        ssrCssVars,
        // TODO
        bindingMetadata,
        // TODO
        inline,
        // TODO
        isTS,
        // 错误handler
        onError,
        // state
        // ast根节点
        root,
        // 上下文自己用来存储发现的一些需要用到的辅助方法 组件 指令 最后都要同步到ast的root中去
        helpers: new Map(),
        components: new Set(),
        directives: new Set(),
        hoists: [],
        // TODO
        imports: [],
        // 解析判断过的节点是否是静态类型的缓存仓库
        constantCache: new Map(),
        // TODO
        temps: 0,
        // 缓存过的表达式的数量
        cached: 0,
        // TODO
        identifiers: Object.create(null),
        // 遍历过程记录所处的递归深度 对应所处哪种节点下的产生的递归
        scopes: {
            // 所处的v-for的深度
            vFor: 0,
            // 组件深度
            vSlot: 0,
            // 所处v-pre的深度
            vPre: 0,
            // 所处v-once的深度
            vOnce: 0
        },
        // 当前正则处理的节点的父节点
        parent: null,
        // 当前正在处理的node
        currentNode: root,
        // 当前节点在父节点的子数组中位置
        childIndex: 0,
        // methods
        // 添加helper名字且计数
        helper(name) {
            const count = context.helpers.get(name) || 0;
            context.helpers.set(name, count + 1);
            return name;
        },
        // 更新或者移除
        removeHelper(name) {
            const count = context.helpers.get(name);
            if (count) {
                const currentCount = count - 1;
                if (!currentCount) {
                    context.helpers.delete(name);
                }
                else {
                    context.helpers.set(name, currentCount);
                }
            }
        },
        // 得到在render使用的别名 createVNode => _createVNode
        helperString(name) {
            return `_${helperNameMap[context.helper(name)]}`;
        },
        // 替换node transform过程中会改变树的结构 所以要更新当前正在处理的节点
        replaceNode(node) {
            /* istanbul ignore if */
            {
                if (!context.currentNode) {
                    throw new Error(`Node being replaced is already removed.`);
                }
                if (!context.parent) {
                    throw new Error(`Cannot replace root node.`);
                }
            }
            context.parent.children[context.childIndex] = context.currentNode = node;
        },
        // 删除nodetransform过程中会改变树的结构 需要删除某个节点
        removeNode(node) {
            if (!context.parent) {
                throw new Error(`Cannot remove root node.`);
            }
            const list = context.parent.children;
            const removalIndex = node
                ? list.indexOf(node)
                : context.currentNode
                    ? context.childIndex
                    : -1;
            /* istanbul ignore if */
            if (removalIndex < 0) {
                throw new Error(`node being removed is not a child of current parent`);
            }
            if (!node || node === context.currentNode) {
                // current node removed
                context.currentNode = null;
                // 钩子
                context.onNodeRemoved();
            }
            else {
                // sibling node removed
                if (context.childIndex > removalIndex) {
                    // 更新被影响的索引
                    context.childIndex--;
                    context.onNodeRemoved();
                }
            }
            // 移除节点
            context.parent.children.splice(removalIndex, 1);
        },
        // 删除钩子
        onNodeRemoved: () => { },
        // TODO
        addIdentifiers(exp) {
        },
        // TODO
        removeIdentifiers(exp) {
        },
        // 增加一个静态内容
        hoist(exp) {
            context.hoists.push(exp);
            // 为一个静态内容取唯一的标识名字 generate函数会有对应的取值操作根据 context.hoists.length 注意值是一个简单表达式 生成代码的时候直接拿来用就可以了
            const identifier = createSimpleExpression(`_hoisted_${context.hoists.length}`, false, exp.loc, 2 /* CAN_HOIST */);
            identifier.hoisted = exp;
            return identifier;
        },
        // 缓存 + 唯一标识
        cache(exp, isVNode = false) {
            // cached作为缓存ID
            return createCacheExpression(++context.cached, exp, isVNode);
        }
    };
    return context;
}
// tranform入口函数 完成所有的转化
function transform(root, options) {
    // tranform上下文 存储信息
    const context = createTransformContext(root, options);
    // 遍历所有子节点 做转化
    traverseNode(root, context);
    // 静态提升 把发现的静态节点或者静态属性 提取到render的外层 被闭包函数所持有 以后每次render重新触发的时候直接取就可以了 不用再次生成
    if (options.hoistStatic) {
        hoistStatic(root, context);
    }
    // 为root节点生成 codegenNode信息
    if (!options.ssr) {
        createRootCodegen(root, context);
    }
    // finalize meta information
    // 上下文的作用已经结束 把汇总信息搬迁到ast 的 root中
    root.helpers = [...context.helpers.keys()];
    root.components = [...context.components];
    root.directives = [...context.directives];
    root.imports = context.imports;
    root.hoists = context.hoists;
    root.temps = context.temps;
    root.cached = context.cached;
}
function createRootCodegen(root, context) {
    const { helper, removeHelper } = context;
    const { children } = root;
    if (children.length === 1) {
        const child = children[0];
        // if the single child is an element, turn it into a block.
        if (isSingleElementRoot(root, child) && child.codegenNode) {
            // single element root is never hoisted so codegenNode will never be
            // SimpleExpressionNode
            const codegenNode = child.codegenNode;
            // 如注释所言 root只有一个单元素节点 总是作为一个block处理
            if (codegenNode.type === 13 /* VNODE_CALL */) {
                if (!codegenNode.isBlock) {
                    removeHelper(CREATE_VNODE);
                    codegenNode.isBlock = true;
                    helper(OPEN_BLOCK);
                    helper(CREATE_BLOCK);
                }
            }
            // 沿用子节点的信息即可
            root.codegenNode = codegenNode;
        }
        else {
            // - single <slot/>, IfNode, ForNode: already blocks.
            // - single text node: always patched.
            // root codegen falls through via genNode()
            // 以上场景 根节点codegenNode的作用就是返回子节点就好了
            root.codegenNode = child;
        }
    }
    else if (children.length > 1) {
        // root has multiple nodes - return a fragment block.
        let patchFlag = 64 /* STABLE_FRAGMENT */;
        let patchFlagText = PatchFlagNames[64 /* STABLE_FRAGMENT */];
        // check if the fragment actually contains a single valid child with
        // the rest being comments
        // 只有一个有效节点 其他都是注释
        if (children.filter(c => c.type !== 3 /* COMMENT */).length === 1) {
            patchFlag |= 2048 /* DEV_ROOT_FRAGMENT */;
            patchFlagText += `, ${PatchFlagNames[2048 /* DEV_ROOT_FRAGMENT */]}`;
        }
        // 多个子节点 创造一个 fragment block 即可
        root.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, root.children, patchFlag + (` /* ${patchFlagText} */` ), undefined, undefined, true);
    }
    else ;
}
// 遍历子节点数组
function traverseChildren(parent, context) {
    let i = 0;
    // 在if场景下 其他分支会被删除合并到branch中 所以只需要递归处理 traverseNode 第一个if节点 其他分支节点在处理branch中处理
    const nodeRemoved = () => {
        i--;
    };
    for (; i < parent.children.length; i++) {
        const child = parent.children[i];
        if (isString(child))
            continue;
        // 父节点 和 当前索引 置位
        context.parent = parent;
        context.childIndex = i;
        // 节点自己被删除掉时候 执行设置好的 nodeRemoved 改变外部遍历的索引位置
        context.onNodeRemoved = nodeRemoved;
        // 递归遍历
        traverseNode(child, context);
    }
}
// 遍历ast树
/**
 * nodeTransforms: [
      transformOnce, // 后序处理
      transformIf, // 后序处理
      transformFor, // 后序处理
      transformExpression,  // 先序处理
      transformSlotOutlet,  // 先序处理
      transformElement, // 后序处理 
      trackSlotScopes,  // 后序处理
      transformText,  // 先序处理
      ignoreSideEffectTags,  // 先序处理
      transformStyle,  // 先序处理
      warnTransitionChildren,  // 后序处理
    ], 
 */
function traverseNode(node, context) {
    // currentNode 置位
    context.currentNode = node;
    // apply transform plugins
    // 取出11个方法
    const { nodeTransforms } = context;
    const exitFns = [];
    for (let i = 0; i < nodeTransforms.length; i++) {
        // 注意这里 我们的 nodeTransforms 函数有2个作用 第一是直接对node做处理 属于先序遍历 第二个有些是返回一个函数 延后执行 等子节点都处理完成后再处理自己的node 属于后续遍历
        // 顺序执行 排在前面的优先级更新 if > for 逐个分析每个方法干了啥
        const onExit = nodeTransforms[i](node, context);
        if (onExit) {
            if (isArray(onExit)) {
                exitFns.push(...onExit);
            }
            else {
                exitFns.push(onExit);
            }
        }
        if (!context.currentNode) {
            // node was removed
            return;
        }
        else {
            // node may have been replaced
            // node信息可能被修改过了
            node = context.currentNode;
        }
    }
    // 根据node的类型处理 如果是叶子节点就添加对应helper 否则就递归遍历
    // nodeTransforms中先序处理的函数处理后 再深度遍历去处理子节点 逻辑也是一样
    switch (node.type) {
        // 2类简单的基础节点 递归出口
        case 3 /* COMMENT */:
            if (!context.ssr) {
                // inject import for the Comment symbol, which is needed for creating
                // comment nodes with `createVNode`
                context.helper(CREATE_COMMENT);
            }
            break;
        case 5 /* INTERPOLATION */:
            // no need to traverse, but we need to inject toString helper
            if (!context.ssr) {
                context.helper(TO_DISPLAY_STRING);
            }
            break;
        // for container types, further traverse downwards
        // v-if的节点在先序处理结果会被变成 if_node的新node 而它的branchs中放着一起解析出来的 IF_BRANCH 节点
        case 9 /* IF */:
            for (let i = 0; i < node.branches.length; i++) {
                traverseNode(node.branches[i], context);
            }
            break;
        // 遍历子节点
        case 10 /* IF_BRANCH */:
        case 11 /* FOR */:
        case 1 /* ELEMENT */:
        case 0 /* ROOT */:
            traverseChildren(node, context);
            break;
    }
    // exit transforms
    context.currentNode = node;
    let i = exitFns.length;
    // 注意后续遍历的执行顺序 后1入栈 后2入栈 后2出栈 后1出栈 
    while (i--) {
        exitFns[i]();
    }
}
// 指令统一处理函数包裹体 
function createStructuralDirectiveTransform(name, fn) {
    // 匹配函数
    const matches = isString(name)
        ? (n) => n === name
        : (n) => name.test(n);
    // if 和 for 起始初始执行的都是下面的函数 只不过 fn 不同而已 
    return (node, context) => {
        // 只处理 element上的if和for
        if (node.type === 1 /* ELEMENT */) {
            const { props } = node;
            // structural directive transforms are not concerned with slots
            // as they are handled separately in vSlot.ts
            // <template v-lost="default">... 属于插槽 有单独的处理 我们处理其他的指令
            if (node.tagType === 3 /* TEMPLATE */ && props.some(isVSlot)) {
                return;
            }
            const exitFns = [];
            // 逐个处理指令
            for (let i = 0; i < props.length; i++) {
                const prop = props[i];
                // 匹配指令名字 if 是 /^(if|else|else-if)$/ for 是 for 
                if (prop.type === 7 /* DIRECTIVE */ && matches(prop.name)) {
                    // structural directives are removed to avoid infinite recursion
                    // also we remove them *before* applying so that it can further
                    // traverse itself in case it moves the node around
                    // 处理一个移除一个
                    props.splice(i, 1);
                    i--;
                    // 取决于具体的fn是否有返回需要后续处理的回调函数
                    const onExit = fn(node, prop, context);
                    if (onExit)
                        exitFns.push(onExit);
                }
            }
            return exitFns;
        }
    };
}

// these keywords should not appear inside expressions, but operators like
// typeof, instanceof and in are allowed
// js 一些关键字
const prohibitedKeywordRE = new RegExp('\\b' +
    ('do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' +
        'super,throw,while,yield,delete,export,import,return,switch,default,' +
        'extends,finally,continue,debugger,function,arguments,typeof,void')
        .split(',')
        .join('\\b|\\b') +
    '\\b');
// strip strings in expressions
// 表达式不能出现的字符集合
const stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g;
/**
 * Validate a non-prefixed expression.
 * This is only called when using the in-browser runtime compiler since it
 * doesn't prefix expressions.
 */
// 用 new Function 校验表达式是否符合单个表达式的标准格式要求
function validateBrowserExpression(node, context, asParams = false, asRawStatements = false) {
    const exp = node.content;
    // empty expressions are validated per-directive since some directives
    // do allow empty expressions.
    if (!exp.trim()) {
        return;
    }
    try {
        new Function(asRawStatements
            ? ` ${exp} `
            : `return ${asParams ? `(${exp}) => {}` : `(${exp})`}`);
    }
    catch (e) {
        // 删除错误信息
        let message = e.message;
        const keywordMatch = exp
            .replace(stripStringRE, '')
            .match(prohibitedKeywordRE);
        if (keywordMatch) {
            message = `avoid using JavaScript keyword as property name: "${keywordMatch[0]}"`;
        }
        context.onError(createCompilerError(43 /* X_INVALID_EXPRESSION */, node.loc, undefined, message));
    }
}

// transform 第四个方法 先序直接处理node 比较简单 直接校验表达式是否合法
const transformExpression = (node, context) => {
    // 插槽表达式 {{...}}
    if (node.type === 5 /* INTERPOLATION */) {
        // 返回原始内容的同时 校验表达式合法性
        node.content = processExpression(node.content, context);
    }
    // 校验元素上绑定的动态prop值的合法性
    else if (node.type === 1 /* ELEMENT */) {
        // handle directives on element
        for (let i = 0; i < node.props.length; i++) {
            const dir = node.props[i];
            // do not process for v-on & v-for since they are special handled
            if (dir.type === 7 /* DIRECTIVE */ && dir.name !== 'for') {
                const exp = dir.exp;
                const arg = dir.arg;
                // do not process exp if this is v-on:arg - we need special handling
                // for wrapping inline statements.
                if (exp &&
                    exp.type === 4 /* SIMPLE_EXPRESSION */ &&
                    !(dir.name === 'on' && arg)) {
                    dir.exp = processExpression(exp, context, 
                    // slot args must be processed as function params
                    dir.name === 'slot');
                    // v-slot = "somePropName" 这个值 exp 是作为函数的函数使用的 (somePropName) => ...
                    // 所以 processExpression 的第三个参数 asParams 为 true
                }
                // 指令的参数是动态表达式 也需要校验 :[someProp] 之类的
                if (arg && arg.type === 4 /* SIMPLE_EXPRESSION */ && !arg.isStatic) {
                    dir.arg = processExpression(arg, context);
                }
            }
        }
    }
};
// Important: since this function uses Node.js only dependencies, it should
// always be used with a leading !true check so that it can be
// tree-shaken from the browser build.
// 校验下表达式是否合法 返回node内容即可
function processExpression(node, context, 
// some expressions like v-slot props & v-for aliases should be parsed as
// function params
// 作为函数参数
asParams = false, 
// v-on handler values may contain multiple statements
asRawStatements = false) {
    {
        {
            // simple in-browser validation (same logic in 2.x)
            validateBrowserExpression(node, context, asParams, asRawStatements);
        }
        return node;
    }
}

// v-if 的 transform 处理函数
// 调用的其实是 createStructuralDirectiveTransform 返回的闭包函数 而闭包函数中负责调用 下面的箭头函数 (node, dir, context) => {
const transformIf = createStructuralDirectiveTransform(/^(if|else|else-if)$/, (node, dir, context) => {
    // 下面的函数是被这句调用返回的 const onExit = fn(node, prop, context);
    // 可以看到 if 对node做了 processIf 的处理 然后返回一个作为后续遍历回调处理 等子节点处理完后再处理
    return processIf(node, dir, context, (ifNode, branch, isRoot) => {
        // 这个方法就是 processCodegen 
        // #1587: We need to dynamically increment the key based on the current
        // node's sibling nodes, since chained v-if/else branches are
        // rendered at the same depth
        const siblings = context.parent.children;
        let i = siblings.indexOf(ifNode);
        let key = 0;
        // 由于在一个dom片段中 可能会有多个v-if并列出现 key就是每个ifnode的唯一标识
        // 在当前ifnode之前有多个ifnode的branches的长度之和 就是key的值
        // key会被下面的后序回调所使用
        while (i-- >= 0) {
            const sibling = siblings[i];
            if (sibling && sibling.type === 9 /* IF */) {
                key += sibling.branches.length;
            }
        }
        // Exit callback. Complete the codegenNode when all children have been
        // transformed.
        // 下面的方法就是实际被放入 exitFns 的方法
        // 所以子节点转化完之后再处理的方法
        // 注意 由于是深度递归 所以第一个if节点在 递归完子节点后 会先调用下面的if分支内容
        // 而其他branch分支则依次直接触发 见processIf的实现
        return () => {
            // 根节点也就是第一个if节点处理
            if (isRoot) {
                ifNode.codegenNode = createCodegenNodeForBranch(branch, key, context);
            }
            // 其余分支情况
            else {
                // attach this branch's codegen node to the v-if root.
                const parentCondition = getParentCondition(ifNode.codegenNode);
                parentCondition.alternate = createCodegenNodeForBranch(branch, key + ifNode.branches.length - 1, context);
            }
        };
    });
});
// target-agnostic transform used for both Client and SSR
// v-if的前序处理部分在这里
function processIf(node, dir, context, processCodegen) {
    // v-if无value的情况 放一个回退的true
    if (dir.name !== 'else' &&
        (!dir.exp || !dir.exp.content.trim())) {
        const loc = dir.exp ? dir.exp.loc : node.loc;
        context.onError(createCompilerError(27 /* X_V_IF_NO_EXPRESSION */, dir.loc));
        dir.exp = createSimpleExpression(`true`, false, loc);
    }
    // 校验表达式格式
    if (dir.exp) {
        validateBrowserExpression(dir.exp, context);
    }
    // 出现v-if的时候它的紧挨着下面一般是伴随着多个 v-if v-else-if ... v-else
    if (dir.name === 'if') {
        // 得到第一个分支结构
        const branch = createIfBranch(node, dir);
        const ifNode = {
            type: 9 /* IF */,
            loc: node.loc,
            branches: [branch]
        };
        // 替换node 为 ifNode 包裹体
        context.replaceNode(ifNode);
        // 生成 codegen 也就是上面那个方法
        if (processCodegen) {
            // 注意第三个参数 isRoot
            return processCodegen(ifNode, branch, true);
        }
    }
    // 处理剩余的分支
    else {
        // locate the adjacent v-if
        const siblings = context.parent.children;
        // 收集 其他分支到v-ifnode之间的注释节点
        const comments = [];
        let i = siblings.indexOf(node);
        // 从当前分支节点往前遍历处理
        while (i-- >= -1) {
            const sibling = siblings[i];
            if (sibling && sibling.type === 3 /* COMMENT */) {
                context.removeNode(sibling);
                comments.unshift(sibling);
                continue;
            }
            // 移除空白内容节点
            if (sibling &&
                sibling.type === 2 /* TEXT */ &&
                !sibling.content.trim().length) {
                context.removeNode(sibling);
                continue;
            }
            if (sibling && sibling.type === 9 /* IF */) {
                // move the node to the if node's branches
                // 合并当前node到最近一个发现的ifnode中
                context.removeNode();
                // 得到分支包裹体
                const branch = createIfBranch(node, dir);
                // 在if 和 else-if以及else 之间允许出现注释节点 一起合入
                if (comments.length) {
                    branch.children = [...comments, ...branch.children];
                }
                // check if user is forcing same key on different branches
                // 检查用户设定的key的唯一性
                {
                    const key = branch.userKey;
                    if (key) {
                        sibling.branches.forEach(({ userKey }) => {
                            if (isSameKey(userKey, key)) {
                                context.onError(createCompilerError(28 /* X_V_IF_SAME_KEY */, branch.userKey.loc));
                            }
                        });
                    }
                }
                // 添加分支到v-ifnode中去
                sibling.branches.push(branch);
                // 非if节点生成codegen 第三个参数注意
                const onExit = processCodegen && processCodegen(sibling, branch, false);
                // since the branch was removed, it will not be traversed.
                // make sure to traverse here.
                // 由于是做了一部分的先序处理 原本的node变成分支节点收缩到它前面的ifnode的branch中了 所以需要显示调用 traverseNode 递归去处理分支的子节点
                traverseNode(branch, context);
                // call on exit
                // 其余分支的exitFn和if分支的不一样 直接执行即可
                if (onExit)
                    onExit();
                // make sure to reset currentNode after traversal to indicate this
                // node has been removed.
                context.currentNode = null;
            }
            // 没发现前面存在if节点
            else {
                context.onError(createCompilerError(29 /* X_V_ELSE_NO_ADJACENT_IF */, node.loc));
            }
            break;
        }
    }
}
// if某个分支包裹体
function createIfBranch(node, dir) {
    return {
        type: 10 /* IF_BRANCH */,
        loc: node.loc,
        condition: dir.name === 'else' ? undefined : dir.exp,
        children: node.tagType === 3 /* TEMPLATE */ && !findDir(node, 'for')
            ? node.children
            : [node],
        userKey: findProp(node, `key`)
    };
}
// 某个branch分支的 CodegenNode
function createCodegenNodeForBranch(branch, keyIndex, context) {
    if (branch.condition) {
        // 条件表达式
        return createConditionalExpression(branch.condition, createChildrenCodegenNode(branch, keyIndex, context), 
        // make sure to pass in asBlock: true so that the comment node call
        // closes the current block.
        // 默认失败情况
        createCallExpression(context.helper(CREATE_COMMENT), [
            '"v-if"' ,
            'true'
        ]));
    }
    else {
        return createChildrenCodegenNode(branch, keyIndex, context);
    }
}
// 生产某个分支节点的gencode 每个branch分支也是产生一个vnodecall
function createChildrenCodegenNode(branch, keyIndex, context) {
    const { helper, removeHelper } = context;
    // key prop
    const keyProperty = createObjectProperty(`key`, createSimpleExpression(`${keyIndex}`, false, locStub, 2 /* CAN_HOIST */));
    const { children } = branch;
    const firstChild = children[0];
    // 多个子节点的情况
    const needFragmentWrapper = children.length !== 1 || firstChild.type !== 1 /* ELEMENT */;
    if (needFragmentWrapper) {
        // v-for单节点 的情况
        if (children.length === 1 && firstChild.type === 11 /* FOR */) {
            // optimize away nested fragments when child is a ForNode
            const vnodeCall = firstChild.codegenNode;
            // 添加默认key到最前面 可以被用户的覆盖
            injectProp(vnodeCall, keyProperty, context);
            return vnodeCall;
        }
        // 普通片段
        else {
            let patchFlag = 64 /* STABLE_FRAGMENT */;
            let patchFlagText = PatchFlagNames[64 /* STABLE_FRAGMENT */];
            // check if the fragment actually contains a single valid child with
            // the rest being comments
            // 片段下只有一个有效节点
            if (children.filter(c => c.type !== 3 /* COMMENT */).length === 1) {
                patchFlag |= 2048 /* DEV_ROOT_FRAGMENT */;
                patchFlagText += `, ${PatchFlagNames[2048 /* DEV_ROOT_FRAGMENT */]}`;
            }
            // 得到vnodecall
            return createVNodeCall(context, helper(FRAGMENT), createObjectExpression([keyProperty]), children, patchFlag + (` /* ${patchFlagText} */` ), undefined, undefined, true, false, branch.loc);
        }
    }
    else {
        const vnodeCall = firstChild
            .codegenNode;
        // Change createVNode to createBlock.
        // 每个branch总是作为一个block来处理
        if (vnodeCall.type === 13 /* VNODE_CALL */ && !vnodeCall.isBlock) {
            removeHelper(CREATE_VNODE);
            vnodeCall.isBlock = true;
            helper(OPEN_BLOCK);
            helper(CREATE_BLOCK);
        }
        // inject branch key
        // 注入默认key
        injectProp(vnodeCall, keyProperty, context);
        return vnodeCall;
    }
}
// 检查2个key是否相同
function isSameKey(a, b) {
    if (!a || a.type !== b.type) {
        return false;
    }
    if (a.type === 6 /* ATTRIBUTE */) {
        if (a.value.content !== b.value.content) {
            return false;
        }
    }
    else {
        // directive
        const exp = a.exp;
        const branchExp = b.exp;
        if (exp.type !== branchExp.type) {
            return false;
        }
        if (exp.type !== 4 /* SIMPLE_EXPRESSION */ ||
            (exp.isStatic !== branchExp.isStatic ||
                exp.content !== branchExp.content)) {
            return false;
        }
    }
    return true;
}
// 找到最下面一层的 三元表达式第二个值
function getParentCondition(node) {
    while (true) {
        if (node.type === 19 /* JS_CONDITIONAL_EXPRESSION */) {
            if (node.alternate.type === 19 /* JS_CONDITIONAL_EXPRESSION */) {
                node = node.alternate;
            }
            else {
                return node;
            }
        }
        else if (node.type === 20 /* JS_CACHE_EXPRESSION */) {
            node = node.value;
        }
    }
}

Vue(v3.0.11)源码简析之createApp的相关实现

对外暴露的高级api createApp它其实调用内部的方法来实现整体逻辑,它自身的逻辑比较简洁,主要都是前文分析过的方法:

// render执行过程需要一些辅助方法 这里2个prop有关+13个node操作有关的方法
const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps);
// lazy create the renderer - this makes core renderer logic tree-shakable
// in case the user only imports reactivity utilities from Vue.
let renderer;
let enabledHydration = false;
// dev环境下只管这个就好了
// 全局唯一render
function ensureRenderer() {
    return renderer || (renderer = createRenderer(rendererOptions));
}
function ensureHydrationRenderer() {
    renderer = enabledHydration
        ? renderer
        : createHydrationRenderer(rendererOptions);
    enabledHydration = true;
    return renderer;
}
// use explicit type casts here to avoid import() calls in rolled-up d.ts
const render = ((...args) => {
    ensureRenderer().render(...args);
});
// todo
const hydrate = ((...args) => {
    ensureHydrationRenderer().hydrate(...args);
});
// 外部调用 返回一个app实例
const createApp = ((...args) => {
    // 确认render 调用render的createApp得到 app实例
    const app = ensureRenderer().createApp(...args);
    // 注入方法
    {
        injectNativeTagCheck(app);
        injectCustomElementCheck(app);
    }
    // 实例的moutn 其实就是调用 render的path
    const { mount } = app;
    // 调用app的mount之前处理下选择器
    app.mount = (containerOrSelector) => {
        const container = normalizeContainer(containerOrSelector);
        if (!container)
            return;
        const component = app._component;
        if (!isFunction(component) && !component.render && !component.template) {
            component.template = container.innerHTML;
        }
        // clear content before mounting
        container.innerHTML = '';
        // 调用实例上的mount
        const proxy = mount(container, false, container instanceof SVGElement);
        if (container instanceof Element) {
            container.removeAttribute('v-cloak');
            container.setAttribute('data-v-app', '');
        }
        // 返回的是组件实例的proxy字段
        return proxy;
    };
    return app;
});

// 注入 是否是原生dom原生元素检测方法
function injectNativeTagCheck(app) {
    // Inject `isNativeTag`
    // this is used for component name validation (dev only)
    Object.defineProperty(app.config, 'isNativeTag', {
        value: (tag) => isHTMLTag(tag) || isSVGTag(tag),
        writable: false
    });
}
// dev only
// 注入 用户自定义元素 检测方法
function injectCustomElementCheck(app) {
    if (isRuntimeOnly()) {
        const value = app.config.isCustomElement;
        Object.defineProperty(app.config, 'isCustomElement', {
            get() {
                return value;
            },
            set() {
                warn(`The \`isCustomElement\` config option is only respected when using the runtime compiler.` +
                    `If you are using the runtime-only build, \`isCustomElement\` must be passed to \`@vue/compiler-dom\` in the build setup instead` +
                    `- for example, via the \`compilerOptions\` option in vue-loader: https://vue-loader.vuejs.org/options.html#compileroptions.`);
            }
        });
    }
}
// 格式化 宿主dom容器
function normalizeContainer(container) {
    if (isString(container)) {
        const res = document.querySelector(container);
        if (!res) {
            warn(`Failed to mount app: mount target selector "${container}" returned null.`);
        }
        return res;
    }
    if (container instanceof window.ShadowRoot &&
        container.mode === 'closed') {
        warn(`mounting on a ShadowRoot with \`{mode: "closed"}\` may lead to unpredictable bugs`);
    }
    return container;
}

// 默认的错误处理方法
function defaultOnError(error) {
    throw error;
}
// 包含完整的编译错误信息提示语句的对象
function createCompilerError(code, loc, messages, additionalMessage) {
    const msg = (messages || errorMessages)[code] + (additionalMessage || ``)
        ;
    const error = new SyntaxError(String(msg));
    error.code = code;
    error.loc = loc;
    return error;
}
// 错误提示信息集合
const errorMessages = {
    // parse errors
    [0 /* ABRUPT_CLOSING_OF_EMPTY_COMMENT */]: 'Illegal comment.',
    [1 /* CDATA_IN_HTML_CONTENT */]: 'CDATA section is allowed only in XML context.',
    [2 /* DUPLICATE_ATTRIBUTE */]: 'Duplicate attribute.',
    [3 /* END_TAG_WITH_ATTRIBUTES */]: 'End tag cannot have attributes.',
    [4 /* END_TAG_WITH_TRAILING_SOLIDUS */]: "Illegal '/' in tags.",
    [5 /* EOF_BEFORE_TAG_NAME */]: 'Unexpected EOF in tag.',
    [6 /* EOF_IN_CDATA */]: 'Unexpected EOF in CDATA section.',
    [7 /* EOF_IN_COMMENT */]: 'Unexpected EOF in comment.',
    [8 /* EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT */]: 'Unexpected EOF in script.',
    [9 /* EOF_IN_TAG */]: 'Unexpected EOF in tag.',
    [10 /* INCORRECTLY_CLOSED_COMMENT */]: 'Incorrectly closed comment.',
    [11 /* INCORRECTLY_OPENED_COMMENT */]: 'Incorrectly opened comment.',
    [12 /* INVALID_FIRST_CHARACTER_OF_TAG_NAME */]: "Illegal tag name. Use '&lt;' to print '<'.",
    [13 /* MISSING_ATTRIBUTE_VALUE */]: 'Attribute value was expected.',
    [14 /* MISSING_END_TAG_NAME */]: 'End tag name was expected.',
    [15 /* MISSING_WHITESPACE_BETWEEN_ATTRIBUTES */]: 'Whitespace was expected.',
    [16 /* NESTED_COMMENT */]: "Unexpected '<!--' in comment.",
    [17 /* UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME */]: 'Attribute name cannot contain U+0022 ("), U+0027 (\'), and U+003C (<).',
    [18 /* UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE */]: 'Unquoted attribute value cannot contain U+0022 ("), U+0027 (\'), U+003C (<), U+003D (=), and U+0060 (`).',
    [19 /* UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME */]: "Attribute name cannot start with '='.",
    [21 /* UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME */]: "'<?' is allowed only in XML context.",
    [22 /* UNEXPECTED_SOLIDUS_IN_TAG */]: "Illegal '/' in tags.",
    // Vue-specific parse errors
    [23 /* X_INVALID_END_TAG */]: 'Invalid end tag.',
    [24 /* X_MISSING_END_TAG */]: 'Element is missing end tag.',
    [25 /* X_MISSING_INTERPOLATION_END */]: 'Interpolation end sign was not found.',
    [26 /* X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END */]: 'End bracket for dynamic directive argument was not found. ' +
        'Note that dynamic directive argument cannot contain spaces.',
    // transform errors
    [27 /* X_V_IF_NO_EXPRESSION */]: `v-if/v-else-if is missing expression.`,
    [28 /* X_V_IF_SAME_KEY */]: `v-if/else branches must use unique keys.`,
    [29 /* X_V_ELSE_NO_ADJACENT_IF */]: `v-else/v-else-if has no adjacent v-if.`,
    [30 /* X_V_FOR_NO_EXPRESSION */]: `v-for is missing expression.`,
    [31 /* X_V_FOR_MALFORMED_EXPRESSION */]: `v-for has invalid expression.`,
    [32 /* X_V_FOR_TEMPLATE_KEY_PLACEMENT */]: `<template v-for> key should be placed on the <template> tag.`,
    [33 /* X_V_BIND_NO_EXPRESSION */]: `v-bind is missing expression.`,
    [34 /* X_V_ON_NO_EXPRESSION */]: `v-on is missing expression.`,
    [35 /* X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET */]: `Unexpected custom directive on <slot> outlet.`,
    [36 /* X_V_SLOT_MIXED_SLOT_USAGE */]: `Mixed v-slot usage on both the component and nested <template>.` +
        `When there are multiple named slots, all slots should use <template> ` +
        `syntax to avoid scope ambiguity.`,
    [37 /* X_V_SLOT_DUPLICATE_SLOT_NAMES */]: `Duplicate slot names found. `,
    [38 /* X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN */]: `Extraneous children found when component already has explicitly named ` +
        `default slot. These children will be ignored.`,
    [39 /* X_V_SLOT_MISPLACED */]: `v-slot can only be used on components or <template> tags.`,
    [40 /* X_V_MODEL_NO_EXPRESSION */]: `v-model is missing expression.`,
    [41 /* X_V_MODEL_MALFORMED_EXPRESSION */]: `v-model value must be a valid JavaScript member expression.`,
    [42 /* X_V_MODEL_ON_SCOPE_VARIABLE */]: `v-model cannot be used on v-for or v-slot scope variables because they are not writable.`,
    [43 /* X_INVALID_EXPRESSION */]: `Error parsing JavaScript expression: `,
    [44 /* X_KEEP_ALIVE_INVALID_CHILDREN */]: `<KeepAlive> expects exactly one child component.`,
    // generic errors
    [45 /* X_PREFIX_ID_NOT_SUPPORTED */]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
    [46 /* X_MODULE_MODE_NOT_SUPPORTED */]: `ES module mode is not supported in this build of compiler.`,
    [47 /* X_CACHE_HANDLER_NOT_SUPPORTED */]: `"cacheHandlers" option is only supported when the "prefixIdentifiers" option is enabled.`,
    [48 /* X_SCOPE_ID_NOT_SUPPORTED */]: `"scopeId" option is only supported in module mode.`
};

// 所有可能会被render函数用到从vue中导入的方法标识
const FRAGMENT = Symbol(`Fragment` );
const TELEPORT = Symbol(`Teleport` );
const SUSPENSE = Symbol(`Suspense` );
const KEEP_ALIVE = Symbol(`KeepAlive` );
const BASE_TRANSITION = Symbol(`BaseTransition` );
const OPEN_BLOCK = Symbol(`openBlock` );
const CREATE_BLOCK = Symbol(`createBlock` );
const CREATE_VNODE = Symbol(`createVNode` );
const CREATE_COMMENT = Symbol(`createCommentVNode` );
const CREATE_TEXT = Symbol(`createTextVNode` );
const CREATE_STATIC = Symbol(`createStaticVNode` );
const RESOLVE_COMPONENT = Symbol(`resolveComponent` );
const RESOLVE_DYNAMIC_COMPONENT = Symbol(`resolveDynamicComponent` );
const RESOLVE_DIRECTIVE = Symbol(`resolveDirective` );
const WITH_DIRECTIVES = Symbol(`withDirectives` );
const RENDER_LIST = Symbol(`renderList` );
const RENDER_SLOT = Symbol(`renderSlot` );
const CREATE_SLOTS = Symbol(`createSlots` );
const TO_DISPLAY_STRING = Symbol(`toDisplayString` );
const MERGE_PROPS = Symbol(`mergeProps` );
const TO_HANDLERS = Symbol(`toHandlers` );
const CAMELIZE = Symbol(`camelize` );
const CAPITALIZE = Symbol(`capitalize` );
const TO_HANDLER_KEY = Symbol(`toHandlerKey` );
const SET_BLOCK_TRACKING = Symbol(`setBlockTracking` );
const PUSH_SCOPE_ID = Symbol(`pushScopeId` );
const POP_SCOPE_ID = Symbol(`popScopeId` );
const WITH_SCOPE_ID = Symbol(`withScopeId` );
const WITH_CTX = Symbol(`withCtx` );
const UNREF = Symbol(`unref` );
const IS_REF = Symbol(`isRef` );
// Name mapping for runtime helpers that need to be imported from 'vue' in
// generated code. Make sure these are correctly exported in the runtime!
// Using `any` here because TS doesn't allow symbols as index type.
// symbol对应的字符串key 生成render函数代码的是会需要用到字符串
const helperNameMap = {
    [FRAGMENT]: `Fragment`,
    [TELEPORT]: `Teleport`,
    [SUSPENSE]: `Suspense`,
    [KEEP_ALIVE]: `KeepAlive`,
    [BASE_TRANSITION]: `BaseTransition`,
    [OPEN_BLOCK]: `openBlock`,
    [CREATE_BLOCK]: `createBlock`,
    [CREATE_VNODE]: `createVNode`,
    [CREATE_COMMENT]: `createCommentVNode`,
    [CREATE_TEXT]: `createTextVNode`,
    [CREATE_STATIC]: `createStaticVNode`,
    [RESOLVE_COMPONENT]: `resolveComponent`,
    [RESOLVE_DYNAMIC_COMPONENT]: `resolveDynamicComponent`,
    [RESOLVE_DIRECTIVE]: `resolveDirective`,
    [WITH_DIRECTIVES]: `withDirectives`,
    [RENDER_LIST]: `renderList`,
    [RENDER_SLOT]: `renderSlot`,
    [CREATE_SLOTS]: `createSlots`,
    [TO_DISPLAY_STRING]: `toDisplayString`,
    [MERGE_PROPS]: `mergeProps`,
    [TO_HANDLERS]: `toHandlers`, 
    [CAMELIZE]: `camelize`,
    [CAPITALIZE]: `capitalize`,
    [TO_HANDLER_KEY]: `toHandlerKey`,
    [SET_BLOCK_TRACKING]: `setBlockTracking`,
    [PUSH_SCOPE_ID]: `pushScopeId`,
    [POP_SCOPE_ID]: `popScopeId`,
    [WITH_SCOPE_ID]: `withScopeId`,
    [WITH_CTX]: `withCtx`,
    [UNREF]: `unref`,
    [IS_REF]: `isRef`
};
// 添加其他的helpe进来
function registerRuntimeHelpers(helpers) {
    Object.getOwnPropertySymbols(helpers).forEach(s => {
        helperNameMap[s] = helpers[s];
    });
}

Vue(v3.0.11)源码简析之模板编译transform的相关实现(2)

// for的处理情况外层与if类似 主要看processFor做了什么
const transformFor = createStructuralDirectiveTransform('for', (node, dir, context) => {
    const { helper, removeHelper } = context;
    // 第二步处理内容
    return processFor(node, dir, context, forNode => {
        // create the loop render function expression now, and add the
        // iterator on exit after all children have been traversed
        // 请求添加 RENDER_LIST helper
        const renderExp = createCallExpression(helper(RENDER_LIST), [
            forNode.source
        ]);
        const keyProp = findProp(node, `key`);
        // 尝试取出我们使用v-for的时候 写的 :key='...'
        const keyProperty = keyProp
            ? createObjectProperty(`key`, keyProp.type === 6 /* ATTRIBUTE */
                ? createSimpleExpression(keyProp.value.content, true)
                : keyProp.exp)
            : null;
        const isStableFragment = forNode.source.type === 4 /* SIMPLE_EXPRESSION */ &&
            forNode.source.constType > 0 /* NOT_CONSTANT */;
        const fragmentFlag = isStableFragment
            // 顺序固定的片段列表
            ? 64 /* STABLE_FRAGMENT */
            : keyProp
                // 以下是顺序不固定 取决于遍历对象是否会改变
                // 带有key的片段
                ? 128 /* KEYED_FRAGMENT */
                // 不带有key
                : 256 /* UNKEYED_FRAGMENT */;
        // 创建v-for的vnode codegen
        // 注意 !isStableFragment 参数值 顺序不确定的是还需要追踪遍历操作
        forNode.codegenNode = createVNodeCall(context, helper(FRAGMENT), undefined, renderExp, fragmentFlag +
            (` /* ${PatchFlagNames[fragmentFlag]} */` ), undefined, undefined, true /* isBlock */, !isStableFragment /* disableTracking */, node.loc);
        return () => {
            // 后序处理方法 所有子节点处理完成
            // finish the codegen now that all children have been traversed
            let childBlock;
            const isTemplate = isTemplateNode(node);
            const { children } = forNode;
            // check <template v-for> key placement
            // template上不放key
            if (isTemplate) {
                node.children.some(c => {
                    if (c.type === 1 /* ELEMENT */) {
                        const key = findProp(c, 'key');
                        if (key) {
                            context.onError(createCompilerError(32 /* X_V_FOR_TEMPLATE_KEY_PLACEMENT */, key.loc));
                            return true;
                        }
                    }
                });
            }
            // template下可能有多个子节点 称为一个片段
            const needFragmentWrapper = children.length !== 1 || children[0].type !== 1 /* ELEMENT */;
            const slotOutlet = isSlotOutlet(node)
                ? node
                : isTemplate &&
                    node.children.length === 1 &&
                    isSlotOutlet(node.children[0])
                    // 特殊场景 template下只有一个slot标签内容
                    ? node.children[0] // api-extractor somehow fails to infer this
                    : null;
            if (slotOutlet) {
                // <slot v-for="..."> or <template v-for="..."><slot/></template>
                // 见注释
                // slot的后序处理在for的前面完成
                childBlock = slotOutlet.codegenNode;
                if (isTemplate && keyProperty) {
                    // <template v-for="..." :key="..."><slot/></template>
                    // we need to inject the key to the renderSlot() call.
                    // the props for renderSlot is passed as the 3rd argument.
                    // 注释写的很清楚了 代码的目的
                    injectProp(childBlock, keyProperty, context);
                }
            }
            else if (needFragmentWrapper) {
                // <template v-for="..."> with text or multi-elements
                // should generate a fragment block for each loop
                childBlock = createVNodeCall(context, helper(FRAGMENT), keyProperty ? createObjectExpression([keyProperty]) : undefined, node.children, 64 /* STABLE_FRAGMENT */ +
                    (` /* ${PatchFlagNames[64 /* STABLE_FRAGMENT */]} */`
                        ), undefined, undefined, true);
            }
            else {
                // Normal element v-for. Directly use the child's codegenNode
                // but mark it as a block.
                childBlock = children[0]
                    .codegenNode;
                // 处理同上
                if (isTemplate && keyProperty) {
                    injectProp(childBlock, keyProperty, context);
                }
                // 块收集 之前子节点的分析结果与当前节点不同 移除之前的判断
                if (childBlock.isBlock !== !isStableFragment) {
                    if (childBlock.isBlock) {
                        // switch from block to vnode
                        removeHelper(OPEN_BLOCK);
                        removeHelper(CREATE_BLOCK);
                    }
                    else {
                        // switch from vnode to block
                        removeHelper(CREATE_VNODE);
                    }
                }
                // 以新的当前判断为准
                childBlock.isBlock = !isStableFragment;
                if (childBlock.isBlock) {
                    helper(OPEN_BLOCK);
                    helper(CREATE_BLOCK);
                }
                else {
                    helper(CREATE_VNODE);
                }
            }
            renderExp.arguments.push(createFunctionExpression(createForLoopParams(forNode.parseResult), childBlock, true /* force newline */));
        };
    });
});

// target-agnostic transform used for both Client and SSR
// for的先序处理部分逻辑在这
function processFor(node, dir, context, processCodegen) {
    if (!dir.exp) {
        context.onError(createCompilerError(30 /* X_V_FOR_NO_EXPRESSION */, dir.loc));
        return;
    }
    // 解析for表达式参数 见 parseForExpression 实现
    const parseResult = parseForExpression(
    // can only be simple expression because vFor transform is applied
    // before expression transform.
    dir.exp, context);
    if (!parseResult) {
        context.onError(createCompilerError(31 /* X_V_FOR_MALFORMED_EXPRESSION */, dir.loc));
        return;
    }
    const { addIdentifiers, removeIdentifiers, scopes } = context;
    const { source, value, key, index } = parseResult;
    // 取出解析的变量赋值就好了
    const forNode = {
        type: 11 /* FOR */,
        loc: dir.loc,
        source,
        valueAlias: value,
        keyAlias: key,
        objectIndexAlias: index,
        parseResult,
        // 注意这里 template本身计入节点元素 只取它的子节点就好了
        children: isTemplateNode(node) ? node.children : [node]
    };
    // 替换成fornode形式
    context.replaceNode(forNode);
    // bookkeeping
    // 目前还处于先序处理 递归深度+1
    scopes.vFor++;
    // 第二步处理
    const onExit = processCodegen && processCodegen(forNode);
    return () => {
        // 遍历结束后 深度减1
        scopes.vFor--;
        if (onExit)
            onExit();
    };
}
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/;
// This regex doesn't cover the case if key or index aliases have destructuring,
// but those do not make sense in the first place, so this works in practice.
const forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/;
const stripParensRE = /^\(|\)$/g;
// 解析for循环的表达式
// (item, key, index) in list
function parseForExpression(input, context) {
    const loc = input.loc;
    const exp = input.content;
    const inMatch = exp.match(forAliasRE);
    // [(item, key, index) in list, (item, key, index), list]
    if (!inMatch)
        return;
    const [, LHS, RHS] = inMatch;
    const result = {
        // 先加source
        source: createAliasExpression(loc, RHS.trim(), exp.indexOf(RHS, LHS.length)),
        value: undefined,
        key: undefined,
        index: undefined
    };
    // 校验一下
    {
        validateBrowserExpression(result.source, context);
    }
    // 去掉()
    let valueContent = LHS.trim()
        .replace(stripParensRE, '')
        .trim();
    const trimmedOffset = LHS.indexOf(valueContent);
    // [', key, index,', 'key', 'index']
    const iteratorMatch = valueContent.match(forIteratorRE);
    if (iteratorMatch) {
        // 留下 item 我们需要的值
        valueContent = valueContent.replace(forIteratorRE, '').trim();
        // key
        const keyContent = iteratorMatch[1].trim();
        let keyOffset;
        if (keyContent) {
            keyOffset = exp.indexOf(keyContent, trimmedOffset + valueContent.length);
            // 给result的key赋值
            result.key = createAliasExpression(loc, keyContent, keyOffset);
            {
                validateBrowserExpression(result.key, context, true);
            }
        }
        if (iteratorMatch[2]) {
            // index
            const indexContent = iteratorMatch[2].trim();
            if (indexContent) {
                result.index = createAliasExpression(loc, indexContent, exp.indexOf(indexContent, result.key
                    ? keyOffset + keyContent.length
                    : trimmedOffset + valueContent.length));
                {
                    validateBrowserExpression(result.index, context, true);
                }
            }
        }
    }
    if (valueContent) {
        // item
        result.value = createAliasExpression(loc, valueContent, trimmedOffset);
        {
            validateBrowserExpression(result.value, context, true);
        }
    }
    // for最多3个选项参数就足够了
    return result;
}
// 被遍历的变量
function createAliasExpression(range, content, offset) {
    return createSimpleExpression(content, false, getInnerRange(range, offset, content.length));
}
// 构造 (value, key || _, index || _) 的 renderlist 参数
function createForLoopParams({ value, key, index }) {
    const params = [];
    if (value) {
        params.push(value);
    }
    if (key) {
        if (!value) {
            params.push(createSimpleExpression(`_`, false));
        }
        params.push(key);
    }
    if (index) {
        if (!key) {
            if (!value) {
                params.push(createSimpleExpression(`_`, false));
            }
            params.push(createSimpleExpression(`__`, false));
        }
        params.push(index);
    }
    return params;
}

const defaultFallback = createSimpleExpression(`undefined`, false);
// A NodeTransform that:
// 1. Tracks scope identifiers for scoped slots so that they don't get prefixed
//    by transformExpression. This is only applied in non-browser builds with
//    { prefixIdentifiers: true }.
// 2. Track v-slot depths so that we know a slot is inside another slot.
//    Note the exit callback is executed before buildSlots() on the same node,
//    so only nested slots see positive numbers.
// transform 第七个方法
// 如第二条注释所言 追踪slot的深度 用来区分嵌套slot的场景
// 但是具体应用场景暂时不明 TODO
const trackSlotScopes = (node, context) => {
    if (node.type === 1 /* ELEMENT */ &&
        (node.tagType === 1 /* COMPONENT */ ||
            node.tagType === 3 /* TEMPLATE */)) {
        // We are only checking non-empty v-slot here
        // since we only care about slots that introduce scope variables.
        const vSlot = findDir(node, 'slot');
        if (vSlot) {
            vSlot.exp;
            // 进入遍历带有slot的节点+1
            context.scopes.vSlot++;
            return () => {
                // 遍历结束后-1
                context.scopes.vSlot--;
            };
        }
    }
};
const buildClientSlotFn = (props, children, loc) => createFunctionExpression(props, children, false /* newline */, true /* isSlot */, children.length ? children[0].loc : loc);
// Instead of being a DirectiveTransform, v-slot processing is called during
// transformElement to build the slots object for a component.
// 除了几种特殊情况下 我们使用组件的时候写在内部的内容都会被解析成slot的形式存在
function buildSlots(node, context, buildSlotFn = buildClientSlotFn) {
    // slot的执行是需要 延迟执行的 需要使用父组件实例的作用域 因此需要 WITH_CTX
    context.helper(WITH_CTX);
    const { children, loc } = node;
    // 存放捕获到的所有slot子内容
    const slotsProperties = [];
    // 动态slot内容 对应下文的几种情况
    const dynamicSlots = [];
    // {default: () => {...}}  buildSlotFn 得到一个函数表达式 默认情况
    const buildDefaultSlotProperty = (props, children) => createObjectProperty(`default`, buildSlotFn(props, children, loc));
    // If the slot is inside a v-for or another v-slot, force it to be dynamic
    // since it likely uses a scope variable.
    // 如注释所言 v-for下可以理解 但是在 <template v-slot="name1"> <slot></slot> </template> 有什么意义呢?
    let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0;
    // 1. Check for slot with slotProps on component itself.
    //    <Comp v-slot="{ prop }"/>
    const onComponentSlot = findDir(node, 'slot', true);
    if (onComponentSlot) {
        const { arg, exp } = onComponentSlot;
        // v-slot:[varibale]="obj"
        if (arg && !isStaticExp(arg)) {
            hasDynamicSlots = true;
        }
        // 添加一个slot的属性  注意 exp 也就是 ‘obj’ 是被当做prop作为函数参数给slot函数处理的 
        slotsProperties.push(createObjectProperty(arg || createSimpleExpression('default', true), buildSlotFn(exp, children, loc)));
    }
    // 2. Iterate through children and check for template slots
    //    <template v-slot:foo="{ prop }">
    let hasTemplateSlots = false;
    let hasNamedDefaultSlot = false;
    // 被添加到默认default的slot的内容
    const implicitDefaultChildren = [];
    const seenSlotNames = new Set();
    for (let i = 0; i < children.length; i++) {
        const slotElement = children[i];
        let slotDir;
        if (!isTemplateNode(slotElement) ||
            !(slotDir = findDir(slotElement, 'slot', true))) {
            // not a <template v-slot>, skip.
            // 组件内部不是template插槽的普通元素节点子内容 放入默认default中
            if (slotElement.type !== 3 /* COMMENT */) {
                implicitDefaultChildren.push(slotElement);
            }
            continue;
        }
        // 非法场景 见文档
        if (onComponentSlot) {
            // already has on-component slot - this is incorrect usage.
            context.onError(createCompilerError(36 /* X_V_SLOT_MIXED_SLOT_USAGE */, slotDir.loc));
            break;
        }
        // 确认是 <template v-slot:foo="{ prop }"> 且用法正确
        hasTemplateSlots = true;
        const { children: slotChildren, loc: slotLoc } = slotElement;
        // 取出 v-slot:foo="{ prop }"
        const { arg: slotName = createSimpleExpression(`default`, true), exp: slotProps, loc: dirLoc } = slotDir;
        // check if name is dynamic.
        // 确认插槽名字
        let staticSlotName;
        if (isStaticExp(slotName)) {
            staticSlotName = slotName ? slotName.content : `default`;
        }
        else {
            hasDynamicSlots = true;
        }
        // 构造 这个slot的函数表达式
        const slotFunction = buildSlotFn(slotProps, slotChildren, slotLoc);
        // check if this slot is conditional (v-if/v-for)
        let vIf;
        let vElse;
        let vFor;
        // 几种带有条件的slot场景
        // <template v-slot:foo="{ prop }" v-if="condition">
        if ((vIf = findDir(slotElement, 'if'))) {
            // 动态插槽
            hasDynamicSlots = true;
            // 条件表达式 参数分别: 条件(vIf.exp) ? 预期子节点(buildDynamicSlot) : 默认内容(defaultFallback)
            dynamicSlots.push(createConditionalExpression(vIf.exp, buildDynamicSlot(slotName, slotFunction), defaultFallback));
        }
        else if ((vElse = findDir(slotElement, /^else(-if)?$/, true /* allowEmpty */))) {
            // find adjacent v-if
            let j = i;
            let prev;
            // 找到紧挨着当前的之前一个v-if节点
            while (j--) {
                prev = children[j];
                if (prev.type !== 3 /* COMMENT */) {
                    break;
                }
            }
            // 剩余的slot添加if-slot中
            if (prev && isTemplateNode(prev) && findDir(prev, 'if')) {
                // remove node
                children.splice(i, 1);
                i--;
                // attach this slot to previous conditional
                let conditional = dynamicSlots[dynamicSlots.length - 1];
                // 事实上会有 多个条件表达式 而 alternate 则是每个表达式的else情况 依次嵌套下去
                while (conditional.alternate.type === 19 /* JS_CONDITIONAL_EXPRESSION */) {
                    conditional = conditional.alternate;
                }
                // v-else-if 以及 v-else
                conditional.alternate = vElse.exp
                    ? createConditionalExpression(vElse.exp, buildDynamicSlot(slotName, slotFunction), defaultFallback)
                    : buildDynamicSlot(slotName, slotFunction);
            }
            else {
                context.onError(createCompilerError(29 /* X_V_ELSE_NO_ADJACENT_IF */, vElse.loc));
            }
        }
        else if ((vFor = findDir(slotElement, 'for'))) {
            hasDynamicSlots = true;
            // 解析 v-for="(item, key, index) in || of xxxx" 参数
            const parseResult = vFor.parseResult ||
                parseForExpression(vFor.exp, context);
            if (parseResult) {
                // Render the dynamic slots as an array and add it to the createSlot()
                // args. The runtime knows how to handle it appropriately.、
                // 请求 RENDER_LIST 辅助方法
                dynamicSlots.push(createCallExpression(context.helper(RENDER_LIST), [
                    parseResult.source,
                    createFunctionExpression(createForLoopParams(parseResult), buildDynamicSlot(slotName, slotFunction), true /* force newline */)
                ]));
            }
            else {
                context.onError(createCompilerError(31 /* X_V_FOR_MALFORMED_EXPRESSION */, vFor.loc));
            }
        }
        // 静态确定 的slot
        else {
            // check duplicate static names
            // slot名字不能重复
            if (staticSlotName) {
                if (seenSlotNames.has(staticSlotName)) {
                    context.onError(createCompilerError(37 /* X_V_SLOT_DUPLICATE_SLOT_NAMES */, dirLoc));
                    continue;
                }
                seenSlotNames.add(staticSlotName);
                if (staticSlotName === 'default') {
                    hasNamedDefaultSlot = true;
                }
            }
            // 重名校验后 添加到$slots对象中
            slotsProperties.push(createObjectProperty(slotName, slotFunction));
        }
    }
    if (!onComponentSlot) {
        if (!hasTemplateSlots) {
            // implicit default slot (on component)
            slotsProperties.push(buildDefaultSlotProperty(undefined, children));
        }
        // 显示取名default了已经
        else if (implicitDefaultChildren.length) {
            // implicit default slot (mixed with named slots)
            if (hasNamedDefaultSlot) {
                context.onError(createCompilerError(38 /* X_V_SLOT_EXTRANEOUS_DEFAULT_SLOT_CHILDREN */, implicitDefaultChildren[0].loc));
            }
            else {
                slotsProperties.push(buildDefaultSlotProperty(undefined, implicitDefaultChildren));
            }
        }
    }
    // 标记子节点中slot的整体类型 2.含有动态不确定 的slot 3. 含有转发slot <template v-slot='..'> ... <slot></lost> </template> 1. 普通情况
    const slotFlag = hasDynamicSlots
        ? 2 /* DYNAMIC */
        : hasForwardedSlots(node.children)
            ? 3 /* FORWARDED */
            : 1 /* STABLE */;
    // 在slots属性最后再加一个 _:slot类型的辅助信息
    let slots = createObjectExpression(slotsProperties.concat(createObjectProperty(`_`, 
    // 2 = compiled but dynamic = can skip normalization, but must run diff
    // 1 = compiled and static = can skip normalization AND diff as optimized
    createSimpleExpression(slotFlag + (` /* ${slotFlagsText[slotFlag]} */` ), false))), loc);
    // 动态slot再加一个辅助helper方法
    if (dynamicSlots.length) {
        slots = createCallExpression(context.helper(CREATE_SLOTS), [
            slots,
            createArrayExpression(dynamicSlots)
        ]);
    }
    return {
        slots,
        hasDynamicSlots
    };
}
// 动态slot生成表达式 [name, fn]
function buildDynamicSlot(name, fn) {
    return createObjectExpression([
        createObjectProperty(`name`, name),
        createObjectProperty(`fn`, fn)
    ]);
}
// 嵌套slot
function hasForwardedSlots(children) {
    for (let i = 0; i < children.length; i++) {
        const child = children[i];
        switch (child.type) {
            case 1 /* ELEMENT */:
                if (child.tagType === 2 /* SLOT */ ||
                    (child.tagType === 0 /* ELEMENT */ &&
                        hasForwardedSlots(child.children))) {
                    return true;
                }
                break;
            case 9 /* IF */:
                if (hasForwardedSlots(child.branches))
                    return true;
                break;
            case 10 /* IF_BRANCH */:
            case 11 /* FOR */:
                if (hasForwardedSlots(child.children))
                    return true;
                break;
        }
    }
    return false;
}

// some directive transforms (e.g. v-model) may return a symbol for runtime
// import, which should be used instead of a resolveDirective call.
// 如注释 patch过程中随vnode生命周期钩子调用的指令
const directiveImportMap = new WeakMap();

// generate a JavaScript AST for this element's codegen
// transform中的第6个方法 后序遍历处理
const transformElement = (node, context) => {
    // perform the work on exit, after all child expressions have been
    // processed and merged.
    return function postTransformElement() {
        node = context.currentNode;
        // 只对元素和组件 处以element转化
        if (!(node.type === 1 /* ELEMENT */ &&
            (node.tagType === 0 /* ELEMENT */ ||
                node.tagType === 1 /* COMPONENT */))) {
            return;
        }
        const { tag, props } = node;
        const isComponent = node.tagType === 1 /* COMPONENT */;
        // The goal of the transform is to create a codegenNode implementing the
        // VNodeCall interface.
        // element转化的作用就是给它创建 codegenNode的实现 也就是 VNodeCall
        // 得到组件或者元素的tag
        const vnodeTag = isComponent
            ? resolveComponentType(node, context)
            : `"${tag}"`;
        // 标记动态组件
        const isDynamicComponent = isObject(vnodeTag) && vnodeTag.callee === RESOLVE_DYNAMIC_COMPONENT;
        // 下面都是创建vnodecall所需要的信息
        let vnodeProps;
        let vnodeChildren;
        let vnodePatchFlag;
        let patchFlag = 0;
        let vnodeDynamicProps;
        let dynamicPropNames;
        let vnodeDirectives;
        // 是否开启 块收集 一种优化方案 分析见generate
        /**
         * 当前node开启块收集的条件:
         * 1. 动态组件<component>
         * 2. TELEPORT
         * 3. SUSPENSE
         * 4. 非组件即 普通元素 且为 (svg || foreignObject || aprop中存在 :key )
         */
        let shouldUseBlock = 
        // dynamic component may resolve to plain elements
        isDynamicComponent ||
            vnodeTag === TELEPORT ||
            vnodeTag === SUSPENSE ||
            (!isComponent &&
                // <svg> and <foreignObject> must be forced into blocks so that block
                // updates inside get proper isSVG flag at runtime. (#639, #643)
                // This is technically web-specific, but splitting the logic out of core
                // leads to too much unnecessary complexity.
                (tag === 'svg' ||
                    tag === 'foreignObject' ||
                    // #938: elements with dynamic keys should be forced into blocks
                    // 含有prop key 就算
                    findProp(node, 'key', true)));
        // props
        // 解析node上所有的用户写的prop
        if (props.length > 0) {
            // 解析props 逻辑与<slot>中调用分析相同
            const propsBuildResult = buildProps(node, context);
            vnodeProps = propsBuildResult.props;
            patchFlag = propsBuildResult.patchFlag;
            dynamicPropNames = propsBuildResult.dynamicPropNames;
            const directives = propsBuildResult.directives;
            // 指令 vnode在patch过程中会随着生活周期而应用对应阶段的指令钩子
            // 进一步解析转化指令的参数
            vnodeDirectives =
                directives && directives.length
                    ? createArrayExpression(directives.map(dir => buildDirectiveArgs(dir, context)))
                    : undefined;
        }
        // children
        if (node.children.length > 0) {
            if (vnodeTag === KEEP_ALIVE) {
                // Although a built-in component, we compile KeepAlive with raw children
                // instead of slot functions so that it can be used inside Transition
                // or other Transition-wrapping HOCs.
                // To ensure correct updates with block optimizations, we need to:
                // 1. Force keep-alive into a block. This avoids its children being
                //    collected by a parent block.
                // 见注释所言 开启block收集
                shouldUseBlock = true;
                // 2. Force keep-alive to always be updated, since it uses raw children.
                // 打上这个标签的总会patch更新
                patchFlag |= 1024 /* DYNAMIC_SLOTS */;
                // 注释写的很清楚了哈 它的子节点只能有一个哦
                if (node.children.length > 1) {
                    context.onError(createCompilerError(44 /* X_KEEP_ALIVE_INVALID_CHILDREN */, {
                        start: node.children[0].loc.start,
                        end: node.children[node.children.length - 1].loc.end,
                        source: ''
                    }));
                }
            }
            // 剩余情况 组件的子内容会被用 slot 来处理的情况
            const shouldBuildAsSlots = isComponent &&
                // Teleport is not a real component and has dedicated runtime handling
                vnodeTag !== TELEPORT &&
                // explained above.
                vnodeTag !== KEEP_ALIVE;
            if (shouldBuildAsSlots) {
                // 构建子slot内容
                const { slots, hasDynamicSlots } = buildSlots(node, context);
                // 使用组件的时候 写在 <some-component>...</component>内部的内容都会被处理成slot
                // 普通的元素的子节点是个数组 它是个对象
                vnodeChildren = slots;
                if (hasDynamicSlots) {
                    patchFlag |= 1024 /* DYNAMIC_SLOTS */;
                }
            }
            // 除去上面的情况下 剩下的对子内容就是看下是否是字符串和添加patchflag了
            else if (node.children.length === 1 && vnodeTag !== TELEPORT) {
                const child = node.children[0];
                const type = child.type;
                // check for dynamic text children
                const hasDynamicTextChild = type === 5 /* INTERPOLATION */ ||
                    type === 8 /* COMPOUND_EXPRESSION */;
                // 静态文字内容
                if (hasDynamicTextChild &&
                    getConstantType(child, context) === 0 /* NOT_CONSTANT */) {
                    patchFlag |= 1 /* TEXT */;
                }
                // pass directly if the only child is a text node
                // (plain / interpolation / expression)
                // 文字child 会被vnode格式化 没关系 直接把字符串当child节点就好了
                if (hasDynamicTextChild || type === 2 /* TEXT */) {
                    vnodeChildren = child;
                }
                else {
                    vnodeChildren = node.children;
                }
            }
            else {
                vnodeChildren = node.children;
            }
        }
        // patchFlag & dynamicPropNames
        if (patchFlag !== 0) {
            {
                // 添加对应的flag字符形式
                if (patchFlag < 0) {
                    // special flags (negative and mutually exclusive)
                    vnodePatchFlag = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`;
                }
                else {
                    // bitwise flags
                    const flagNames = Object.keys(PatchFlagNames)
                        .map(Number)
                        .filter(n => n > 0 && patchFlag & n)
                        .map(n => PatchFlagNames[n])
                        .join(`, `);
                    vnodePatchFlag = patchFlag + ` /* ${flagNames} */`;
                }
            }
            // 添加动态属性的名字集合
            if (dynamicPropNames && dynamicPropNames.length) {
                vnodeDynamicProps = stringifyDynamicPropNames(dynamicPropNames);
            }
        }
        // 构建元素node的codegenvnode
        node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren, vnodePatchFlag, vnodeDynamicProps, vnodeDirectives, !!shouldUseBlock, false /* disableTracking */, node.loc);
    };
};
// 解析组件类型
function resolveComponentType(node, context, ssr = false) {
    const { tag } = node;
    // 1. dynamic component
    // <component>
    const isProp = isComponentTag(tag)
        // :is = '...'
        ? findProp(node, 'is')
        // v-is= '...'
        : findDir(node, 'is');
    // 出现了v-is :is 动态解析组件或者内容 RESOLVE_DYNAMIC_COMPONENT
    if (isProp) {
        const exp = isProp.type === 6 /* ATTRIBUTE */
            // 静态确定的值
            ? isProp.value && createSimpleExpression(isProp.value.content, true)
            : isProp.exp;
        if (exp) {
            // 需要 RESOLVE_DYNAMIC_COMPONENT 来解析动态组件||动态替换的节点信息
            return createCallExpression(context.helper(RESOLVE_DYNAMIC_COMPONENT), [
                exp
            ]);
            // 返回
        }
    }
    // 2. built-in components (Teleport, Transition, KeepAlive, Suspense...)
    const builtIn = isCoreComponent(tag) || context.isBuiltInComponent(tag);
    // 直接返回内置组件的名字就好了 render到时候会单独调用他们对应的组件实现
    if (builtIn) {
        // built-ins are simply fallthroughs / have special handling during ssr
        // so we don't need to import their runtime equivalents
        if (!ssr)
            context.helper(builtIn);
        return builtIn;
    }
    // 5. user component (resolve)
    // 用户自定义组件
    context.helper(RESOLVE_COMPONENT);
    // 捕获一个用户自定义组件 加入上下文中
    context.components.add(tag);
    // 格式化组件名
    return toValidAssetId(tag, `component`);
}
// 解析构建props
function buildProps(node, context, props = node.props, ssr = false) {
    const { tag, loc: elementLoc } = node;
    const isComponent = node.tagType === 1 /* COMPONENT */;
    // 存储props集合
    // 普通已确定的prop
    let properties = [];
    // 存在动态属性 需要动态合并的场景
    const mergeArgs = [];
    // 运行时修改dom的指令
    const runtimeDirectives = [];
    // patchFlag analysis
    let patchFlag = 0;
    // prop中存在 ref="..."
    let hasRef = false;
    let hasClassBinding = false;
    let hasStyleBinding = false;
    // 混合事件
    let hasHydrationEventBinding = false;
    let hasDynamicKeys = false;
    let hasVnodeHook = false;
    // 收集所有的 动态绑定的静态prop key名字
    const dynamicPropNames = [];
    // patchfalg patch的辅助信息
    const analyzePatchFlag = ({ key, value }) => {
        if (isStaticExp(key)) {
            const name = key.content;
            const isEventHandler = isOn(name);
            // 混合事件的场景定义
            if (!isComponent &&
                isEventHandler &&
                // omit the flag for click handlers because hydration gives click
                // dedicated fast path.
                name.toLowerCase() !== 'onclick' &&
                // omit v-model handlers
                name !== 'onUpdate:modelValue' &&
                // omit onVnodeXXX hooks
                !isReservedProp(name)) {
                hasHydrationEventBinding = true;
            }
            if (isEventHandler && isReservedProp(name)) {
                hasVnodeHook = true;
            }
            // 下面几种场景忽略
            if (value.type === 20 /* JS_CACHE_EXPRESSION */ ||
                ((value.type === 4 /* SIMPLE_EXPRESSION */ ||
                    value.type === 8 /* COMPOUND_EXPRESSION */) &&
                    getConstantType(value, context) > 0)) {
                // skip if the prop is a cached handler or has constant value
                return;
            }
            if (name === 'ref') {
                hasRef = true;
            }
            else if (name === 'class' && !isComponent) {
                hasClassBinding = true;
            }
            else if (name === 'style' && !isComponent) {
                hasStyleBinding = true;
            }
            // 收集目前可以静态确定的prop名字
            else if (name !== 'key' && !dynamicPropNames.includes(name)) {
                dynamicPropNames.push(name);
            }
        }
        else {
            hasDynamicKeys = true;
        }
    };
    // 主体处理逻辑从这里看起
    for (let i = 0; i < props.length; i++) {
        // static attribute
        const prop = props[i];
        // 处理静态attr
        if (prop.type === 6 /* ATTRIBUTE */) {
            const { loc, name, value } = prop;
            let isStatic = true;
            if (name === 'ref') {
                hasRef = true;
            }
            // skip :is on <component>
            // is 在解析组件类型的时候已经处理过了
            if (name === 'is' && isComponentTag(tag)) {
                continue;
            }
            // createObjectProperty 参数是 key 和 value 返回一个js属性对象
            // key:createSimpleExpression(name, true, getInnerRange(loc, 0, name.length))
            // value: createSimpleExpression(value ? value.content : '', isStatic, value ? value.loc : loc))
            // 创建一个 简单的键值对对象就好了
            properties.push(createObjectProperty(createSimpleExpression(name, true, getInnerRange(loc, 0, name.length)), createSimpleExpression(value ? value.content : '', isStatic, value ? value.loc : loc)));
        }
        // 剩下的则是某种形式的指令了
        else {
            // directives
            const { name, arg, exp, loc } = prop;
            const isBind = name === 'bind';
            const isOn = name === 'on';
            // skip v-slot - it is handled by its dedicated transform.
            // buildSlots 另外处理
            if (name === 'slot') {
                // v-slot 只能在组件上使用 在父组件写的插槽内容可以使用子组件作用域的变量
                if (!isComponent) {
                    context.onError(createCompilerError(39 /* X_V_SLOT_MISPLACED */, loc));
                }
                continue;
            }
            // skip v-once - it is handled by its dedicated transform.
            // 有单独的处理
            if (name === 'once') {
                continue;
            }
            // skip v-is and :is on <component>
            // 解析组件类型的已处理
            if (name === 'is' ||
                (isBind && isComponentTag(tag) && isBindKey(arg, 'is'))) {
                continue;
            }
            // skip v-on in SSR compilation
            // 忽略
            if (isOn && ssr) {
                continue;
            }
            // special case for v-bind and v-on with no argument
            // :id="xxx" v-bind="obj" || v-on="handler"
            if (!arg && (isBind || isOn)) {
                // v-bind="obj" || v-on="hanlders"
                hasDynamicKeys = true;
                if (exp) {
                    // 如果动态prop存在的时候 也有其他静态prop同时存在 它们都在properties中
                    // 注意 dedupeProperties 去重合并的效果范围
                    if (properties.length) {
                        // createObjectExpression 得到一个对象表达式包裹体
                        mergeArgs.push(createObjectExpression(dedupeProperties(properties), elementLoc));
                        // 之前处理过的prop放入 mergeArgs
                        properties = [];
                    }
                    if (isBind) {
                        // 添加动态表达式key值
                        mergeArgs.push(exp);
                    }
                    else {
                        // v-on="obj" -> toHandlers(obj)
                        // 特殊写法 需要 TO_HANDLERS 辅助方法
                        mergeArgs.push({
                            type: 14 /* JS_CALL_EXPRESSION */,
                            loc,
                            callee: context.helper(TO_HANDLERS),
                            arguments: [exp]
                        });
                    }
                }
                // 上述情况 必须有值的表达式 错误情况
                else {
                    context.onError(createCompilerError(isBind
                        ? 33 /* X_V_BIND_NO_EXPRESSION */
                        : 34 /* X_V_ON_NO_EXPRESSION */, loc));
                }
                continue;
            }
            const directiveTransform = context.directiveTransforms[name];
            if (directiveTransform) {
                // has built-in directive transform.
                /**
                 *  directiveTransforms: {
                        on: transformOn,
                        bind: transformBind,
                        model: transformModel,
                        cloak: noopDirectiveTransform,
                        html: transformVHtml,
                        text: transformVText,
                        show: transformShow
                    },
                 */
                // 内置的几种指令 都定义的处理方法 见每个指令的单独分析
                // needRuntime 如果存在的话 是某种指令名称
                const { props, needRuntime } = directiveTransform(prop, node, context);
                // 添加对应的patchflag
                !ssr && props.forEach(analyzePatchFlag);
                // 加入处理后的props 它们也算名称确定的类型
                properties.push(...props);
                // 需要patch过程中调用的指令
                if (needRuntime) {
                    runtimeDirectives.push(prop);
                    /**
                     *  
                        const V_SHOW = Symbol(`vShow` );
                        类似上面的这些指令属于 symbol类型 添加进 directiveImportMap
                     */
                    if (isSymbol(needRuntime)) {
                        directiveImportMap.set(prop, needRuntime);
                    }
                }
            }
            else {
                // no built-in transform, this is a user custom directive.
                // 用户自定义的指令 转化执行时机在patch更新vnode和dom的时候
                runtimeDirectives.push(prop);
            }
        }
    }
    let propsExpression = undefined;
    // has v-bind="object" or v-on="object", wrap with mergeProps
    if (mergeArgs.length) {
        // 如果存在上面的情况 把静态确定的props都合并到其中
        if (properties.length) {
            mergeArgs.push(createObjectExpression(dedupeProperties(properties), elementLoc));
        }
        // 多个 v-bind="object" 请求 MERGE_PROPS 帮助
        if (mergeArgs.length > 1) {
            propsExpression = createCallExpression(context.helper(MERGE_PROPS), mergeArgs, elementLoc);
        }
        else {
            // single v-bind with nothing else - no need for a mergeProps call
            // 单个对象 obj
            propsExpression = mergeArgs[0];
        }
    }
    // 没有上面的动态prop
    else if (properties.length) {
        propsExpression = createObjectExpression(dedupeProperties(properties), elementLoc);
    }
    // patchFlag analysis
    // 动态属性prop :[xxx]
    if (hasDynamicKeys) {
        patchFlag |= 16 /* FULL_PROPS */;
    }
    else {
        // 几种场景 见analyzePatchFlag的逻辑
        if (hasClassBinding) {
            patchFlag |= 2 /* CLASS */;
        }
        if (hasStyleBinding) {
            patchFlag |= 4 /* STYLE */;
        }
        if (dynamicPropNames.length) {
            patchFlag |= 8 /* PROPS */;
        }
        if (hasHydrationEventBinding) {
            patchFlag |= 32 /* HYDRATE_EVENTS */;
        }
    }
    // 总是需要patch中更新的场景
    if ((patchFlag === 0 || patchFlag === 32 /* HYDRATE_EVENTS */) &&
        (hasRef || hasVnodeHook || runtimeDirectives.length > 0)) {
        patchFlag |= 512 /* NEED_PATCH */;
    }
    return {
        props: propsExpression,
        // 运行时指令
        directives: runtimeDirectives,
        patchFlag,
        // 所有发现的动态属性名字都在这里了
        dynamicPropNames
    };
}
// Dedupe props in an object literal.
// Literal duplicated attributes would have been warned during the parse phase,
// however, it's possible to encounter duplicated `onXXX` handlers with different
// modifiers. We also need to merge static and dynamic class / style attributes.
// - onXXX handlers / style: merge into array
// - class: merge into single expression with concatenation
// 如注释所言 事件 style class 3种情况下合并值来达到去重效果
function dedupeProperties(properties) {
    const knownProps = new Map();
    const deduped = [];
    for (let i = 0; i < properties.length; i++) {
        const prop = properties[i];
        // dynamic keys are always allowed
        // 动态属性直接放入最终队列
        if (prop.key.type === 8 /* COMPOUND_EXPRESSION */ || !prop.key.isStatic) {
            deduped.push(prop);
            continue;
        }
        const name = prop.key.content;
        const existing = knownProps.get(name);
        if (existing) {
            if (name === 'style' || name === 'class' || name.startsWith('on')) {
                // 数组化
                mergeAsArray(existing, prop);
            }
            // unexpected duplicate, should have emitted error during parse
        }
        else {
            knownProps.set(name, prop);
            deduped.push(prop);
        }
    }
    return deduped;
}
// 多个prop同名但是值 或者 参数不同 合并成一个数组值
function mergeAsArray(existing, incoming) {
    if (existing.value.type === 17 /* JS_ARRAY_EXPRESSION */) {
        existing.value.elements.push(incoming.value);
    }
    else {
        existing.value = createArrayExpression([existing.value, incoming.value], existing.loc);
    }
}
// 构建运行时指令参数
function buildDirectiveArgs(dir, context) {
    const dirArgs = [];
    const runtime = directiveImportMap.get(dir);
    if (runtime) {
        // built-in directive with runtime
        // 如v-show之类的 之前在解析的时候就被放入 directiveImportMap 了
        dirArgs.push(context.helperString(runtime));
    }
    else {
        {
            // inject statement for resolving directive
            // 请求 RESOLVE_DIRECTIVE 辅助方法
            context.helper(RESOLVE_DIRECTIVE);
            // 剩余的指令
            context.directives.add(dir.name);
            // 格式化名字
            dirArgs.push(toValidAssetId(dir.name, `directive`));
        }
    }
    const { loc } = dir;
    // 某种指令: v-demo:name.modify="test"
    // 按照一定顺序添加参数
    if (dir.exp)
        dirArgs.push(dir.exp);
    if (dir.arg) {
        if (!dir.exp) {
            dirArgs.push(`void 0`);
        }
        dirArgs.push(dir.arg);
    }
    if (Object.keys(dir.modifiers).length) {
        if (!dir.arg) {
            if (!dir.exp) {
                dirArgs.push(`void 0`);
            }
            dirArgs.push(`void 0`);
        }
        const trueExpression = createSimpleExpression(`true`, false, loc);
        // 构造modify参数的通用格式
        dirArgs.push(createObjectExpression(dir.modifiers.map(modifier => createObjectProperty(modifier, trueExpression)), loc));
    }
    // 返回一个数组表达式
    return createArrayExpression(dirArgs, dir.loc);
}
// 动态属性的name字符格式化添加 会被render函数中展示出来
function stringifyDynamicPropNames(props) {
    let propsNamesString = `[`;
    for (let i = 0, l = props.length; i < l; i++) {
        propsNamesString += JSON.stringify(props[i]);
        if (i < l - 1)
            propsNamesString += ', ';
    }
    return propsNamesString + `]`;
}
// 识别 <component></component>
function isComponentTag(tag) {
    return tag[0].toLowerCase() + tag.slice(1) === 'component';
}

Vue(v3.0.11)源码简析之render对象的相关实现(2)

全局唯一的render对象,提供了patch方法,可以支持任意类型的vnode映射到实际的宿主dom元素中,且支持挂载和更新;更新也实现了全量对比和优化对比2种方式,也用到了最长递增子序列算法,可谓是vue中最终把虚拟dom的信息实打实的同步到实际dom中的最终实现者,看下它们是如何实现的:

// The fast path for blocks.
// 动态vnode块中的对比
const patchBlockChildren = (oldChildren, newChildren, fallbackContainer, parentComponent, parentSuspense, isSVG, slotScopeIds) => {
    for (let i = 0; i < newChildren.length; i++) {
        const oldVNode = oldChildren[i];
        const newVNode = newChildren[i];
        // Determine the container (parent element) for the patch.
        // 找到合适的宿主dom元素
        const container = 
        // - In the case of a Fragment, we need to provide the actual parent
        // of the Fragment itself so it can move its children.
        oldVNode.type === Fragment ||
            // - In the case of different nodes, there is going to be a replacement
            // which also requires the correct parent container
            !isSameVNodeType(oldVNode, newVNode) ||
            // - In the case of a component, it could contain anything.
            oldVNode.shapeFlag & 6 /* COMPONENT */ ||
            oldVNode.shapeFlag & 64 /* TELEPORT */
            ? hostParentNode(oldVNode.el)
            : // In other cases, the parent container is not actually used so we
                // just pass the block element here to avoid a DOM parentNode call.
                fallbackContainer;
        // 继续递归调用patch去每个动态vnode 依次完成更新 注意最后个参数 开启优化对比
        patch(oldVNode, newVNode, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, true);
    }
};
// 全量对比更新props对象
const patchProps = (el, vnode, oldProps, newProps, parentComponent, parentSuspense, isSVG) => {
    if (oldProps !== newProps) {
        // 新prop值添加和覆盖
        for (const key in newProps) {
            // empty string is not valid prop
            if (isReservedProp(key))
                continue;
            const next = newProps[key];
            const prev = oldProps[key];
            if (next !== prev ||
                (hostForcePatchProp && hostForcePatchProp(el, key))) {
                hostPatchProp(el, key, prev, next, isSVG, vnode.children, parentComponent, parentSuspense, unmountChildren);
            }
        }
        // 不存在的props key值移除
        if (oldProps !== EMPTY_OBJ) {
            for (const key in oldProps) {
                if (!isReservedProp(key) && !(key in newProps)) {
                    hostPatchProp(el, key, oldProps[key], null, isSVG, vnode.children, parentComponent, parentSuspense, unmountChildren);
                }
            }
        }
    }
};
// 来看看 片段vnode是如何处理的 它并不直接反映在最终的dom结构中 只是一种辅助vnode节点
const processFragment = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
    // 建立首尾2个插入的锚点
    const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateText(''));
    const fragmentEndAnchor = (n2.anchor = n1 ? n1.anchor : hostCreateText(''));
    let { patchFlag, dynamicChildren, slotScopeIds: fragmentSlotScopeIds } = n2;
    // 存在优化对比
    if (patchFlag > 0) {
        optimized = true;
    }
    // check if this is a slot fragment with :slotted scope ids
    // 合并 slotScopeIds 尾部追加 作用不明暂时 TODO
    if (fragmentSlotScopeIds) {
        slotScopeIds = slotScopeIds
            ? slotScopeIds.concat(fragmentSlotScopeIds)
            : fragmentSlotScopeIds;
    }
    // 关闭优化
    if (isHmrUpdating) {
        // HMR updated, force full diff
        patchFlag = 0;
        optimized = false;
        dynamicChildren = null;
    }
    // 旧片段不存在 就是挂载新的咯
    if (n1 == null) {
        hostInsert(fragmentStartAnchor, container, anchor);
        hostInsert(fragmentEndAnchor, container, anchor);
        // a fragment can only have array children
        // since they are either generated by the compiler, or implicitly created
        // from arrays.
        // 基于 fragmentEndAnchor 锚点位置 挂载 mountChildren
        mountChildren(n2.children, container, fragmentEndAnchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
    }
    // 又到了对比的时候了
    else {
        if (patchFlag > 0 &&
            patchFlag & 64 /* STABLE_FRAGMENT */ &&
            dynamicChildren &&
            // #2715 the previous fragment could've been a BAILed one as a result
            // of renderSlot() with no valid children
            n1.dynamicChildren) {
            // a stable fragment (template root or <template v-for>) doesn't need to
            // patch children order, but it may contain dynamicChildren.
            // 可优化对比下 对比block中的内容即可
            patchBlockChildren(n1.dynamicChildren, dynamicChildren, container, parentComponent, parentSuspense, isSVG, slotScopeIds);
            // 同上文 实现分析见下文
            if (parentComponent && parentComponent.type.__hmrId) {
                traverseStaticChildren(n1, n2);
            }
            else if (
            // #2080 if the stable fragment has a key, it's a <template v-for> that may
            //  get moved around. Make sure all root level vnodes inherit el.
            // #2134 or if it's a component root, it may also get moved around
            // as the component is being moved.
            // TODO
            n2.key != null ||
                (parentComponent && n2 === parentComponent.subTree)) {
                traverseStaticChildren(n1, n2, true /* shallow */);
            }
        }
        else {
            // keyed / unkeyed, or manual fragments.
            // for keyed & unkeyed, since they are compiler generated from v-for,
            // each child is guaranteed to be a block so the fragment will never
            // have dynamicChildren.
            // 全量对比children列表即可 如注释所言
            patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
        }
    }
};
// 处理组件vnode
const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
    // 被某个patch调用传入的 slotScopeIds 赋值
    n2.slotScopeIds = slotScopeIds;
    if (n1 == null) {
        // KEPT_ALIVE 接管
        if (n2.shapeFlag & 512 /* COMPONENT_KEPT_ALIVE */) {
            parentComponent.ctx.activate(n2, container, anchor, isSVG, optimized);
        }
        // 挂载组件vnode
        else {
            mountComponent(n2, container, anchor, parentComponent, parentSuspense, isSVG, optimized);
        }
    }
    // 更新组件vnode
    else {
        updateComponent(n1, n2, optimized);
    }
};
// 看看组件vnode是如何被挂载的
const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
    // 每个组件创建一个组件实例
    const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense));
    // 忽略
    if (instance.type.__hmrId) {
        registerHMR(instance);
    }
    {
        // 当前待挂载的vnode入栈
        pushWarningContext(initialVNode);
        // 打上对应的开始标记
        startMeasure(instance, `mount`);
    }
    // inject renderer internals for keepAlive
    // keepalive需要internals来使用一些render的内部方法
    if (isKeepAlive(initialVNode)) {
        instance.ctx.renderer = internals;
    }
    // resolve props and slots for setup context
    {
        startMeasure(instance, `init`);
    }
    // 解析props和slots内容以及其他用户配置项 并导入到组件实例上
    setupComponent(instance);
    {
        endMeasure(instance, `init`);
    }
    // setup() is async. This component relies on async logic to be resolved
    // before proceeding
    // setup的结果存在异步依赖的情况
    if (instance.asyncDep) {
        // Suspense 来控制当异步有结果后该做什么 setupRenderEffect
        parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect);
        // Give it a placeholder if this is not hydration
        // TODO handle self-defined fallback
        // 暂时添加一个注释节点占位符 等再次更新vnode的时候用实际组件vnode替换掉
        if (!initialVNode.el) {
            const placeholder = (instance.subTree = createVNode(Comment));
            processCommentNode(null, placeholder, container, anchor);
        }
        return;
    }
    // 同步结果 直接调用setupRenderEffect 建立响应式render effect函数
    setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized);
    {
        popWarningContext();
        endMeasure(instance, `mount`);
    }
};
// 2个组件vnode对比更新
const updateComponent = (n1, n2, optimized) => {
    const instance = (n2.component = n1.component);
    // 检测是否有prop或者子内容发生改变了
    if (shouldUpdateComponent(n1, n2, optimized)) {
        if (instance.asyncDep &&
            !instance.asyncResolved) {
            // async & still pending - just update props and slots
            // since the component's reactive effect for render isn't set-up yet
            {
                pushWarningContext(n2);
            }
            // 异步组件尚未加载完成 不过props和slots内容是来自父组件的 已经确定的内容可以先处理
            updateComponentPreRender(instance, n2, optimized);
            {
                popWarningContext();
            }
            return;
        }
        else {
            // 执行更新
            // normal update
            instance.next = n2;
            // in case the child component is also queued, remove it to avoid
            // double updating the same child component in the same flush.
            // 下面有执行了 从队列中移除
            invalidateJob(instance.update);
            // instance.update is the reactive effect runner.
            instance.update();
        }
    }
    else {
        // no update needed. just copy over properties
        // 更新引用即可
        n2.component = n1.component;
        n2.el = n1.el;
        instance.vnode = n2;
    }
};
// 重点:响应式renderEffect的具体实现
const setupRenderEffect = (instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized) => {
    // create reactive effect for rendering
    // 注意 这个是立即触发一次getter 也就是第一个参数函数 因为我们要生成vnode 子树 用于挂载 同时建立依赖收集
    instance.update = effect(function componentEffect() {
        if (!instance.isMounted) {
            let vnodeHook;
            const { el, props } = initialVNode;
            // 这才是组件的生命周期应用的地方
            const { bm, m, parent } = instance;
            // beforeMount hook
            if (bm) {
                invokeArrayFns(bm);
            }
            // onVnodeBeforeMount
            if ((vnodeHook = props && props.onVnodeBeforeMount)) {
                invokeVNodeHook(vnodeHook, parent, initialVNode);
            }
            // render
            {
                startMeasure(instance, `render`);
            }
            // 调用render 得到vnode子树 处理属性继承等逻辑
            // 同时触发这个组件上的响应式属性的依赖收集
            const subTree = (instance.subTree = renderComponentRoot(instance));
            {
                endMeasure(instance, `render`);
            }
            // 忽略
            if (el && hydrateNode) {
                {
                    startMeasure(instance, `hydrate`);
                }
                // vnode has adopted host node - perform hydration instead of mount.
                hydrateNode(initialVNode.el, subTree, instance, parentSuspense, null);
                {
                    endMeasure(instance, `hydrate`);
                }
            }
            else {
                {
                    startMeasure(instance, `patch`);
                }
                // 再次调用patch 把子树内容更新到dom上
                patch(null, subTree, container, anchor, instance, parentSuspense, isSVG);
                {
                    endMeasure(instance, `patch`);
                }
                initialVNode.el = subTree.el;
            }
            // 对应的钩子们 都推入post队列中 render后执行
            // mounted hook
            if (m) {
                queuePostRenderEffect(m, parentSuspense);
            }
            // onVnodeMounted
            if ((vnodeHook = props && props.onVnodeMounted)) {
                const scopedInitialVNode = initialVNode;
                queuePostRenderEffect(() => {
                    invokeVNodeHook(vnodeHook, parent, scopedInitialVNode);
                }, parentSuspense);
            }
            // activated hook for keep-alive roots.
            // #1742 activated hook must be accessed after first render
            // since the hook may be injected by a child keep-alive
            const { a } = instance;
            if (a &&
                initialVNode.shapeFlag & 256 /* COMPONENT_SHOULD_KEEP_ALIVE */) {
                queuePostRenderEffect(a, parentSuspense);
            }
            // 实例挂载置位
            instance.isMounted = true;
            {
                // 忽略
                devtoolsComponentAdded(instance);
            }
            // #2458: deference mount-only object parameters to prevent memleaks
            initialVNode = container = anchor = null;
        }
        else {
            // 再次触发effect的时候 子组件自己更新或者父组件更新重新渲染对比子组件 2种情况next值不同
            // updateComponent
            // This is triggered by mutation of component's own state (next: null)
            // OR parent calling processComponent (next: VNode)
            let { next, bu, u, parent, vnode } = instance;
            let originNext = next;
            let vnodeHook;
            {
                pushWarningContext(next || instance.vnode);
            }
            if (next) {
                next.el = vnode.el;
                // 已确定的内容先更新
                updateComponentPreRender(instance, next, optimized);
            }
            else {
                next = vnode;
            }
            // beforeUpdate hook
            if (bu) {
                invokeArrayFns(bu);
            }
            // onVnodeBeforeUpdate
            if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
                invokeVNodeHook(vnodeHook, parent, next, vnode);
            }
            // render
            {
                startMeasure(instance, `render`);
            }
            // 得到新子树vnode
            const nextTree = renderComponentRoot(instance);
            {
                endMeasure(instance, `render`);
            }
            const prevTree = instance.subTree;
            instance.subTree = nextTree;
            {
                startMeasure(instance, `patch`);
            }
            // 还是递归调用patch去完成所有的事情
            patch(prevTree, nextTree, 
            // parent may have changed if it's in a teleport
            hostParentNode(prevTree.el), 
            // anchor may have changed if it's in a fragment
            getNextHostNode(prevTree), instance, parentSuspense, isSVG);
            {
                endMeasure(instance, `patch`);
            }
            next.el = nextTree.el;
            if (originNext === null) {
                // self-triggered update. In case of HOC, update parent component
                // vnode el. HOC is indicated by parent instance's subTree pointing
                // to child component's vnode
                // 更新高阶组件的el TODO
                updateHOCHostEl(instance, nextTree.el);
            }
            // updated hook
            if (u) {
                queuePostRenderEffect(u, parentSuspense);
            }
            // onVnodeUpdated
            if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
                queuePostRenderEffect(() => {
                    invokeVNodeHook(vnodeHook, parent, next, vnode);
                }, parentSuspense);
            }
            {
                devtoolsComponentUpdated(instance);
            }
            {
                popWarningContext();
            }
        }
    }, createDevEffectOptions(instance) );
};
// 触发render之前更新组件实例部分属性
const updateComponentPreRender = (instance, nextVNode, optimized) => {
    nextVNode.component = instance;
    const prevProps = instance.vnode.props;
    instance.vnode = nextVNode;
    instance.next = null;
    // 重新编译得到的props 把它的新值更新到原本实例上的旧props对象中去
    updateProps(instance, nextVNode.props, prevProps, optimized);
    // 对应上面类似的更新slots对象
    updateSlots(instance, nextVNode.children, optimized);
    pauseTracking();
    // props update may have triggered pre-flush watchers.
    // flush them before the render update.
    // 组件实例的props是浅层响应式
    flushPreFlushCbs(undefined, instance.update);
    resetTracking();
};
// 全量对比子节点
const patchChildren = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized = false) => {
    const c1 = n1 && n1.children;
    const prevShapeFlag = n1 ? n1.shapeFlag : 0;
    const c2 = n2.children;
    const { patchFlag, shapeFlag } = n2;
    // fast path
    // 片段 对比2个列表即可
    if (patchFlag > 0) {
        if (patchFlag & 128 /* KEYED_FRAGMENT */) {
            // this could be either fully-keyed or mixed (some keyed some not)
            // presence of patchFlag means children are guaranteed to be arrays
            // 调用 patchKeyedChildren 对比带key的2个列表
            patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
            return;
        }
        else if (patchFlag & 256 /* UNKEYED_FRAGMENT */) {
            // unkeyed
            // 调用 patchUnkeyedChildren 处理不带key的
            patchUnkeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
            return;
        }
    }
    // 之前是子节点数组 现在是个字符串 替换掉就好了
    // children has 3 possibilities: text, array or no children.
    if (shapeFlag & 8 /* TEXT_CHILDREN */) {
        // text children fast path
        if (prevShapeFlag & 16 /* ARRAY_CHILDREN */) {
            unmountChildren(c1, parentComponent, parentSuspense);
        }
        if (c2 !== c1) {
            hostSetElementText(container, c2);
        }
    }
    else {
        // 2个都是数组列表子节点 全量对比
        if (prevShapeFlag & 16 /* ARRAY_CHILDREN */) {
            // prev children was array
            if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
                // two arrays, cannot assume anything, do full diff
                patchKeyedChildren(c1, c2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
            }
            else {
                // no new children, just unmount old
                // 没有子节点了
                unmountChildren(c1, parentComponent, parentSuspense, true);
            }
        }
        // 如注释所言
        else {
            // prev children was text OR null
            // new children is array OR null
            if (prevShapeFlag & 8 /* TEXT_CHILDREN */) {
                hostSetElementText(container, '');
            }
            // mount new if array
            if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
                mountChildren(c2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
            }
        }
    }
};
// 对比2个不带key的子数组
const patchUnkeyedChildren = (c1, c2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
    c1 = c1 || EMPTY_ARR;
    c2 = c2 || EMPTY_ARR;
    const oldLength = c1.length;
    const newLength = c2.length;
    const commonLength = Math.min(oldLength, newLength);
    let i;
    // 从这个逻辑可以看到 这个没有key的情况 只是简单的按照长度一个一个依次对比 相同dom元素的2次列表对比 会复用了原dom元素 只是更新对应属性而已
    for (i = 0; i < commonLength; i++) {
        const nextChild = (c2[i] = optimized
            // 优化模式主要是 mounted过的vnode才克隆 否则沿用这个vnode本身即可
            ? cloneIfMounted(c2[i])
            // 非优化情况总是会克隆一个出来
            : normalizeVNode(c2[i]));
        // 还是调用patch去更新公共长度内的节点
        patch(c1[i], nextChild, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
    }
    // 公共长度之外的部分 移除或者增加 即可 注意最后个参数 commonLength
    if (oldLength > newLength) {
        // remove old
        unmountChildren(c1, parentComponent, parentSuspense, true, false, commonLength);
    }
    else {
        // mount new
        mountChildren(c2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, commonLength);
    }
};
// can be all-keyed or mixed
// 而中间含有(部分)key的对比子数组情况
const patchKeyedChildren = (c1, c2, container, parentAnchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
    let i = 0;
    const l2 = c2.length;
    let e1 = c1.length - 1; // prev ending index
    let e2 = l2 - 1; // next ending index
    // 1. sync from start
    // (a b) c
    // (a b) d e
    while (i <= e1 && i <= e2) {
        const n1 = c1[i];
        const n2 = (c2[i] = optimized
            ? cloneIfMounted(c2[i])
            : normalizeVNode(c2[i]));
        // 从数组的前面开始比较 相同的vnode 递归调用patch去对比更新它在子节点们
        if (isSameVNodeType(n1, n2)) {
            patch(n1, n2, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
        }
        // 直到遇到2者不同或者结束的时候
        else {
            break;
        }
        i++;
    }
    // 2. sync from end
    // a (b c)
    // d e (b c)
    // 尝试从尾部开始对比 对比后 缩短长度值 逻辑同上
    while (i <= e1 && i <= e2) {
        const n1 = c1[e1];
        const n2 = (c2[e2] = optimized
            ? cloneIfMounted(c2[e2])
            : normalizeVNode(c2[e2]));
        if (isSameVNodeType(n1, n2)) {
            patch(n1, n2, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
        }
        else {
            break;
        }
        e1--;
        e2--;
    }
    // 3. common sequence + mount
    // (a b)
    // (a b) c
    // i = 2, e1 = 1, e2 = 2
    // (a b)
    // c (a b)
    // i = 0, e1 = -1, e2 = 0
    // c1遍历完了 c2还有 那剩下的直接添加就好了
    if (i > e1) {
        if (i <= e2) {
            const nextPos = e2 + 1;
            const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor;
            while (i <= e2) {
                patch(null, (c2[i] = optimized
                    ? cloneIfMounted(c2[i])
                    : normalizeVNode(c2[i])), container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
                i++;
            }
        }
    }
    // 4. common sequence + unmount
    // (a b) c
    // (a b)
    // i = 2, e1 = 2, e2 = 1
    // a (b c)
    // (b c)
    // i = 0, e1 = 0, e2 = -1
    // c2完了 c1还有 把c1存在的删除吧
    else if (i > e2) {
        while (i <= e1) {
            unmount(c1[i], parentComponent, parentSuspense, true);
            i++;
        }
    }
    // 5. unknown sequence
    // [i ... e1 + 1]: a b [c d e] f g
    // [i ... e2 + 1]: a b [e d c h] f g
    // i = 2, e1 = 4, e2 = 5
    // 除去上面四种最理想简单的场景外 剩下的必须要做遍历处理了
    else {
        const s1 = i; // prev starting index
        const s2 = i; // next starting index
        // 5.1 build key:index map for newChildren
        const keyToNewIndexMap = new Map();
        // 建立新节点的key和这个key所在c2数组中的索引位置的map结构
        for (i = s2; i <= e2; i++) {
            const nextChild = (c2[i] = optimized
                ? cloneIfMounted(c2[i])
                : normalizeVNode(c2[i]));
            if (nextChild.key != null) {
                if (keyToNewIndexMap.has(nextChild.key)) {
                    warn(`Duplicate keys found during update:`, JSON.stringify(nextChild.key), `Make sure keys are unique.`);
                }
                keyToNewIndexMap.set(nextChild.key, i);
            }
        }
        // 5.2 loop through old children left to be patched and try to patch
        // matching nodes & remove nodes that are no longer present
        let j;
        let patched = 0;
        // 待遍历处理的新节点个数
        const toBePatched = e2 - s2 + 1;
        let moved = false;
        // used to track whether any node has moved
        // 测试是否发生了移动了
        let maxNewIndexSoFar = 0;
        // works as Map<newIndex, oldIndex>
        // Note that oldIndex is offset by +1
        // and oldIndex = 0 is a special value indicating the new node has
        // no corresponding old node.
        // used for determining longest stable subsequence
        // 同类型vnode根据c2位置找到它之前在c1的位置的map
        const newIndexToOldIndexMap = new Array(toBePatched);
        for (i = 0; i < toBePatched; i++)
            newIndexToOldIndexMap[i] = 0;
        // 先遍历c1数组
        for (i = s1; i <= e1; i++) {
            const prevChild = c1[i];
            // c2数组已处理完 剩下 都是可以删除的了
            if (patched >= toBePatched) {
                // all new children have been patched so this can only be a removal
                unmount(prevChild, parentComponent, parentSuspense, true);
                continue;
            }
            let newIndex;
            if (prevChild.key != null) {
                // 根据旧vnode的key找到相同key对应新的vnode的在c2中的位置
                newIndex = keyToNewIndexMap.get(prevChild.key);
            }
            else {
                // key-less node, try to locate a key-less node of the same type
                // 缺少key 遍历找到第一个同类型的vnode 没办法 只能这样找
                for (j = s2; j <= e2; j++) {
                    if (newIndexToOldIndexMap[j - s2] === 0 &&
                        isSameVNodeType(prevChild, c2[j])) {
                        newIndex = j;
                        break;
                    }
                }
            }
            if (newIndex === undefined) {
                unmount(prevChild, parentComponent, parentSuspense, true);
            }
            else {
                // 标记newIndex对应的新vnode在旧c1数组中的位置
                newIndexToOldIndexMap[newIndex - s2] = i + 1;
                // 更新已对比过的新vnode最远位置
                if (newIndex >= maxNewIndexSoFar) {
                    maxNewIndexSoFar = newIndex;
                }
                // 因为我们按顺序遍历旧c1数组 如果每次根据c1的元素从前往后从c2中找对应的新vnode
                // 如果newIndex存在 但是却没有依次递增 那就说明原c1对应的vnode在新的数组中 顺序被打乱了呢 我们称之为 被移动乱序了
                else {
                    moved = true;
                }
                // 在调整实际dom中排列的顺序之前 先把更新dom的工作做了 递归调用patch去完成
                // 只是更新了这个vnode对应的el的实际属性 但是最终它的位置如果需要改变 还需要下面的方法去处理
                patch(prevChild, c2[newIndex], container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
                // 已处理+1
                patched++;
            }
        }
        // 5.3 move and mount
        // generate longest stable subsequence only when nodes have moved
        // 接下来处理c2中剩下的 以及 移动的情况
        const increasingNewIndexSequence = moved
            // getSequence用最长递增子序列算法得到最长递增子序列
            // newIndexToOldIndexMap 的索引代表这个vnode在c2的位置 值代表着c1中的位置
            ? getSequence(newIndexToOldIndexMap)
            : EMPTY_ARR;
        j = increasingNewIndexSequence.length - 1;
        // looping backwards so that we can use last patched node as anchor
        // 开始挂载或者移动的c2的元素了
        for (i = toBePatched - 1; i >= 0; i--) {
            const nextIndex = s2 + i;
            // 取到c2中的新vnode
            const nextChild = c2[nextIndex];
            // 注意锚点元素在不断靠前
            const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : parentAnchor;
            // 未被赋值 代表c1中没有的新node
            if (newIndexToOldIndexMap[i] === 0) {
                // mount new
                patch(null, nextChild, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
            }
            // 已有的 那就准备移动了
            else if (moved) {
                // move if:
                // There is no stable subsequence (e.g. a reverse)
                // OR current node is not among the stable sequence
                // increasingNewIndexSequence 存储着已知的最长递增子序列数组
                // 完全反序的 无递增子序列 所有的都需要移动
                // 不与递增序列相匹配的的代表需要被移动 匹配的保留位置不动 不需要移动
                if (j < 0 || i !== increasingNewIndexSequence[j]) {
                    move(nextChild, container, anchor, 2 /* REORDER */);
                }
                // i === increasingNewIndexSequence[j] 匹配一个 就缩短increasingNewIndexSequence 对比前一个
                else {
                    j--;
                }
            }
        }
    }
};
// 移动元素而不是删除然后重新创建一个新的
const move = (vnode, container, anchor, moveType, parentSuspense = null) => {
    const { el, type, transition, children, shapeFlag } = vnode;
    if (shapeFlag & 6 /* COMPONENT */) {
        // 对于组件而言 其实我们移动是它的子树
        move(vnode.component.subTree, container, anchor, moveType);
        return;
    }
    // 调用自己的move实现
    if (shapeFlag & 128 /* SUSPENSE */) {
        vnode.suspense.move(container, anchor, moveType);
        return;
    }
    // 调用自己的move实现
    if (shapeFlag & 64 /* TELEPORT */) {
        type.move(vnode, container, anchor, internals);
        return;
    }
    // 逐一移动片段的元素
    if (type === Fragment) {
        hostInsert(el, container, anchor);
        for (let i = 0; i < children.length; i++) {
            move(children[i], container, anchor, moveType);
        }
        hostInsert(vnode.anchor, container, anchor);
        return;
    }
    // 移动一段html
    if (type === Static) {
        moveStaticNode(vnode, container, anchor);
        return;
    }
    // single nodes
    const needTransition = moveType !== 2 /* REORDER */ &&
        shapeFlag & 1 /* ELEMENT */ &&
        transition;
    // transition在移动时候的分析详情见transition组件的分析
    if (needTransition) {
        if (moveType === 0 /* ENTER */) {
            transition.beforeEnter(el);
            hostInsert(el, container, anchor);
            queuePostRenderEffect(() => transition.enter(el), parentSuspense);
        }
        else {
            const { leave, delayLeave, afterLeave } = transition;
            const remove = () => hostInsert(el, container, anchor);
            const performLeave = () => {
                leave(el, () => {
                    remove();
                    afterLeave && afterLeave();
                });
            };
            if (delayLeave) {
                delayLeave(el, remove, performLeave);
            }
            else {
                performLeave();
            }
        }
    }
    else {
        // 简单的把已存在dom树中的元素移动到新位置 一个dom元素只能在树中存在一个位置 移动已在树中的 原来位置上的就不在了
        hostInsert(el, container, anchor);
    }
};
// 卸载已挂载的vnode
const unmount = (vnode, parentComponent, parentSuspense, doRemove = false, optimized = false) => {
    const { type, props, ref, children, dynamicChildren, shapeFlag, patchFlag, dirs } = vnode;
    // unset ref
    // 首先移除父组件对自己的引用
    if (ref != null) {
        setRef(ref, null, parentSuspense, null);
    }
    // 调用自己的实现
    if (shapeFlag & 256 /* COMPONENT_SHOULD_KEEP_ALIVE */) {
        parentComponent.ctx.deactivate(vnode);
        return;
    }
    const shouldInvokeDirs = shapeFlag & 1 /* ELEMENT */ && dirs;
    // vnode 钩子
    let vnodeHook;
    if ((vnodeHook = props && props.onVnodeBeforeUnmount)) {
        invokeVNodeHook(vnodeHook, parentComponent, vnode);
    }
    // 普通组件vnode走自己的逻辑
    if (shapeFlag & 6 /* COMPONENT */) {
        unmountComponent(vnode.component, parentSuspense, doRemove);
    }
    else {
        // 调用自己实现
        if (shapeFlag & 128 /* SUSPENSE */) {
            vnode.suspense.unmount(parentSuspense, doRemove);
            return;
        }
        // 调用指令钩子
        if (shouldInvokeDirs) {
            invokeDirectiveHook(vnode, null, parentComponent, 'beforeUnmount');
        }
        // 调用自己实现
        if (shapeFlag & 64 /* TELEPORT */) {
            vnode.type.remove(vnode, parentComponent, parentSuspense, optimized, internals, doRemove);
        }
        // 可以优化对比的场景
        else if (dynamicChildren &&
            // #1153: fast path should not be taken for non-stable (v-for) fragments
            (type !== Fragment ||
                (patchFlag > 0 && patchFlag & 64 /* STABLE_FRAGMENT */))) {
            // fast path for block nodes: only need to unmount dynamic children.
            unmountChildren(dynamicChildren, parentComponent, parentSuspense, false, true);
        }
        // 全量删除
        else if ((type === Fragment &&
            (patchFlag & 128 /* KEYED_FRAGMENT */ ||
                patchFlag & 256 /* UNKEYED_FRAGMENT */)) ||
            (!optimized && shapeFlag & 16 /* ARRAY_CHILDREN */)) {
            unmountChildren(children, parentComponent, parentSuspense);
        }
        // 实际执行删除
        if (doRemove) {
            remove(vnode);
        }
    }
    // 钩子
    if ((vnodeHook = props && props.onVnodeUnmounted) || shouldInvokeDirs) {
        queuePostRenderEffect(() => {
            vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode);
            shouldInvokeDirs &&
                invokeDirectiveHook(vnode, null, parentComponent, 'unmounted');
        }, parentSuspense);
    }
};
// 实际执行移除的方法
const remove = vnode => {
    const { type, el, anchor, transition } = vnode;
    // 下面2类特殊处理
    if (type === Fragment) {
        removeFragment(el, anchor);
        return;
    }
    if (type === Static) {
        removeStaticNode(vnode);
        return;
    }
    // 普通移除 和 带transition的后面分析
    const performRemove = () => {
        hostRemove(el);
        if (transition && !transition.persisted && transition.afterLeave) {
            transition.afterLeave();
        }
    };
    if (vnode.shapeFlag & 1 /* ELEMENT */ &&
        transition &&
        !transition.persisted) {
        const { leave, delayLeave } = transition;
        const performLeave = () => leave(el, performRemove);
        if (delayLeave) {
            delayLeave(vnode.el, performRemove, performLeave);
        }
        else {
            performLeave();
        }
    }
    else {
        performRemove();
    }
};
// 逐个移除片段中的内容即可
const removeFragment = (cur, end) => {
    // For fragments, directly remove all contained DOM nodes.
    // (fragment child nodes cannot have transition)
    let next;
    while (cur !== end) {
        next = hostNextSibling(cur);
        hostRemove(cur);
        cur = next;
    }
    hostRemove(end);
};
// 普通组件vnode的卸载
const unmountComponent = (instance, parentSuspense, doRemove) => {
    if (instance.type.__hmrId) {
        unregisterHMR(instance);
    }
    const { bum, effects, update, subTree, um } = instance;
    // beforeUnmount hook
    if (bum) {
        invokeArrayFns(bum);
    }
    // 收集的effects 都停止掉
    if (effects) {
        for (let i = 0; i < effects.length; i++) {
            stop(effects[i]);
        }
    }
    // update may be null if a component is unmounted before its async
    // setup has resolved.
    // 不需要继续响应变化了
    if (update) {
        stop(update);
        // 我们挂载的是子树 移除的也是子树
        unmount(subTree, instance, parentSuspense, doRemove);
    }
    // unmounted hook
    if (um) {
        queuePostRenderEffect(um, parentSuspense);
    }
    queuePostRenderEffect(() => {
        instance.isUnmounted = true;
    }, parentSuspense);
    // A component with async dep inside a pending suspense is unmounted before
    // its async dep resolves. This should remove the dep from the suspense, and
    // cause the suspense to resolve immediately if that was the last dep.
    // 如注释所言 异步依赖没有等到结果就卸载了 强制手动执行parentSuspense.resolve()
    if (parentSuspense &&
        parentSuspense.pendingBranch &&
        !parentSuspense.isUnmounted &&
        instance.asyncDep &&
        !instance.asyncResolved &&
        instance.suspenseId === parentSuspense.pendingId) {
        parentSuspense.deps--;
        if (parentSuspense.deps === 0) {
            parentSuspense.resolve();
        }
    }
    {
        // 忽略
        devtoolsComponentRemoved(instance);
    }
};
// 卸载子vnode列表
const unmountChildren = (children, parentComponent, parentSuspense, doRemove = false, optimized = false, start = 0) => {
    for (let i = start; i < children.length; i++) {
        unmount(children[i], parentComponent, parentSuspense, doRemove, optimized);
    }
};
// 获取vnode挂载的el的下一个兄弟节点
const getNextHostNode = vnode => {
    if (vnode.shapeFlag & 6 /* COMPONENT */) {
        return getNextHostNode(vnode.component.subTree);
    }
    if (vnode.shapeFlag & 128 /* SUSPENSE */) {
        return vnode.suspense.next();
    }
    return hostNextSibling((vnode.anchor || vnode.el));
};
// 外部调用的高级api
const render = (vnode, container, isSVG) => {
    // 分情况调用2个方法
    if (vnode == null) {
        if (container._vnode) {
            unmount(container._vnode, null, null, true);
        }
    }
    else {
        patch(container._vnode || null, vnode, container, null, null, null, isSVG);
    }
    // 处理patch过程可能产生的post任务们
    flushPostFlushCbs();
    // 建立旧vnode
    container._vnode = vnode;
};
// 供一些方法当参数的内部方法集合
const internals = {
    p: patch,
    um: unmount,
    m: move,
    r: remove,
    mt: mountComponent,
    mc: mountChildren,
    pc: patchChildren,
    pbc: patchBlockChildren,
    n: getNextHostNode,
    o: options
};
let hydrate;
let hydrateNode;
// 忽略
if (createHydrationFns) {
    [hydrate, hydrateNode] = createHydrationFns(internals);
}
// 返回的全局render对象
return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate)
};
}
// 执行vnode钩子回调
function invokeVNodeHook(hook, instance, vnode, prevVNode = null) {
callWithAsyncErrorHandling(hook, instance, 7 /* VNODE_HOOK */, [
    vnode,
    prevVNode
]);
}
/**
* #1156
* When a component is HMR-enabled, we need to make sure that all static nodes
* inside a block also inherit the DOM element from the previous tree so that
* HMR updates (which are full updates) can retrieve the element for patching.
*
* #2080
* Inside keyed `template` fragment static children, if a fragment is moved,
* the children will always moved so that need inherit el form previous nodes
* to ensure correct moved position.
*/
// block内更新了动态节点内容 剩下的静态节点也需要正确继承之前对应的dom元素 
function traverseStaticChildren(n1, n2, shallow = false) {
const ch1 = n1.children;
const ch2 = n2.children;
if (isArray(ch1) && isArray(ch2)) {
    for (let i = 0; i < ch1.length; i++) {
        // this is only called in the optimized path so array children are
        // guaranteed to be vnodes
        const c1 = ch1[i];
        let c2 = ch2[i];
        if (c2.shapeFlag & 1 /* ELEMENT */ && !c2.dynamicChildren) {
            if (c2.patchFlag <= 0 || c2.patchFlag === 32 /* HYDRATE_EVENTS */) {
                c2 = ch2[i] = cloneIfMounted(ch2[i]);
                c2.el = c1.el;
            }
            // 注意这个递归
            if (!shallow)
                traverseStaticChildren(c1, c2);
        }
        // also inherit for comment nodes, but not placeholders (e.g. v-if which
        // would have received .el during block patch)
        if (c2.type === Comment && !c2.el) {
            c2.el = c1.el;
        }
    }
}
}
// https://en.wikipedia.org/wiki/Longest_increasing_subsequence
// 获取最长递增子序列的算法实现
function getSequence(arr) {
// 假设arr 为 [1,0,2,5,0,4,3] 数组的 0 是代表新元素 不用处理 其他情况:索引代表当前node在c2的位置
// 值代表这个node在c1的位置+1 所以最小从1开始 为了和0区分哈
// 克隆一份 p
const p = arr.slice();
const result = [0];
let i, j, u, v, c;
const len = arr.length;
for (i = 0; i < len; i++) {
    const arrI = arr[i];
    // 只处理非0的
    if (arrI !== 0) {
        j = result[result.length - 1];
        // 比尾部最大元素还要大 代表一直在递增
        if (arr[j] < arrI) {
            // p[i] 代表 比 arr[i] 小的上一个元素的位置
            p[i] = j;
            // 推入结果 递增序列+1 目前截止已发现的最长序列
            result.push(i);
            continue;
        }
        // 不满足严格递增了
        u = 0;
        v = result.length - 1;
        // 二分查找 在当前已知的最长增长序列中 找到当前 i 合适的插入位置 u
        while (u < v) {
            c = ((u + v) / 2) | 0;
            if (arr[result[c]] < arrI) {
                u = c + 1;
            }
            else {
                v = c;
            }
        }
        // 确保新的位置符合递增
        if (arrI < arr[result[u]]) {
            if (u > 0) {
                //更新p[i]对应的比i小的上一个位置的位置 此时是u-1
                p[i] = result[u - 1];
            }
            // 需要更新result数组中u位置的值 把它换成新发现的 替换了没关系因为 P 中保存了任何一个元素上一个比他小的 我们再次构建出来 对于每个非零 i 这个元素要么放入尾部 要么二分插入到合适的位置 可能会产生更长的子序列
            result[u] = i;
        }
    }
}
// 准备重建result
u = result.length;
v = result[u - 1];
// 根据最后一次更新的result结果来反向构建
while (u-- > 0) {
    // 赋值v确保 根据v的赋值顺序 保证 从后往前依次递减 反向也就是递增拉
    result[u] = v;
    // 从P中取出上一个比它小的元素的位置
    v = p[v];
}
return result;
}

简单聊一聊generator和async函数的模拟实现吧(1)

// 文章参考 https://github.com/mqyqingfeng/Blog/issues/102

// 1. 同步generator函数 手动执行

// function* helloWorldGenerator() {
    // console.log('start ');
    // yield 'hello';
    // let r2 = yield 'world';
    // console.log('r2: ', r2);
    // return 'ending';
// }

// generator函数的执行特点就不介绍了 我们来看如何模拟这个过程的实现

// 首先 为了实现代码 执行一段内容 暂停 然后再 执行一段 暂停…… 这样的效果 我们需要把原来的代码重写成下面这个样子

// _context 上下文用来维持 迭代器对象的内部状态
function helloWorldGenerator$(_context) {
    while (1) {
        switch (_context.prev = _context.next) {
            // 这里面的 0 3 5 8 等值 我的理解是成处理之后的代码行数 即:下一步该执行哪一行代码了 这是 regenerator 工具转换生成的 :)
            case 0:
                // 这里的内容就我们本来的一段代码
                console.log('start ');
                _context.next = 3;
                return 'hello';
                // yield hello 这里暂停的效果 体现在这里

            case 3:
                _context.next = 5;
                return 'world';

            case 5:
                // next(xxx) 接受参数的效果体现在这里
                r2 = _context.sent;
                console.log('r2: ', r2);
                return _context.abrupt("return", 'ending');
                // return 'ending' 

            case 8:
            case "end":
                return _context.stop();
                // 迭代器停止
        }
    }
}

// 请跳到 regeneratorRuntime 的使用场景:
var regeneratorRuntime = (function (exports) {
    // 一些辅助函数
    var Op = Object.prototype;
    var hasOwn = Op.hasOwnProperty;
    var undefined; // More compressible than void 0.
    var $Symbol = typeof Symbol === "function" ? Symbol : {};
    var iteratorSymbol = $Symbol.iterator || "@@iterator";
    var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator";
    var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag";

    function define(obj, key, value) {
        Object.defineProperty(obj, key, {
            value: value,
            enumerable: true,
            configurable: true,
            writable: true
        });
        return obj[key];
    }

    // 生成器 当做函数看待 只从它身上取 prototype
    function Generator() { }
    // 生成器函数 纯函数看待
    function GeneratorFunction() { }
    // 生成器函数原型对象 我们对它同时看做 函数 和 对象 并赋予各自的属性
    function GeneratorFunctionPrototype() { }

    var IteratorPrototype = {};
    IteratorPrototype[iteratorSymbol] = function () {
        return this;
    };

    var getProto = Object.getPrototypeOf;
    // 取出数组上存在的原生迭代器对象实例 就是下面的Array Iterator {}  然后取出它继承的原型对象的原型对象 也就是 仅仅次于Object用于继承的原型对象那个拥有 Symbol(Symbol.iterator): ƒ [Symbol.iterator]() 属性的原型对象
    /*
        Array Iterator {}
        __proto__: Array Iterator
            next: ƒ next()
            Symbol(Symbol.toStringTag): "Array Iterator"
            __proto__:
                Symbol(Symbol.iterator): ƒ [Symbol.iterator]()
                __proto__: Object
    */
    // 原生迭代器原型对象
    var NativeIteratorPrototype = getProto && getProto(getProto(values([])));
    if (NativeIteratorPrototype &&
        NativeIteratorPrototype !== Op &&
        hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {
        // This environment has a native %IteratorPrototype%; use it instead
        // of the polyfill.
        IteratorPrototype = NativeIteratorPrototype;
    }

    // GeneratorFunctionPrototype 当做函数看待
    // Gp: 生成器函数原型对象 Object.create(IteratorPrototype) 以原生迭代器原型继承构造一个新对象
    var Gp = GeneratorFunctionPrototype.prototype = Object.create(IteratorPrototype);
    // 补充原型对象的构造函数属性
    Gp.constructor = GeneratorFunctionPrototype;

    // GeneratorFunctionPrototype 当做对象再看待
    GeneratorFunction.prototype = GeneratorFunctionPrototype;
    GeneratorFunctionPrototype.constructor = GeneratorFunction;

    // 可能大家不理解上面这几行代码设置原型有啥用 我们写完 mark 和 wrap函数之后再回来分析这几行代码 的目标效果

    Generator.prototype = GeneratorFunctionPrototype.prototype;

    // 再给它加个名字吧
    GeneratorFunction.displayName = define(
        GeneratorFunctionPrototype,
        toStringTagSymbol,
        "GeneratorFunction"
    );

    // 先来实现mark 看它标记了啥:
    exports.mark = function (genFun) {
        if (Object.setPrototypeOf) {
            // genFun.__proto__ = GeneratorFunctionPrototype;
            Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);
        } else {
            genFun.__proto__ = GeneratorFunctionPrototype;
            define(genFun, toStringTagSymbol, "GeneratorFunction");
        }
        // 设置 genFun.prototype = 一个新对象 (new obj, obj.__proto__ = Gp)
        genFun.prototype = Object.create(Gp);


        return genFun;
    };

    // 再看 wrap 包装了啥:
    // innerFn 执行业务代码 outerFn 就是被mark过的函数
    function wrap(innerFn, outerFn, self, tryLocsList) {
        // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.
        // outerFn.prototype 也就是 genFun.prototype 是 Object.create(Gp)得到 也就是 Gp的实例 而 Generator.prototype 就是 Gp 所以满足条件
        var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator;
        // 好了 hw 就是来自于 Object.create(protoGenerator.prototype) 构造的实例 hw.__proto__ === protoGenerator.prototype; hw.__proto__.__proto__ === Gp
        var generator = Object.create(protoGenerator.prototype);
        // 原来 上下文 对象来自于这里构造的实例
        var context = new Context(tryLocsList || []);

        // The ._invoke method unifies the implementations of the .next,
        // .throw, and .return methods.
        // 实例上还有一个方法 
        generator._invoke = makeInvokeMethod(innerFn, self, context);

        // 这就是 hw 迭代器实例对象
        return generator;
    }
    exports.wrap = wrap;

    /*
        现在我们回头看下那几行原型设置的效果:
        规范约定如下: hw.__proto__ === helloWorldGenerator.prototype; hw.__proto__.__proto__ === helloWorldGenerator.__proto__.prototype
        这意味着 我们通过 hw = genFun() 得到的迭代器对象 需要 满足类似 new genFun() 方式得到的原型继承关系,我们来看下hw是什么:
        hw = Object.create(helloWorldGenerator.prototype);
        所以 hw.__proto__ === helloWorldGenerator.prototype 第一个条件成立;
        而 hw.__proto__.__proto__ 在上面的 wrap 分析了 其实 就是 Gp对象,再看 helloWorldGenerator.__proto__ 其实是 GeneratorFunctionPrototype 而 
        GeneratorFunctionPrototype.prototype 就是 Gp,好吧 第二个条件成立 所以满足规范要求了

        facebook人写的代码真是 666...
    */

    // 好了 我们先构建一个简单的 Context:

    function Context(tryLocsList) {

        this.reset(true);
    }

    // 简单的放一些方法到原型对象上去
    // 从转码后的业务代码中可以看到 我们需要的实现有下面几个方法:
    Context.prototype = {
        constructor: Context,

        reset: function (skipTempReset) {
            this.prev = 0;
            this.next = 0;

            this.sent = this._sent = undefined;
            this.done = false;

            this.method = "next";
            this.arg = undefined;
        },

        // 这个最后执行 _context.stop();
        stop: function () {
            this.done = true;

            return this.rval;
        },

        dispatchException: function (exception) {

        },

        // 这个 在最后一个yield语句之后执行 _context.abrupt("return", 'ending');
        abrupt: function (type, arg) {

            var record = {};
            record.type = type;
            record.arg = arg;

            return this.complete(record);
        },

        // 好吧 这个也要实现。。。
        complete: function (record, afterLoc) {
            if (record.type === "throw") {
                throw record.arg;
            }

            if (record.type === "break" ||
                record.type === "continue") {
                this.next = record.arg;
                // 现在我们只关注 return 和 normal 
            } else if (record.type === "return") {
                this.rval = this.arg = record.arg;
                this.method = "return";
                // 业务代码执行最后的stop操作
                this.next = "end";
            } else if (record.type === "normal" && afterLoc) {
                this.next = afterLoc;
            }

            // 完成态自动切换到结束态 需要返回一个特殊的continue类型的返回值
            return ContinueSentinel;
        },

        finish: function (finallyLoc) {

        },

        "catch": function (tryLoc) {

        },

        delegateYield: function (iterable, resultName, nextLoc) {

        }
    };

    // 继续实现 makeInvokeMethod:

    // 迭代器 状态机 的几种状态
    var GenStateSuspendedStart = "suspendedStart";
    var GenStateSuspendedYield = "suspendedYield";
    var GenStateExecuting = "executing";
    var GenStateCompleted = "completed";

    // 状态机继续迭代的特殊返回值
    var ContinueSentinel = {};

    // 返回一个 invoke 内部闭包函数 持有 innerFn context 等自由变量
    function makeInvokeMethod(innerFn, self, context) {
        // 状态机初始化起始状态
        var state = GenStateSuspendedStart;

        return function invoke(method, arg) {
            // todo
        };
    }

    // 然后我们调用的时候从 hw上调用next(),我们在直接原型上加:

    function defineIteratorMethods(prototype) {
        ["next", "throw", "return"].forEach(function (method) {
            define(prototype, method, function (arg) {
                // 其实都是执行实例的 _invoke y也就是上面的 内部函数invoke
                return this._invoke(method, arg);
            });
        });
    }

    defineIteratorMethods(Gp);

    // 所以重点就是 invoke 的实现:

    function makeInvokeMethod(innerFn, self, context) {
        // 状态机初始化起始状态
        var state = GenStateSuspendedStart;

        return function invoke(method, arg) {

            // 初次调用处于 suspendedStart 执行到某个yield语句暂停 处于 suspendedYield 结束后处于 completed
            // 而 executing 指的是 业务代码执行的过程中处于的状态 之后便会过度到 suspendedYield || completed 
            // 不应该在再次invoke的时候遇到的
            if (state === GenStateExecuting) {
                throw new Error("Generator is already running");
            }

            // 结束态后 再次调用迭代器
            if (state === GenStateCompleted) {
                if (method === "throw") {
                    throw arg;
                }

                // Be forgiving, per 25.3.3.3.3 of the spec:
                // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume
                return doneResult();
            }

            // 设置上下文的数据 用户调用的方法 以及 参数
            context.method = method;
            context.arg = arg;

            // 状态机迭代开始啦
            while (true) {

                // 先只实现next吧
                if (context.method === "next") {
                    // Setting context._sent for legacy support of Babel's
                    // function.sent implementation.
                    // next方法是可以接受用户传入的参数噢
                    context.sent = context._sent = context.arg;

                } else if (context.method === "throw") {
                    // ...

                } else if (context.method === "return") {
                    // 之前由于所有的yield结束后 再次next 会自动化调用一次return类型的操作
                    // 所以这个return操作现在已经支持了 这次直接就触发这个逻辑就可以了
                    context.abrupt("return", context.arg);
                }

                // 进入业务代码执行的状态啦
                state = GenStateExecuting;

                // 一次执行结果的记录 context 就是最开始我们看到的上下文了 就是在这里调用我们的业务代码函数了
                // 在完成态自动过滤到结束态的时候会自动化迭代2次 注意最后次的业务代码中调用了 _context.stop();
                var record = tryCatch(innerFn, self, context);
                // 正常返回值
                if (record.type === "normal") {
                    // If an exception is thrown from innerFn, we leave state ===
                    // GenStateExecuting and loop back for another invocation.
                    // 更新状态 完成态 || 暂停态
                    state = context.done
                        ? GenStateCompleted
                        : GenStateSuspendedYield;

                    // 完成态切换到结束态 需要继续迭代状态机
                    if (record.arg === ContinueSentinel) {
                        continue;
                    }

                    // 这就是我们预期的返回值拉
                    return {
                        value: record.arg,
                        done: context.done
                    };

                    // 错误返回值 状态机继续执行 通过上面的 throw分支代码来实现
                } else if (record.type === "throw") {
                    state = GenStateCompleted;
                    // Dispatch the exception by looping back around to the
                    // context.dispatchException(context.arg) call above.
                    context.method = "throw";
                    context.arg = record.arg;
                }
            }
        };
    }

    // 执行业务代码 对返回结果做了包装处理
    function tryCatch(fn, obj, arg) {
        try {
            return { type: "normal", arg: fn.call(obj, arg) };
        } catch (err) {
            return { type: "throw", arg: err };
        }
    }

    // 返回一个迭代器对象
    function values(iterable) {
        if (iterable) {
            var iteratorMethod = iterable[iteratorSymbol];
            // 原生部署了迭代器生成函数 调用得到一个迭代器即可
            if (iteratorMethod) {
                return iteratorMethod.call(iterable);
            }

            // next是个函数的对象 也算吧
            if (typeof iterable.next === "function") {
                return iterable;
            }

            // 具有长度属性的类数组对象 也算吧
            if (!isNaN(iterable.length)) {
                var i = -1, next = function next() {
                    while (++i < iterable.length) {
                        if (hasOwn.call(iterable, i)) {
                            next.value = iterable[i];
                            next.done = false;
                            return next;
                        }
                    }

                    next.value = undefined;
                    next.done = true;

                    return next;
                };

                // 注意返回的这个next 函数 next.next 才是执行的函数 且持有自由变量 i 用来迭代状态
                return next.next = next;
            }
        }

        // Return an iterator with no values.
        return { next: doneResult };
    }

    exports.values = values;

    function doneResult() {
        return { value: undefined, done: true };
    }

    // 返回一个对象
    return exports;
})({});


// 从上面的代码可以看出来 一段一段执行的效果 其实是在某个地方多次调用 改造后的函数 helloWorldGenerator$(_context) ,同时让_context上下文维持着内部状态来实现的
// 我们再添加一下对这个函数的包装函数吧:

var _marked = /*#__PURE__*/regeneratorRuntime.mark(helloWorldGenerator);

function helloWorldGenerator() {
    var r1, r2;
    return regeneratorRuntime.wrap(helloWorldGenerator$, _marked);
}

// 我们通过 var hw = helloWorldGenerator(); 其实得到的迭代器对象来自于: regeneratorRuntime.wrap(helloWorldGenerator$, _marked)
// 而 _marked 来自于 regeneratorRuntime.mark(helloWorldGenerator),所以重点都来自于这个对象: regeneratorRuntime,我们来看下它的实现:
// 请跳到 regeneratorRuntime 的实现:

var hw = helloWorldGenerator();

console.log(hw.next()); // {value: "hello", done: false}
console.log(hw.next()); // {value: "world", done: false}
// 注意这个输入的next参数的输出
console.log(hw.next('next input value')); // {value: "world", done: true}
console.log(hw.next()); // {value: undefined, done: true}

// 至此我们实现了迭代器的基础结构,以及then接口语义 :)

Vue(v3.0.11)源码简析之组件子节点自动继承属性(非props和emits)的相关实现

vue文档描述了这种场景:我们写在组件上的props,如果不在子组件的声明props和emits中,则会被放到attrs中,如果子组件渲染出来是单个vnode,则自动继承所有的attrs到自己身上,如果是个片段且没有子节点显示声明我要使用$attrs,则输出警告信息。
上述功能实现的时机是在渲染组件调用patch,patch要得到render函数对应的vnode子树的时候介入的,得到vnode子树之后,我们就可以根据子树的结构来实施自动继承逻辑了:

/**
 * dev only flag to track whether $attrs was used during render.
 * If $attrs was used during render then the warning for failed attrs
 * fallthrough can be suppressed.
 */
// 用来标记 某个render过程中有vnode使用到了 $attrs 这个内置prop,它是用来承接所有非prop和emit声明的组件属性,都会被解析放到$attrs对象中去
let accessedAttrs = false;
function markAttrsAccessed() {
    accessedAttrs = true;
}
// 根据组件实例对象 调用render得到渲染的vnode树 返回值以这颗新的vnode树的根节点给调用者
function renderComponentRoot(instance) {
    /**
     * 取出组件实例上要用到的属性
     */
    const { type: Component, vnode, proxy, withProxy, props, propsOptions: [propsOptions], slots, attrs, emit, render, renderCache, data, setupState, ctx } = instance;
    let result;
    // 设置当前准备渲染的组件实例
    const prev = setCurrentRenderingInstance(instance);
    {
        // 先置为false 在这个组件渲染过程中如果有谁用到了 $attrs 则会重新置为true
        accessedAttrs = false;
    }
    try {
        // 准备接受非prop和emit的组件属性
        let fallthroughAttrs;
        // 原始vnode信息中的类型是组件 也就是我们平常自己写的组件对象
        if (vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */) {
            // withProxy is a proxy with a different `has` trap only for
            // runtime-compiled render functions using `with` block.
            // 如注释所言 其实就多一个has的一层代理 基本同 proxy
            const proxyToUse = withProxy || proxy;
            // 执行render 绑定作用域 proxyToUse 注意后面 5个参数 后面在分析生成render的时候再将他们的使用
            // render返回的是一个新的vnode树 上面的字段都是确定好的了 可以用于渲染得到真实的dom树
            // normalizeVNode 保证返回的格式总是一个vnode根节点
            result = normalizeVNode(render.call(proxyToUse, proxyToUse, renderCache, props, setupState, data, ctx));
            // 继承 无人接受的 用户设置的属性
            fallthroughAttrs = attrs;
        }
        else {
            /**
             *  const DynamicHeading = (props, context) => {
                    return Vue.h(`h${props.level}`, context.attrs, context.slots)
                }

                DynamicHeading.props = ['level'];

                app.component('dynamic-heading', DynamicHeading)

                以这种方式创建的函数组件的情况
             */
            // functional
            const render = Component;
            // in dev, mark attrs accessed if optional props (attrs === props)
            // 后文会有分析 这种情况 如果没设置函数组件的prop那么2者就会相等
            if (true && attrs === props) {
                // 开启标记
                markAttrsAccessed();
            }
            // 运行用户设置的render得到vnode内容
            result = normalizeVNode(render.length > 1
                // 函数组件对应文档中的调用的时候看声明了几个参数 1.第一个是 props 第二个则是 context 包含 3个属性
                ? render(props, true
                    ? {
                        get attrs() {
                            // 开启标记
                            markAttrsAccessed();
                            return attrs;
                        },
                        slots,
                        emit
                    }
                    : { attrs, slots, emit })
                : render(props, null /* we know it doesn't need it */));
            // 取得要继承的回落attrs 对函数组件设了props那attrs就是已经解析好的了 否则调用 getFunctionalFallthrough 提取
            fallthroughAttrs = Component.props
                ? attrs
                : getFunctionalFallthrough(attrs);
        }
        // attr merging
        // in dev mode, comments are preserved, and it's possible for a template
        // to have comments along side the root element which makes it a fragment
        // 根节点 之后有可能会被修改
        let root = result;
        let setRoot = undefined;
        if (true &&
            result.patchFlag > 0 &&
            // DEV_ROOT_FRAGMENT 表示 一个片段中只有一个元素是有效节点其他都是注释的情况 也默认继承attrs到这个有效节点上
            result.patchFlag & 2048 /* DEV_ROOT_FRAGMENT */) {
            ;
            // 得到新节点和重设方法
            [root, setRoot] = getChildRoot(result);
        }
        if (Component.inheritAttrs !== false && fallthroughAttrs) {
            const keys = Object.keys(fallthroughAttrs);
            const { shapeFlag } = root;
            // 存在需要继承的回落属性
            if (keys.length) {
                // 可以继承的2类vnode节点
                if (shapeFlag & 1 /* ELEMENT */ ||
                    shapeFlag & 6 /* COMPONENT */) {
                    if (propsOptions && keys.some(isModelListener)) {
                        // If a v-model listener (onUpdate:xxx) has a corresponding declared
                        // prop, it indicates this component expects to handle v-model and
                        // it should not fallthrough.
                        // related: #1543, #1643, #1989
                        // 移除onUpdate:xxxx类型的事件 不需要被回落继承
                        fallthroughAttrs = filterModelListeners(fallthroughAttrs, propsOptions);
                    }
                    // 返回合并回落属性后重新克隆出来的一个的新的root节点 
                    root = cloneVNode(root, fallthroughAttrs);
                }
                // 无vnode可以继承存在的回落属性
                else if (true && !accessedAttrs && root.type !== Comment) {
                    const allAttrs = Object.keys(attrs);
                    // 无人继承的属性们
                    const eventAttrs = [];
                    const extraAttrs = [];
                    for (let i = 0, l = allAttrs.length; i < l; i++) {
                        const key = allAttrs[i];
                        if (isOn(key)) {
                            // ignore v-model handlers when they fail to fallthrough
                            if (!isModelListener(key)) {
                                // remove `on`, lowercase first letter to reflect event casing
                                // accurately
                                // 只收集非onUpdate:xxx的其他事件
                                eventAttrs.push(key[2].toLowerCase() + key.slice(3));
                            }
                        }
                        else {
                            extraAttrs.push(key);
                        }
                    }
                    // 对应文档中的输出警告信息 存在无法被自动继承的回落属性
                    if (extraAttrs.length) {
                        warn(`Extraneous non-props attributes (` +
                            `${extraAttrs.join(', ')}) ` +
                            `were passed to component but could not be automatically inherited ` +
                            `because component renders fragment or text root nodes.`);
                    }
                    if (eventAttrs.length) {
                        warn(`Extraneous non-emits event listeners (` +
                            `${eventAttrs.join(', ')}) ` +
                            `were passed to component but could not be automatically inherited ` +
                            `because component renders fragment or text root nodes. ` +
                            `If the listener is intended to be a component custom event listener only, ` +
                            `declare it using the "emits" option.`);
                    }
                }
            }
        }
        // inherit directives
        if (vnode.dirs) {
            // 元素 组件
            if (true && !isElementRoot(root)) {
                warn(`Runtime directive used on component with non-element root node. ` +
                    `The directives will not function as intended.`);
            }
            // 继承指令部分
            root.dirs = root.dirs ? root.dirs.concat(vnode.dirs) : vnode.dirs;
        }
        // inherit transition data
        // transition 的场景之后再分析 todo
        if (vnode.transition) {
            if (true && !isElementRoot(root)) {
                // 元素 组件
                warn(`Component inside <Transition> renders non-element root node ` +
                    `that cannot be animated.`);
            }
            // 高级抽象组件 <Transition> 才有 transition 属性
            root.transition = vnode.transition;
        }
        // 修改root在原子数组中的引用
        if (true && setRoot) {
            setRoot(root);
        }
        else {
            result = root;
        }
    }
    catch (err) {
        // 生成子树如果失败了 就整体作为一个注释返回
        blockStack.length = 0;
        handleError(err, instance, 1 /* RENDER_FUNCTION */);
        result = createVNode(Comment);
    }
    // 恢复实例
    setCurrentRenderingInstance(prev);
    return result;
}
/**
 * dev only
 * In dev mode, template root level comments are rendered, which turns the
 * template into a fragment root, but we need to locate the single element
 * root for attrs and scope id processing.
 */
// 尝试从vnode的子节点中选出一个唯一的有效节点 作为新的根节点替换当前vnode
const getChildRoot = (vnode) => {
    const rawChildren = vnode.children;
    const dynamicChildren = vnode.dynamicChildren;
    // 尝试从子节点中提取出新的根节点
    const childRoot = filterSingleRoot(rawChildren);
    // 不满足条件 返回原始值即可
    if (!childRoot) {
        return [vnode, undefined];
    }
    // 如果找到了 找到这个节点在原子数组节点以及动态节点数组中的位置
    const index = rawChildren.indexOf(childRoot);
    const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1;
    // 重设root节点的方法实现
    const setRoot = (updatedRoot) => {
        // updatedRoot 就是上面找到的目标节点被 normalizeVNode 处理之后的
        // 更新引用
        rawChildren[index] = updatedRoot;
        if (dynamicChildren) {
            if (dynamicIndex > -1) {
                // 直接更新
                dynamicChildren[dynamicIndex] = updatedRoot;
            }
            // 变成了一个动态节点 那就添加即可
            else if (updatedRoot.patchFlag > 0) {
                vnode.dynamicChildren = [...dynamicChildren, updatedRoot];
            }
        }
    };
    // 注意返回的时候按照 normalizeVNode 格式化了
    return [normalizeVNode(childRoot), setRoot];
};
// 从一个vnode片段中筛选出可能出现的单个有效vnode节点
function filterSingleRoot(children) {
    let singleRoot;
    for (let i = 0; i < children.length; i++) {
        const child = children[i];
        // 非vnode情况直接不处理
        if (isVNode(child)) {
            // ignore user comment
            // 非注释节点才处理 或者 v-if最终的回退情况 一个内容是v-if的注释节点占位符
            if (child.type !== Comment || child.children === 'v-if') {
                if (singleRoot) {
                    // has more than 1 non-comment child, return now
                    // 返回undefined告诉调用者 寻找失败
                    return;
                }
                else {
                    singleRoot = child;
                }
            }
        }
        else {
            return;
        }
    }
    // 找到了 确实存在这样的子节点 返回它
    return singleRoot;
}
// 从函数式组件上 继承的attrs只有3种
const getFunctionalFallthrough = (attrs) => {
    let res;
    for (const key in attrs) {
        if (key === 'class' || key === 'style' || isOn(key)) {
            (res || (res = {}))[key] = attrs[key];
        }
    }
    return res;
};
// 移除 onUpdate:xxxx 这样的prop 不需要作为回落继承
const filterModelListeners = (attrs, props) => {
    const res = {};
    for (const key in attrs) {
        if (!isModelListener(key) || !(key.slice(9) in props)) {
            res[key] = attrs[key];
        }
    }
    return res;
};
// 可以继承attrs的几类vnode类型
const isElementRoot = (vnode) => {
    return (vnode.shapeFlag & 6 /* COMPONENT */ ||
        vnode.shapeFlag & 1 /* ELEMENT */ ||
        vnode.type === Comment // potential v-if branch switch
    );
};
// patch更新之前 需要对比2个相同类型的vnode是否确实需要更新
function shouldUpdateComponent(prevVNode, nextVNode, optimized) {
    const { props: prevProps, children: prevChildren, component } = prevVNode;
    const { props: nextProps, children: nextChildren, patchFlag } = nextVNode;
    // ['event1', 'event2'] 这样的
    const emits = component.emitsOptions;
    // Parent component's render function was hot-updated. Since this may have
    // caused the child component's slots content to have changed, we need to
    // force the child to update as well.
    // 这个先忽略
    if ((prevChildren || nextChildren) && isHmrUpdating) {
        return true;
    }
    // 指令和transitin包裹的vnode总是要更新
    // force child update for runtime directive or transition on component vnode.
    if (nextVNode.dirs || nextVNode.transition) {
        return true;
    }
    // 开启优化的情况下 回忆一下 patchFlag 为 -1 和 -2 2个值的时候 -1 代表是静态节点 不会改变 -2 则是关闭优化策略
    if (optimized && patchFlag >= 0) {
        // 动态插槽
        if (patchFlag & 1024 /* DYNAMIC_SLOTS */) {
            // slot content that references values that might have changed,
            // e.g. in a v-for
            return true;
        }
        // 动态key prop
        if (patchFlag & 16 /* FULL_PROPS */) {
            // 之前不存在
            if (!prevProps) {
                return !!nextProps;
            }
            // 之前存在 对比新值即可
            // presence of this flag indicates props are always non-null
            return hasPropsChanged(prevProps, nextProps, emits);
        }
        else if (patchFlag & 8 /* PROPS */) {
            const dynamicProps = nextVNode.dynamicProps;
            for (let i = 0; i < dynamicProps.length; i++) {
                const key = dynamicProps[i];
                // 对比2个prop值是否变化即可 注意不需要对比emits中的自定义事件 因为他们的值不是null就是用户设置的事件配置对象
                if (nextProps[key] !== prevProps[key] &&
                    !isEmitListener(emits, key)) {
                    return true;
                }
            }
        }
    }
    else {
        // this path is only taken by manually written render functions
        // so presence of any children leads to a forced update
        // 用户手写的render函数
        if (prevChildren || nextChildren) {
            // $stable 代表稳定
            if (!nextChildren || !nextChildren.$stable) {
                return true;
            }
        }
        if (prevProps === nextProps) {
            return false;
        }
        if (!prevProps) {
            return !!nextProps;
        }
        if (!nextProps) {
            return true;
        }
        return hasPropsChanged(prevProps, nextProps, emits);
    }
    return false;
}
// 2个prop对象的对比 没啥好说的 遍历就是了
function hasPropsChanged(prevProps, nextProps, emitsOptions) {
    const nextKeys = Object.keys(nextProps);
    if (nextKeys.length !== Object.keys(prevProps).length) {
        return true;
    }
    for (let i = 0; i < nextKeys.length; i++) {
        const key = nextKeys[i];
        if (nextProps[key] !== prevProps[key] &&
            !isEmitListener(emitsOptions, key)) {
            return true;
        }
    }
    return false;
}
// 更新高阶组件的托管元素 也就是指的 这个组件实际渲染出来的是那个dom元素节点 多个高阶组件可以嵌套以最底下的那个组件的真实dom元素作为hostEle
// 这也指示出来 高阶组件只是一种虚拟的逻辑组件 并不真实反映在dom结构中 它们都对应真实占位的那个元素
// HOC指的 是  某个组件render的结果返回的是 另一个组件的vnode
// 详情见 http://devui.huawei.com/components/zh-cn/overview
function updateHOCHostEl({ vnode, parent }, el // HostNode
) {
    // 一直往上找
    while (parent && parent.subTree === vnode) {
        (vnode = parent.vnode).el = el;
        parent = parent.parent;
    }
}

Vue(v3.0.11)源码简析之组件实例的this的相关实现

我们用到的vue中的组件实例proxy,也就是常用的this,到底是由什么属性构成的呢?在this取值和赋值究竟被什么逻辑所控制呢?

/**
 * #2437 In Vue 3, functional components do not have a public instance proxy but
 * they exist in the internal parent chain. For code that relies on traversing
 * public $parent chains, skip functional ones and go to the parent instead.
 */
// 获取组件实例的this对象 注意注释里面描述的 函数组件的情况
const getPublicInstance = (i) => {
    if (!i)
        return null;
    if (isStatefulComponent(i))
        return i.exposed ? i.exposed : i.proxy;
    return getPublicInstance(i.parent);
};
// this上默认自带的几个属性方法 this.xx 获取的 组件实例上的属性其实
const publicPropertiesMap = extend(Object.create(null), {
    // 组件实例
    $: i => i,
    // 宿主dom元素
    $el: i => i.vnode.el,
    // 响应式data对象
    $data: i => i.data,
    // 非响应式的几个属性
    $props: i => (shallowReadonly(i.props) ),
    $attrs: i => (shallowReadonly(i.attrs) ),
    $slots: i => (shallowReadonly(i.slots) ),
    $refs: i => (shallowReadonly(i.refs) ),
    // 父组件的this对象
    $parent: i => getPublicInstance(i.parent),
    $root: i => getPublicInstance(i.root),
    // 前文分析过的emit方法
    $emit: i => i.emit,
    // 合并生成options新对象返回给调用者
    $options: i => (resolveMergedOptions(i) ),
    // 刷新当前组件 其实就是把update这个effect重新推入job队列
    $forceUpdate: i => () => queueJob(i.update),
    // 前文分析过的调用 nextTick 
    $nextTick: i => nextTick.bind(i.proxy),
    // 前文分析过调用 watch
    $watch: i => (instanceWatch.bind(i) )
});
// 从this对象上取值的时候 它其实是 一个代理对象 而取值的逻辑由handler所控制 主要控制 get set has 这三类操作
// get的第一个参数 target 是this的原始对象 ctx 后面会分析它的构成 其中 _ 指向 组件实例
// 看看我们通过this.xxx取值的时候主要做了类型解析缓存,返回对应响应式对象仓库的值
const PublicInstanceProxyHandlers = {
    get({ _: instance }, key) {
        // accessCache 某个key的类型缓存
        const { ctx, setupState, data, props, accessCache, type, appContext } = instance;
        // let @vue/reactivity know it should never observe Vue public instances.
        // 不可设置响应式
        if (key === "__v_skip" /* SKIP */) {
            return true;
        }
        // for internal formatters to know that this is a Vue instance
        if (key === '__isVue') {
            return true;
        }
        // data / props / ctx
        // This getter gets called for every property access on the render context
        // during render and is a major hotspot. The most expensive part of this
        // is the multiple hasOwn() calls. It's much faster to do a simple property
        // access on a plain object, so we use an accessCache object (with null
        // prototype) to memoize what access type a key corresponds to.
        let normalizedProps;
        // 从下面的代码可以看出 其实this可以获取多种仓库来源的数据 setup提供的上下文数据 源自data构建的上下文数据 ctx默认自带的上下文数据 props上的数据
        if (key[0] !== '$') {
            const n = accessCache[key];
            if (n !== undefined) {
                switch (n) {
                    case 0 /* SETUP */:
                        return setupState[key];
                    case 1 /* DATA */:
                        return data[key];
                    case 3 /* CONTEXT */:
                        return ctx[key];
                    case 2 /* PROPS */:
                        return props[key];
                    // default: just fallthrough
                }
            }
            // 他们都是响应式的噢
            // 优先setup上下文上取
            else if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
                accessCache[key] = 0 /* SETUP */;
                return setupState[key];
            }
            // 然后是data上
            else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
                accessCache[key] = 1 /* DATA */;
                return data[key];
            }
            else if (
            // only cache other properties when instance has declared (thus stable)
            // props
            (normalizedProps = instance.propsOptions[0]) &&
                hasOwn(normalizedProps, key)) {
                accessCache[key] = 2 /* PROPS */;
                return props[key];
            }
            // 最后才是ctx上的
            else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
                accessCache[key] = 3 /* CONTEXT */;
                return ctx[key];
            }
            // 其他的情况
            else if (shouldCacheAccess) {
                accessCache[key] = 4 /* OTHER */;
            }
        }
        // 上面分析的几个默认属性方法
        const publicGetter = publicPropertiesMap[key];
        let cssModule, globalProperties;
        // public $xxx properties
        if (publicGetter) {
            // 标记有render过程中有节点需要$attrs
            if (key === '$attrs') {
                track(instance, "get" /* GET */, key);
                markAttrsAccessed();
            }
            return publicGetter(instance);
        }
        else if (
        // css module (injected by vue-loader)
        // TODO
        (cssModule = type.__cssModules) &&
            (cssModule = cssModule[key])) {
            return cssModule;
        }
        else if (ctx !== EMPTY_OBJ && hasOwn(ctx, key)) {
            // user may set custom properties to `this` that start with `$`
            accessCache[key] = 3 /* CONTEXT */;
            return ctx[key];
        }
        else if (
        // global properties
        // 最后再从全局配置的属性中取
        ((globalProperties = appContext.config.globalProperties),
            hasOwn(globalProperties, key))) {
            return globalProperties[key];
        }
        else if (currentRenderingInstance &&
            (!isString(key) ||
                // #1091 avoid internal isRef/isVNode checks on component instance leading
                // to infinite warning loop
                key.indexOf('__v') !== 0)) {
            // 内部属性获取警告
            if (data !== EMPTY_OBJ &&
                (key[0] === '$' || key[0] === '_') &&
                hasOwn(data, key)) {
                warn(`Property ${JSON.stringify(key)} must be accessed via $data because it starts with a reserved ` +
                    `character ("$" or "_") and is not proxied on the render context.`);
            }
            // render中找不到的全局变量 报错
            else if (instance === currentRenderingInstance) {
                warn(`Property ${JSON.stringify(key)} was accessed during render ` +
                    `but is not defined on instance.`);
            }
        }
    },
    // 再看this.xxx = xxx的时候
    set({ _: instance }, key, value) {
        const { data, setupState, ctx } = instance;
        if (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) {
            setupState[key] = value;
        }
        else if (data !== EMPTY_OBJ && hasOwn(data, key)) {
            data[key] = value;
        }
        // 不要给props的key赋值
        else if (hasOwn(instance.props, key)) {
            warn(`Attempting to mutate prop "${key}". Props are readonly.`, instance);
            return false;
        }
        // 保留key $xxxxx
        if (key[0] === '$' && key.slice(1) in instance) {
            warn(`Attempting to mutate public property "${key}". ` +
                    `Properties starting with $ are reserved and readonly.`, instance);
            return false;
        }
        else {
            if (key in instance.appContext.config.globalProperties) {
                // 对全局配置的属性上 写的时候同步到ctx仓库中去
                Object.defineProperty(ctx, key, {
                    enumerable: true,
                    configurable: true,
                    value
                });
            }
            else {
                ctx[key] = value;
            }
        }
        return true;
    },
    // 检测是否存在的时候 xxx in this
    has({ _: { data, setupState, accessCache, ctx, appContext, propsOptions } }, key) {
        let normalizedProps;
        // 检测几类数据仓库中的自有属性中是否含有key即可
        return (accessCache[key] !== undefined ||
            (data !== EMPTY_OBJ && hasOwn(data, key)) ||
            (setupState !== EMPTY_OBJ && hasOwn(setupState, key)) ||
            ((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
            hasOwn(ctx, key) ||
            hasOwn(publicPropertiesMap, key) ||
            hasOwn(appContext.config.globalProperties, key));
    }
};
//  this 不可遍历
{
    PublicInstanceProxyHandlers.ownKeys = (target) => {
        warn(`Avoid app logic that relies on enumerating keys on a component instance. ` +
            `The keys will be empty in production mode to avoid performance overhead.`);
        return Reflect.ownKeys(target);
    };
}
// render过程获取this上的属性的时候 再加多一个has的handler
const RuntimeCompiledPublicInstanceProxyHandlers = extend({}, PublicInstanceProxyHandlers, {
    get(target, key) {
        // fast path for unscopables when using `with` block
        // with过程不可获取的属性
        if (key === Symbol.unscopables) {
            return;
        }
        // 简单调用 PublicInstanceProxyHandlers 
        return PublicInstanceProxyHandlers.get(target, key, target);
    },
    has(_, key) {
        // 现在检测内部属性key 抛出警告 符合isGloballyWhitelisted白名单的非_开头的全局变量 是交给上面PublicInstanceProxyHandlers的get来检测的
        const has = key[0] !== '_' && !isGloballyWhitelisted(key);
        if (!has && PublicInstanceProxyHandlers.has(_, key)) {
            warn(`Property ${JSON.stringify(key)} should not start with _ which is a reserved prefix for Vue internals.`);
        }
        return has;
    }
});
// In dev mode, the proxy target exposes the same properties as seen on `this`
// for easier console inspection. In prod mode it will be an empty object so
// these properties definitions can be skipped.
// 构建ctx的实现
function createRenderContext(instance) {
    const target = {};
    // expose internal instance for proxy handlers
    // 上文说道的 _ 返回 组件实例
    Object.defineProperty(target, `_`, {
        configurable: true,
        enumerable: false,
        get: () => instance
    });
    // expose public properties
    // this提供的几个自带的方法
    Object.keys(publicPropertiesMap).forEach(key => {
        Object.defineProperty(target, key, {
            configurable: true,
            enumerable: false,
            get: () => publicPropertiesMap[key](instance),
            // intercepted by the proxy so no need for implementation,
            // but needed to prevent set errors
            set: NOOP
        });
    });
    // expose global properties
    const { globalProperties } = instance.appContext.config;
    // 顶级配置对象 同步到ctx 上
    Object.keys(globalProperties).forEach(key => {
        Object.defineProperty(target, key, {
            configurable: true,
            enumerable: false,
            get: () => globalProperties[key],
            set: NOOP
        });
    });
    return target;
}
// dev only
// 我们获取prop通过this.xxx其实只是从props对象中取值而已
function exposePropsOnRenderContext(instance) {
    const { ctx, propsOptions: [propsOptions] } = instance;
    if (propsOptions) {
        Object.keys(propsOptions).forEach(key => {
            Object.defineProperty(ctx, key, {
                enumerable: true,
                configurable: true,
                get: () => instance.props[key],
                set: NOOP
            });
        });
    }
}
// dev only
// setup上下文数据仓库 一样同prop的处理方案 且有名称限定规则
function exposeSetupStateOnRenderContext(instance) {
    const { ctx, setupState } = instance;
    Object.keys(toRaw(setupState)).forEach(key => {
        if (key[0] === '$' || key[0] === '_') {
            warn(`setup() return property ${JSON.stringify(key)} should not start with "$" or "_" ` +
                `which are reserved prefixes for Vue internals.`);
            return;
        }
        Object.defineProperty(ctx, key, {
            enumerable: true,
            configurable: true,
            get: () => setupState[key],
            set: NOOP
        });
    });
}

Vue(v3.0.11)源码简析之模板编译transform的相关实现(3)

// transform 第五个转化方法 先序处理 对于 <slot></slot> 标签而言 我们只是返回 用户在父节点定义的内容 || 默认的回退内容(可能没有) children 在遍历中继续对它自己做转化 slot并不关注
const transformSlotOutlet = (node, context) => {
    // <slot>... 标签
    /**
     * <div id="app">
            <slot>4444</slot>
        </div> 
     * 
     */
    if (isSlotOutlet(node)) {
        const { children, loc } = node;
        // 解析出来props 和 name
        const { slotName, slotProps } = processSlotOutlet(node, context);
        // 就简单的当做 $slots 处理就好了
        const slotArgs = [
            context.prefixIdentifiers ? `_ctx.$slots` : `$slots`,
            slotName
        ];
        if (slotProps) {
            slotArgs.push(slotProps);
        }
        // slot标签下面 带有默认的渲染内容 444
        if (children.length) {
            if (!slotProps) {
                slotArgs.push(`{}`);
            }
            // <slot></slot> 其实是从父组件那里取到的属性 $slots 去找对应name的值 也就是一个箭头函数 这个函数的返回值||自带的默认值children 才是真实需要渲染的信息
            // 创建一个函数表达式 返回默认渲染信息 符合文档中slot内部作为默认渲染内容的语义
            slotArgs.push(createFunctionExpression([], children, false, false, loc));
        }
        // 由于 slotted 为true dev环境下面的代码作用暂不清楚 留待以后研究
        if (context.scopeId && !context.slotted) {
            if (!slotProps) {
                slotArgs.push(`{}`);
            }
            if (!children.length) {
                slotArgs.push(`undefined`);
            }
            slotArgs.push(`true`);
        }
        // 需要 RENDER_SLOT helper
        node.codegenNode = createCallExpression(context.helper(RENDER_SLOT), slotArgs, loc);
    }
};
// 解析 slot 标签上的 name和prop
function processSlotOutlet(node, context) {
    // 默认值
    let slotName = `"default"`;
    let slotProps = undefined;
    // 非name 的 prop
    const nonNameProps = [];
    for (let i = 0; i < node.props.length; i++) {
        const p = node.props[i];
        // 静态attr
        if (p.type === 6 /* ATTRIBUTE */) {
            if (p.value) {
                // 可以写 name="user-name" 这样的静态attr
                if (p.name === 'name') {
                    slotName = JSON.stringify(p.value.content);
                }
                else {
                    // attr合入nonNameProps
                    // 驼峰化
                    p.name = camelize(p.name);
                    nonNameProps.push(p);
                }
            }
        }
        else {
            // 你也可以写  :name = "u-name"
            if (p.name === 'bind' && isBindKey(p.arg, 'name')) {
                if (p.exp)
                    slotName = p.exp;
            }
            else {
                // 其他prop 静态表达式确定值驼峰化
                if (p.name === 'bind' && p.arg && isStaticExp(p.arg)) {
                    p.arg.content = camelize(p.arg.content);
                }
                nonNameProps.push(p);
            }
        }
    }
    if (nonNameProps.length > 0) {
        // 构建 props 和普通元素 对象上的解析props都是一样的
        const { props, directives } = buildProps(node, context, nonNameProps);
        slotProps = props;
        // 不支持指令
        if (directives.length) {
            context.onError(createCompilerError(35 /* X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET */, directives[0].loc));
        }
    }
    // 返回 name和props
    return {
        slotName,
        slotProps
    };
}

// 对 v-on:click.stop="handlers" 这样的prop做转换
// 事件绑定其实也算一种属性绑定
// 函数表达式 () => || function xxx( 这2种情况
const fnExpRE = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^\s*function(?:\s+[\w$]+)?\s*\(/;
// v-on:click.stop="console.log(123)"
// v-on:[eventVariable].stop="console.log(123)"
// arg = click modifiers = ['stop']  exp 包含语句
const transformOn = (dir, node, context, augmentor) => {
    const { loc, modifiers, arg } = dir;
    // 错误场景
    if (!dir.exp && !modifiers.length) {
        context.onError(createCompilerError(34 /* X_V_ON_NO_EXPRESSION */, loc));
    }
    let eventName;
    // 普通场景下 事件名字都是 简单表达式
    if (arg.type === 4 /* SIMPLE_EXPRESSION */) {
        // 事件名静态确定的
        if (arg.isStatic) {
            const rawName = arg.content;
            // for all event listeners, auto convert it to camelCase. See issue #2249
            // onClick 事件名字归一化
            // 事件名字用一个 简单表达式对象来描述 isStatic = true
            eventName = createSimpleExpression(toHandlerKey(camelize(rawName)), true, arg.loc);
        }
        // 动态变量作为事件名
        else {
            // #2388
            // 事件名用混合表达式对象来描述
            /**
             *  createCompoundExpression 的第一个参数 children的内容如下
             *  [
                    "_toHandlerKey(",
                    {
                        "type": 4,
                        "content": "event",
                        "isStatic": false,
                        "constType": 0,
                        "loc": {
                            "start": {
                                "column": 18,
                                "line": 2,
                                "offset": 18
                            },
                            "end": {
                                "column": 25,
                                "line": 2,
                                "offset": 25
                            },
                            "source": "[event]"
                        }
                    },
                    ")"
                ]
                组合起来就是 _toHandlerKey(...) 效果同上面的静态情况类似
             */
            eventName = createCompoundExpression([
                `${context.helperString(TO_HANDLER_KEY)}(`,
                arg,
                `)`
            ]);
        }
    }
    // 组合表达式场景 todo?
    else {
        // already a compound expression.
        eventName = arg;
        // 处理很简单就是前后再次添加 _toHandlerKey( 和 )
        eventName.children.unshift(`${context.helperString(TO_HANDLER_KEY)}(`);
        eventName.children.push(`)`);
    }
    // handler processing
    // 处理事件表达式
    let exp = dir.exp;
    if (exp && !exp.content.trim()) {
        exp = undefined;
    }
    // 事件缓存  `"cacheHandlers" option is only supported when the "prefixIdentifiers" option is enabled.`
    let shouldCache = context.cacheHandlers && !exp;
    if (exp) {
        // handler是某个对象的属性成员
        // v-on:click.stop="obj.event" || v-on:click.stop="obj['event']"
        const isMemberExp = isMemberExpression(exp.content);
        // fnExpRE: () => || function xxx(
        // 不是 成员变量 自带函数体 之外的 就是执行语句了
        const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content));
        // 多个语句
        const hasMultipleStatements = exp.content.includes(`;`);
        {
            // 校验参数表达式 注意第四个参数 asRawStatements 用原始表达式即可
            validateBrowserExpression(exp, context, false, hasMultipleStatements);
        }
        if (isInlineStatement || (shouldCache && isMemberExp)) {
            // wrap inline statement in a function expression
            // 其余情况已经是函数表达式 || 成员变量 了 render函数运行时自动取值就好了
            // 函数包裹体 ($event) => (用户代码) || (..args) => {event1(...);event2(...)}
            exp = createCompoundExpression([
                `${isInlineStatement
                  ? `$event`
                  : `${``}(...args)`} => ${hasMultipleStatements ? `{` : `(`}`,
                exp,
                hasMultipleStatements ? `}` : `)`
            ]);
        }
    }
    // 准备返回体
    let ret = {
        props: [
            // js 对象 键值对
            createObjectProperty(eventName, exp || createSimpleExpression(`() => {}`, false, loc))
        ]
    };
    // apply extended compiler augmentor
    // 对应 transformOn$1 中的最后一个参数方法
    // 第二阶段的处理 modifiers
    if (augmentor) {
        ret = augmentor(ret);
    }
    // 缓存handler 
    if (shouldCache) {
        // cache handlers so that it's always the same handler being passed down.
        // this avoids unnecessary re-renders when users use inline handlers on
        // components.
        ret.props[0].value = context.cache(ret.props[0].value);
    }
    return ret;
};

// v-bind without arg is handled directly in ./transformElements.ts due to it affecting
// codegen for the entire props object. This transform here is only for v-bind
// *with* args.
// 如注释所言 只处理 v-bind:key 这种 v-bind="obj" 归 transformElement 负责
const transformBind = (dir, node, context) => {
    const { exp, modifiers, loc } = dir;
    const arg = dir.arg;
    // 添加兼容处理字符
    if (arg.type !== 4 /* SIMPLE_EXPRESSION */) {
        arg.children.unshift(`(`);
        arg.children.push(`) || ""`);
    }
    else if (!arg.isStatic) {
        arg.content = `${arg.content} || ""`;
    }
    // .prop is no longer necessary due to new patch behavior
    // .sync is replaced by v-model:arg
    // 加驼峰处理
    if (modifiers.includes('camel')) {
        if (arg.type === 4 /* SIMPLE_EXPRESSION */) {
            if (arg.isStatic) {
                arg.content = camelize(arg.content);
            }
            else {
                // 动态参数key 一样的处理 执行时机不同而已
                arg.content = `${context.helperString(CAMELIZE)}(${arg.content})`;
            }
        }
        // 数组形式的组合表达式
        else {
            arg.children.unshift(`${context.helperString(CAMELIZE)}(`);
            arg.children.push(`)`);
        }
    }
    // 值空错误场景
    if (!exp ||
        (exp.type === 4 /* SIMPLE_EXPRESSION */ && !exp.content.trim())) {
        context.onError(createCompilerError(33 /* X_V_BIND_NO_EXPRESSION */, loc));
        return {
            props: [createObjectProperty(arg, createSimpleExpression('', true, loc))]
        };
    }
    // 处理比较简单
    return {
        props: [createObjectProperty(arg, exp)]
    };
};

// Merge adjacent text nodes and expressions into a single expression
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
// text node转化 后序处理 所有子节点转化结束后再处理node 合并子节点中的text node
const transformText = (node, context) => {
    // 以下几种情况才有可能存在多个子节点内容 或许需要对text内容子节点做进一步的合并处理 
    if (node.type === 0 /* ROOT */ ||
        node.type === 1 /* ELEMENT */ ||
        node.type === 11 /* FOR */ ||
        node.type === 10 /* IF_BRANCH */) {
        // perform the transform on node exit so that all expressions have already
        // been processed.
        return () => {
            const children = node.children;
            let currentContainer = undefined;
            let hasText = false;
            for (let i = 0; i < children.length; i++) {
                const child = children[i];
                if (isText(child)) {
                    // 当前发现一个text node
                    hasText = true;
                    // 从它往后再次尝试合并
                    for (let j = i + 1; j < children.length; j++) {
                        // 上一个text node(可能被合并过)的下一个
                        const next = children[j];
                        if (isText(next)) {
                            if (!currentContainer) {
                                // 组合text node
                                currentContainer = children[i] = {
                                    type: 8 /* COMPOUND_EXPRESSION */,
                                    loc: child.loc,
                                    children: [child]
                                };
                            }
                            // merge adjacent text node into current
                            // 合并 前一个 + 后一个
                            currentContainer.children.push(` + `, next);
                            // 移除当前已处理过的
                            children.splice(j, 1);
                            j--;
                        }
                        else {
                            // 不是连续的textnode无法合并 这一轮尝试到此失败 中断
                            currentContainer = undefined;
                            break;
                        }
                    }
                }
            }
            // 没有text node 以及 root 和 单元素节点 该节点的内容不需要添加更多的信息了
            if (!hasText ||
                // if this is a plain element with a single text child, leave it
                // as-is since the runtime has dedicated fast path for this by directly
                // setting textContent of the element.
                // for component root it's always normalized anyway.
                (children.length === 1 &&
                    (node.type === 0 /* ROOT */ ||
                        (node.type === 1 /* ELEMENT */ &&
                            node.tagType === 0 /* ELEMENT */)))) {
                return;
            }
            // pre-convert text nodes into createTextVNode(text) calls to avoid
            // runtime normalization.
            // 剩余情况 还需要再添加一些转化信息
            for (let i = 0; i < children.length; i++) {
                const child = children[i];
                // 只负责处理text node就好了
                if (isText(child) || child.type === 8 /* COMPOUND_EXPRESSION */) {
                    const callArgs = [];
                    // createTextVNode defaults to single whitespace, so if it is a
                    // single space the code could be an empty call to save bytes.
                    // 静态内容 或者 空的纯文本内容 忽略它
                    if (child.type !== 2 /* TEXT */ || child.content !== ' ') {
                        callArgs.push(child);
                    }
                    // mark dynamic text with flag so it gets patched inside a block
                    // getConstantType 返回 0 意味着当前child的子节点中含有动态信息  所以需要添加动态text的patchflag 
                    if (!context.ssr &&
                        getConstantType(child, context) === 0 /* NOT_CONSTANT */) {
                        callArgs.push(1 /* TEXT */ +
                            (` /* ${PatchFlagNames[1 /* TEXT */]} */` ));
                    }
                    // 子节点被替换成 TEXT_CALL 包裹体:调用 CREATE_TEXT 方法 生成一段text内容
                    children[i] = {
                        type: 12 /* TEXT_CALL */,
                        content: child,
                        loc: child.loc,
                        // 动态text的gennode其实也是一个函数表达式而已
                        codegenNode: createCallExpression(context.helper(CREATE_TEXT), callArgs)
                    };
                }
            }
        };
    }
};

// nodeTransforms 第一个方法 由于是 反序后续遍历处理 所以会缓存所有处理过后的信息 优先级最高
const seen = new WeakSet();
const transformOnce = (node, context) => {
    // 存在 v-once 
    if (node.type === 1 /* ELEMENT */ && findDir(node, 'once', true)) {
        // 是否已经处理过了
        if (seen.has(node)) {
            return;
        }
        seen.add(node);
        // 暂停track 不需要触发依赖收集 添加对应helper
        context.helper(SET_BLOCK_TRACKING);
        // 返回后续处理函数
        return () => {
            const cur = context.currentNode;
            // 如果有被其他方法处理过得到的 codegenNode 就给它添加一层cache包裹
            // 直接把别的tranform处理过得到的codegen信息拿来用就好了
            if (cur.codegenNode) {
                cur.codegenNode = context.cache(cur.codegenNode, true /* isVNode */);
            }
        };
    }
};

// model的第一步基础处理
const transformModel = (dir, node, context) => {
    const { exp, arg } = dir;
    // 空值错误场景
    if (!exp) {
        context.onError(createCompilerError(40 /* X_V_MODEL_NO_EXPRESSION */, dir.loc));
        return createTransformProps();
    }
    const rawExp = exp.loc.source;
    const expString = exp.type === 4 /* SIMPLE_EXPRESSION */ ? exp.content : rawExp;
    // im SFC <script setup> inline mode, the exp may have been transformed into
    // _unref(exp)
    // todo ?
    context.bindingMetadata[rawExp];
    const maybeRef = !true    /* SETUP_CONST */;
    // v-model的值 需要为成员表达式类型
    if (!isMemberExpression(expString) && !maybeRef) {
        context.onError(createCompilerError(41 /* X_V_MODEL_MALFORMED_EXPRESSION */, exp.loc));
        return createTransformProps();
    }
    // v-model:propName = '某成员变量' || v-model="某成员变量"
    const propName = arg ? arg : createSimpleExpression('modelValue', true);
    const eventName = arg
        ? isStaticExp(arg)
            ? `onUpdate:${arg.content}`
            : createCompoundExpression(['"onUpdate:" + ', arg])
        : `onUpdate:modelValue`;
    let assignmentExp;
    const eventArg = context.isTS ? `($event: any)` : `$event`;
    // v-model被拆成2部分 第一步 绑定:value = '成员变量' 然后绑定一个自定义事件 input || change (事件分析那里会有实现) onUpdate:modelValue = ($event) => (exp = $event)
    {
        assignmentExp = createCompoundExpression([
            `${eventArg} => (`,
            exp,
            ` = $event)`
        ]);
    }
    // 一个v-model变2个prop其实
    const props = [
        // modelValue: foo
        createObjectProperty(propName, dir.exp),
        // "onUpdate:modelValue": $event => (foo = $event)
        createObjectProperty(eventName, assignmentExp)
    ];
    // 对应文档中描述的 v-model的modelModifiers
    // modelModifiers: { foo: true, "bar-baz": true }
    // 不过只限定在组件上使用
    if (dir.modifiers.length && node.tagType === 1 /* COMPONENT */) {
        const modifiers = dir.modifiers
            // capture: true 如上文
            .map(m => (isSimpleIdentifier(m) ? m : JSON.stringify(m)) + `: true`)
            .join(`, `);
        const modifiersKey = arg
            ? isStaticExp(arg)
                ? `${arg.content}Modifiers`
                : createCompoundExpression([arg, ' + "Modifiers"'])
            : `modelModifiers`;
        // 添加进props中关于modify的信息
        props.push(createObjectProperty(modifiersKey, createSimpleExpression(`{ ${modifiers} }`, false, dir.loc, 2 /* CAN_HOIST */)));
    }
    return createTransformProps(props);
};
function createTransformProps(props = []) {
    return { props };
}

// 基础转化函数 后面还会增加几个
function getBaseTransformPreset(prefixIdentifiers) {
    return [
        [
            transformOnce,
            transformIf,
            transformFor,
            ...([transformExpression]
                    ),
            transformSlotOutlet,
            transformElement,
            trackSlotScopes,
            transformText
        ],
        {
            on: transformOn,
            bind: transformBind,
            model: transformModel
        }
    ];
}
// we name it `baseCompile` so that higher order compilers like
// @vue/compiler-dom can export `compile` while re-exporting everything else.
// 编译函数入口 完成3个步骤 1.生成 ast 2.对ast进行transform转换 3. generate生成render函数code字符串
function baseCompile(template, options = {}) {
    const onError = options.onError || defaultOnError;
    const isModuleMode = options.mode === 'module';
    /* istanbul ignore if */
    {
        if (options.prefixIdentifiers === true) {
            onError(createCompilerError(45 /* X_PREFIX_ID_NOT_SUPPORTED */));
        }
        else if (isModuleMode) {
            onError(createCompilerError(46 /* X_MODULE_MODE_NOT_SUPPORTED */));
        }
    }
    const prefixIdentifiers = !true ;
    if (options.cacheHandlers) {
        onError(createCompilerError(47 /* X_CACHE_HANDLER_NOT_SUPPORTED */));
    }
    if (options.scopeId && !isModuleMode) {
        onError(createCompilerError(48 /* X_SCOPE_ID_NOT_SUPPORTED */));
    }
    // 以上几个选项都不支持
    // ast
    const ast = isString(template) ? baseParse(template, options) : template;
    const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();
    // transform
    transform(ast, extend({}, options, {
        prefixIdentifiers,
        nodeTransforms: [
            ...nodeTransforms,
            ...(options.nodeTransforms || []) // user transforms
        ],
        directiveTransforms: extend({}, directiveTransforms, options.directiveTransforms || {} // user transforms
        )
    }));
    // generate
    return generate(ast, extend({}, options, {
        prefixIdentifiers
    }));
}

// 目前源码中未实现
const noopDirectiveTransform = () => ({ props: [] });

// 标识符
const V_MODEL_RADIO = Symbol(`vModelRadio` );
const V_MODEL_CHECKBOX = Symbol(`vModelCheckbox` );
const V_MODEL_TEXT = Symbol(`vModelText` );
const V_MODEL_SELECT = Symbol(`vModelSelect` );
const V_MODEL_DYNAMIC = Symbol(`vModelDynamic` );
const V_ON_WITH_MODIFIERS = Symbol(`vOnModifiersGuard` );
const V_ON_WITH_KEYS = Symbol(`vOnKeysGuard` );
const V_SHOW = Symbol(`vShow` );
const TRANSITION$1 = Symbol(`Transition` );
const TRANSITION_GROUP = Symbol(`TransitionGroup` );
registerRuntimeHelpers({
    [V_MODEL_RADIO]: `vModelRadio`,
    [V_MODEL_CHECKBOX]: `vModelCheckbox`,
    [V_MODEL_TEXT]: `vModelText`,
    [V_MODEL_SELECT]: `vModelSelect`,
    [V_MODEL_DYNAMIC]: `vModelDynamic`,
    [V_ON_WITH_MODIFIERS]: `withModifiers`,
    [V_ON_WITH_KEYS]: `withKeys`,
    [V_SHOW]: `vShow`,
    [TRANSITION$1]: `Transition`,
    [TRANSITION_GROUP]: `TransitionGroup`
});

/* eslint-disable no-restricted-globals */
let decoder;
// 取临时节点的文本内容返回
function decodeHtmlBrowser(raw) {
    (decoder || (decoder = document.createElement('div'))).innerHTML = raw;
    return decoder.textContent;
}

// 返回原始标签内的内容
const isRawTextContainer = /*#__PURE__*/ makeMap('style,iframe,script,noscript', true);
// 解析的一些辅助方法 用来覆盖已有的方法默认值
const parserOptions = {
    // 自闭合元素标签
    isVoidTag,
    // html元素tag
    isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
    // <pre>
    isPreTag: tag => tag === 'pre',
    // 带有&的html编码转译 默认是直接正则替代 这个decodeHtmlBrowser见上文
    decodeEntities: decodeHtmlBrowser ,
    // 内置组件
    isBuiltInComponent: (tag) => {
        if (isBuiltInType(tag, `Transition`)) {
            return TRANSITION$1;
        }
        else if (isBuiltInType(tag, `TransitionGroup`)) {
            return TRANSITION_GROUP;
        }
    },
    // 详情见文档吧 我们常用的就是html 0类型
    // https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher
    getNamespace(tag, parent) {
        let ns = parent ? parent.ns : 0 /* HTML */;
        if (parent && ns === 2 /* MATH_ML */) {
            if (parent.tag === 'annotation-xml') {
                if (tag === 'svg') {
                    return 1 /* SVG */;
                }
                if (parent.props.some(a => a.type === 6 /* ATTRIBUTE */ &&
                    a.name === 'encoding' &&
                    a.value != null &&
                    (a.value.content === 'text/html' ||
                        a.value.content === 'application/xhtml+xml'))) {
                    ns = 0 /* HTML */;
                }
            }
            else if (/^m(?:[ions]|text)$/.test(parent.tag) &&
                tag !== 'mglyph' &&
                tag !== 'malignmark') {
                ns = 0 /* HTML */;
            }
        }
        else if (parent && ns === 1 /* SVG */) {
            if (parent.tag === 'foreignObject' ||
                parent.tag === 'desc' ||
                parent.tag === 'title') {
                ns = 0 /* HTML */;
            }
        }
        if (ns === 0 /* HTML */) {
            if (tag === 'svg') {
                return 1 /* SVG */;
            }
            if (tag === 'math') {
                return 2 /* MATH_ML */;
            }
        }
        return ns;
    },
    // https://html.spec.whatwg.org/multipage/parsing.html#parsing-html-fragments
    // 编译parser正在解析的文本类型
    getTextMode({ tag, ns }) {
        if (ns === 0 /* HTML */) {
            if (tag === 'textarea' || tag === 'title') {
                return 1 /* RCDATA */;
            }
            // 原始内容 不做任何处理
            if (isRawTextContainer(tag)) {
                return 2 /* RAWTEXT */;
            }
        }
        return 0 /* DATA */;
    }
};

// Parse inline CSS strings for static style attributes into an object.
// This is a NodeTransform since it works on the static `style` attribute and
// converts it into a dynamic equivalent:
// style="color: red" -> :style='{ "color": "red" }'
// It is then processed by `transformElement` and included in the generated
// props.
// transform 第十个方法 先序遍历 直接处理当前node
const transformStyle = node => {
    if (node.type === 1 /* ELEMENT */) {
        node.props.forEach((p, i) => {
            // 只有以 style="color: red" 这样写的静态初始attr 才需要额外处理 动态写的 :style="xxx" 被当做v-bind:style 处理了 算动态prop
            if (p.type === 6 /* ATTRIBUTE */ && p.name === 'style' && p.value) {
                // replace p with an expression node
                // 转化成动态prop的一部分
                node.props[i] = {
                    type: 7 /* DIRECTIVE */,
                    name: `bind`,
                    // 注意第二个参数 isStatic 是true 意味着不会改变
                    arg: createSimpleExpression(`style`, true, p.loc),
                    exp: parseInlineCSS(p.value.content, p.loc),
                    modifiers: [],
                    loc: p.loc
                };
            }
        });
    }
};
// 行内style转化
const parseInlineCSS = (cssText, loc) => {
    const normalized = parseStringStyle(cssText);
    // 对象字符串化
    return createSimpleExpression(JSON.stringify(normalized), false, loc, 3 /* CAN_STRINGIFY */);
};

// 编译函数运行过程中的错误信息
function createDOMCompilerError(code, loc) {
    return createCompilerError(code, loc, DOMErrorMessages );
}
// 尝试写一些错误场景 可以看到下面的提示
const DOMErrorMessages = {
    [49 /* X_V_HTML_NO_EXPRESSION */]: `v-html is missing expression.`,
    [50 /* X_V_HTML_WITH_CHILDREN */]: `v-html will override element children.`,
    [51 /* X_V_TEXT_NO_EXPRESSION */]: `v-text is missing expression.`,
    [52 /* X_V_TEXT_WITH_CHILDREN */]: `v-text will override element children.`,
    [53 /* X_V_MODEL_ON_INVALID_ELEMENT */]: `v-model can only be used on <input>, <textarea> and <select> elements.`,
    [54 /* X_V_MODEL_ARG_ON_ELEMENT */]: `v-model argument is not supported on plain elements.`,
    [55 /* X_V_MODEL_ON_FILE_INPUT_ELEMENT */]: `v-model cannot be used on file inputs since they are read-only. Use a v-on:change listener instead.`,
    [56 /* X_V_MODEL_UNNECESSARY_VALUE */]: `Unnecessary value binding used alongside v-model. It will interfere with v-model's behavior.`,
    [57 /* X_V_SHOW_NO_EXPRESSION */]: `v-show is missing expression.`,
    [58 /* X_TRANSITION_INVALID_CHILDREN */]: `<Transition> expects exactly one child element or component.`,
    [59 /* X_IGNORED_SIDE_EFFECT_TAG */]: `Tags with side effect (<script> and <style>) are ignored in client component templates.`
};

// v-html 转化 分析同 v-text 同样 propname 为 innerHTML
const transformVHtml = (dir, node, context) => {
    const { exp, loc } = dir;
    if (!exp) {
        context.onError(createDOMCompilerError(49 /* X_V_HTML_NO_EXPRESSION */, loc));
    }
    if (node.children.length) {
        context.onError(createDOMCompilerError(50 /* X_V_HTML_WITH_CHILDREN */, loc));
        node.children.length = 0;
    }
    // 返回简单的 innerHTML: exp 的prop就好了
    return {
        props: [
            createObjectProperty(createSimpleExpression(`innerHTML`, true, loc), exp || createSimpleExpression('', true))
        ]
    };
};

// v-text 转化 propname 为 textContent
const transformVText = (dir, node, context) => {
    const { exp, loc } = dir;
    // 必须要有 值表达式
    if (!exp) {
        context.onError(createDOMCompilerError(51 /* X_V_TEXT_NO_EXPRESSION */, loc));
    }
    // 元素的text会被替换 无需原始内容
    if (node.children.length) {
        context.onError(createDOMCompilerError(52 /* X_V_TEXT_WITH_CHILDREN */, loc));
        node.children.length = 0;
    }
    // 创建prop key-value
    return {
        props: [
            createObjectProperty(createSimpleExpression(`textContent`, true), exp
                ? createCallExpression(context.helperString(TO_DISPLAY_STRING), [exp], loc)
                : createSimpleExpression('', true))
        ]
    };
};

// 和on一样 分2步处理
const transformModel$1 = (dir, node, context) => {
    // 第一步
    const baseResult = transformModel(dir, node, context);
    // base transform has errors OR component v-model (only need props)
    // 第二步
    // 对于组件而言 v-model 对于组件来说只需要 prop
    if (!baseResult.props.length || node.tagType === 1 /* COMPONENT */) {
        return baseResult;
    }
    // v-model:title 这样的只能用在组件上
    if (dir.arg) {
        context.onError(createDOMCompilerError(54 /* X_V_MODEL_ARG_ON_ELEMENT */, dir.arg.loc));
    }
    // 有v-model value就不需要了 会自动添加
    function checkDuplicatedValue() {
        const value = findProp(node, 'value');
        if (value) {
            context.onError(createDOMCompilerError(56 /* X_V_MODEL_UNNECESSARY_VALUE */, value.loc));
        }
    }
    const { tag } = node;
    // 确定几种可能用到v-model的原生dom元素的类型 添加对应的helper方法
    const isCustomElement = context.isCustomElement(tag);
    if (tag === 'input' ||
        tag === 'textarea' ||
        tag === 'select' ||
        isCustomElement) {
        // 实际使用的指令
        let directiveToUse = V_MODEL_TEXT;
        let isInvalidType = false;
        if (tag === 'input' || isCustomElement) {
            const type = findProp(node, `type`);
            if (type) {
                if (type.type === 7 /* DIRECTIVE */) {
                    // :type="foo"
                    // input元素类型属于动态变量的场景
                    directiveToUse = V_MODEL_DYNAMIC;
                }
                // 确定值
                else if (type.value) {
                    switch (type.value.content) {
                        case 'radio':
                            directiveToUse = V_MODEL_RADIO;
                            break;
                        case 'checkbox':
                            directiveToUse = V_MODEL_CHECKBOX;
                            break;
                        // input file 不支持
                        case 'file':
                            isInvalidType = true;
                            context.onError(createDOMCompilerError(55 /* X_V_MODEL_ON_FILE_INPUT_ELEMENT */, dir.loc));
                            break;
                        default:
                            // text type
                            checkDuplicatedValue();
                            break;
                    }
                }
            }
            // 可能存在动态key的场景 
            else if (hasDynamicKeyVBind(node)) {
                // element has bindings with dynamic keys, which can possibly contain
                // "type".
                directiveToUse = V_MODEL_DYNAMIC;
            }
            else {
                // text type
                checkDuplicatedValue();
            }
        }
        else if (tag === 'select') {
            directiveToUse = V_MODEL_SELECT;
        }
        else {
            // textarea
            checkDuplicatedValue();
        }
        // inject runtime directive
        // by returning the helper symbol via needRuntime
        // the import will replaced a resolveDirective call.
        if (!isInvalidType) {
            // v-model是一种需要在patch的时候跟dom以及事件打交道的指令
            baseResult.needRuntime = context.helper(directiveToUse);
        }
    }
    // 剩余元素不支持v-model
    else {
        context.onError(createDOMCompilerError(53 /* X_V_MODEL_ON_INVALID_ELEMENT */, dir.loc));
    }
    // native vmodel doesn't need the `modelValue` props since they are also
    // passed to the runtime as `binding.value`. removing it reduces code size.
    // 变成指令的value绑定了
    baseResult.props = baseResult.props.filter(p => !(p.key.type === 4 /* SIMPLE_EXPRESSION */ &&
        p.key.content === 'modelValue'));
    return baseResult;
};

const isEventOptionModifier = /*#__PURE__*/ makeMap(`passive,once,capture`);
const isNonKeyModifier = /*#__PURE__*/ makeMap(
// event propagation management
`stop,prevent,self,`   +
    // system modifiers + exact
    `ctrl,shift,alt,meta,exact,` +
    // mouse
    `middle`);
// left & right could be mouse or key modifiers based on event type
const maybeKeyModifier = /*#__PURE__*/ makeMap('left,right');
const isKeyboardEvent = /*#__PURE__*/ makeMap(`onkeyup,onkeydown,onkeypress`, true);
// 分3种类型 分别存储
const resolveModifiers = (key, modifiers) => {
    // 键盘有关的
    const keyModifiers = [];
    // 剩余的
    const nonKeyModifiers = [];
    // 单独存储 passive,once,capture
    const eventOptionModifiers = [];
    for (let i = 0; i < modifiers.length; i++) {
        const modifier = modifiers[i];
        // 事件选项
        if (isEventOptionModifier(modifier)) {
            // eventOptionModifiers: modifiers for addEventListener() options,
            // e.g. .passive & .capture
            eventOptionModifiers.push(modifier);
        }
        // 剩余的都是键盘鼠标事件了
        else {
            // runtimeModifiers: modifiers that needs runtime guards
            if (maybeKeyModifier(modifier)) {
                // 静态事件名可确认的话
                if (isStaticExp(key)) {
                    // 直接判断是否键盘事件
                    if (isKeyboardEvent(key.content)) {
                        keyModifiers.push(modifier);
                    }
                    else {
                        nonKeyModifiers.push(modifier);
                    }
                }
                // 动态场景无法现在直接确认 就都先加入
                else {
                    keyModifiers.push(modifier);
                    nonKeyModifiers.push(modifier);
                }
            }
            else {
                if (isNonKeyModifier(modifier)) {
                    nonKeyModifiers.push(modifier);
                }
                else {
                    keyModifiers.push(modifier);
                }
            }
        }
    }
    return {
        keyModifiers,
        nonKeyModifiers,
        eventOptionModifiers
    };
};
const transformClick = (key, event) => {
    // 普通已确认的onClick这样的
    const isStaticClick = isStaticExp(key) && key.content.toLowerCase() === 'onclick';
    return isStaticClick
        // 替换event即可  
        ? createSimpleExpression(event, true)
        // 类似动态表达式 是一个组合表达字符串数组
        : key.type !== 4 /* SIMPLE_EXPRESSION */
            // 其实也是替换event 只不过推迟执行机到render函数执行而已
            ? createCompoundExpression([
                `(`,
                key,
                `) === "onClick" ? "${event}" : (`,
                key,
                `)`
            ])
            : key;
};
// 返回 transformOn 的结果 是一个闭包函数
// 分成2部分处理 baseResult 是前面的基础处理后得到的
// v-on:[event].stop="m();m();m()"
const transformOn$1 = (dir, node, context) => {
    return transformOn(dir, node, context, baseResult => {
        // 第二部分处理 函数的modifiers参数
        const { modifiers } = dir;
        if (!modifiers.length)
            return baseResult;
        let { key, value: handlerExp } = baseResult.props[0];
        // 取出3类 参数
        const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(key, modifiers);
        // normalize click.right and click.middle since they don't actually fire
        // 替换key的内容为有效事件名
        if (nonKeyModifiers.includes('right')) {
            key = transformClick(key, `onContextmenu`);
        }
        if (nonKeyModifiers.includes('middle')) {
            key = transformClick(key, `onMouseup`);
        }
        // 需要添加辅助方法了 vOnModifiersGuard
        if (nonKeyModifiers.length) {
            // 替换hanlder 需要借助 vOnModifiersGuard 重新组织函数逻辑 添加这些modifty的逻辑进去
            // 所以是用 createCallExpression
            handlerExp = createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [
                handlerExp,
                JSON.stringify(nonKeyModifiers)
            ]);
        }
        // 键盘事件modify
        if (keyModifiers.length &&
            // if event name is dynamic, always wrap with keys guard
            (!isStaticExp(key) || isKeyboardEvent(key.content))) {
            // 处理同上
            handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [
                handlerExp,
                JSON.stringify(keyModifiers)
            ]);
        }
        // 普通的事件参数了
        if (eventOptionModifiers.length) {
            // 如 PassiveOnce
            const modifierPostfix = eventOptionModifiers.map(capitalize).join('');
            key = isStaticExp(key)
                ? createSimpleExpression(`${key.content}${modifierPostfix}`, true)
                : createCompoundExpression([`(`, key, `) + "${modifierPostfix}"`]);
        }
        // 重新组织 prop的 key-value
        return {
            props: [createObjectProperty(key, handlerExp)]
        };
    });
};

//  transform阶段v-show指令并不需要额外的转化 它是一个运行时才产生作用的指令 也就是它是在patch过程中 随着伴随着一些生命周期阶段而被执行的
const transformShow = (dir, node, context) => {
    const { exp, loc } = dir;
    if (!exp) {
        context.onError(createDOMCompilerError(57 /* X_V_SHOW_NO_EXPRESSION */, loc));
    }
    return {
        props: [],
        needRuntime: context.helper(V_SHOW)
    };
};

// nodeTransforms 第十一个方法 后序遍历处理 子节点转化完成后检查个数是否符合要求
const warnTransitionChildren = (node, context) => {
    if (node.type === 1 /* ELEMENT */ &&
        node.tagType === 1 /* COMPONENT */) {
        // 确定组件名为 TRANSITION
        const component = context.isBuiltInComponent(node.tag);
        if (component === TRANSITION$1) {
            return () => {
                // 多个可以被渲染的子节点 则抛出警告
                if (node.children.length && hasMultipleChildren(node)) {
                    context.onError(createDOMCompilerError(58 /* X_TRANSITION_INVALID_CHILDREN */, {
                        start: node.children[0].loc.start,
                        end: node.children[node.children.length - 1].loc.end,
                        source: ''
                    }));
                }
            };
        }
    }
};
// 确认是否有多个子节点会被渲染
function hasMultipleChildren(node) {
    // #1352 filter out potential comment nodes.
    const children = (node.children = node.children.filter(c => c.type !== 3 /* COMMENT */));
    const child = children[0];
    return (children.length !== 1 ||
        child.type === 11 /* FOR */ ||
        (child.type === 9 /* IF */ && child.branches.some(hasMultipleChildren)));
}

// transform 第九个方法 忽略script 和 style 节点内的内容 先序直接处理
const ignoreSideEffectTags = (node, context) => {
    if (node.type === 1 /* ELEMENT */ &&
        node.tagType === 0 /* ELEMENT */ &&
        (node.tag === 'script' || node.tag === 'style')) {
        // 输出错误信息
        context.onError(createDOMCompilerError(59 /* X_IGNORED_SIDE_EFFECT_TAG */, node.loc));
        // 同时移除这个node节点 忽略
        context.removeNode();
    }
};

简单聊一聊JS中的作用域吧

// 默认就始终有一个 全局作用域 我们可以把它称为 S0
var a = 'S0';

// 在当前作用域内我们引用标识 a 的时候就是指的上面的变量

// 全局变量在es5之前有个特性 在任何地方未声明某个变量直接使用的话 会全局作用域内添加该变量的声明且添加为全局对象的属性

b = 0; // 不会报错 且 window.b = 0;
console.log(window.b);

// es6 之后就只创建新的全局变量b 但是 不再添加为全局对象的属性

// 接下来的内容都以es6为标准来讨论:

// 有哪些代码可以创造新的作用域呢?

// 1. 函数声明代码块

// 函数代码内部为新的一个函数作用域
function F1() {
    var a = 'F1';
    console.log(a);
}

F1(); // 'F!'

function F2() {
    console.log(a);
}

F2(); // S0

// 以上输出显示了 作用域的查找是 从内向外 查找的

function F3() {
    // 作用域 F3
    var a = 'F3';
    function _F3() {
        // 作用域 _F3
        console.log(a);
    }
    return _F3;
}

F3()(); // 输出 F3  而不是 全局的 S0

// 上面的代码说明了 js 作用域是 词法作用域 而不是动态作用域 即:在词法分析阶段确定函数的作用域范围,而是在函数运行的时候

// 同时,F3运行完之后返回的内部函数_F3 还能引用本应该消失的作用域内的变量 a = F3,说明其实 _F3 这个函数的作用域中依旧保存着F3的作用域
// Scopes: [_F3, F3, S0] 类似这样的情况

// 实际上,浏览器debug状态显示函数的时候,可以看到一个内部属性 [[Scopes]] ,里面按照上面的顺序保存着对应的 上下文 或者说是 作用域吧
// 上面的效果 就构成了我们平常看到的闭包: 

/**
 * 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。
 * 也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。 
 */

// 如果 _F3 没有引用 F3 作用域的任何变量,其实不需要继续保持F3的作用域的, 编译器也做了优化,会把上面的情况的F3作用域移除,就跟普通函数一样了。
// 大家可以从浏览器对比下2次的Scopes数组的不同之处就明白了

// 另外,代码在执行的时候,作用域是类似用栈的特性来管理的,遇到一个作用域就入栈,这段代码执行完,弹出上个作用域,继续执行代码。

// 2. 块级作用域 任意 {  } 之间的代码块 都会产生块级作用域

// 由于var的变量提升效果 导致变量会被提前声明到最近的一个 函数作用域 或者 全局作用域 的代码片段的顶部

{
    // B0 作用域
    var c = 'B0';
    console.log(c); // B0
}
console.log(c); // BO

// 上面的代码其实 等效于 下面的

var d;
{
    // B0 作用域
    d = 'B0';
    console.log(d); // B0
}
console.log(d); // B0

// 不过 let 和 const 没有变量提升的效果 它们声明的变量直接和当前作用域绑定 且未声明之前引用会报错

{
    // B1 作用域
    let d = 'B1';
    console.log(d); // B1
}
console.log(d); // B0

// foo('outside');  // TypeError: foo is not a function
{
    function foo(location) {
        console.log('foo is called ' + location);
    }
    foo('inside'); // 正常工作并且打印 'foo is called inside'
}
foo('outside');  // 正常工作并且打印 'foo is called outside'
// 上面调用foo会报错 而 下面调用不会报错
// 上面的写法等于下面这种:

// var foo;
// // foo('outside');  // TypeError: foo is not a function
// {
//     // 重新赋值foo
//     function foo(location) {
//       console.log('foo is called ' + location);
//     }
//     foo('inside'); // 正常工作并且打印 'foo is called inside'
//   }
// foo('outside');  // 正常工作并且打印 'foo is called outside'

function f() { console.log('I am outside!'); }

(function () {
    if (false) {
        // 重复声明一次函数f
        function f() { console.log('I am inside!'); }
    }

    // f(); // 会报错
}());

/**
 * 原来,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式。

允许在块级作用域内声明函数。
函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
同时,函数声明还会提升到所在的块级作用域的头部。
注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。 
 */

// 在块级作用域内声明函数 具体的表现依赖于浏览器的实现 建议大家只在全局或者函数作用域内声明函数比较好

// 所以才会有下面这个经典代码
var handlers = [];
for (var i = 0; i < 2; ++i) {
    handlers[i] = function () {
        console.log(i);
    }
}
handlers[0](); // 2
// 原因是 handlers[0] 这个函数的 作用域 Scopes 中存有2个上下文 第一个是自己的 第二个是全局的 而 i 变量来自于 for 的 var i 提升到了外层的全局作用域 
// 知道原因后 就知道怎么改了

// 取消变量提升
for (let i = 0; i < 2; ++i) {
    handlers[i] = function () {
        console.log(i);
    }
}
handlers[0](); // 0

// 在中间增加一个作用域 里面存有每轮循环的i的值
// 作用域 0
for (var i = 0; i < 2; ++i) {
    handlers[i] = (function (index) {
        // 作用域 1
        return function () {
            // 作用域 2
            console.log(index);
        }
    })(i);
}
handlers[0](); // 0

// 这里提一下 变量提升相互覆盖的优先级

function testOrder(a, b) {
    console.log(a); // a 是形参 实参值为 1
    console.log(b); // 函数提升过来的 b 优先级最高 可以覆盖 形参 和 var提升的的变量 所以输出的是 函数b 而不是 4
    function b() {
        // 函数提升
    }
    var a = 2; // a 是形参 忽略var的提升 只是赋值a
    var b = 3; // 同上
    console.log(a);
    console.log(b);
}
testOrder(1, 4);
// 输出 1 函数b 2 3

// 还有一个很有意思的 函数参数默认值产生的作用域

(function () {
    var x = 1;

    function f(x, y = x) {
        console.log(y);
    }

    f(2) // 2

    /**
     * 上面代码中,参数y的默认值等于变量x。调用函数f时,参数形成一个单独的作用域。在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x,所以输出是2。 
     */
})();

(function () {
    let x = 1;

    function f(y = x) {
        let x = 2;
        console.log(y);
    }

    f() // 1

    /**
     * 上面代码中,函数f调用时,参数y = x形成一个单独的作用域。这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x。函数调用时,函数体内部的局部变量x影响不到默认值变量x。 
     */
})();

(function () {
    let foo = 'outer';

    function bar(func = () => foo) {
        let foo = 'inner';
        console.log(func());
    }

    bar(); // outer

    /**
     * 上面代码中,函数bar的参数func的默认值是一个匿名函数,返回值为变量foo。函数参数形成的单独作用域里面,并没有定义变量foo,所以foo指向外层的全局变量foo,因此输出outer。  
     */
})();


(function () {
    var x = 1;
    function foo(x, y = function () { x = 2; }) {
        var x = 3;
        y();
        console.log(x);
    }

    foo() // 3
    x // 1

    /**
     * 上面代码中,函数foo的参数形成一个单独作用域。这个作用域里面,首先声明了变量x,然后声明了变量y,y的默认值是一个匿名函数。这个匿名函数内部的变量x,指向同一个作用域的第一个参数x。函数foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是同一个变量,因此执行y后,内部变量x和外部全局变量x的值都没变。 
     */
})();


// 3. with 作用域

(function () {
    var a = 'outer_with'
    var withObj = { a: 'inner_with' }
    with (withObj) {
        console.log(a);
    }
})();

/**
 * JavaScript查找某个未使用命名空间的变量时,会通过作用域链来查找,作用域链是跟执行代码的context或者包含这个变量的函数有关。'with'语句将某个对象添加到作用域链的顶部,如果在statement中有某个未使用命名空间的变量,跟作用域链中的某个属性同名,则这个变量将指向这个属性值。如果沒有同名的属性,则将拋出ReferenceError异常。 
 *  总结一下,就是在作用余数组Scpoes顶部 动态新增一个作用域
 */

// 4. eval 作用域

var evalVar = 1;

(function () {
    var evalVar = 2;
    function test() {
        var evalVar = 3;
        console.log(eval('evalVar'));  // 直接调用,使用本地作用域,结果是 3
        eval("var evalVar = 4;"); // 直接改变当前作用域
        console.log(evalVar); // 4;
        var geval = eval; // 等价于在全局作用域调用
        console.log(geval('evalVar')); // 间接调用,使用全局作用域,1 直接穿透了所有的中间本地作用域 直接跳到全局作用域了
        (0, eval)('evalVar'); // 另一个间接调用 1
    }
    test();
})();

// 严格模式下 eval 拥有自己的作用域

(function () {
    var evalVar = 2;
    function test() {
        "use strict";
        var evalVar = 3;
        console.log(eval('evalVar'));  // 直接调用,使用本地作用域,结果是 3
        eval("var evalVar = 4;"); // 创建属于自己的作用域
        console.log(evalVar); // 3;
        var geval = eval; // 等价于在全局作用域调用
        console.log(geval('evalVar')); // 间接调用,使用全局作用域,1 直接穿透了所有的中间本地作用域 直接跳到全局作用域了
        (0, eval)('evalVar'); // 另一个间接调用 1
    }
    test();
})();

// 5. Function 构造的参数作用域问题

/**
 * 由 Function 构造器创建的函数不会创建当前环境的闭包,它们总是被创建于全局环境,因此在运行时它们只能访问全局变量和自己的局部变量,不能访问它们被 Function 构造器创建时所在的作用域的变量。这一点与使用 eval 执行创建函数的代码不同。 
 */
var x = 10;
(function () {
    function createFunction1() {
        var x = 20;
        return new Function('return x;'); // 这里的 x 指向最上面全局作用域内的 x
    }

    function createFunction2() {
        var x = 20;
        function f() {
            return x; // 这里的 x 指向上方本地作用域内的 x
        }
        return f;
    }

    var f1 = createFunction1();
    console.log(f1());          // 10
    var f2 = createFunction2();
    console.log(f2());          // 20
})();


// 6. catch 语句的特殊之处
try {
    throw "myException"; // generates an exception
}
catch (e) {
    // statements to handle any exceptions
    console.log(e); // pass exception object to error handler
}

/**
 * catch块指定一个标识符(在上面的示例中为e),该标识符保存由throw语句指定的值。
 * catch块是唯一的,因为当输入catch块时,JavaScript 会创建此标识符,并将其添加到当前作用域;标识符仅在catch块执行时存在;catch块执行完成后,标识符不再可用。 
 * 
 */

// 上面的代码情况可以解释绝大部分情况我们对变量的引用的作用域查找的分析了,但是 我们需要注意有2个变量是函数内部定义的:
// this 和 arguments 每个函数内部都有这2个默认参数
/**
 * 在JavaScript中,一个变量名进入作用域的方式有 4 种:

Language-defined:所有的作用域默认都会给出 this 和 arguments 两个变量名(global没有arguments);
Formal parameters(形参):函数有形参,形参会添加到函数的作用域中;
Function declarations(函数声明):如 function foo() {};
Variable declarations(变量声明):如 var foo,包括_函数表达式_。
函数声明和变量声明总是会被移动(即hoist)到它们所在的作用域的顶部(这对你是透明的)。

而变量的解析顺序(优先级),与变量进入作用域的4种方式的顺序一致 
 */

// 看下 this 指的什么: this的上下文基于函数调用的情况。和函数在哪定义无关,而和函数怎么调用有关。

// 在全局上下文(任何函数以外),this指向全局对象。

console.log(this === window); // true

// 简单调用,即独立函数调用。由于this没有通过call来指定,且this必须指向对象,那么默认就指向全局对象。

function f1() {
    return this;
}

f1() === window; // global object

// 在严格模式下,this保持进入execution context时被设置的值。如果没有设置,那么默认是undefined。它可以被设置为任意值**(包括null/undefined/1等等基础值,不会被转换成对象)**。

function f2() {
    "use strict"; // see strict mode
    return this;
}

f2() === undefined;

// 在箭头函数中,this由词法/静态作用域设置(set lexically)。它被设置为包含它的execution context的this,并且不再被调用方式影响(call/apply/bind)。

var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true

// Call as a method of a object
var obj = { foo: foo };
console.log(obj.foo() === globalObject); // true

// Attempt to set this using call
console.log(foo.call(obj) === globalObject); // true

// Attempt to set this using bind
foo = foo.bind(obj);
console.log(foo() === globalObject); // true

// 当函数作为对象方法调用时,this指向该对象。

var o = {
    prop: 37,
    f: function () {
        return this.prop;
    }
};

console.log(o.f()); // logs 37

// 原型链上的方法根对象方法一样,作为对象方法调用时this指向该对象。

// 在构造函数(函数用new调用)中,this指向要被constructed的新对象。

// Function.prototype上的call和apply可以指定函数运行时的this。

function add(c, d) {
    return this.a + this.b + c + d;
}

var o = { a: 1, b: 3 };
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34

// 注意,当用call和apply而传进去作为this的不是对象时,将会调用内置的ToObject操作转换成对象。所以4将会装换成new Number(4),而null/undefined由于无法转换成对象,全局对象将作为this。

// ES5引进了Function.prototype.bind。f.bind(someObject)会创建新的函数(函数体和作用域与原函数一致),但this被永久绑定到someObject,不论你怎么调用。

// this自动设置为触发事件的dom元素。

// 文章参考: https://github.com/creeperyang/blog/issues/16

// MDN 中也有有this很详细的介绍 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this

// 这个里面的js的this 也写的很有深度 https://github.com/mqyqingfeng/Blog/issues/7

// 下面是一些比较有趣的this的指向问题 请多多注意

// 注意,construct()方法中的this指向的是handler,而不是实例对象。
{
    const handler = {
        construct: function (target, args) {
            console.log(this === handler);
            return new target(...args);
        }
    }

    let p = new Proxy(function () { }, handler);
    new p() // true
}

/**
 * 虽然 Proxy 可以代理针对目标对象的访问,但它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证与目标对象的行为一致。主要原因就是在 Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。 
 */
{
    const target = {
        m: function () {
            console.log(this === proxy);
        }
    };
    const handler = {};

    const proxy = new Proxy(target, handler);

    target.m() // false
    proxy.m()  // true
}

// 此外,有些原生对象的内部属性,只有通过正确的this才能拿到,所以 Proxy 也无法代理这些原生对象的属性。
{
    const target = new Date();
    const handler = {};
    const proxy = new Proxy(target, handler);

    proxy.getDate();
    // TypeError: this is not a Date object.
}

// 上面代码中,getDate()方法只能在Date对象实例上面拿到,如果this不是Date对象实例就会报错。这时,this绑定原始对象,就可以解决这个问题。
{
    const target = new Date('2015-01-01');
    const handler = {
        get(target, prop) {
            if (prop === 'getDate') {
                return target.getDate.bind(target);
            }
            return Reflect.get(target, prop);
        }
    };
    const proxy = new Proxy(target, handler);

    proxy.getDate() // 1
}

// 另外,Proxy 拦截函数内部的this,指向的是handler对象。

{
    const handler = {
        get: function (target, key, receiver) {
            console.log(this === handler);
            return 'Hello, ' + key;
        },
        set: function (target, key, value) {
            console.log(this === handler);
            target[key] = value;
            return true;
        }
    };

    const proxy = new Proxy({}, handler);

    proxy.foo
    // true
    // Hello, foo

    proxy.foo = 1
    // true
}

// 上面例子中,get()和set()拦截函数内部的this,指向的都是handler对象。

// Reflect.get方法查找并返回target对象的name属性,如果没有该属性,则返回undefined。

{
    var myObject = {
        foo: 1,
        bar: 2,
        get baz() {
            return this.foo + this.bar;
        },
    }

    Reflect.get(myObject, 'foo') // 1
    Reflect.get(myObject, 'bar') // 2
    Reflect.get(myObject, 'baz') // 3
}

// 如果name属性部署了读取函数(getter),则读取函数的this绑定receiver。

{
    var myObject = {
        foo: 1,
        bar: 2,
        get baz() {
            return this.foo + this.bar;
        },
    };

    var myReceiverObject = {
        foo: 4,
        bar: 4,
    };

    Reflect.get(myObject, 'baz', myReceiverObject) // 8
}

// Reflect.set方法设置target对象的name属性等于value。

{
    var myObject = {
        foo: 1,
        set bar(value) {
            return this.foo = value;
        },
    }

    myObject.foo // 1

    Reflect.set(myObject, 'foo', 2);
    myObject.foo // 2

    Reflect.set(myObject, 'bar', 3)
    myObject.foo // 3
}
// 如果name属性设置了赋值函数,则赋值函数的this绑定receiver。

{
    var myObject = {
        foo: 4,
        set bar(value) {
            return this.foo = value;
        },
    };

    var myReceiverObject = {
        foo: 0,
    };

    Reflect.set(myObject, 'bar', 1, myReceiverObject);
    myObject.foo // 4
    myReceiverObject.foo // 1
}

{
    // Generator 函数总是返回一个遍历器,ES6 规定这个遍历器是 Generator 函数的实例,也继承了 Generator 函数的prototype对象上的方法。

    function* g() { }

    g.prototype.hello = function () {
        return 'hi!';
    };

    let obj = g();

    obj instanceof g // true
    obj.hello() // 'hi!'
    // 上面代码表明,Generator 函数g返回的遍历器obj,是g的实例,而且继承了g.prototype。但是,如果把g当作普通的构造函数,并不会生效,因为g返回的总是遍历器对象,而不是this对象。

    function* g() {
        this.a = 11;
    }

    let obj = g();
    obj.next();
    obj.a // undefined
    // 上面代码中,Generator 函数g在this对象上面添加了一个属性a,但是obj对象拿不到这个属性。

    // Generator 函数也不能跟new命令一起用,会报错。

    function* F() {
        yield this.x = 2;
        yield this.y = 3;
    }

    new F()
    // TypeError: F is not a constructor
    // 上面代码中,new命令跟构造函数F一起使用,结果报错,因为F不是构造函数。

    // 那么,有没有办法让 Generator 函数返回一个正常的对象实例,既可以用next方法,又可以获得正常的this?

    // 下面是一个变通方法。首先,生成一个空对象,使用call方法绑定 Generator 函数内部的this。这样,构造函数调用以后,这个空对象就是 Generator 函数的实例对象了。

    function* F() {
        this.a = 1;
        yield this.b = 2;
        yield this.c = 3;
    }
    var obj = {};
    var f = F.call(obj);

    f.next();  // Object {value: 2, done: false}
    f.next();  // Object {value: 3, done: false}
    f.next();  // Object {value: undefined, done: true}

    obj.a // 1
    obj.b // 2
    obj.c // 3
    // 上面代码中,首先是F内部的this对象绑定obj对象,然后调用它,返回一个 Iterator 对象。这个对象执行三次next方法(因为F内部有两个yield表达式),完成 F 内部所有代码的运行。这时,所有内部属性都绑定在obj对象上了,因此obj对象也就成了F的实例。

    // 上面代码中,执行的是遍历器对象f,但是生成的对象实例是obj,有没有办法将这两个对象统一呢?

    // 一个办法就是将obj换成F.prototype。

    function* F() {
        this.a = 1;
        yield this.b = 2;
        yield this.c = 3;
    }
    var f = F.call(F.prototype);

    f.next();  // Object {value: 2, done: false}
    f.next();  // Object {value: 3, done: false}
    f.next();  // Object {value: undefined, done: true}

    f.a // 1
    f.b // 2
    f.c // 3
    // 再将F改成构造函数,就可以对它执行new命令了。

    function* gen() {
        this.a = 1;
        yield this.b = 2;
        yield this.c = 3;
    }

    function F() {
        return gen.call(gen.prototype);
    }

    var f = new F();

    f.next();  // Object {value: 2, done: false}
    f.next();  // Object {value: 3, done: false}
    f.next();  // Object {value: undefined, done: true}

    f.a // 1
    f.b // 2
    f.c // 3
}

{
    // 类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。

    class Logger {
        printName(name = 'there') {
            this.print(`Hello ${name}`);
        }

        print(text) {
            console.log(text);
        }
    }

    const logger = new Logger();
    const { printName } = logger;
    printName(); // TypeError: Cannot read property 'print' of undefined
    // 上面代码中,printName方法中的this,默认指向Logger类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是undefined),从而导致找不到print方法而报错。
}

{
    // 注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。

    class Foo {
        static bar() {
            this.baz();
        }
        static baz() {
            console.log('hello');
        }
        baz() {
            console.log('world');
        }
    }

    Foo.bar() // hello
    // 上面代码中,静态方法bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。
}

{
    // ES6 规定,在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。

    class A {
        constructor() {
            this.x = 1;
        }
        print() {
            console.log(this.x);
        }
    }

    class B extends A {
        constructor() {
            super();
            this.x = 2;
        }
        m() {
            super.print();
        }
    }

    let b = new B();
    b.m() // 2
    // 上面代码中,super.print()虽然调用的是A.prototype.print(),但是A.prototype.print()内部的this指向子类B的实例,导致输出的是2,而不是1。也就是说,实际上执行的是super.print.call(this)。

    // 由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。

    class A {
        constructor() {
            this.x = 1;
        }
    }

    class B extends A {
        constructor() {
            super();
            this.x = 2;
            super.x = 3;
            console.log(super.x); // undefined
            console.log(this.x); // 3
        }
    }

    let b = new B();
    // 上面代码中,super.x赋值为3,这时等同于对this.x赋值为3。而当读取super.x的时候,读的是A.prototype.x,所以返回undefined。

    // 如果super作为对象,用在静态方法之中,这时super将指向父类,而不是父类的原型对象。

    class Parent {
        static myMethod(msg) {
            console.log('static', msg);
        }

        myMethod(msg) {
            console.log('instance', msg);
        }
    }

    class Child extends Parent {
        static myMethod(msg) {
            super.myMethod(msg);
        }

        myMethod(msg) {
            super.myMethod(msg);
        }
    }

    Child.myMethod(1); // static 1

    var child = new Child();
    child.myMethod(2); // instance 2
    // 上面代码中,super在静态方法之中指向父类,在普通方法之中指向父类的原型对象。

    // 另外,在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。

    class A {
        constructor() {
            this.x = 1;
        }
        static print() {
            console.log(this.x);
        }
    }

    class B extends A {
        constructor() {
            super();
            this.x = 2;
        }
        static m() {
            super.print();
        }
    }

    B.x = 3;
    B.m() // 3
    // 上面代码中,静态方法B.m里面,super.print指向父类的静态方法。这个方法里面的this指向的是B,而不是B的实例。
}

// 首先,就是this关键字。ES6 模块之中,顶层的this指向undefined;CommonJS 模块的顶层this指向当前模块,这是两者的一个重大差异。

// 文章参考 https://es6.ruanyifeng.com/


Vue(v3.0.11)源码简析之一些内置错误输出方法的相关实现

简易注释一下vue中内部的用于输出错误信息的一些辅助方法:

// 渲染组件的时候会形成组件实例栈 因此在输出错误信息的时候需要追踪栈的信息
const stack = [];
// 入栈
function pushWarningContext(vnode) {
    stack.push(vnode);
}
// 出栈
function popWarningContext() {
    stack.pop();
}
// 删除警告信息
function warn(msg, ...args) {
    // avoid props formatting or warn handler tracking deps that might be mutated
    // during patch, leading to infinite recursion.
    // 删除警告信息的过程中不需要建立依赖收集
    pauseTracking();
    // 取出组件实例
    const instance = stack.length ? stack[stack.length - 1].component : null;
    // app顶级配置用户设置的 warnHandler
    const appWarnHandler = instance && instance.appContext.config.warnHandler;
    // 返回vnode树
    const trace = getComponentTrace();
    // 删除错误信息 调用 callWithErrorHandling 注意几个参数
    if (appWarnHandler) {
        callWithErrorHandling(appWarnHandler, instance, 11 /* APP_WARN_HANDLER */, [
            msg + args.join(''),
            instance && instance.proxy,
            trace
                .map(({ vnode }) => `at <${formatComponentName(instance, vnode.type)}>`)
                .join('\n'),
            trace
        ]);
    }
    // 无用户配置的方法 就用默认的简单输出
    else {
        const warnArgs = [`[Vue warn]: ${msg}`, ...args];
        /* istanbul ignore if */
        if (trace.length &&
            // avoid spamming console during tests
            !false) {
            warnArgs.push(`\n`, ...formatTrace(trace));
        }
        console.warn(...warnArgs);
    }
    // 输出完毕 恢复
    resetTracking();
}
// 追踪组件调用栈
function getComponentTrace() {
    let currentVNode = stack[stack.length - 1];
    if (!currentVNode) {
        return [];
    }
    // we can't just use the stack because it will be incomplete during updates
    // that did not start from the root. Re-construct the parent chain using
    // instance parent pointers.
    const normalizedStack = [];
    // 从当前出错的vnode开始追踪它的父节点
    while (currentVNode) {
        const last = normalizedStack[0];
        // 统计递归次数
        if (last && last.vnode === currentVNode) {
            last.recurseCount++;
        }
        else {
            normalizedStack.push({
                vnode: currentVNode,
                recurseCount: 0
            });
        }
        // 对父节点继续追踪
        const parentInstance = currentVNode.component && currentVNode.component.parent;
        currentVNode = parentInstance && parentInstance.vnode;
    }
    // 返回vnode栈
    return normalizedStack;
}
/* istanbul ignore next */
// 格式化之前得到的vnode栈
function formatTrace(trace) {
    const logs = [];
    trace.forEach((entry, i) => {
        logs.push(...(i === 0 ? [] : [`\n`]), ...formatTraceEntry(entry));
    });
    return logs;
}
// 每个vnode对象按照标准格式输出信息 
function formatTraceEntry({ vnode, recurseCount }) {
    const postfix = recurseCount > 0 ? `... (${recurseCount} recursive calls)` : ``;
    const isRoot = vnode.component ? vnode.component.parent == null : false;
    const open = ` at <${formatComponentName(vnode.component, vnode.type, isRoot)}`;
    const close = `>` + postfix;
    return vnode.props
        ? [open, ...formatProps(vnode.props), close]
        : [open + close];
}
/* istanbul ignore next */
// 整体输出props
function formatProps(props) {
    const res = [];
    const keys = Object.keys(props);
    keys.slice(0, 3).forEach(key => {
        res.push(...formatProp(key, props[key]));
    });
    if (keys.length > 3) {
        res.push(` ...`);
    }
    return res;
}
/* istanbul ignore next */
// 删除单个prop信息 根据不同类型输出不同文案
function formatProp(key, value, raw) {
    if (isString(value)) {
        value = JSON.stringify(value);
        return raw ? value : [`${key}=${value}`];
    }
    else if (typeof value === 'number' ||
        typeof value === 'boolean' ||
        value == null) {
        return raw ? value : [`${key}=${value}`];
    }
    else if (isRef(value)) {
        value = formatProp(key, toRaw(value.value), true);
        return raw ? value : [`${key}=Ref<`, value, `>`];
    }
    else if (isFunction(value)) {
        return [`${key}=fn${value.name ? `<${value.name}>` : ``}`];
    }
    else {
        value = toRaw(value);
        return raw ? value : [`${key}=`, value];
    }
}

// 错误发生到哪些函数运行的过程中的 辅助提示信息
const ErrorTypeStrings = {
    ["bc" /* BEFORE_CREATE */]: 'beforeCreate hook',
    ["c" /* CREATED */]: 'created hook',
    ["bm" /* BEFORE_MOUNT */]: 'beforeMount hook',
    ["m" /* MOUNTED */]: 'mounted hook',
    ["bu" /* BEFORE_UPDATE */]: 'beforeUpdate hook',
    ["u" /* UPDATED */]: 'updated',
    ["bum" /* BEFORE_UNMOUNT */]: 'beforeUnmount hook',
    ["um" /* UNMOUNTED */]: 'unmounted hook',
    ["a" /* ACTIVATED */]: 'activated hook',
    ["da" /* DEACTIVATED */]: 'deactivated hook',
    ["ec" /* ERROR_CAPTURED */]: 'errorCaptured hook',
    ["rtc" /* RENDER_TRACKED */]: 'renderTracked hook',
    ["rtg" /* RENDER_TRIGGERED */]: 'renderTriggered hook',
    [0 /* SETUP_FUNCTION */]: 'setup function',
    [1 /* RENDER_FUNCTION */]: 'render function',
    [2 /* WATCH_GETTER */]: 'watcher getter',
    [3 /* WATCH_CALLBACK */]: 'watcher callback',
    [4 /* WATCH_CLEANUP */]: 'watcher cleanup function',
    [5 /* NATIVE_EVENT_HANDLER */]: 'native event handler',
    [6 /* COMPONENT_EVENT_HANDLER */]: 'component event handler',
    [7 /* VNODE_HOOK */]: 'vnode hook',
    [8 /* DIRECTIVE_HOOK */]: 'directive hook',
    [9 /* TRANSITION_HOOK */]: 'transition hook',
    [10 /* APP_ERROR_HANDLER */]: 'app errorHandler',
    [11 /* APP_WARN_HANDLER */]: 'app warnHandler',
    [12 /* FUNCTION_REF */]: 'ref function',
    [13 /* ASYNC_COMPONENT_LOADER */]: 'async component loader',
    [14 /* SCHEDULER */]: 'scheduler flush. This is likely a Vue internals bug. ' +
        'Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue-next'
};
// 带有errorHandler的方法执行 同步执行
function callWithErrorHandling(fn, instance, type, args) {
    let res;
    try {
        res = args ? fn(...args) : fn();
    }
    // 捕获错误
    catch (err) {
        handleError(err, instance, type);
    }
    return res;
}
function callWithAsyncErrorHandling(fn, instance, type, args) {
    if (isFunction(fn)) {
        const res = callWithErrorHandling(fn, instance, type, args);
        if (res && isPromise(res)) {
            // 加个异步捕获
            res.catch(err => {
                handleError(err, instance, type);
            });
        }
        return res;
    }
    const values = [];
    // 数组 返回promise数组
    for (let i = 0; i < fn.length; i++) {
        values.push(callWithAsyncErrorHandling(fn[i], instance, type, args));
    }
    return values;
}
function handleError(err, instance, type, throwInDev = true) {
    const contextVNode = instance ? instance.vnode : null;
    if (instance) {
        let cur = instance.parent;
        // the exposed instance is the render proxy to keep it consistent with 2.x
        const exposedInstance = instance.proxy;
        // in production the hook receives only the error code
        const errorInfo = ErrorTypeStrings[type] ;
        // 在删除错误信息之前一次性执行所有的钩子 反向递归向上
        while (cur) {
            const errorCapturedHooks = cur.ec;
            // 错误捕获的钩子 居然还有这个钩子 太细节了vue
            if (errorCapturedHooks) {
                for (let i = 0; i < errorCapturedHooks.length; i++) {
                    if (errorCapturedHooks[i](err, exposedInstance, errorInfo) === false) {
                        return;
                    }
                }
            }
            cur = cur.parent;
        }
        // app-level handling
        // 优先调用全局配置的用户函数
        const appErrorHandler = instance.appContext.config.errorHandler;
        if (appErrorHandler) {
            callWithErrorHandling(appErrorHandler, null, 10 /* APP_ERROR_HANDLER */, [err, exposedInstance, errorInfo]);
            return;
        }
    }
    // 失败回退的默认处理
    logError(err, type, contextVNode, throwInDev);
}
//输出错误信息
function logError(err, type, contextVNode, throwInDev = true) {
    {
        const info = ErrorTypeStrings[type];
        if (contextVNode) {
            pushWarningContext(contextVNode);
        }
        warn(`Unhandled error${info ? ` during execution of ${info}` : ``}`);
        if (contextVNode) {
            popWarningContext();
        }
        // crash in dev by default so it's more noticeable
        // 抛出错误或者输出错误
        if (throwInDev) {
            throw err;
        }
        else {
            console.error(err);
        }
    }
}

Vue(v3.0.11)源码简析之render对象的相关实现(1)

全局唯一的render对象,提供了patch方法,可以支持任意类型的vnode映射到实际的宿主dom元素中,且支持挂载和更新;更新也实现了全量对比和优化对比2种方式,也用到了最长递增子序列算法,可谓是vue中最终把虚拟dom的信息实打实的同步到实际dom中的最终实现者,看下它们是如何实现的:

// dev模式下 创建响应式effect render函数时候的默认配置参数
function createDevEffectOptions(instance) {
  return {
      // trigger触发后原函数被推入job队列中 并不马上重新执行
      scheduler: queueJob,
      allowRecurse: true,
      // 2个钩子
      onTrack: instance.rtc ? e => invokeArrayFns(instance.rtc, e) : void 0,
      onTrigger: instance.rtg ? e => invokeArrayFns(instance.rtg, e) : void 0
  };
}
// 推入post队列 注意组件实例父链上存在Suspense的场景即可
const queuePostRenderEffect = queueEffectWithSuspense
  ;
// 组件patch挂载dom完成之后 组件实例和vnode都已经是最新的稳定状态了 我们可以在后序遍历的最后调用这个方法
// 去更新父组件对子组件的ref属性中的引用信息了
const setRef = (rawRef, oldRawRef, parentSuspense, vnode) => {
  // 文档中有写 ref是个数组是对v-for和ref同时存在的情况
  if (isArray(rawRef)) {
      // 遍历逐一处理即可
      rawRef.forEach((r, i) => setRef(r, oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef), parentSuspense, vnode));
      return;
  }
  // 待更新新ref值 父组件需要更新的引用值
  let value;
  // 卸载的vnode 父组件对这个子组件的ref为null
  if (!vnode) {
      // means unmount
      value = null;
  }
  // 异步包装组件不作处理 在异步组件内部 实际的子组件会继承这个ref 从而让父组件可以找到这个实际的ref指向的组件
  else if (isAsyncWrapper(vnode)) {
      // when mounting async components, nothing needs to be done,
      // because the template ref is forwarded to inner component
      return;
  }
  // 组件ref 指向组件实例
  else if (vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */) {
      value = vnode.component.exposed || vnode.component.proxy;
  }
  // 指向元素
  else {
      value = vnode.el;
  }
  // 父组件咯
  const { i: owner, r: ref } = rawRef;
  if (!owner) {
      warn(`Missing ref owner context. ref cannot be used on hoisted vnodes. ` +
          `A vnode with ref must be created inside the render function.`);
      return;
  }
  const oldRef = oldRawRef && oldRawRef.r;
  const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs;
  // setupState见后文的分析 这里同步更新就好了
  const setupState = owner.setupState;
  // unset old ref
  // 移除旧引用
  if (oldRef != null && oldRef !== ref) {
      if (isString(oldRef)) {
          refs[oldRef] = null;
          if (hasOwn(setupState, oldRef)) {
              setupState[oldRef] = null;
          }
      }
      else if (isRef(oldRef)) {
          oldRef.value = null;
      }
  }
  // 更新新引用
  if (isString(ref)) {
      // 设置更新job
      const doSet = () => {
          refs[ref] = value;
          if (hasOwn(setupState, ref)) {
              setupState[ref] = value;
          }
      };
      // #1789: for non-null values, set them after render
      // null values means this is unmount and it should not overwrite another
      // ref with the same key
      if (value) {
          // 以最高优先级推入post队列中 render之后去更新ref
          doSet.id = -1;
          queuePostRenderEffect(doSet, parentSuspense);
      }
      else {
          doSet();
      }
  }
  // 文档描述的ref还有第二种值的形式
  else if (isRef(ref)) {
      // 分析同上
      const doSet = () => {
          ref.value = value;
      };
      if (value) {
          doSet.id = -1;
          queuePostRenderEffect(doSet, parentSuspense);
      }
      else {
          doSet();
      }
  }
  // 第三种 函数 执行就可以了
  else if (isFunction(ref)) {
      callWithErrorHandling(ref, owner, 12 /* FUNCTION_REF */, [value, refs]);
  }
  else {
      warn('Invalid template ref type:', value, `(${typeof value})`);
  }
};
/**
* The createRenderer function accepts two generic arguments:
* HostNode and HostElement, corresponding to Node and Element types in the
* host environment. For example, for runtime-dom, HostNode would be the DOM
* `Node` interface and HostElement would be the DOM `Element` interface.
*
* Custom renderers can pass in the platform specific types like this:
*
* ``` js
* const { render, createApp } = createRenderer<Node, Element>({
*   patchProp,
*   ...nodeOps
* })
* ```
*/
function createRenderer(options) {
  return baseCreateRenderer(options);
}
// Separate API for creating hydration-enabled renderer.
// Hydration logic is only used when calling this function, making it
// tree-shakable.
function createHydrationRenderer(options) {
  return baseCreateRenderer(options, createHydrationFunctions);
}
// 直接看下面的具体基础render的实现就好了
// 负责把vnode渲染成真实的dom树并且挂载到宿主dom中去的render对象究竟是如何实现的
// implementation
function baseCreateRenderer(options, createHydrationFns) {
  // 先忽略
  {
      const target = getGlobalThis();
      target.__VUE__ = true;
      setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__);
  }
  // 这些个方法都是浏览器下DOM操作的函数简单封装 实现见后文 目前只需要关注它可以实现实际的DOM操作即可
  const { insert: hostInsert, remove: hostRemove, patchProp: hostPatchProp, forcePatchProp: hostForcePatchProp, createElement: hostCreateElement, createText: hostCreateText, createComment: hostCreateComment, setText: hostSetText, setElementText: hostSetElementText, parentNode: hostParentNode, nextSibling: hostNextSibling, setScopeId: hostSetScopeId = NOOP, cloneNode: hostCloneNode, insertStaticContent: hostInsertStaticContent } = options;
  // Note: functions inside this closure should use `const xxx = () => {}`
  // style in order to prevent being inlined by minifiers.
  // 高级调用API 任意形式的 vnode都可以被这个方法调用完成渲染过程
  // 所以它内部调用了很多其他基础方法 控制逻辑也很复杂
  /**
   * 参数很多:
   * 1. n1 旧vnode 2. n2 新vnode 3. container 待插入的宿主DOM元素 4. parentComponent 插入时候参考的宿主DOM的锚点位置
   * 5. parentComponent 父组件实例对象 6. parentSuspense 父组件链条中存在的最近一个Suspense对象
   * 7. isSVG是否是svg元素
   * 8. slotScopeIds TODO
   * 9. optimized TODO
   */
  const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, slotScopeIds = null, optimized = false) => {
      // patching & not same type, unmount old tree
      // 前后vnode的类型发生了改变 直接先无脑移除第一个就是了
      if (n1 && !isSameVNodeType(n1, n2)) {
          // 找到原vnode节点挂载的dom的后一个元素作为锚点位置
          anchor = getNextHostNode(n1);
          // 执行挂载vnode逻辑
          unmount(n1, parentComponent, parentSuspense, true);
          n1 = null;
      }
      // 关闭优化模式 需要全量对比
      if (n2.patchFlag === -2 /* BAIL */) {
          optimized = false;
          n2.dynamicChildren = null;
      }
      // 取出判断vnode类型的 type和辅助flag vnode所属的ref信息
      const { type, ref, shapeFlag } = n2;
      // 分清楚调用其他基础方法去实现
      switch (type) {
          // 可以直接处理的基础类型 text node
          case Text:
              processText(n1, n2, container, anchor);
              break;
          // 可以直接处理的基础类型 comment node
          case Comment:
              processCommentNode(n1, n2, container, anchor);
              break;
          // 可以直接处理的基础类型 一段html内容直接插入
          case Static:
              if (n1 == null) {
                  mountStaticNode(n2, container, anchor, isSVG);
              }
              else {
                  patchStaticNode(n1, n2, container, isSVG);
              }
              break;
          // 一种辅助模拟节点 代表有多个子节点的情况 实际并不反映在最终的dom结构中
          // 只会把子节点们插入到目标dom中
          case Fragment:
              processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
              break;
          default:
              // 普通元素vnode
              if (shapeFlag & 1 /* ELEMENT */) {
                  processElement(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
              }
              // 用户和大部分内置组件对象的vnode
              else if (shapeFlag & 6 /* COMPONENT */) {
                  processComponent(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
              }
              // 特殊的内置组件vnode TELEPORT 调用自己实现的 process
              else if (shapeFlag & 64 /* TELEPORT */) {
                  type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, internals);
              }
              // 特殊的内置组件vnode SUSPENSE 调用自己实现的 process
              else if (shapeFlag & 128 /* SUSPENSE */) {
                  type.process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, internals);
              }
              else {
                  warn('Invalid VNode type:', type, `(${typeof type})`);
              }
      }
      // set ref
      // 上面的更新完成后 最终完成父组件中关于新vnode的引用更新
      if (ref != null && parentComponent) {
          setRef(ref, n1 && n1.ref, parentSuspense, n2);
      }
  };
  // text node 更新比较简单 往对应的宿主dom中插入text node或者更新node内容即可
  const processText = (n1, n2, container, anchor) => {
      if (n1 == null) {
          hostInsert((n2.el = hostCreateText(n2.children)), container, anchor);
      }
      else {
          const el = (n2.el = n1.el);
          if (n2.children !== n1.children) {
              hostSetText(el, n2.children);
          }
      }
  };
  // 插入注释node即可
  const processCommentNode = (n1, n2, container, anchor) => {
      if (n1 == null) {
          hostInsert((n2.el = hostCreateComment(n2.children || '')), container, anchor);
      }
      else {
          // there's no support for dynamic comments
          n2.el = n1.el;
      }
  };
  // 插入一段html内容 对应v-html 注意返回值是插入元素列表的 首尾元素
  const mountStaticNode = (n2, container, anchor, isSVG) => {
      [n2.el, n2.anchor] = hostInsertStaticContent(n2.children, container, anchor, isSVG);
  };
  /**
   * Dev / HMR only
   */
  // 更新之前的v-html内容
  const patchStaticNode = (n1, n2, container, isSVG) => {
      // static nodes are only patched during dev for HMR
      if (n2.children !== n1.children) {
          // 取出后面的锚点
          const anchor = hostNextSibling(n1.anchor);
          // remove existing
          // 移除之前的v-html插入的内容
          removeStaticNode(n1);
          // 插入新的 更新引用
          [n2.el, n2.anchor] = hostInsertStaticContent(n2.children, container, anchor, isSVG);
      }
      // 更新引用
      else {
          n2.el = n1.el;
          n2.anchor = n1.anchor;
      }
  };
  // 移动一段之前的html节点列表到指定dom中去
  const moveStaticNode = ({ el, anchor }, container, nextSibling) => {
      let next;
      // 从前到后一个一个插在nextSibling的前面即可
      while (el && el !== anchor) {
          next = hostNextSibling(el);
          hostInsert(el, container, nextSibling);
          el = next;
      }
      // 最后一个也插入 因为anchor是列表中最后一个
      hostInsert(anchor, container, nextSibling);
  };
  // 删除一段html节点内容
  const removeStaticNode = ({ el, anchor }) => {
      let next;
      // 一个一个删除
      while (el && el !== anchor) {
          next = hostNextSibling(el);
          hostRemove(el);
          el = next;
      }
      hostRemove(anchor);
  };
  // 处理元素vnode
  const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
      isSVG = isSVG || n2.type === 'svg';
      // 分别调用对应的具体实现
      if (n1 == null) {
          mountElement(n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
      }
      else {
          patchElement(n1, n2, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);
      }
  };
  // 挂载vnode到宿主dom中
  const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
      let el;
      let vnodeHook;
      // 取出vnode上的属性变量对象们 准备构建最终的元素了
      const { type, props, shapeFlag, transition, patchFlag, dirs } = vnode;
      {
          // 注意第三个参数 is 代表自定义dom元素的情况 props参数另有它用 具体下面的注释
          // 目前只需要知道 创建了目标tag元素节点就可以了
          el = vnode.el = hostCreateElement(vnode.type, isSVG, props && props.is, props);
          // mount children first, since some props may rely on child content
          // being already rendered, e.g. `<select value>`
          // 递归出口 节点的文字内容需要更新 基础情况
          if (shapeFlag & 8 /* TEXT_CHILDREN */) {
              // 赋值更新dom元素text内容即可
              hostSetElementText(el, vnode.children);
          }
          // 存在子节点数组 哪怕只有一个 也被视为需要递归去处理
          // 采用深度递归 优先处理子节点 把当前el当做子节点的宿主dom元素 后续遍历完成后最后再插入父节点到最终的宿主dom中
          else if (shapeFlag & 16 /* ARRAY_CHILDREN */) {
              // 对于列表节点 需要额外遍历处理 调用 mountChildren 帮忙
              mountChildren(vnode.children, el, null, parentComponent, parentSuspense, isSVG && type !== 'foreignObject', slotScopeIds, optimized || !!vnode.dynamicChildren);
          }
          // 运行时指令 钩子 created 触发 :元素的创建后执行
          if (dirs) {
              invokeDirectiveHook(vnode, null, parentComponent, 'created');
          }
          // props
          // props中的属性们 也需要一一映射到实际的元素上了
          if (props) {
              for (const key in props) {
                  if (!isReservedProp(key)) {
                      // 调用 hostPatchProp 也就是 patchProp 来完成
                      hostPatchProp(el, key, null, props[key], isSVG, vnode.children, parentComponent, parentSuspense, unmountChildren);
                  }
              }
              // 内置保留的6个vnode钩子之一 onVnodeBeforeMount 触发
              if ((vnodeHook = props.onVnodeBeforeMount)) {
                  invokeVNodeHook(vnodeHook, parentComponent, vnode);
              }
          }
          // scopeId
          // 存在scopeId的话 设置到元素上去
          setScopeId(el, vnode, vnode.scopeId, slotScopeIds, parentComponent);
      }
      // dom元素保持2个引用指向vnode和父组件实例 方面之后取出来对比
      {
          Object.defineProperty(el, '__vnode', {
              value: vnode,
              enumerable: false
          });
          Object.defineProperty(el, '__vueParentComponent', {
              value: parentComponent,
              enumerable: false
          });
      }
      // 运行时指令 钩子 beforeMount 触发 :元素的未挂载到宿主父元素之前执行
      if (dirs) {
          invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount');
      }
      // #1583 For inside suspense + suspense not resolved case, enter hook should call when suspense resolved
      // #1689 For inside suspense + suspense resolved case, just call it
      // 具体场景有待Suspense的分析 不过从注释来看 是 Suspense 组件的渲染结果已确定之后
      // transition包裹下的内容需要触发动画效果
      const needCallTransitionHooks = (!parentSuspense || (parentSuspense && !parentSuspense.pendingBranch)) &&
          transition &&
          !transition.persisted;
      // 调用 transition.beforeEnter 具体分析见transition组件
      if (needCallTransitionHooks) {
          transition.beforeEnter(el);
      }
      // 完成当前元素的插入到宿主dom中 完成dom操作
      hostInsert(el, container, anchor);
      // 下面3个钩子 在el实际插入后都需要触发
      // 我们需要和组件的生命周期钩子区分开 组件的生命周期指的往往是组件实例所处哪个阶段
      // 而vnode钩子 指令钩子 transition钩子都是针对el元素讨论的
      if ((vnodeHook = props && props.onVnodeMounted) ||
          needCallTransitionHooks ||
          dirs) {
          // 3个方法作为一个job一起入列
          queuePostRenderEffect(() => {
              vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode);
              needCallTransitionHooks && transition.enter(el);
              dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted');
          }, parentSuspense);
      }
  };
  // 设置作用域id
  const setScopeId = (el, vnode, scopeId, slotScopeIds, parentComponent) => {
      // 单个
      if (scopeId) {
          hostSetScopeId(el, scopeId);
      }
      // 从前往后依次覆盖
      if (slotScopeIds) {
          for (let i = 0; i < slotScopeIds.length; i++) {
              hostSetScopeId(el, slotScopeIds[i]);
          }
      }
      // 存在父组件对象的vnode
      if (parentComponent) {
          let subTree = parentComponent.subTree;
          if (subTree.patchFlag > 0 &&
              subTree.patchFlag & 2048 /* DEV_ROOT_FRAGMENT */) {
              // 特殊情况 片段中只有一个有效子vnode 更新子树根节点
              subTree =
                  filterSingleRoot(subTree.children) || subTree;
          }
          // 这个条件只有HOC高阶组件才满足 继承id 具体原因见其他高阶组件的分析 TODO
          if (vnode === subTree) {
              const parentVNode = parentComponent.vnode;
              setScopeId(el, parentVNode, parentVNode.scopeId, parentVNode.slotScopeIds, parentComponent.parent);
          }
      }
  };
  // 看下存在多个子vnode的情况如何初次挂载
  const mountChildren = (children, container, anchor, parentComponent, parentSuspense, isSVG, optimized, slotScopeIds, start = 0) => {
      for (let i = start; i < children.length; i++) {
          // 对child还做了格式化处理
          // 具体实现见vnode的分析章节
          const child = (children[i] = optimized
              ? cloneIfMounted(children[i])
              : normalizeVNode(children[i]));
          // 可以看到 确实是深度遍历 递归调用高级方法patch去处理
          patch(null, child, container, anchor, parentComponent, parentSuspense, isSVG, optimized, slotScopeIds);
      }
  };
  // 来看如何对比2个元素vnode
  const patchElement = (n1, n2, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized) => {
      // 同一个dom元素 不同的话之前已经考虑过了 能到这里来说明就是一个
      const el = (n2.el = n1.el);
      let { patchFlag, dynamicChildren, dirs } = n2;
      // #1426 take the old vnode's patch flag into account since user may clone a
      // compiler-generated vnode, which de-opts to FULL_PROPS
      patchFlag |= n1.patchFlag & 16 /* FULL_PROPS */;
      // 取出新旧props对象
      const oldProps = n1.props || EMPTY_OBJ;
      const newProps = n2.props || EMPTY_OBJ;
      let vnodeHook;
      // 触发vnode BeforeUpdate钩子
      if ((vnodeHook = newProps.onVnodeBeforeUpdate)) {
          invokeVNodeHook(vnodeHook, parentComponent, n2, n1);
      }
      // 触发指令 beforeUpdate钩子
      if (dirs) {
          invokeDirectiveHook(n2, n1, parentComponent, 'beforeUpdate');
      }
      // 全量对比 这3个参数的值值得注意
      if (isHmrUpdating) {
          // HMR updated, force full diff
          patchFlag = 0;
          optimized = false;
          dynamicChildren = null;
      }
      // 之前编译过程得到的一些辅助标志位 可以帮忙指示我们如何处理更新
      if (patchFlag > 0) {
          // the presence of a patchFlag means this element's render code was
          // generated by the compiler and can take the fast path.
          // in this path old node and new node are guaranteed to have the same shape
          // (i.e. at the exact same position in the source template)
          if (patchFlag & 16 /* FULL_PROPS */) {
              // element props contain dynamic keys, full diff needed
              // 动态prop key 全量对比props对象
              patchProps(el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG);
          }
          // 处理正常的key的props
          else {
              // class
              // this flag is matched when the element has dynamic class bindings.
              if (patchFlag & 2 /* CLASS */) {
                  if (oldProps.class !== newProps.class) {
                      hostPatchProp(el, 'class', null, newProps.class, isSVG);
                  }
              }
              // style
              // this flag is matched when the element has dynamic style bindings
              if (patchFlag & 4 /* STYLE */) {
                  hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG);
              }
              // props
              // This flag is matched when the element has dynamic prop/attr bindings
              // other than class and style. The keys of dynamic prop/attrs are saved for
              // faster iteration.
              // Note dynamic keys like :[foo]="bar" will cause this optimization to
              // bail out and go through a full diff because we need to unset the old key
              if (patchFlag & 8 /* PROPS */) {
                  // if the flag is present then dynamicProps must be non-null
                  // 编译过程把动态props值对应的key都提前提取出来了已经
                  const propsToUpdate = n2.dynamicProps;
                  for (let i = 0; i < propsToUpdate.length; i++) {
                      const key = propsToUpdate[i];
                      const prev = oldProps[key];
                      const next = newProps[key];
                      // 根据情况 更新prop hostForcePatchProp指的是value prop
                      if (next !== prev ||
                          (hostForcePatchProp && hostForcePatchProp(el, key))) {
                          hostPatchProp(el, key, prev, next, isSVG, n1.children, parentComponent, parentSuspense, unmountChildren);
                      }
                  }
              }
          }
          // text
          // This flag is matched when the element has only dynamic text children.
          // 更新node的text内容
          if (patchFlag & 1 /* TEXT */) {
              if (n1.children !== n2.children) {
                  hostSetElementText(el, n2.children);
              }
          }
      }
      // 没有辅助信息的可优化对比情况 只能全量对比了咯
      else if (!optimized && dynamicChildren == null) {
          // unoptimized, full diff
          patchProps(el, n2, oldProps, newProps, parentComponent, parentSuspense, isSVG);
      }
      const areChildrenSVG = isSVG && n2.type !== 'foreignObject';
      // 可以看到 元素更新对比 是先序处理 因为被删除的旧节点 根本不需要更新 直接转为unmoutn和mount新节点的情况即可
      // 内容会改变的动态vnode都会收集在一个block的顶级vnode的dynamicChildren中
      // 优化模式下 我们可以只对比更新这里面的内容 节省对比的开销
      // 只有一个块中的顶层节点才有dynamicChildren属性
      if (dynamicChildren) {
          // 调用 patchBlockChildren 对比动态子节点
          patchBlockChildren(n1.dynamicChildren, dynamicChildren, el, parentComponent, parentSuspense, areChildrenSVG, slotScopeIds);
          // 一些场景下 需要遍历一次静态节点 详见下文
          if (parentComponent && parentComponent.type.__hmrId) {
              traverseStaticChildren(n1, n2);
          }
      }
      // 无优化 全量对比
      else if (!optimized) {
          // full diff
          patchChildren(n1, n2, el, null, parentComponent, parentSuspense, areChildrenSVG, slotScopeIds, false);
      }
      // 触发vnode和指令的updated钩子
      if ((vnodeHook = newProps.onVnodeUpdated) || dirs) {
          queuePostRenderEffect(() => {
              vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, n2, n1);
              dirs && invokeDirectiveHook(n2, n1, parentComponent, 'updated');
          }, parentSuspense);
      }
  };

Vue(v3.0.11)源码简析之任务队列的相关实现

来看看vue中任务队列的实现注解:

// 正式开始更新任务队列了
let isFlushing = false;
// 每次推入一个新的任务 就尝试进行更新队列 这个值变true 不过由于执行第一个任务函数的时候可能产生新的任务进入队列
// 这时候这个变量就保证我们只需要把任务放入队列合适的位置即可 更新队列的函数 在一个tick中只启动一次就好了
let isFlushPending = false;
// 所有的待更新任务都放这个队列中
const queue = [];
// 如果处于正在更新的状态 那正在执行的任务所处队列中的索引位置
let flushIndex = 0;
// 前置待更新的回调函数队列 在同一个tick中 在queue的任务之前执行
const pendingPreFlushCbs = [];
// 前置正在更新的回调函数队列
let activePreFlushCbs = null;
// 正在执行的前置回调函数 对应的索引位置
let preFlushIndex = 0;
// 后置回调 语义同上面的pre相反  在同一个tick中 在queue的任务之后执行
const pendingPostFlushCbs = [];
let activePostFlushCbs = null;
let postFlushIndex = 0;
// 处于resolve的状态的promise实例 作为初始默认值使用
const resolvedPromise = Promise.resolve();
// 启动刷新任务队列后的 实际promise实例
let currentFlushPromise = null;
// 在刷新pre回调队列的时候 如果产生了新的任务 不得与下面这个 预先调用设置了的不算入任务队列的方法相同: 也就是说 通过 flushPreFlushCbs 第二个参数设置的函数 更新队列产生的任务中不包含这个函数
let currentPreFlushParentJob = null;
// 一个tick中刷新队列的时候 同一个任务出现的次数上限
const RECURSION_LIMIT = 100;
// 下个tick执行fn
function nextTick(fn) {
    const p = currentFlushPromise || resolvedPromise;
    return fn ? p.then(this ? fn.bind(this) : fn) : p;
}
// #2768
// Use binary-search to find a suitable position in the queue,
// so that the queue maintains the increasing order of job's id,
// which can prevent the job from being skipped and also can avoid repeated patching.
// 二分查找插入的位置 从id 小到大排序
function findInsertionIndex(job) {
    // the start index should be `flushIndex + 1`
    // 假如 [1,3,5,7] 要插入 4 那最终 start 会等于 2:start代表队列第一个大于等于job的id的位置 也是我们插入的位置
    let start = flushIndex + 1;
    let end = queue.length;
    const jobId = getId(job);
    while (start < end) {
        const middle = (start + end) >>> 1;
        const middleJobId = getId(queue[middle]);
        middleJobId < jobId ? (start = middle + 1) : (end = middle);
    }
    return start;
}
// 任务入列
function queueJob(job) {
    // the dedupe search uses the startIndex argument of Array.includes()
    // by default the search index includes the current job that is being run
    // so it cannot recursively trigger itself again.
    // if the job is a watch() callback, the search will start with a +1 index to
    // allow it recursively trigger itself - it is the user's responsibility to
    // ensure it doesn't end up in an infinite loop.
    // 空队列直接入队
    // isFlushing && job.allowRecurse 正在执行且允许递归产生同个任务函数的情况 才从  flushIndex + 1 位置开始排查 否则就是 flushIndex 当前开始
    // 对应前面讲的 currentPreFlushParentJob 存在的情况 不允许它入队列
    if ((!queue.length ||
        !queue.includes(job, isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex)) &&
        job !== currentPreFlushParentJob) {
        const pos = findInsertionIndex(job);
        if (pos > -1) {
            queue.splice(pos, 0, job);
        }
        else {
            queue.push(job);
        }
        // 每个任务进来都尝试调用一次 queueFlush 来刷新队列
        queueFlush();
    }
}
// 尝试刷新队列
function queueFlush() {
    // 2个状态都为false才可以 意味着 一个tick的刷新过程中 任务加入队列后  flushJobs 只执行一次
    if (!isFlushing && !isFlushPending) {
        isFlushPending = true;
        currentFlushPromise = resolvedPromise.then(flushJobs);
    }
}
// 移除未处理的任务
function invalidateJob(job) {
    const i = queue.indexOf(job);
    if (i > flushIndex) {
        queue.splice(i, 1);
    }
}
// cb入cb队列 抽象方法  适用上面的pre和post队列
function queueCb(cb, activeQueue, pendingQueue, index) {
    if (!isArray(cb)) {
        // 同样的逻辑 分析同上
        if (!activeQueue ||
            !activeQueue.includes(cb, cb.allowRecurse ? index + 1 : index)) {
            pendingQueue.push(cb);
        }
    }
    else {
        // if cb is an array, it is a component lifecycle hook which can only be
        // triggered by a job, which is already deduped in the main queue, so
        // we can skip duplicate check here to improve perf
        pendingQueue.push(...cb);
    }
    // 也尝试刷新队列
    queueFlush();
}
// pre cb入队列
function queuePreFlushCb(cb) {
    queueCb(cb, activePreFlushCbs, pendingPreFlushCbs, preFlushIndex);
}
// post cb入队列
function queuePostFlushCb(cb) {
    queueCb(cb, activePostFlushCbs, pendingPostFlushCbs, postFlushIndex);
}
// 刷新pre队列的实现
function flushPreFlushCbs(seen, parentJob = null) {
    if (pendingPreFlushCbs.length) {
        // 置位
        currentPreFlushParentJob = parentJob;
        // 当前处理队列置位
        activePreFlushCbs = [...new Set(pendingPreFlushCbs)];
        // 清空
        pendingPreFlushCbs.length = 0;
        {
            // 记录出现次数的缓存对象
            seen = seen || new Map();
        }
        for (preFlushIndex = 0; preFlushIndex < activePreFlushCbs.length; preFlushIndex++) {
            {
                // 统计次数
                checkRecursiveUpdates(seen, activePreFlushCbs[preFlushIndex]);
            }
            // 执行cb
            activePreFlushCbs[preFlushIndex]();
        }
        // 重置变量
        activePreFlushCbs = null;
        preFlushIndex = 0;
        currentPreFlushParentJob = null;
        // recursively flush until it drains
        // 一个tick中 pre任务总是先执行完 所以需要递归执行
        flushPreFlushCbs(seen, parentJob);
    }
}
// 刷新post cb队列
function flushPostFlushCbs(seen) {
    if (pendingPostFlushCbs.length) {
        // 取出任务来处理
        const deduped = [...new Set(pendingPostFlushCbs)];
        pendingPostFlushCbs.length = 0;
        // #1947 already has active queue, nested flushPostFlushCbs call
        // 嵌套 flushPostFlushCbs 调用 出现时机暂时有待分析 todo
        if (activePostFlushCbs) {
            activePostFlushCbs.push(...deduped);
            return;
        }
        activePostFlushCbs = deduped;
        {
            seen = seen || new Map();
        }
        // 也排序
        activePostFlushCbs.sort((a, b) => getId(a) - getId(b));
        // 分析同上
        for (postFlushIndex = 0; postFlushIndex < activePostFlushCbs.length; postFlushIndex++) {
            {
                checkRecursiveUpdates(seen, activePostFlushCbs[postFlushIndex]);
            }
            activePostFlushCbs[postFlushIndex]();
        }
        activePostFlushCbs = null;
        postFlushIndex = 0;
    }
}
// id越小 优先级越高 默认无限大
const getId = (job) => job.id == null ? Infinity : job.id;
// 刷新任务队列
function flushJobs(seen) {
    // 状态置位
    isFlushPending = false;
    isFlushing = true;
    {
        seen = seen || new Map();
    }
    // 先处理pre
    flushPreFlushCbs(seen);
    // Sort queue before flush.
    // This ensures that:
    // 1. Components are updated from parent to child. (because parent is always
    //    created before the child so its render effect will have smaller
    //    priority number)
    // 2. If a component is unmounted during a parent component's update,
    //    its update can be skipped.
    // 排序的原因见注释 父组件的render在随着effect函数再次执行的时候 子组件如果被移除了 那么不会产生新的子组件render的effect函数了 也就不需要执行它了呢:)
    queue.sort((a, b) => getId(a) - getId(b));
    try {
        for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
            const job = queue[flushIndex];
            // 分析同上
            if (job) {
                if (true) {
                    checkRecursiveUpdates(seen, job);
                }
                callWithErrorHandling(job, null, 14 /* SCHEDULER */);
            }
        }
    }
    finally {
        flushIndex = 0;
        queue.length = 0;
        // 执行post队列
        flushPostFlushCbs(seen);
        isFlushing = false;
        currentFlushPromise = null;
        // some postFlushCb queued jobs!
        // keep flushing until it drains.
        // 对应注释的情况 再启动一次 flushJobs 即可
        if (queue.length || pendingPostFlushCbs.length) {
            flushJobs(seen);
        }
    }
}
// 统计出现次数 并不需要直接的递归调用自己 a->a 也可以 a->b->c->a->b.... 这样的
function checkRecursiveUpdates(seen, fn) {
    if (!seen.has(fn)) {
        seen.set(fn, 1);
    }
    else {
        const count = seen.get(fn);
        if (count > RECURSION_LIMIT) {
            throw new Error(`Maximum recursive updates exceeded. ` +
                `This means you have a reactive effect that is mutating its own ` +
                `dependencies and thus recursively triggering itself. Possible sources ` +
                `include component template, render function, updated hook or ` +
                `watcher source function.`);
        }
        else {
            seen.set(fn, count + 1);
        }
    }
}

Vue(v3.0.11)源码简析之跟dom实际操作有关的实现和patchProps是如何把各类prop映射到dom上的相关实现

这部分比较直接,我们自己写也会,直接上代码吧:

// Actual implementation
// h其实就是返回vnode
function h(type, propsOrChildren, children) {
    const l = arguments.length;
    if (l === 2) {
        // 只有2个参数的情况 分 3种
        if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
            // single vnode without props
            if (isVNode(propsOrChildren)) {
                return createVNode(type, null, [propsOrChildren]);
            }
            // props without children
            return createVNode(type, propsOrChildren);
        }
        else {
            // omit props
            return createVNode(type, null, propsOrChildren);
        }
    }
    // 大于等于3个参数
    else {
        if (l > 3) {
            children = Array.prototype.slice.call(arguments, 2);
        }
        else if (l === 3 && isVNode(children)) {
            children = [children];
        }
        return createVNode(type, propsOrChildren, children);
    }
}

/**
 * Actual implementation
 */
/**
 * v-for生成的vnode会被render解析成这样
 *  _renderList(list, (item, key, index) => {
      return {
        name: key,
        fn: _withCtx(() => [
          _hoisted_1
        ])
      }
    })
    第一个参数是 被遍历的表达式 第二个参数是生成vnode的函数 它的3个参数就是我们能用的三个参数
 */
function renderList(source, renderItem) {
    let ret;
    // 遍历数组 字符串 只有2个可用参数
    if (isArray(source) || isString(source)) {
        ret = new Array(source.length);
        for (let i = 0, l = source.length; i < l; i++) {
            ret[i] = renderItem(source[i], i);
        }
    }
    // 数字
    else if (typeof source === 'number') {
        if (!Number.isInteger(source)) {
            warn(`The v-for range expect an integer value but got ${source}.`);
            return [];
        }
        ret = new Array(source);
        for (let i = 0; i < source; i++) {
            ret[i] = renderItem(i + 1, i);
        }
    }
    // 遍历对象
    else if (isObject(source)) {
        // 存在迭代器属性的几类对象 如map
        if (source[Symbol.iterator]) {
            // 直接构造
            ret = Array.from(source, renderItem);
        }
        else {
            // 普通对象 
            const keys = Object.keys(source);
            ret = new Array(keys.length);
            // 对象的keys的排序不确定是如何进行的噢
            for (let i = 0, l = keys.length; i < l; i++) {
                const key = keys[i];
                ret[i] = renderItem(source[key], key, i);
            }
        }
    }
    else {
        ret = [];
    }
    return ret;
}

/**
 * For prefixing keys in v-on="obj" with "on"
 * @private
 */
// 给props对象中的事件属性名字格式化成 change -> onChange
function toHandlers(obj) {
    const ret = {};
    if (!isObject(obj)) {
        warn(`v-on with no argument expects an object value.`);
        return ret;
    }
    for (const key in obj) {
        ret[toHandlerKey(key)] = obj[key];
    }
    return ret;
}

/**
 * Compiler runtime helper for creating dynamic slots object
 * @private
 */
// 动态插槽和静态插槽不一样 静态插槽是render中直接是withCtx包裹的匿名函数 而动态插槽呢 是被解析成
/**
 * _createSlots({ _: 2  DYNAMIC  }, [
    _renderList(5, (i) => {
        return {
          name: i,
          fn: _withCtx(() => [
            _hoisted_1
          ])
        }
      })
    不过最终也是返回一个对象
 */
function createSlots(slots, dynamicSlots) {
    for (let i = 0; i < dynamicSlots.length; i++) {
        const slot = dynamicSlots[i];
        // array of dynamic slot generated by <template v-for="..." #[...]>
        if (isArray(slot)) {
            for (let j = 0; j < slot.length; j++) {
                slots[slot[j].name] = slot[j].fn;
            }
        }
        else if (slot) {
            // conditional single slot generated by <template v-if="..." #foo>
            slots[slot.name] = slot.fn;
        }
    }
    return slots;
}

// Core API ------------------------------------------------------------------
const version = "3.0.11";
/**
 * SSR utils for \@vue/server-renderer. Only exposed in cjs builds.
 * @internal
 */
const ssrUtils = (null);

const svgNS = 'http://www.w3.org/2000/svg';
const doc = (typeof document !== 'undefined' ? document : null);
// 临时节点
let tempContainer;
let tempSVGContainer;
// 基础的dom节点操作
const nodeOps = {
    // 插入节点 基于anchor锚点的位置
    insert: (child, parent, anchor) => {
        parent.insertBefore(child, anchor || null);
    },
    // 删除
    remove: child => {
        const parent = child.parentNode;
        if (parent) {
            parent.removeChild(child);
        }
    },
    // 创建元素
    createElement: (tag, isSVG, is, props) => {
        const el = isSVG
            ? doc.createElementNS(svgNS, tag)
            : doc.createElement(tag, is ? { is } : undefined);
        if (tag === 'select' && props && props.multiple != null) {
            el.setAttribute('multiple', props.multiple);
        }
        return el;
    },
    createText: text => doc.createTextNode(text),
    createComment: text => doc.createComment(text),
    // 只有text commnet这样的设置nodevalue才有意义
    setText: (node, text) => {
        node.nodeValue = text;
    },
    // 节点的文字内容 纯文本展示
    setElementText: (el, text) => {
        el.textContent = text;
    },
    // 父节点
    parentNode: node => node.parentNode,
    // 下个兄弟节点
    nextSibling: node => node.nextSibling,
    querySelector: selector => doc.querySelector(selector),
    setScopeId(el, id) {
        el.setAttribute(id, '');
    },
    // 克隆节点
    cloneNode(el) {
        const cloned = el.cloneNode(true);
        // #3072
        // - in `patchDOMProp`, we store the actual value in the `el._value` property.
        // - normally, elements using `:value` bindings will not be hoisted, but if
        //   the bound value is a constant, e.g. `:value="true"` - they do get
        //   hoisted.
        // - in production, hoisted nodes are cloned when subsequent inserts, but
        //   cloneNode() does not copy the custom property we attached.
        // - This may need to account for other custom DOM properties we attach to
        //   elements in addition to `_value` in the future.
        // 继承特意保留的这个属性 原因见注释
        if (`_value` in el) {
            cloned._value = el._value;
        }
        return cloned;
    },
    // __UNSAFE__
    // Reason: innerHTML.
    // Static content here can only come from compiled templates.
    // As long as the user only uses trusted templates, this is safe.
    // 插入一段html
    insertStaticContent(content, parent, anchor, isSVG) {
        // 临时节点
        const temp = isSVG
            ? tempSVGContainer ||
                (tempSVGContainer = doc.createElementNS(svgNS, 'svg'))
            : tempContainer || (tempContainer = doc.createElement('div'));
        // 插入一段html进临时节点
        temp.innerHTML = content;
        const first = temp.firstChild;
        let node = first;
        let last = node;
        // 目标html片段的节点逐个插入 最后返回首尾节点给调用者
        while (node) {
            last = node;
            nodeOps.insert(node, parent, anchor);
            node = temp.firstChild;
        }
        // 注意返回子节点的第一个和最后一个
        return [first, last];
    }
};

// compiler should normalize class + :class bindings on the same element
// into a single binding ['staticClass', dynamic]
// class映射到dom上
function patchClass(el, value, isSVG) {
    if (value == null) {
        value = '';
    }
    if (isSVG) {
        el.setAttribute('class', value);
    }
    else {
        // directly setting className should be faster than setAttribute in theory
        // if this is an element during a transition, take the temporary transition
        // classes into account.
        const transitionClasses = el._vtc;
        // 合并class的名字
        if (transitionClasses) {
            value = (value
                ? [value, ...transitionClasses]
                : [...transitionClasses]).join(' ');
        }
        el.className = value;
    }
}
// style映射到dom上
function patchStyle(el, prev, next) {
    const style = el.style;
    if (!next) {
        el.removeAttribute('style');
    }
    else if (isString(next)) {
        if (prev !== next) {
            const current = style.display;
            style.cssText = next;
            // indicates that the `display` of the element is controlled by `v-show`,
            // so we always keep the current `display` value regardless of the `style` value,
            // thus handing over control to `v-show`.
            // 恢复被v-show改变的display历史值
            if ('_vod' in el) {
                style.display = current;
            }
        }
    }
    else {
        for (const key in next) {
            setStyle(style, key, next[key]);
        }
        // 移除不存在的了
        if (prev && !isString(prev)) {
            for (const key in prev) {
                if (next[key] == null) {
                    setStyle(style, key, '');
                }
            }
        }
    }
}
// 单个style映射到dom上
const importantRE = /\s*!important$/;
function setStyle(style, name, val) {
    if (isArray(val)) {
        val.forEach(v => setStyle(style, name, v));
    }
    else {
        if (name.startsWith('--')) {
            // custom property definition
            // 自定义style
            style.setProperty(name, val);
        }
        else {
            // 选择合适的前缀 自动拼接起来
            const prefixed = autoPrefix(style, name);
            if (importantRE.test(val)) {
                // !important
                style.setProperty(hyphenate(prefixed), val.replace(importantRE, ''), 'important');
            }
            else {
                style[prefixed] = val;
            }
        }
    }
}
// 支持的前缀
const prefixes = ['Webkit', 'Moz', 'ms'];
// 处理过的缓存
const prefixCache = {};
// 寻找前缀化是否存在style对象上 同时缓存处理结果
function autoPrefix(style, rawName) {
    const cached = prefixCache[rawName];
    if (cached) {
        return cached;
    }
    let name = camelize(rawName);
    if (name !== 'filter' && name in style) {
        return (prefixCache[rawName] = name);
    }
    name = capitalize(name);
    for (let i = 0; i < prefixes.length; i++) {
        const prefixed = prefixes[i] + name;
        if (prefixed in style) {
            return (prefixCache[rawName] = prefixed);
        }
    }
    return rawName;
}

// 静态attr特性映射到dom上
const xlinkNS = 'http://www.w3.org/1999/xlink';
function patchAttr(el, key, value, isSVG) {
    if (isSVG && key.startsWith('xlink:')) {
        if (value == null) {
            el.removeAttributeNS(xlinkNS, key.slice(6, key.length));
        }
        else {
            el.setAttributeNS(xlinkNS, key, value);
        }
    }
    else {
        // note we are only checking boolean attributes that don't have a
        // corresponding dom prop of the same name here.
        const isBoolean = isSpecialBooleanAttr(key);
        // 特定布尔值类型的attr 只有值严格为false表示不存在
        if (value == null || (isBoolean && value === false)) {
            el.removeAttribute(key);
        }
        else {
            el.setAttribute(key, isBoolean ? '' : value);
        }
    }
}

// 映射动态prop值到dom元素上
// __UNSAFE__
// functions. The user is responsible for using them with only trusted content.
function patchDOMProp(el, key, value, 
// the following args are passed only due to potential innerHTML/textContent
// overriding existing VNodes, in which case the old tree must be properly
// unmounted.
prevChildren, parentComponent, parentSuspense, unmountChildren) {
    // 节点内容被替换 所以需要调用 unmountChildren 但是它需要几个参数 一次才有后面4个参数
    if (key === 'innerHTML' || key === 'textContent') {
        if (prevChildren) {
            unmountChildren(prevChildren, parentComponent, parentSuspense);
        }
        el[key] = value == null ? '' : value;
        return;
    }
    if (key === 'value' && el.tagName !== 'PROGRESS') {
        // store value as _value as well since
        // non-string values will be stringified.
        // 保留原始值 不被string化
        el._value = value;
        const newValue = value == null ? '' : value;
        if (el.value !== newValue) {
            el.value = newValue;
        }
        return;
    }
    // 缺省值对于不同的prop属性 有不同的处理方式 详情见注释即可
    if (value === '' || value == null) {
        const type = typeof el[key];
        if (value === '' && type === 'boolean') {
            // e.g. <select multiple> compiles to { multiple: '' }
            el[key] = true;
            return;
        }
        else if (value == null && type === 'string') {
            // e.g. <div :id="null">
            el[key] = '';
            el.removeAttribute(key);
            return;
        }
        else if (type === 'number') {
            // e.g. <img :width="null">
            el[key] = 0;
            el.removeAttribute(key);
            return;
        }
    }
    // some properties perform value validation and throw
    try {
        el[key] = value;
    }
    catch (e) {
        {
            warn(`Failed setting prop "${key}" on <${el.tagName.toLowerCase()}>: ` +
                `value ${value} is invalid.`, e);
        }
    }
}

// Async edge case fix requires storing an event listener's attach timestamp.
// 事件戳 获取函数 根据浏览器实际情况确定 见下面的注释
let _getNow = Date.now;
let skipTimestampCheck = false;
if (typeof window !== 'undefined') {
    // Determine what event timestamp the browser is using. Annoyingly, the
    // timestamp can either be hi-res (relative to page load) or low-res
    // (relative to UNIX epoch), so in order to compare time we have to use the
    // same timestamp type when saving the flush timestamp.
    if (_getNow() > document.createEvent('Event').timeStamp) {
        // if the low-res timestamp which is bigger than the event timestamp
        // (which is evaluated AFTER) it means the event is using a hi-res timestamp,
        // and we need to use the hi-res version for event listeners as well.
        _getNow = () => performance.now();
    }
    // #3485: Firefox <= 53 has incorrect Event.timeStamp implementation
    // and does not fire microtasks in between event propagation, so safe to exclude.
    const ffMatch = navigator.userAgent.match(/firefox\/(\d+)/i);
    skipTimestampCheck = !!(ffMatch && Number(ffMatch[1]) <= 53);
}
// To avoid the overhead of repeatedly calling performance.now(), we cache
// and use the same timestamp for all event listeners attached in the same tick.
// 一个tick内只获取一次事件戳 下个tick就重置了
let cachedNow = 0;
const p = Promise.resolve();
const reset = () => {
    cachedNow = 0;
};
// 当前tick中缓存时间戳 下个tick重置reset
const getNow = () => cachedNow || (p.then(reset), (cachedNow = _getNow()));
// 调用浏览器的api添加和移除是事件
function addEventListener(el, event, handler, options) {
    el.addEventListener(event, handler, options);
}
function removeEventListener(el, event, handler, options) {
    el.removeEventListener(event, handler, options);
}
// 添加dom事件的包裹函数
function patchEvent(el, rawName, prevValue, nextValue, instance = null) {
    // vei = vue event invokers
    const invokers = el._vei || (el._vei = {});
    const existingInvoker = invokers[rawName];
    if (nextValue && existingInvoker) {
        // patch
        existingInvoker.value = nextValue;
    }
    else {
        // 获取name 和 事件modify参数
        const [name, options] = parseName(rawName);
        if (nextValue) {
            // add
            const invoker = (invokers[rawName] = createInvoker(nextValue, instance));
            addEventListener(el, name, invoker, options);
        }
        else if (existingInvoker) {
            // remove
            removeEventListener(el, name, existingInvoker, options);
            invokers[rawName] = undefined;
        }
    }
}
// 解析事件名字和dom事件option参数
const optionsModifierRE = /(?:Once|Passive|Capture)$/;
function parseName(name) {
    let options;
    if (optionsModifierRE.test(name)) {
        options = {};
        let m;
        // 提取modify参数
        while ((m = name.match(optionsModifierRE))) {
            name = name.slice(0, name.length - m[0].length);
            options[m[0].toLowerCase()] = true;
        }
    }
    // 返回去on后的name 提取的参数
    return [hyphenate(name.slice(2)), options];
}
// 创建事件包裹函数 内置时间判断逻辑
function createInvoker(initialValue, instance) {
    const invoker = (e) => {
        // async edge case #6566: inner click event triggers patch, event handler
        // attached to outer element during patch, and triggered again. This
        // happens because browsers fire microtask ticks between event propagation.
        // the solution is simple: we save the timestamp when a handler is attached,
        // and the handler would only fire if the event passed to it was fired
        // AFTER it was attached.
        const timeStamp = e.timeStamp || _getNow();
        // 触发时间大于 创建时间
        // 注释里面的那个issue很有意思 大家可以仔细看下链接
        // https://github.com/vuejs/vue/issues/6566
        if (skipTimestampCheck || timeStamp >= invoker.attached - 1) {
            callWithAsyncErrorHandling(patchStopImmediatePropagation(e, invoker.value), instance, 5 /* NATIVE_EVENT_HANDLER */, [e]);
        }
    };
    invoker.value = initialValue;
    invoker.attached = getNow();
    return invoker;
}
// 执行stopImmediatePropagation方法,阻止click事件冒泡,并且阻止p元素上绑定的其他click事件的事件监听函数的执行.
// https://developer.mozilla.org/zh-CN/docs/Web/API/Event/stopImmediatePropagation
function patchStopImmediatePropagation(e, value) {
    if (isArray(value)) {
        const originalStop = e.stopImmediatePropagation;
        e.stopImmediatePropagation = () => {
            originalStop.call(e);
            e._stopped = true;
        };
        return value.map(fn => (e) => !e._stopped && fn(e));
    }
    else {
        return value;
    }
}

// 高级调用api 支持映射所有种类的props到dom上
const nativeOnRE = /^on[a-z]/;
const forcePatchProp = (_, key) => key === 'value';
const patchProp = (el, key, prevValue, nextValue, isSVG = false, prevChildren, parentComponent, parentSuspense, unmountChildren) => {
    switch (key) {
        // special
        case 'class':
            patchClass(el, nextValue, isSVG);
            break;
        case 'style':
            patchStyle(el, prevValue, nextValue);
            break;
        default:
            if (isOn(key)) {
                // ignore v-model listeners
                // 是通过指令来控制事件的触发的
                if (!isModelListener(key)) {
                    patchEvent(el, key, prevValue, nextValue, parentComponent);
                }
            }
            // 动态绑定的prop
            else if (shouldSetAsProp(el, key, nextValue, isSVG)) {
                patchDOMProp(el, key, nextValue, prevChildren, parentComponent, parentSuspense, unmountChildren);
            }
            // 剩余的就是静态attr
            else {
                // special case for <input v-model type="checkbox"> with
                // :true-value & :false-value
                // store value as dom properties since non-string values will be
                // stringified.
                // 保留原始值
                if (key === 'true-value') {
                    el._trueValue = nextValue;
                }
                else if (key === 'false-value') {
                    el._falseValue = nextValue;
                }
                // 设置特性attr
                patchAttr(el, key, nextValue, isSVG);
            }
            break;
    }
};
function shouldSetAsProp(el, key, value, isSVG) {
    if (isSVG) {
        // most keys must be set as attribute on svg elements to work
        // ...except innerHTML
        if (key === 'innerHTML') {
            return true;
        }
        // or native onclick with function values
        if (key in el && nativeOnRE.test(key) && isFunction(value)) {
            return true;
        }
        return false;
    }
    // spellcheck and draggable are numerated attrs, however their
    // corresponding DOM properties are actually booleans - this leads to
    // setting it with a string "false" value leading it to be coerced to
    // `true`, so we need to always treat them as attributes.
    // Note that `contentEditable` doesn't have this problem: its DOM
    // property is also enumerated string values.
    if (key === 'spellcheck' || key === 'draggable') {
        return false;
    }
    // #1787, #2840 form property on form elements is readonly and must be set as
    // attribute.
    if (key === 'form') {
        return false;
    }
    // #1526 <input list> must be set as attribute
    if (key === 'list' && el.tagName === 'INPUT') {
        return false;
    }
    // #2766 <textarea type> must be set as attribute
    if (key === 'type' && el.tagName === 'TEXTAREA') {
        return false;
    }
    // native onclick with string value, must be set as attribute
    if (nativeOnRE.test(key) && isString(value)) {
        return false;
    }
    // 出去上面注释中所言的情况 key存在于el元素上的属性 就按照prop处理
    return key in el;
}

function useCssModule(name = '$style') {
    /* istanbul ignore else */
    {
        {
            warn(`useCssModule() is not supported in the global build.`);
        }
        return EMPTY_OBJ;
    }
}

es6-promise 源码简析

var myPromise = (function () {

      // 首先, 我们需要一个Promise构造函数

      function Promise(resolver) {
          // 给这个promise实例打上属于我们可以识别的id标记
          this[PROMISE_ID] = nextId();
          // 当前promise实例的结果:可能是resolve输入的参数,也可以是reject的原因,也可以是报错产生的错误
          // 这个promise实例当前所处的状态保存在_state
          this._result = this._state = undefined;
          // 当前promise实例通过原型对象上的then添加的订阅者们,then的几个参数会被组装成一个订阅者对象
          this._subscribers = [];

          // noop 意味着 无需执行构造函数的参数函数,例如then内部产生的新promise实例就没有这个resolver参数,而是noop作为参数
          if (noop !== resolver) {
              // 初始化promise实例的几个内部状态值
              initializePromise(this, resolver)
          }
      }

      var PROMISE_ID = Math.random().toString(36).substring(2);

      var id = 0;
      function nextId() {
          return id++;
      }

      function noop() { }

      function initializePromise(promise, resolver) {
          // promise内部代码中错误都需要传递下去 意味着我们需要捕获任意可能出现的错误 并把它reject出去
          try {
              // 执行用户的方法 且 提供2个部署好的方法参数
              // 实际上由公共的 resolve 和 reject 来负责处理接下来的事情 比如 修改promise实例的状态 保存结果 等等
              resolver(function resolvePromise(value) {
                  // 闭包 所以可以找到应该修改哪个promise的状态
                  resolve(promise, value);
              }, function rejectPromise(reason) {
                  // 同上
                  reject(promise, reason);
              });
          } catch (e) {
              reject(promise, e);
          }
      }

      // 一般情况下 是在用户函数中 异步任务的回调中去调用 resolve || reject 方法来改变promise状态的 当然 也可以直接同步代码去调用
      // 我们先考虑异步的情况 意味着 promise实例从原型对象找到的then方法会先执行 所以我们不妨先看下 then 做了啥

      function then(onFulfillment, onRejection) {
          // 由于会创建新的promise实例 所以把then所属的对象 也就是前一个promise实例 也就是 现在的this 当做parent来对待
          var parent = this;

          // 从原型对象上取得Promise构造函数 来构造新实例对象 这时候我们不需要执行用户函数代码 所以传入noop
          var child = new this.constructor(noop);

          if (child[PROMISE_ID] === undefined) {
              makePromise(child);
          }

          // 父promise的状态
          var _state = parent._state;


          // 第一个对应父promise 构造函数 中的用户代码 同步修改了父promise的状态 通过 reject或者resolve
          if (_state) {
              // 因为状态被同步代码直接修改了 所以直接触发一次订阅者部署的对应状态的方法
              // 取得对应的resolve或者reject
              var callback = arguments[_state - 1];
              // 推入队列中 下个tick触发
              asap(function () {
                  return invokeCallback(_state, child, callback, parent._result);
              });
          // 第二步 对应 普通的异步 异步回调尚未触发 then先执行
          } else {
              // 添加一个订阅者
              subscribe(parent, child, onFulfillment, onRejection);
          }

          // 返回新的promise实例 我们可以继续使用这个新promise了
          return child;
      }

      Promise.prototype.then = then;

      function makePromise(promise) {
          promise[PROMISE_ID] = id++;
          promise._state = undefined;
          promise._result = undefined;
          promise._subscribers = [];
      }

      // 我们先看 普通的 异步场景 的处理 subscribe(parent, child, onFulfillment, onRejection); 如何给父promise添加一个订阅者

      var PENDING = void 0;
      var FULFILLED = 1;
      var REJECTED = 2;

      function subscribe(parent, child, onFulfillment, onRejection) {
          // 给父promise的订阅者集合中添加子promise这个订阅者
          var _subscribers = parent._subscribers;
          var length = _subscribers.length;


          parent._onerror = null;

          // 之前可能存在有其他的订阅者了 因为我们可以 p.then p.then ... 对同一个实例多次调用then追加新的订阅者
          _subscribers[length] = child;
          // 对应状态 对应方法
          _subscribers[length + FULFILLED] = onFulfillment;
          _subscribers[length + REJECTED] = onRejection;

          // 第一次添加观察者的时候 且 父promise的状态居然已经确认了 说明是被同步改变了父promise的状态 需要立即发布这个改变
          // 当然 异步场景不会触发这个条件 只是普通的添加一个观察者就可以了
          if (length === 0 && parent._state) {
              // 尽快安排 publish 这个方法执行
              asap(publish, parent);
          }
      }

      // 看完异步情况的then操作后 看下 异步任务回调触发我们部署的resolve做了啥:

      function resolve(promise, value) {
          // 尝试解析 用户传回来的 value 但是由于这个值类型可以任意 promise协议又对不同类型的值要求不同的处理 所以才有下面的逻辑
          if (promise === value) {
              // 别返回当前promise实例哈
              reject(promise, selfFulfillment());
          } else if (objectOrFunction(value)) {
              // 返回值是个对象的时候 我们分情况逐一讨论如何处理了
              var then$$1 = void 0;
              try {
                  then$$1 = value.then;
              } catch (error) {
                  reject(promise, error);
                  return;
              }
              // 这个对象可能是个promise类型的 继续尝试去进一步解析它
              handleMaybeThenable(promise, value, then$$1);
          } else {
              // 普通值 用基础情况处理函数 fulfill 来接受这个结果
              fulfill(promise, value);
          }
      }

      function handleMaybeThenable(promise, maybeThenable, then$$1) {
          // 这个可能是promise对象的对象 看它是否满足下面几个条件 1. 原型对象的构造函数是不是我们设置的构造函数Promise 2. then函数是不是原型对象上的同一个函数 
          // 3. 构造函数的静态方法resolve是不是我们的定义的Promise的静态方法
          // 满足的话 说明 这个resolve返回的就是一个promise实例
          if (maybeThenable.constructor === promise.constructor && then$$1 === then && maybeThenable.constructor.resolve === resolve$1) {
              handleOwnThenable(promise, maybeThenable);
          } else {
              // 下面是不满足上面3个情况的场景
              if (then$$1 === undefined) {
                  fulfill(promise, maybeThenable);
                  // 存在then方法的一个函数 其实是类似promise实例的 函数对象
              } else if (isFunction(then$$1)) {
                  handleForeignThenable(promise, maybeThenable, then$$1);
              } else {
                  // 普通对象 无then
                  fulfill(promise, maybeThenable);
              }
          }
      }

      // 对于就是一个普通promise实例的返回值的处理比较简单
      function handleOwnThenable(promise, thenable) {
          // 如果它的状态已确定 例如 Promise.resolve 这种产生的
          // 就以它result作为结果 state作为状态来 更新当前promise实例
          if (thenable._state === FULFILLED) {
              fulfill(promise, thenable._result);
          } else if (thenable._state === REJECTED) {
              reject(promise, thenable._result);
          } else {
              // 如果这个promise还没确认 那就只能给它添加新的一个订阅者了
              // 这时候我们不需要子promise了 只需要添加对应状态的回调函数就可以了
              // 等这个未确定的promise状态变更后触发就好了
              subscribe(thenable, undefined, function (value) {
                  return resolve(promise, value);
              }, function (reason) {
                  return reject(promise, reason);
              });
          }
      }

      function tryThen(then$$1, value, fulfillmentHandler, rejectionHandler) {
          try {
              then$$1.call(value, fulfillmentHandler, rejectionHandler);
          } catch (e) {
              return e;
          }
      }

      // 对 拥有then方法的函数对象来说 可能麻烦一些处理
      function handleForeignThenable(promise, thenable, then$$1) {
          // 按照promise协议 我们需要立即执行这个函数对象的then方法 但是也是把它推入队列中 下个tick执行
          asap(function (promise) {
              // sealed代表已执行过了
              var sealed = false;
              // 我们需要继续部署2个方法给这个then使用 用来改变原先promise对象状态
              var error = tryThen(then$$1, thenable, function (value) {
                  if (sealed) {
                      return;
                  }
                  sealed = true;
                  // 这个很有意思 用户传回来的value不是thenable对象自己 就再次resolve它 尝试解析它的类型
                  // 是的话就直接fulfill了 值的类型已确定 不做额外的处理了
                  if (thenable !== value) {
                      resolve(promise, value);
                  } else {
                      fulfill(promise, value);
                  }
              }, function (reason) {
                  if (sealed) {
                      return;
                  }
                  sealed = true;

                  // 拒绝
                  reject(promise, reason);
              }, 'Settle: ' + (promise._label || ' unknown promise'));

              if (!sealed && error) {
                  sealed = true;
                  reject(promise, error);
              }
          }, promise);
      }

      function selfFulfillment() {
          return new TypeError("You cannot resolve a promise with itself");
      }

      // 简单的基础情况之一的 处理 fulfill(promise, value);

      function fulfill(promise, value) {
          // 处于改变后的状态 不再接受新的改变了 也就是 只有pending状态的才可以
          if (promise._state !== PENDING) {
              return;
          }

          // 修改状态和值
          promise._result = value;
          promise._state = FULFILLED;

          // 发布事件 这个promise的订阅者们可以干活了
          if (promise._subscribers.length !== 0) {
              asap(publish, promise);
          }
      }

      // asap 代表着 as soon as possible 尽快的
      // 先看 asap 的实现

      var len = 0;
      var queue = new Array(1000);

      var asap = function asap(callback, arg) {
          // 推入队列
          queue[len] = callback;
          queue[len + 1] = arg;
          len += 2;
          // 刚好是2个代表着 可以触发一次调度队列中的任务操作了
          if (len === 2) {
              // If len is 2, that means that we need to schedule an async flush.
              // If additional callbacks are queued before the queue is flushed, they
              // will be processed by this flush that we are scheduling.
              // 开始这一次的调度了
              scheduleFlush();
          }
      };

      // es6-promise 依次用了 process.nextTick vertxNext BrowserMutationObserver MessageChannel setTimeout 来模拟异步调度 我们直接取最简单的就好了
      var scheduleFlush = void 0;

      function useSetTimeout() {
          // Store setTimeout reference so es6-promise will be unaffected by
          // other code modifying setTimeout (like sinon.useFakeTimers())
          var globalSetTimeout = setTimeout;
          return function () {
              return globalSetTimeout(flush, 1);
          };
      }

      scheduleFlush = useSetTimeout();

      // asap 推入队列 异步调度一次 flush 方法来刷新一次任务队列

      function flush() {
          // 由于是异步触发 所以在flush之前 可以同步添加很多个任务进去 我们一次性更新上轮tick中所有添加的任务
          for (var i = 0; i < len; i += 2) {
              var callback = queue[i];
              var arg = queue[i + 1];

              // 执行它
              callback(arg);

              queue[i] = undefined;
              queue[i + 1] = undefined;
          }

          // len清零 确保下次正常从0开始
          len = 0;
      }

      // 还记得我们在 fulfill 方法中用asap推入了一个啥任务吗? 是的 就是 publish 发布事件 通知这个promise的订阅者开始干活啦

      function publish(promise) {
          // 由于每个publish任务推入的时候我们都保留了它对应的promise实例
          // 所以可以直接取出订阅者集合
          var subscribers = promise._subscribers;
          var settled = promise._state;

          if (subscribers.length === 0) {
              return;
          }

          var child = void 0,
              callback = void 0,
              detail = promise._result;

          for (var i = 0; i < subscribers.length; i += 3) {
              child = subscribers[i];
              // 根据父promise的状态取出对应的 resolve或者reject 回调
              callback = subscribers[i + settled];

              // 我们先考虑存在 child 也就是普通的 promise实例后面接then情况添加的观察者 这种情况 对应存在 child 子实例
              if (child) {
                  invokeCallback(settled, child, callback, detail);
              } else {
                  // 不存在子promise的情况 属于 resolve返回一个promise 这种情况下 我们手动给它添加的订阅者 只需要触发我们手动添加的回调即可
                  callback(detail);
              }
          }

          // 清空
          promise._subscribers.length = 0;
      }

      // 看下 如何处理 订阅者的回调的 invokeCallback

      function invokeCallback(settled, promise, callback, detail) {
          var hasCallback = isFunction(callback),
              value = void 0,
              error = void 0,
              succeeded = true;

          // callback方法存在 说明 通过then确实追加了至少一个回调 then(fun1, fun2) ...
          if (hasCallback) {
              // 尝试执行一次 resolve或者 reject 当然 也需要捕获可能出现的错误
              try {
                  value = callback(detail);
              } catch (e) {
                  succeeded = false;
                  error = e;
              }

              // 别返回这个子promise实例哈
              if (promise === value) {
                  reject(promise, cannotReturnOwn());
                  return;
              }
          } else {
              // 没有用户添加的resolve或者reject 我们以父promise的结果来作为这个子promise实例的结果
              value = detail;
          }

          // 只对处于pending状态的当前promise做状态修改哦
          if (promise._state !== PENDING) {
              // noop
          } else if (hasCallback && succeeded) {
              // 注意这个 callback 其实可能是 resolve或者reject中的任意一个
              // 但是 由于 返回的值类型不确定 有可能又是一个新promise 所以我们需要继续resolve它
              resolve(promise, value);
          } else if (succeeded === false) {
              // 报错了 还是需要继续再次reject它的
              reject(promise, error);
              // 没有callback 那就是 继承父状态 修改then创建的这个promise实例的状态即可
          } else if (settled === FULFILLED) {
              fulfill(promise, value);
              // 这种没有部署reject 回调的情况,如果报错,错误会导致promise链上的所有势力都被reject掉,同时由于都没部署reject回调
              // 最终这个错误会一致透传到 最后部署的catch方法上,所谓的错误传递
          } else if (settled === REJECTED) {
              reject(promise, value);
          }
      }

      // 一些辅助工具方法
      function objectOrFunction(x) {
          var type = typeof x;
          return x !== null && (type === 'object' || type === 'function');
      }

      function isFunction(x) {
          return typeof x === 'function';
      }



      var _isArray = void 0;
      if (Array.isArray) {
          _isArray = Array.isArray;
      } else {
          _isArray = function (x) {
              return Object.prototype.toString.call(x) === '[object Array]';
          };
      }

      var isArray = _isArray;

      function cannotReturnOwn() {
          return new TypeError('A promises callback cannot return that same promise.');
      }

      // 之前介绍了fullfill 现在看下 reject

      function reject(promise, reason) {
          // 与fullfill相对于的reject
          if (promise._state !== PENDING) {
              return;
          }
          // 修改promise的状态
          promise._state = REJECTED;
          promise._result = reason;

          // 推入一个任务
          asap(publishRejection, promise);
      }

      function publishRejection(promise) {
          // 主要是执行钩子
          if (promise._onerror) {
              promise._onerror(promise._result);
          }

          // 还是发布变化 通知这个promise可能存在的订阅者
          publish(promise);
      }

      // 接下来看下 catch 和 finally 的实现去

      Promise.prototype.catch = function _catch(onRejection) {
          // 额 直接用实例的then去追加一个订阅者就好了...
          return this.then(null, onRejection);
      };

      Promise.prototype.finally = function _finally(callback) {
          var promise = this;
          var constructor = promise.constructor;

          // 用户部署了callback
          if (isFunction(callback)) {
              // finally最终也是通过then返回新的promise 不过注意 callback的返回值并不作为再次添加的then的参数了 而是原来的promise 的result
              // 无论前一个promise的状态是啥 callback总是执行一次 符合协议规范 good
              return promise.then(function (value) {
                  return constructor.resolve(callback()).then(function () {
                      return value;
                  });
              }, function (reason) {
                  return constructor.resolve(callback()).then(function () {
                      throw reason;
                  });
              });
          }

          // 好吧 finally没部署用户的函数的话 直接就then...
          return promise.then(callback, callback);
      };

      // 上面用到了 Promise.resolve 和 Promise.reject 看下它的实现去

      function resolve$1(object) {
          /*jshint validthis:true */
          var Constructor = this;

          // 协议规定了 如果object是个promise对象 直接返回
          if (object && typeof object === 'object' && object.constructor === Constructor) {
              return object;
          }

          // 构造新的实例 同时直接resolve修改它的状态 发布变化
          var promise = new Constructor(noop);
          resolve(promise, object);
          return promise;
      }
      Promise.resolve = resolve$1;

      function reject$1(reason) {
          /*jshint validthis:true */
          var Constructor = this;
          var promise = new Constructor(noop);
          reject(promise, reason);
          return promise;
      }

      Promise.reject = reject$1;

      // 基础方法搞得差不多了 最后再加个常用的 Promise.all 和 Promise.race 吧 :)

      Promise.all = function all(entries) {
          return new Enumerator(this, entries).promise;
      };

      // 哦豁 出现了新的类 Enumerator

      var Enumerator = function () {
          function Enumerator(Constructor, input) {
              this._instanceConstructor = Constructor;
              // 返回的是一个promise实例 支持链式调用
              this.promise = new Constructor(noop);

              if (!this.promise[PROMISE_ID]) {
                  makePromise(this.promise);
              }

              // 参数必须是个数组噢
              if (isArray(input)) {
                  this.length = input.length;
                  this._remaining = input.length;

                  // 用来保存结果集合
                  this._result = new Array(this.length);

                  // 空数组情况 直接fulfill就好了
                  if (this.length === 0) {
                      fulfill(this.promise, this._result);
                  } else {
                      this.length = this.length || 0;
                      // 枚举每个promise
                      this._enumerate(input);
                      // 如果全都是同步变更状态的promise的话 就也可以直接fulfill了
                      if (this._remaining === 0) {
                          fulfill(this.promise, this._result);
                      }
                  }
              } else {
                  reject(this.promise, validationError());
              }
          }

          Enumerator.prototype._enumerate = function _enumerate(input) {
              for (var i = 0; this._state === PENDING && i < input.length; i++) {
                  // 每个promise依次触发
                  this._eachEntry(input[i], i);
              }
          };

          Enumerator.prototype._eachEntry = function _eachEntry(entry, i) {
              var c = this._instanceConstructor;
              var resolve$$1 = c.resolve;

              // 就是我们实现的静态resolve的时候
              if (resolve$$1 === resolve$1) {
                  var _then = void 0;
                  var error = void 0;
                  var didError = false;
                  // 同之前的分析
                  try {
                      _then = entry.then;
                  } catch (e) {
                      didError = true;
                      error = e;
                  }

                  // 已是确定状态的promise 直接调用 _settledAt 更新集合状态
                  if (_then === then && entry._state !== PENDING) {
                      this._settledAt(entry._state, i, entry._result);
                  } else if (typeof _then !== 'function') {
                      // 不是promise就更简单了 直接取结果就好了
                      this._remaining--;
                      this._result[i] = entry;
                  } else if (c === Promise) {
                      // 是我们自己的Promise构造函数
                      var promise = new c(noop);
                      if (didError) {
                          reject(promise, error);
                      } else {
                          // 直接按照之前的处理就好了 追加订阅者等待更新就是了
                          handleMaybeThenable(promise, entry, _then);
                      }
                      // 然后我们再在handleMaybeThenable的基础上再追加一个订阅者用来更新集合状态
                      // publish一更新 2个订阅者都会执行的
                      this._willSettleAt(promise, i);
                  } else {
                      // 用户部署的其他Promise构造函数来构造promise实例 也是追加一个订阅者就是了
                      this._willSettleAt(new c(function (resolve$$1) {
                          return resolve$$1(entry);
                      }), i);
                  }
              } else {
                  // 先看 用户自己在Promise上重载了resolve方法的情况 那就调用它resolve$$1(entry) 然后设置一个订阅者
                  this._willSettleAt(resolve$$1(entry), i);
              }
          };

          // 关注的某个promise状态已经改变了
          Enumerator.prototype._settledAt = function _settledAt(state, i, value) {
              var promise = this.promise;


              if (promise._state === PENDING) {
                  // 有一个状态已更新了
                  this._remaining--;

                  // all来说 任意一个失败了 就 直接reject 了 否则要等全部的都fulfill才会结束
                  if (state === REJECTED) {
                      reject(promise, value);
                  } else {
                      // 保存结果
                      this._result[i] = value;
                  }
              }

              // 最终成功态
              if (this._remaining === 0) {
                  fulfill(promise, this._result);
              }
          };

          // 将来某个时刻来改变promise的状态
          Enumerator.prototype._willSettleAt = function _willSettleAt(promise, i) {
              var enumerator = this;

              // 给这个promise添加一个订阅者 当它状态确定后 触发 enumerator._settledAt 来实现集合内部的对应 i 位置信息更新
              subscribe(promise, undefined, function (value) {
                  return enumerator._settledAt(FULFILLED, i, value);
              }, function (reason) {
                  return enumerator._settledAt(REJECTED, i, reason);
              });
          };

          return Enumerator;
      }();

      function validationError() {
          return new Error('Array Methods must be provided an Array');
      }

      // race

      Promise.race = function race(entries) {
          /*jshint validthis:true */
          var Constructor = this;

          if (!isArray(entries)) {
              return new Constructor(function (_, reject) {
                  return reject(new TypeError('You must pass an array to race.'));
              });
          } else {
              return new Constructor(function (resolve, reject) {
                  var length = entries.length;
                  for (var i = 0; i < length; i++) {
                      // 任意一个fulfill的就结束了 这个就很秒 状态不会再更改了
                      Constructor.resolve(entries[i]).then(resolve, reject);
                  }
              });
          }
      };

      return Promise;

  })()

  // 写几个测试用例简单试试哈

  var p1 = new myPromise((resolve, reject) => {
      setTimeout(() => {
          resolve('1');
      }, 2000);
  })
      .then(() => { console.log('2') }, () => { console.log('3') })

  var p2 = new myPromise((resolve, reject) => {
      setTimeout(() => {
          reject('4');
      }, 2000);
  })
      .then(() => { console.log('5') }, () => { console.log('6') })

  var p3 = new myPromise((resolve, reject) => {
      throw new Error('new error');
  })
      .then(() => { console.log('7') }, (err) => { console.log(err) })

  var ps = [new myPromise((resolve) => { setTimeout(() => { resolve('ps1') }, 2000) }), new myPromise((resolve) => { setTimeout(() => { resolve('ps2') }, 4000) })];
  myPromise.all(ps).then((res) => {
      console.log(res);
  })

  var ps2 = [new myPromise((resolve) => { setTimeout(() => { resolve('ps1') }, 2000) }), new myPromise((resolve) => { setTimeout(() => { resolve('ps2') }, 4000) })];
  myPromise.race(ps2).then((res) => {
      console.log(res);
  })

  // promise的大概实现就是上面的了 多看多写多测 就会啦 祝大家找工作顺利~


  // 源码参考 es6-promise 库源码
  // 规范参考 https://www.ituring.com.cn/article/66566

简单聊一聊 webpack(v5.37.1) 的整体流程吧

首先参照文档中描述的,直接引用js文件暴露的方法来调用, /src/main.js:

const webpack = require('webpack');
const userConfig = require('../webpack.config.js');

const compiler = webpack(userConfig);

compiler.run((err, stats) => { // [Stats Object](#stats-object)
    // ...
  
    compiler.close((closeErr) => {
      // ...
      console.log('close')
    });
  });

然后写个最简单的配置文件就好了,/webpack.config.js:

const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    index: './src/index.js',
  },
  devtool: 'inline-source-map',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
  }
};

然后构建我们的源码文件:
/src/index.js:

const a = require('./a.js');

/src/a.js:

const b = require('./b.js');

module.exports.e = () => {
    console.log('called form a.js');
}

/src/b.js:

const c = require('./c.js');

module.exports.e = () => {
    console.log('called form b.js');
}

/src/c.js:

module.exports.e = () => {
    console.log('called form c.js');
}

它们之间的依赖关系很清晰明了的为: index -> a -> b -> c

然后为了方便调试debug,我们设置vscode的launch.json文件的启动文件为:

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}\\src\\main.js"
        }
    ]
}

之后就可以打上断点,愉快的开始测试webpack的功能了(噩梦开始了) :)

在进入逻辑之前,可以先看下最终的输出:index.bundle.js:

/******/ (() => { // webpackBootstrap
/******/ 	var __webpack_modules__ = ({

/***/ "./src/a.js":
/*!******************!*\
  !*** ./src/a.js ***!
  \******************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {

const b = __webpack_require__(/*! ./b.js */ "./src/b.js");

module.exports.e = () => {
    console.log('called form a.js');
}


/***/ }),

/***/ "./src/b.js":
/*!******************!*\
  !*** ./src/b.js ***!
  \******************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {

const c = __webpack_require__(/*! ./c.js */ "./src/c.js");

module.exports.e = () => {
    console.log('called form b.js');
}

/***/ }),

/***/ "./src/c.js":
/*!******************!*\
  !*** ./src/c.js ***!
  \******************/
/***/ ((module) => {

module.exports.e = () => {
    console.log('called form c.js');
}

/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
// const axios = require('axios').default;
// console.log(axios)

const a = __webpack_require__(/*! ./a.js */ "./src/a.js");
})();

/******/ })()
;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIj...省略

可以看到,当前同步依赖的js都被收集起来放到模块对象中了,id索引就是它们的路径名字,同时也添加了webpack的一些引导代码,方便使用这些模块;
这是同步的场景,异步的场景我们也可以构造一下,添加一个import()动态导入的模块,看下输出的bundle.js,就可以看到webpack是如何通过动态追加script标签
来加载单独打包的异步module包,然后通过预先定义的webpackJsonpCallback方式来实现追加添加的module,实现的很有意思,说实话,这是一个经典的Jsonp的使用场景,解决了我长久以来对异步模块按需加载的迷惑,果然还是源码出真知:)
好了,看完最终的输出,我们就开始根据源码的运行逻辑,看下如何根据配置文件一步步构造出这样的输出来吧:
从node_modules找到对应的包源码,从package.json找到入口文件即可:

const createCompiler = rawOptions => {
	const options = getNormalizedWebpackOptions(rawOptions);
	applyWebpackOptionsBaseDefaults(options);
	const compiler = new Compiler(options.context);
	compiler.options = options;
	new NodeEnvironmentPlugin({
		infrastructureLogging: options.infrastructureLogging
	}).apply(compiler);
	if (Array.isArray(options.plugins)) {
		for (const plugin of options.plugins) {
			if (typeof plugin === "function") {
				plugin.call(compiler, compiler);
			} else {
				plugin.apply(compiler);
			}
		}
	}
	applyWebpackOptionsDefaults(options);
	compiler.hooks.environment.call();
	compiler.hooks.afterEnvironment.call();
	new WebpackOptionsApply().process(options, compiler);
	compiler.hooks.initialize.call();
	return compiler;
};

const webpack = /** @type {WebpackFunctionSingle & WebpackFunctionMulti} */ (
	/**
	 * @param {WebpackOptions | (ReadonlyArray<WebpackOptions> & MultiCompilerOptions)} options options
	 * @param {Callback<Stats> & Callback<MultiStats>=} callback callback
	 * @returns {Compiler | MultiCompiler}
	 */
	(options, callback) => {
		const create = () => {
			if (!webpackOptionsSchemaCheck(options)) {
				getValidateSchema()(webpackOptionsSchema, options);
			}
			/** @type {MultiCompiler|Compiler} */
			let compiler;
			let watch = false;
			/** @type {WatchOptions|WatchOptions[]} */
			let watchOptions;
			if (Array.isArray(options)) {
				/** @type {MultiCompiler} */
				compiler = createMultiCompiler(
					options,
					/** @type {MultiCompilerOptions} */ (options)
				);
				watch = options.some(options => options.watch);
				watchOptions = options.map(options => options.watchOptions || {});
			} else {
				const webpackOptions = /** @type {WebpackOptions} */ (options);
				/** @type {Compiler} */
				compiler = createCompiler(webpackOptions);
				watch = webpackOptions.watch;
				watchOptions = webpackOptions.watchOptions || {};
			}
			return { compiler, watch, watchOptions };
		};
		if (callback) {
			try {
				const { compiler, watch, watchOptions } = create();
				if (watch) {
					compiler.watch(watchOptions, callback);
				} else {
					compiler.run((err, stats) => {
						compiler.close(err2 => {
							callback(err || err2, stats);
						});
					});
				}
				return compiler;
			} catch (err) {
				process.nextTick(() => callback(err));
				return null;
			}
		} else {
			const { compiler, watch } = create();
			if (watch) {
				util.deprecate(
					() => {},
					"A 'callback' argument need to be provided to the 'webpack(options, callback)' function when the 'watch' option is set. There is no way to handle the 'watch' option without a callback.",
					"DEP_WEBPACK_WATCH_WITHOUT_CALLBACK"
				)();
			}
			return compiler;
		}
	}
);

上面的代码最重要的就1行:

const compiler = new Compiler(options.context);

构造了 compiler 实例,我们之后也是直接调用它的run方法来执行的;
而关于默认配置项的处理大家有兴趣的话可以自行看下,钩子类型的介绍见tapable的分析,接着去看compiler实例的方法去:

run(callback) {
		if (this.running) {
			return callback(new ConcurrentCompilationError());
		}

		let logger;

		const finalCallback = (err, stats) => {
			...
		};

		const startTime = Date.now();

		this.running = true;

		const onCompiled = (err, compilation) => {
			...
		};

		const run = () => {
			this.hooks.beforeRun.callAsync(this, err => {
				if (err) return finalCallback(err);

				this.hooks.run.callAsync(this, err => {
					if (err) return finalCallback(err);

					this.readRecords(err => {
						if (err) return finalCallback(err);

						this.compile(onCompiled);
					});
				});
			});
		};

		if (this.idle) {
			this.cache.endIdle(err => {
				if (err) return finalCallback(err);

				this.idle = false;
				run();
			});
		} else {
			run();
		}
	}

因为compiler实例具有很多的生命周期钩子,整个webapck执行过程其实就是这些个钩子在不断的按顺序依次执行,但是由于存在异步调用的回调,
所以基本上都是以回调callback的形式传入下一步的函数,从而导致的callback地狱的产生,这还是webpack有意拆分了主体逻辑的代码:),不过这也够我们
在看源码的时候喝一壶了,因为,callback嵌套的深度实在是太深了...
在这里有个我自己的调试技巧,就是在我们不知道这个函数内部的执行逻辑有多深的时候(可能有上千行,几十个回调,包括异步调用再异步调用等),我们可以预先在这个函数最终的异步回调打个断点,观察它的结果,然后再单独仔细分析这个函数中间做了什么,这样一点点的研究这个复杂的函数;

我们接着往下看:

compile(callback) {
		const params = this.newCompilationParams();
		this.hooks.beforeCompile.callAsync(params, err => {
			if (err) return callback(err);

			this.hooks.compile.call(params);

			const compilation = this.newCompilation(params);

			const logger = compilation.getLogger("webpack.Compiler");

			logger.time("make hook");
			this.hooks.make.callAsync(compilation, err => {
				logger.timeEnd("make hook");
				if (err) return callback(err);

				logger.time("finish make hook");
				this.hooks.finishMake.callAsync(compilation, err => {
					logger.timeEnd("finish make hook");
					if (err) return callback(err);

					process.nextTick(() => {
						logger.time("finish compilation");
						compilation.finish(err => {
							logger.timeEnd("finish compilation");
							if (err) return callback(err);

							logger.time("seal compilation");
							compilation.seal(err => {
								logger.timeEnd("seal compilation");
								if (err) return callback(err);

								logger.time("afterCompile hook");
								this.hooks.afterCompile.callAsync(compilation, err => {
									logger.timeEnd("afterCompile hook");
									if (err) return callback(err);

									return callback(null, compilation);
								});
							});
						});
					});
				});
			});
		});
	}

在webpack有2个很重要的生命周期大家需要注意:1 make:它解析了module及它的依赖项,收集了所有的modules,形成了module map结构;2 seal: 根据上面得到的modules和module map 构造出了chunk map,准备好了assets这些资产文件生成所需要的信息;
有了以上,大家发现其实距离最终的输出文件其实很接近了,而事实上也是这样的,它们就是构造最终输出的所需的大部分必要条件了;

当然,还有一个大家需要注意:const compilation = this.newCompilation(params); 代表一次编译的过程,compiler多次编译过程共享的控制实例,具体的某一次编译其实由 compilation 所控制的;

由于这个make的过程包含 module 的解析参数构造过程, 解析过程,添加过程, build过程, 然后依赖遍历 重复上述的几个过程,从而完成module树的构造;
过程太长不分析了,大家注意跟踪代码的时候在回调中打断点去看代码,不然是真的会迷失在源码中的,webpack还放了个异步任务队列在那,无处不在的缓存,大家看的时候可以选择性忽略不影响主线逻辑的代码去阅读;

主要是大家可以看下webpack中 moduleGraph是怎么描述模块之间的依赖关系:

moduleGraph.setResolvedModule(
  connectOrigin ? originModule : null,
  dependency,
  module
);

setResolvedModule(originModule, dependency, module) {
		const connection = new ModuleGraphConnection(
			originModule,
			dependency,
			module,
			undefined,
			dependency.weak,
			dependency.getCondition(this)
		);
		this._dependencyMap.set(dependency, connection);
		const connections = this._getModuleGraphModule(module).incomingConnections;
		connections.add(connection);
		const mgm = this._getModuleGraphModule(originModule);
		if (mgm.outgoingConnections === undefined) {
			mgm.outgoingConnections = new Set();
		}
		mgm.outgoingConnections.add(connection);
	}

大家跟到这些代码的时候就可以仔细看下它是如何描述 图 的节点之间的引用关系了,出边和入边也做了对应的抽象处理,这些都是很关键的代码。

而chunk由多个module构成,判断哪些module组成这个chunk,则是由上面的信息以及module的类型可以判断出来,当然一些插件也可以做相应的修改,如spliteChuck这样负责抽取module的插件,不过到底还是遍历module的依赖,依次收集到chunkGraph的chunk中来,最终依赖make阶段读取的文件源码内容得到的buffer,经过对应的处理替换后,得到assets信息,而这些就是生来模块的信息来源,最终emitAssets阶段则是根据输出文件的类型,添加对应的引用代码片段,最终输出我们看到的这样一些奇怪但是又正常工作的代码:)

最后,webpack的源码读起来比较晕,因为过程很长,中间夹杂着太多的异步调用以及插件的各类钩子注入,所以全程都离不开debug的帮助:(
不过在坚持一段时间,读完了整体流程后,就可以慢慢的仔细读一些细节代码了,比如说一些插件的单独实现,总之:
先整体,先流程,再局部,再细节。
祝大家,阅读webpack源码顺利~~~(逃

Vue(v3.0.11)源码简析之注册钩子函数及watch的相关实现

文档中对应的描述很清晰的介绍各类用法,二话不说直接上代码:

// 添加业务函数到组件实例对应的生命周期钩子中去
function injectHook(type, hook, target = currentInstance, prepend = false) {
    if (target) {
        // 数组形式存储多个待执行方法
        const hooks = target[type] || (target[type] = []);
        // cache the error handling wrapper for injected hooks so the same hook
        // can be properly deduped by the scheduler. "__weh" stands for "with error
        // handling".
        // 包装业务函数一层 实际vue执行的是 __weh
        const wrappedHook = hook.__weh ||
            (hook.__weh = (...args) => {
                if (target.isUnmounted) {
                    return;
                }
                // disable tracking inside all lifecycle hooks
                // since they can potentially be called inside effects.
                // 不需要依赖收集了
                pauseTracking();
                // Set currentInstance during hook invocation.
                // This assumes the hook does not synchronously trigger other hooks, which
                // can only be false when the user does something really funky.
                // 但是还是保留注册时期对应的组件实例
                setCurrentInstance(target);
                // 执行业务函数
                const res = callWithAsyncErrorHandling(hook, target, type, args);
                setCurrentInstance(null);
                resetTracking();
                return res;
            });
        // 前||后 插入
        if (prepend) {
            hooks.unshift(wrappedHook);
        }
        else {
            hooks.push(wrappedHook);
        }
        return wrappedHook;
    }
    else {
        const apiName = toHandlerKey(ErrorTypeStrings[type].replace(/ hook$/, ''));
        warn(`${apiName} is called when there is no active component instance to be ` +
            `associated with. ` +
            `Lifecycle injection APIs can only be used during execution of setup().` +
            (` If you are using async setup(), make sure to register lifecycle ` +
                    `hooks before the first await statement.`
                ));
    }
}
// 返回闭包函数 持有声明周期钩子名称的自由变量的匿名函数
const createHook = (lifecycle) => (hook, target = currentInstance) => 
// post-create lifecycle registrations are noops during SSR
!isInSSRComponentSetup && injectHook(lifecycle, hook, target);
// 几个可以在组合API使用的注册钩子函数函数
const onBeforeMount = createHook("bm" /* BEFORE_MOUNT */);
const onMounted = createHook("m" /* MOUNTED */);
const onBeforeUpdate = createHook("bu" /* BEFORE_UPDATE */);
const onUpdated = createHook("u" /* UPDATED */);
const onBeforeUnmount = createHook("bum" /* BEFORE_UNMOUNT */);
const onUnmounted = createHook("um" /* UNMOUNTED */);
const onRenderTriggered = createHook("rtg" /* RENDER_TRIGGERED */);
const onRenderTracked = createHook("rtc" /* RENDER_TRACKED */);
const onErrorCaptured = (hook, target = currentInstance) => {
    injectHook("ec" /* ERROR_CAPTURED */, hook, target);
};

// Simple effect.
// 不带cb的watch effect本身在依赖改变后会再次执行
function watchEffect(effect, options) {
    return doWatch(effect, null, options);
}
// initial value for watchers to trigger on undefined initial values
// 初始值 方便对比是首次变化不
const INITIAL_WATCHER_VALUE = {};
// implementation
// 带cb的watch 依赖改变后执行cb
function watch(source, cb, options) {
    if (!isFunction(cb)) {
        warn(`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
            `Use \`watchEffect(fn, options?)\` instead. \`watch\` now only ` +
            `supports \`watch(source, cb, options?) signature.`);
    }
    return doWatch(source, cb, options);
}
// 具体实现如下 第三个参数代表了实际支持的所有配置参数
function doWatch(source, cb, { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ, instance = currentInstance) {
    // watchEffect的使用场景限制有2
    if (!cb) {
        if (immediate !== undefined) {
            warn(`watch() "immediate" option is only respected when using the ` +
                `watch(source, callback, options?) signature.`);
        }
        if (deep !== undefined) {
            warn(`watch() "deep" option is only respected when using the ` +
                `watch(source, callback, options?) signature.`);
        }
    }
    // 被watch的对象 需要是响应式集合 才能建立依赖收集
    const warnInvalidSource = (s) => {
        warn(`Invalid watch source: `, s, `A watch source can only be a getter/effect function, a ref, ` +
            `a reactive object, or an array of these types.`);
    };
    // 开始准备分情况构建 getter 函数了
    let getter;
    let forceTrigger = false;
    if (isRef(source)) {
        getter = () => source.value;
        // 浅层的话 _shallow = true 修改value下的某个key不会导致trigger触发 但是watch的时候 触发
        forceTrigger = !!source._shallow;
    }
    // 普通响应式 开启深度追踪
    else if (isReactive(source)) {
        getter = () => source;
        deep = true;
    }
    // 数组中可以放的几类类型 同上面的警告信息对应
    else if (isArray(source)) {
        getter = () => source.map(s => {
            if (isRef(s)) {
                return s.value;
            }
            // 对象的话 为了建立所有属性的依赖收集 需要深度遍历 依次触发一次get操作
            else if (isReactive(s)) {
                return traverse(s);
            }
            else if (isFunction(s)) {
                return callWithErrorHandling(s, instance, 2 /* WATCH_GETTER */, [
                    instance && instance.proxy
                ]);
            }
            else {
                warnInvalidSource(s);
            }
        });
    }
    else if (isFunction(source)) {
        if (cb) {
            // getter with cb
            // 包裹一下
            getter = () => callWithErrorHandling(source, instance, 2 /* WATCH_GETTER */, [
                instance && instance.proxy
            ]);
        }
        else {
            // no cb -> simple effect
            getter = () => {
                if (instance && instance.isUnmounted) {
                    return;
                }
                // watchEffect的场景话 没有cb 相对应再次触发的就是getter作为cb 对应文档中描述的 清除副作用注册函数 getter再次触发的时候执行清除函数 内容由用户决定
                if (cleanup) {
                    cleanup();
                }
                // 注意参数 onInvalidate 与文档中描述对应
                return callWithAsyncErrorHandling(source, instance, 3 /* WATCH_CALLBACK */, [onInvalidate]);
            };
        }
    }
    else {
        getter = NOOP;
        warnInvalidSource(source);
    }
    if (cb && deep) {
        const baseGetter = getter;
        // 构建可以 触发对象所有属性的依赖收集的 getter
        getter = () => traverse(baseGetter());
    }
    // 文档中描述的清除函数了
    let cleanup;
    // 注册清除函数的方法
    let onInvalidate = (fn) => {
        // runner是个effect 加在它的onStop钩子上
        cleanup = runner.options.onStop = () => {
            callWithErrorHandling(fn, instance, 4 /* WATCH_CLEANUP */);
        };
    };
    // 旧值 || 初始值 
    let oldValue = isArray(source) ? [] : INITIAL_WATCHER_VALUE;
    const job = () => {
        if (!runner.active) {
            return;
        }
        if (cb) {
            // watch(source, cb)
            // 触发getter 得到新值
            const newValue = runner();
            if (deep || forceTrigger || hasChanged(newValue, oldValue)) {
                // cleanup before running cb again
                // 对应文档 有cb再次触发的话 也先执行清除副作用函数
                if (cleanup) {
                    cleanup();
                }
                callWithAsyncErrorHandling(cb, instance, 3 /* WATCH_CALLBACK */, [
                    newValue,
                    // pass undefined as the old value when it's changed for the first time
                    oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
                    onInvalidate
                ]);
                oldValue = newValue;
            }
        }
        else {
            // watchEffect
            // getter作为cb
            runner();
        }
    };
    // important: mark the job as a watcher callback so that scheduler knows
    // it is allowed to self-trigger (#1727)
    // 用于cb的watch是可以继续递归产生副作用的
    job.allowRecurse = !!cb;
    let scheduler;
    // 同步刷新的watch 直接执行job即可 也就是执行cb
    if (flush === 'sync') {
        scheduler = job;
    }
    // 在render之后执行cb  那就推入post任务队列
    else if (flush === 'post') {
        scheduler = () => queuePostRenderEffect(job, instance && instance.suspense);
    }
    // 默认缺省值是 pre
    else {
        // default: 'pre'
        // 在组件render之前触发
        scheduler = () => {
            // 无组件实例的话 推入 pre 队列
            if (!instance || instance.isMounted) {
                queuePreFlushCb(job);
            }
            else {
                // with 'pre' option, the first call must happen before
                // the component is mounted so it is called synchronously.
                // 被trigger触发执行的 scheduler 此时实例存在 为保证在render之前执行 现在就直接执行job就好了
                job();
            }
        };
    }
    // 用effect 以 getter 作为执行函数来建立依赖收集 同时指定了 scheduler 当依赖改变后执行 scheduler
    // 如果没有 scheduler 依赖改后 执行的 runner 也就是 getter
    const runner = effect(getter, {
        lazy: true,
        onTrack,
        onTrigger,
        scheduler
    });
    // watch方法运行的时刻如果存在组件实例 也就是setup组合API中 那么把这个effect收集到组件的effects中 方便后面清理
    recordInstanceBoundEffect(runner, instance);
    // initial run
    if (cb) {
        // 对应文档中的立即执行
        if (immediate) {
            job();
        }
        else {
            // 立即触发一次响应式getter建立依赖收集即可
            oldValue = runner();
        }
    }
    else if (flush === 'post') {
        // 组件实例处于suspense控制下的话 这个阶段产生的effect都被suspense.effects存储控制 因为实在render之后执行 具体分析见suspense的章节吧
        // 当前我们简单的 认为 推入post队列在 render之后执行即可
        queuePostRenderEffect(runner, instance && instance.suspense);
    }
    else {
        // watchEffect 触发响应式getter
        runner();
    }
    // 对应文档的移除函数
    return () => {
        stop(runner);
        // 之前收集了 现在移除
        if (instance) {
            remove(instance.effects, runner);
        }
    };
}
// this.$watch
// 组件实例的proxy上的$watch方法 注意它绑定了this为组件实例
function instanceWatch(source, cb, options) {
    const publicThis = this.proxy;
    // 只接受 string和函数呢
    const getter = isString(source)
        ? () => publicThis[source]
        : source.bind(publicThis);
    // 注意cb也绑定了this
    return doWatch(getter, cb.bind(publicThis), options, this);
}
// 遍历对象 触发所有属性的get
function traverse(value, seen = new Set()) {
    // 普通值 递归出口
    if (!isObject(value) || seen.has(value)) {
        return value;
    }
    // 缓存 防止循环引用
    seen.add(value);
    // 递归遍历
    if (isRef(value)) {
        traverse(value.value, seen);
    }
    else if (isArray(value)) {
        for (let i = 0; i < value.length; i++) {
            traverse(value[i], seen);
        }
    }
    else if (isSet(value) || isMap(value)) {
        value.forEach((v) => {
            traverse(v, seen);
        });
    }
    else {
        for (const key in value) {
            traverse(value[key], seen);
        }
    }
    // 每个属性的get都触发 包含对象本身
    return value;
}

简单聊一聊generator和async函数的模拟实现吧(5)

// async 函数 手动继发 await

// const fetchData = (data) => new Promise((resolve) => setTimeout(resolve, 1000, data + 1))

// const fetchValue = async function () {
//     var value1 = await fetchData(1);
//     var value2 = await fetchData(value1);
//     var value3 = await fetchData(value2);
//     console.log(value3)
// };

// fetchValue(); // 3秒后输出 4

var regeneratorRuntime = (function (exports) {
    // 一些辅助函数
    var Op = Object.prototype;
    var hasOwn = Op.hasOwnProperty;
    var undefined; // More compressible than void 0.
    var $Symbol = typeof Symbol === "function" ? Symbol : {};
    var iteratorSymbol = $Symbol.iterator || "@@iterator";
    var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator";
    var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag";

    function define(obj, key, value) {
        Object.defineProperty(obj, key, {
            value: value,
            enumerable: true,
            configurable: true,
            writable: true
        });
        return obj[key];
    }

    // 生成器 当做函数看待 只从它身上取 prototype
    function Generator() { }
    // 生成器函数 纯函数看待
    function GeneratorFunction() { }
    // 生成器函数原型对象 我们对它同时看做 函数 和 对象 并赋予各自的属性
    function GeneratorFunctionPrototype() { }

    var IteratorPrototype = {};
    IteratorPrototype[iteratorSymbol] = function () {
        return this;
    };

    var getProto = Object.getPrototypeOf;
    // 取出数组上存在的原生迭代器对象实例 就是下面的Array Iterator {}  然后取出它继承的原型对象的原型对象 也就是 仅仅次于Object用于继承的原型对象那个拥有 Symbol(Symbol.iterator): ƒ [Symbol.iterator]() 属性的原型对象
    /*
        Array Iterator {}
        __proto__: Array Iterator
            next: ƒ next()
            Symbol(Symbol.toStringTag): "Array Iterator"
            __proto__:
                Symbol(Symbol.iterator): ƒ [Symbol.iterator]()
                __proto__: Object
    */
    // 原生迭代器原型对象
    var NativeIteratorPrototype = getProto && getProto(getProto(values([])));
    if (NativeIteratorPrototype &&
        NativeIteratorPrototype !== Op &&
        hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {
        // This environment has a native %IteratorPrototype%; use it instead
        // of the polyfill.
        IteratorPrototype = NativeIteratorPrototype;
    }

    // GeneratorFunctionPrototype 当做函数看待
    // Gp: 生成器函数原型对象 Object.create(IteratorPrototype) 以原生迭代器原型继承构造一个新对象
    var Gp = GeneratorFunctionPrototype.prototype = Object.create(IteratorPrototype);
    // 补充原型对象的构造函数属性
    Gp.constructor = GeneratorFunctionPrototype;

    // GeneratorFunctionPrototype 当做对象再看待
    GeneratorFunction.prototype = GeneratorFunctionPrototype;
    GeneratorFunctionPrototype.constructor = GeneratorFunction;

    // 可能大家不理解上面这几行代码设置原型有啥用 我们写完 mark 和 wrap函数之后再回来分析这几行代码 的目标效果

    Generator.prototype = GeneratorFunctionPrototype.prototype;

    // 再给它加个名字吧
    GeneratorFunction.displayName = define(
        GeneratorFunctionPrototype,
        toStringTagSymbol,
        "GeneratorFunction"
    );

    // 先来实现mark 看它标记了啥:
    exports.mark = function (genFun) {
        if (Object.setPrototypeOf) {
            // genFun.__proto__ = GeneratorFunctionPrototype;
            Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);
        } else {
            genFun.__proto__ = GeneratorFunctionPrototype;
            define(genFun, toStringTagSymbol, "GeneratorFunction");
        }
        // 设置 genFun.prototype = 一个新对象 (new obj, obj.__proto__ = Gp)
        genFun.prototype = Object.create(Gp);


        return genFun;
    };

    // 再看 wrap 包装了啥:
    // innerFn 执行业务代码 outerFn 就是被mark过的函数
    function wrap(innerFn, outerFn, self, tryLocsList) {
        // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.
        // outerFn.prototype 也就是 genFun.prototype 是 Object.create(Gp)得到 也就是 Gp的实例 而 Generator.prototype 就是 Gp 所以满足条件
        var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator;
        // 好了 hw 就是来自于 Object.create(protoGenerator.prototype) 构造的实例 hw.__proto__ === protoGenerator.prototype; hw.__proto__.__proto__ === Gp
        var generator = Object.create(protoGenerator.prototype);
        // 原来 上下文 对象来自于这里构造的实例
        var context = new Context(tryLocsList || []);

        // The ._invoke method unifies the implementations of the .next,
        // .throw, and .return methods.
        // 实例上还有一个方法 
        generator._invoke = makeInvokeMethod(innerFn, self, context);

        // 这就是 hw 迭代器实例对象
        return generator;
    }
    exports.wrap = wrap;

    /*
        现在我们回头看下那几行原型设置的效果:
        规范约定如下: hw.__proto__ === helloWorldGenerator.prototype; hw.__proto__.__proto__ === helloWorldGenerator.__proto__.prototype
        这意味着 我们通过 hw = genFun() 得到的迭代器对象 需要 满足类似 new genFun() 方式得到的原型继承关系,我们来看下hw是什么:
        hw = Object.create(helloWorldGenerator.prototype);
        所以 hw.__proto__ === helloWorldGenerator.prototype 第一个条件成立;
        而 hw.__proto__.__proto__ 在上面的 wrap 分析了 其实 就是 Gp对象,再看 helloWorldGenerator.__proto__ 其实是 GeneratorFunctionPrototype 而 
        GeneratorFunctionPrototype.prototype 就是 Gp,好吧 第二个条件成立 所以满足规范要求了

        facebook人写的代码真是 666...
    */

    // return版本新增:
    function pushTryEntry(locs) {
        // 依次保存4个关键的位置信息 [try, catch, finally, finally after]
        var entry = { tryLoc: locs[0] };

        if (1 in locs) {
            entry.catchLoc = locs[1];
        }

        if (2 in locs) {
            entry.finallyLoc = locs[2];
            entry.afterLoc = locs[3];
        }

        this.tryEntries.push(entry);
    }

    // return版本新增:
    function resetTryEntry(entry) {
        var record = entry.completion || {};
        record.type = "normal";
        delete record.arg;
        entry.completion = record;
    }

    // 好了 我们先构建一个简单的 Context:

    function Context(tryLocsList) {
        // return版本新增: 补充对 tryLocsList 的处理 这个变量的名字透露出它的功能了。。try代码块的位置列表。。。
        this.tryEntries = [{ tryLoc: "root" }];
        tryLocsList.forEach(pushTryEntry, this);

        this.reset(true);
    }

    // 简单的放一些方法到原型对象上去
    // 从转码后的业务代码中可以看到 我们需要的实现有下面几个方法:
    Context.prototype = {
        constructor: Context,

        reset: function (skipTempReset) {
            this.prev = 0;
            this.next = 0;

            this.sent = this._sent = undefined;
            this.done = false;

            this.method = "next";
            this.arg = undefined;

            // return版本新增:
            this.tryEntries.forEach(resetTryEntry);
        },

        // 这个最后执行 _context.stop();
        stop: function () {
            this.done = true;

            // return版本新增:
            var rootEntry = this.tryEntries[0];
            var rootRecord = rootEntry.completion;
            if (rootRecord.type === "throw") {
                throw rootRecord.arg;
            }

            return this.rval;
        },

        dispatchException: function (exception) {
            // 停止态的无法再throw了
            if (this.done) {
                throw exception;
            }

            // context实例
            var context = this;
            // 处理错误的最终方法
            function handle(loc, caught) {
                // 默认抛出错误
                record.type = "throw";
                record.arg = exception;
                context.next = loc;

                // 如果部署了catch代码就可以捕获
                if (caught) {
                    // If the dispatched exception was caught by a catch block,
                    // then let that catch block handle the exception normally.
                    // 继续正常执行next的代码 不会报错
                    context.method = "next";
                    context.arg = undefined;
                }

                return !!caught;
            }

            // 继续从后往前找catch代码块
            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
                var entry = this.tryEntries[i];
                var record = entry.completion;

                // 无人捕获错误 
                if (entry.tryLoc === "root") {
                    // Exception thrown outside of any try block that could handle
                    // it, so set the completion value of the entire function to
                    // throw the exception.
                    return handle("end");
                }

                // 根据是否部署了catch来处理 注意 handle的第一个参数 下一步代码执行位置 第二个参数 是否捕获错误
                if (entry.tryLoc <= this.prev) {
                    var hasCatch = hasOwn.call(entry, "catchLoc");
                    var hasFinally = hasOwn.call(entry, "finallyLoc");

                    if (hasCatch && hasFinally) {
                        if (this.prev < entry.catchLoc) {
                            return handle(entry.catchLoc, true);
                        } else if (this.prev < entry.finallyLoc) {
                            return handle(entry.finallyLoc);
                        }

                    } else if (hasCatch) {
                        if (this.prev < entry.catchLoc) {
                            return handle(entry.catchLoc, true);
                        }

                    } else if (hasFinally) {
                        if (this.prev < entry.finallyLoc) {
                            return handle(entry.finallyLoc);
                        }

                    } else {
                        throw new Error("try statement without catch or finally");
                    }
                }
            }
        },

        // 这个 在最后一个yield语句之后执行 _context.abrupt("return", 'ending');
        // 任意 .return() 语句也是走的下面这个方法
        abrupt: function (type, arg) {
            // return版本重写:
            // 我们看待 return(7) 导致发生了什么: 它调用了 abrupt('return ', 7);
            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
                var entry = this.tryEntries[i];
                // 仔细看下面几个判断 分别确定了这个return语句触发的距离它最近的那个try语句块 且 保证了这个try语句块执行了 且 拥有finally块 这时候我们就需要修改一下原本的执行顺序了
                if (entry.tryLoc <= this.prev &&
                    hasOwn.call(entry, "finallyLoc") &&
                    this.prev < entry.finallyLoc) {
                    var finallyEntry = entry;
                    break;
                }
            }

            // break和continue的场景 现在还不涉及 先忽略吧
            if (finallyEntry &&
                (type === "break" ||
                    type === "continue") &&
                finallyEntry.tryLoc <= arg &&
                arg <= finallyEntry.finallyLoc) {
                // Ignore the finally entry if control is not jumping to a
                // location outside the try/catch block.
                finallyEntry = null;
            }

            var record = finallyEntry ? finallyEntry.completion : {};
            record.type = type;
            record.arg = arg;

            // 注意这个哈 再次自动调用了next...且设置了下一步执行哪一句代码 即我们看到的:先执行finally语句中的内容
            if (finallyEntry) {
                this.method = "next";
                this.next = finallyEntry.finallyLoc;
                // 迭代器再迭代一次
                return ContinueSentinel;
            }

            return this.complete(record);
        },

        // 好吧 这个也要实现。。。
        complete: function (record, afterLoc) {
            if (record.type === "throw") {
                throw record.arg;
            }

            if (record.type === "break" ||
                record.type === "continue") {
                this.next = record.arg;
                // 现在我们只关注 return 和 normal 
            } else if (record.type === "return") {
                this.rval = this.arg = record.arg;
                this.method = "return";
                // 业务代码执行最后的stop操作
                this.next = "end";
            } else if (record.type === "normal" && afterLoc) {
                this.next = afterLoc;
            }

            // 完成态自动切换到结束态 需要返回一个特殊的continue类型的返回值
            return ContinueSentinel;
        },
        // return版本新增:
        finish: function (finallyLoc) {
            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
                // 依旧同上 找到最近的一个try语句块
                var entry = this.tryEntries[i];
                // 2者匹配 意味着 finally中的语句执行完 可以执行return原本的功能了 即 终止迭代器 返回 return的值
                if (entry.finallyLoc === finallyLoc) {
                    // 终止
                    this.complete(entry.completion, entry.afterLoc);
                    // 清除这条entry的状态 作用已完成
                    resetTryEntry(entry);
                    // 继续迭代 朝着stop状态出发
                    return ContinueSentinel;
                }
            }
        },

        // catch新增
        "catch": function (tryLoc) {
            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
                var entry = this.tryEntries[i];
                // 找到对应的catch entry
                if (entry.tryLoc === tryLoc) {
                    var record = entry.completion;
                    if (record.type === "throw") {
                        // 取出我们传入的参数
                        var thrown = record.arg;
                        resetTryEntry(entry);
                    }
                    return thrown;
                }
            }

            // The context.catch method must only be called with a location
            // argument that corresponds to a known catch block.
            throw new Error("illegal catch attempt");
        },

        delegateYield: function (iterable, resultName, nextLoc) {

        }
    };

    // 继续实现 makeInvokeMethod:

    // 迭代器 状态机 的几种状态
    var GenStateSuspendedStart = "suspendedStart";
    var GenStateSuspendedYield = "suspendedYield";
    var GenStateExecuting = "executing";
    var GenStateCompleted = "completed";

    // 状态机继续迭代的特殊返回值
    var ContinueSentinel = {};

    // 返回一个 invoke 内部闭包函数 持有 innerFn context 等自由变量
    function makeInvokeMethod(innerFn, self, context) {
        // 状态机初始化起始状态
        var state = GenStateSuspendedStart;

        return function invoke(method, arg) {
            // todo
        };
    }

    // 然后我们调用的时候从 hw上调用next(),我们在直接原型上加:

    function defineIteratorMethods(prototype) {
        ["next", "throw", "return"].forEach(function (method) {
            define(prototype, method, function (arg) {
                // 其实都是执行实例的 _invoke y也就是上面的 内部函数invoke
                return this._invoke(method, arg);
            });
        });
    }

    defineIteratorMethods(Gp);

    // 所以重点就是 invoke 的实现:

    function makeInvokeMethod(innerFn, self, context) {
        // 状态机初始化起始状态
        var state = GenStateSuspendedStart;

        return function invoke(method, arg) {

            // 初次调用处于 suspendedStart 执行到某个yield语句暂停 处于 suspendedYield 结束后处于 completed
            // 而 executing 指的是 业务代码执行的过程中处于的状态 之后便会过度到 suspendedYield || completed 
            // 不应该在再次invoke的时候遇到的
            if (state === GenStateExecuting) {
                throw new Error("Generator is already running");
            }

            // 结束态后 再次调用迭代器
            if (state === GenStateCompleted) {
                if (method === "throw") {
                    throw arg;
                }

                // Be forgiving, per 25.3.3.3.3 of the spec:
                // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume
                return doneResult();
            }

            // 设置上下文的数据 用户调用的方法 以及 参数
            context.method = method;
            context.arg = arg;

            // 状态机迭代开始啦
            while (true) {

                // 先只实现next吧
                if (context.method === "next") {
                    // Setting context._sent for legacy support of Babel's
                    // function.sent implementation.
                    // next方法是可以接受用户传入的参数噢
                    context.sent = context._sent = context.arg;

                } else if (context.method === "throw") {
                    // throw新增:
                    // 实例调用throw之前必须next一次,不然状态无法变成 暂停态或者停止态
                    if (state === GenStateSuspendedStart) {
                        state = GenStateCompleted;
                        throw context.arg;
                    }

                    // 触发错误捕获事件
                    context.dispatchException(context.arg);
                } else if (context.method === "return") {
                    // 存在try语句块的情况下 调用return语句会导致代码块的执行顺序发生改变 具体变化分析见 abrupt 代码的变化
                    context.abrupt("return", context.arg);
                }

                // 进入业务代码执行的状态啦
                state = GenStateExecuting;

                // 一次执行结果的记录 context 就是最开始我们看到的上下文了 就是在这里调用我们的业务代码函数了
                // 在完成态自动过滤到结束态的时候会自动化迭代2次 注意最后次的业务代码中调用了 _context.stop();
                var record = tryCatch(innerFn, self, context);
                // 正常返回值
                if (record.type === "normal") {
                    // If an exception is thrown from innerFn, we leave state ===
                    // GenStateExecuting and loop back for another invocation.
                    // 更新状态 完成态 || 暂停态
                    state = context.done
                        ? GenStateCompleted
                        : GenStateSuspendedYield;

                    // 完成态切换到结束态 需要继续迭代状态机
                    if (record.arg === ContinueSentinel) {
                        continue;
                    }

                    // 这就是我们预期的返回值拉
                    return {
                        value: record.arg,
                        done: context.done
                    };

                    // 错误返回值 状态机继续执行 通过上面的 throw分支代码来实现
                } else if (record.type === "throw") {
                    // 需要抛出错误 迭代终止
                    state = GenStateCompleted;
                    // Dispatch the exception by looping back around to the
                    // context.dispatchException(context.arg) call above.
                    context.method = "throw";
                    context.arg = record.arg;
                }
            }
        };
    }

    // 执行业务代码 对返回结果做了包装处理
    function tryCatch(fn, obj, arg) {
        try {
            return { type: "normal", arg: fn.call(obj, arg) };
        } catch (err) {
            return { type: "throw", arg: err };
        }
    }

    // 返回一个迭代器对象
    function values(iterable) {
        if (iterable) {
            var iteratorMethod = iterable[iteratorSymbol];
            // 原生部署了迭代器生成函数 调用得到一个迭代器即可
            if (iteratorMethod) {
                return iteratorMethod.call(iterable);
            }

            // next是个函数的对象 也算吧
            if (typeof iterable.next === "function") {
                return iterable;
            }

            // 具有长度属性的类数组对象 也算吧
            if (!isNaN(iterable.length)) {
                var i = -1, next = function next() {
                    while (++i < iterable.length) {
                        if (hasOwn.call(iterable, i)) {
                            next.value = iterable[i];
                            next.done = false;
                            return next;
                        }
                    }

                    next.value = undefined;
                    next.done = true;

                    return next;
                };

                // 注意返回的这个next 函数 next.next 才是执行的函数 且持有自由变量 i 用来迭代状态
                return next.next = next;
            }
        }

        // Return an iterator with no values.
        return { next: doneResult };
    }

    exports.values = values;

    function doneResult() {
        return { value: undefined, done: true };
    }

    // async 异步新增部分:

    // 先看下这几个有意思的参数 第一个就是我们被改写的业务函数了 第二个这里为null 第三个 null 第四个 没有try 也是 null 第五个 就是 Promise构造函数了
    exports.async = function (innerFn, outerFn, self, tryLocsList, PromiseImpl) {
        if (PromiseImpl === void 0) PromiseImpl = Promise;

        // 构造了一个异步迭代器实例 2个 参数 第一个是我们之前实现了的 wrap 构造了一个同步迭代器实例, 第二个是 Promise构造函数
        var iter = new AsyncIterator(
            wrap(innerFn, outerFn, self, tryLocsList),
            PromiseImpl
        );

        // 启动异步迭代器 这里outerFn是null哦 iter.next() 整一个promise实例出来
        return exports.isGeneratorFunction(outerFn)
            ? iter // If outerFn is a generator, return the full iterator.
            : iter.next().then(function (result) {
                // 注意最后这个 iter.next() 这个then是加在promise最后的then
                return result.done ? result.value : iter.next();
            });
    };

    // 是否是生成器函数
    exports.isGeneratorFunction = function (genFun) {
        var ctor = typeof genFun === "function" && genFun.constructor;
        return ctor
            ? ctor === GeneratorFunction ||
            // For the native GeneratorFunction constructor, the best we can
            // do is to check its .name property.
            (ctor.displayName || ctor.name) === "GeneratorFunction"
            : false;
    };

    // Within the body of any async function, `await x` is transformed to
    // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test
    // `hasOwn.call(value, "__await")` to determine if the yielded value is
    // meant to be awaited.

    // 对于异步包裹的处理就是打上一个特殊的await标记呢
    exports.awrap = function (arg) {
        return { __await: arg };
    };

    // 异步迭代器构造函数
    function AsyncIterator(generator, PromiseImpl) {
        function invoke(method, arg, resolve, reject) {
            // 执行业务代码 generator[method]
            var record = tryCatch(generator[method], generator, arg);
            // 业务代码直接报错了
            if (record.type === "throw") {
                reject(record.arg);
            } else {
                var result = record.arg;
                var value = result.value;
                if (value &&
                    typeof value === "object" &&
                    hasOwn.call(value, "__await")) {
                    // 需要等待
                    // promise状态变更后 再次触发invoke即可 继续出发下一个await
                    return PromiseImpl.resolve(value.__await).then(function (value) {
                        invoke("next", value, resolve, reject);
                    }, function (err) {
                        invoke("throw", err, resolve, reject);
                    });
                }

                // 所有的await存在都结束了
                return PromiseImpl.resolve(value).then(function (unwrapped) {
                    // When a yielded Promise is resolved, its final value becomes
                    // the .value of the Promise<{value,done}> result for the
                    // current iteration.
                    result.value = unwrapped;
                    resolve(result);
                }, function (error) {
                    // If a rejected Promise was yielded, throw the rejection back
                    // into the async generator function so it can be handled there.
                    return invoke("throw", error, resolve, reject);
                });
            }
        }

        var previousPromise;

        // 入队列 promise链
        function enqueue(method, arg) {
            function callInvokeWithMethodAndArg() {
                return new PromiseImpl(function (resolve, reject) {
                    invoke(method, arg, resolve, reject);
                });
            }

            return previousPromise =
                // If enqueue has been called before, then we want to wait until
                // all previous Promises have been resolved before calling invoke,
                // so that results are always delivered in the correct order. If
                // enqueue has not been called before, then it is important to
                // call invoke immediately, without waiting on a callback to fire,
                // so that the async generator function has the opportunity to do
                // any necessary setup in a predictable way. This predictability
                // is why the Promise constructor synchronously invokes its
                // executor callback, and why async functions synchronously
                // execute code before the first await. Since we implement simple
                // async functions in terms of async generators, it is especially
                // important to get this right, even though it requires care.
                previousPromise ? previousPromise.then(
                    callInvokeWithMethodAndArg,
                    // Avoid propagating failures to Promises returned by later
                    // invocations of the iterator.
                    callInvokeWithMethodAndArg
                ) : callInvokeWithMethodAndArg();
        }

        // Define the unified helper method that is used to implement .next,
        // .throw, and .return (see defineIteratorMethods).
        // 跟同步迭代器的接口相同 不过实现不一样了
        this._invoke = enqueue;
    }

    // 也给原型上加上next等3个方法
    defineIteratorMethods(AsyncIterator.prototype);
    AsyncIterator.prototype[asyncIteratorSymbol] = function () {
        return this;
    };
    exports.AsyncIterator = AsyncIterator;

    // 返回一个对象
    return exports;
})({});

// 转换后如下:

// 箭头函数其实就是变成了普通的函数
var fetchData = function (data) {
    return new Promise(function (resolve) {
        return setTimeout(resolve, 1000, data + 1);
    });
};

var fetchValue = function _callee() {
    var value1, value2, value3;
    return regeneratorRuntime.async(function _callee$(_context) {
        while (1) {
            switch (_context.prev = _context.next) {
                case 0:
                    _context.next = 2;
                    return regeneratorRuntime.awrap(fetchData(1));

                case 2:
                    value1 = _context.sent;
                    _context.next = 5;
                    return regeneratorRuntime.awrap(fetchData(value1));

                case 5:
                    value2 = _context.sent;
                    _context.next = 8;
                    return regeneratorRuntime.awrap(fetchData(value2));

                case 8:
                    value3 = _context.sent;
                    console.log(value3);

                case 10:
                case "end":
                    return _context.stop();
            }
        }
    }, null, null, null, Promise);
};

// fetchValue();  // 3秒后输出 4

// 我们之前的迭代器 其实不支持 异步操作,现在 async await 需要我们来实现啦,看下如何通过迭代器模拟 async 函数:)

// 经过上面的异步迭代器的代码后 我们可以发下 await 其实是对结果做了 Promise包裹处理 而 新的异步迭代器也重写了对返回值的处理,形成了一个promise链的调用,一直到里面存在 
// 多个 await 全部执行完 迭代器才会停止掉

// 从新增的异步代码中可以看到,对await的处理是做了异步标识标记,结果用promise包裹,then中添加启动下一行的invoke代码,由此构成自动执行完下面的代码逻辑

// 然后我们再看下 下面这个情况。。。

/*
    // async 函数 for of 自动继发 await

    const fetchData = (data) => new Promise((resolve) => setTimeout(resolve, 1000, data))

    const fetchValue = async function () {
        for(let i=0;i<3;++i) {
            let res = await fetchData(i);
            console.log(res);
        }
    };

    fetchValue(); // 每嗝一秒输出 0 1 2
*/

var fetchData = function (data) {
    return new Promise(function (resolve) {
        return setTimeout(resolve, 1000, data);
    });
};

var fetchValue = function _callee() {
    var i, res;
    return regeneratorRuntime.async(function _callee$(_context) {
        while (1) {
            switch (_context.prev = _context.next) {
                case 0:
                    i = 0;

                case 1:
                    if (!(i < 3)) {
                        _context.next = 9;
                        break;
                    }

                    _context.next = 4;
                    return regeneratorRuntime.awrap(fetchData(i));

                case 4:
                    res = _context.sent;
                    console.log(res);

                case 6:
                    ++i;
                    _context.next = 1;
                    break;

                case 9:
                case "end":
                    return _context.stop();
            }
        }
    }, null, null, null, Promise);
};

// fetchValue(); // 每嗝一秒输出 0 1 2

// 直接把for循环改写成了状态机... 666

// 再看下这个

/*
    // async 函数 for of 自动并发 await

    const fetchData = (data) => new Promise((resolve) => setTimeout(resolve, 1000*(data), data))

    const fetchValue = async function () {
        let ps =([fetchData(1), fetchData(2), fetchData(3)]);
        for(let i=0;i<3;++i) {
            let res = await ps[i];
            console.log(res);
        }
    };

    fetchValue(); // 每嗝一秒输出 1 2 3
*/

// 转译如下:

var fetchData = function (data) {
    return new Promise(function (resolve) {
        return setTimeout(resolve, 1000 * data, data);
    });
};

var fetchValue = function _callee() {
    var ps, i, res;
    return regeneratorRuntime.async(function _callee$(_context) {
        while (1) {
            switch (_context.prev = _context.next) {
                case 0:
                    ps = [fetchData(1), fetchData(2), fetchData(3)];
                    i = 0;

                case 2:
                    if (!(i < 3)) {
                        _context.next = 10;
                        break;
                    }

                    _context.next = 5;
                    return regeneratorRuntime.awrap(ps[i]);

                case 5:
                    res = _context.sent;
                    console.log(res);

                case 7:
                    ++i;
                    _context.next = 2;
                    break;

                case 10:
                case "end":
                    return _context.stop();
            }
        }
    }, null, null, null, Promise);
};

// fetchValue(); // 每嗝一秒输出 0 1 2

// 大家可以发现 并发和继发的代码很类似其实 都是一个个等promise结束 只不过 继续是 一个完成了再触发下一个promise 并发则是 几个promsie先触发,再尝试去等待第一个promise结束,结束后再去等待下一个,并发的时候几个promise状态时同时在发生改变的,所以可以并发。

// 最后的最后 看下 for await of 和 异步生成器

/*
    async function* asyncGenerator() {
        var i = 0;
        while (i < 3) {
            yield i++;
        }
    }

    (async function() {
        for await (num of asyncGenerator()) {
            console.log(num);
        }
    })(); // 依次异步输出 0 1 2
*/

// 然而很可惜 regenrator 无法支持对 异步遍历器的转码

// 不过我们可以自己尝试着写一个哈 因为是 异步串行顺序输出 所以直接照搬之前的结果就好了 :)

var fetchData = function (data) {
    return Promise.resolve(data);
};

var fetchValue = function _callee() {
    var i, res;
    return regeneratorRuntime.async(function _callee$(_context) {
        while (1) {
            switch (_context.prev = _context.next) {
                case 0:
                    i = 0;

                case 1:
                    if (!(i < 3)) {
                        _context.next = 9;
                        break;
                    }

                    _context.next = 4;
                    return regeneratorRuntime.awrap(fetchData(i));

                case 4:
                    res = _context.sent;
                    console.log(res);

                case 6:
                    ++i;
                    _context.next = 1;
                    break;

                case 9:
                case "end":
                    return _context.stop();
            }
        }
    }, null, null, null, Promise);
};

fetchValue(); // 依次异步输出 0 1 2

Vue(v3.0.11)源码简析之effect相关实现

effect(fn, options)方法用来创建响应式函数,当fn执行的时候,会在代码中触发被观察对象的get,从而引起依赖收集,收集的对象就是我们当前调用的effect(fn, options)返回的effect函数,当被观察者对象被修改后,trigger负责再次触发effect重新执行,而在vue中,创建effect时候的fn往往会执行组件的render函数,得到新的vnode对象,然后再借助patch更新dom视图。
接下来看下具体的代码实现:

// 用来存放所有响应式对象的 key:被观察者 -> value:观察者 的存储仓库
// key只能是对象object value是一个Map结构 Map以用户要观察的对象的key作为key 
// 而对应的value则是一个 Set结构 存放在它发生改变后执行的多个 响应式函数 effect
const targetMap = new WeakMap();
// effect在执行过程中 由于存在组件渲染 会再次生成render函数从而产生新的effect函数 并调用 由此形成effect栈
const effectStack = [];
// 当前正在执行的effect函数 执行过程中 响应式被观察的变量触发get的时候 会把当前effect加入自己的观察者set中 称之为 依赖收集
let activeEffect;
// 在依赖收集的过程中 一些方法触发了被观察对象的遍历操作 那么当对象发生改变的时候 这个遍历操作的结果也有可能被改变 因此 需要我们再次触发effect更新最新的结果
// 这个key值就是用来记录遍历操作的
const ITERATE_KEY = Symbol('iterate');
// 对于Map结构 独有的 .keys() 我们也收集有触发它的操作 当我们从map中移除或者添加新元素的时候 之前有依赖这个遍历key操作的结果 也会发生改变 我们就需要再次触发effect去更新结果
// 这个key值就是用来收集keys操作的
const MAP_KEY_ITERATE_KEY = Symbol('Map key iterate');
// 标识fn是否是effect函数
function isEffect(fn) {
    return fn && fn._isEffect === true;
}
// 外部调用入口 根据fn和配置项 返回一个effect函数
function effect(fn, options = EMPTY_OBJ) {
    if (isEffect(fn)) {
        fn = fn.raw;
    }
    // 内部调用 createReactiveEffect
    const effect = createReactiveEffect(fn, options);
    // 立即执行一次 触发依赖收集
    if (!options.lazy) {
        effect();
    }
    return effect;
}
// 停止一个effect 也就是当被观察对象改变后 也不再触发这个effect方法了
function stop(effect) {
    if (effect.active) {
        // 清理
        cleanup(effect);
        // stop钩子
        if (effect.options.onStop) {
            effect.options.onStop();
        }
        effect.active = false;
    }
}
// 实际实现
let uid = 0;
function createReactiveEffect(fn, options) {
    const effect = function reactiveEffect() {
        // scheduler 代表接受vue的队列调度
        // 不存在 scheduler 意味着再次触发这个 effect 还可以执行 fn 但是由于effect已经从被观察者的set中被移除了 所以自动触发不会 只能手动触发了
        if (!effect.active) {
            return options.scheduler ? undefined : fn();
        }
        // 防止无休递归调用自己
        if (!effectStack.includes(effect)) {
            // 每次执行之前 清理一次
            cleanup(effect);
            try {
                // 开启track标志 可以触发依赖收集了接下来
                enableTracking();
                // 入栈
                effectStack.push(effect);
                // activeEffect置位 可以被收集了
                activeEffect = effect;
                // 执行 fn 其中的代码会触发被观察遍历的get操作
                // 注意这个return的执行时机
                // fn() 方法会执行 但是它的结果 并不马上通过return返回给effect的调用者
                // 而是会在finally中的代码块执行结束后 看finally中有return回来的结果不 有的话用这个结果覆盖我们fn的结果
                // 没有再直接返回fn的结果
                // 这个顺序是真的容易搞错
                return fn();
            }
            finally {
                // 出栈
                effectStack.pop();
                // 恢复
                resetTracking();
                // 恢复上一个值
                activeEffect = effectStack[effectStack.length - 1];
            }
        }
    };
    // id
    effect.id = uid++;
    // 允许递归调用自己
    effect.allowRecurse = !!options.allowRecurse;
    effect._isEffect = true;
    effect.active = true;
    effect.raw = fn;
    // 当前effect被哪些 被观察者的set 所持有 方面在停止effect的时候 直接从set中移除effect
    effect.deps = [];
    effect.options = options;
    return effect;
}
// 清理effect 从被观察者的 观察者集合中移除这个观察者
function cleanup(effect) {
    const { deps } = effect;
    if (deps.length) {
        for (let i = 0; i < deps.length; i++) {
            deps[i].delete(effect);
        }
        deps.length = 0;
    }
}
// 开启 停止 依赖收集的功能标志位
let shouldTrack = true;
// effect存在调用栈 因此 对应的这个也存在同样的场景
const trackStack = [];
// 暂停
function pauseTracking() {
    trackStack.push(shouldTrack);
    shouldTrack = false;
}
// 开启
function enableTracking() {
    trackStack.push(shouldTrack);
    shouldTrack = true;
}
// 恢复
function resetTracking() {
    const last = trackStack.pop();
    // 默认是true 同上面的初始化 如果是栈的最底层了
    shouldTrack = last === undefined ? true : last;
}
// 每个被响应式对象的某个key的值或者自身被触发 get 然后在代理hanlder都存在的track追踪方法
function track(target, type, key) {
    // 关闭追踪 或者 无有效effect观察者函数
    if (!shouldTrack || activeEffect === undefined) {
        return;
    }
    // 统一从 targetMap 根据待观察对象 target 取 map
    let depsMap = targetMap.get(target);
    if (!depsMap) {
        targetMap.set(target, (depsMap = new Map()));
    }
    // 根据 key 从map 中取 set
    let dep = depsMap.get(key);
    if (!dep) {
        depsMap.set(key, (dep = new Set()));
    }
    // 添加新的观察者到自己的set中 同时 观察者effect的deps被观察者集合也持有 当前set
    if (!dep.has(activeEffect)) {
        dep.add(activeEffect);
        activeEffect.deps.push(dep);
        // onTrack 钩子
        if (activeEffect.options.onTrack) {
            activeEffect.options.onTrack({
                effect: activeEffect,
                target,
                type,
                key
            });
        }
    }
}
// 对应track 当我们改变了被观察者对象的某个key的值 或者自身的情况 handler对set做了拦截处理 其中就有trigger的调用
// 6个参数 前5个比较好理解 target比较好理解 被改变的对象 而trigger方法的触发其实在对target对象的修改生效之后 
// 这意味着 如 map.clear() 这样的操作的话 target集合在此时已经是空的了 而 oldTarget则是我们拷贝的一份旧的集合数据
function trigger(target, type, key, newValue, oldValue, oldTarget) {
    // 取出map
    const depsMap = targetMap.get(target);
    if (!depsMap) {
        // never been tracked
        return;
    }
    // 所有待执行的effect集合
    const effects = new Set();
    // 以set集合为单位添加
    const add = (effectsToAdd) => {
        if (effectsToAdd) {
            effectsToAdd.forEach(effect => {
                // 非 activeEffect 或者 允许activeEffect递归调用自己
                if (effect !== activeEffect || effect.allowRecurse) {
                    effects.add(effect);
                }
            });
        }
    };
    // 清空类型的修改 那之前target对象对应的map中所有的key对应的set的观察者都需要更新
    if (type === "clear" /* CLEAR */) {
        // collection being cleared
        // trigger all effects for target
        depsMap.forEach(add);
    }
    // 对于数组对象而言 长度类型的改变事件
    else if (key === 'length' && isArray(target)) {
        depsMap.forEach((dep, key) => {
            // 一个是我们原本额外 对数组对象的一些可以引起数组长度发生改变的操作 如push splice等 而设置的length类型事件
            // 还有原本数组的key 索引在新的length newValue长度之后 意味着 有元素被删除了
            if (key === 'length' || key >= newValue) {
                add(dep);
            }
        });
    }
    else {
        // schedule runs for SET | ADD | DELETE
        // 修改 添加 删除 3类普通操作
        if (key !== void 0) {
            add(depsMap.get(key));
        }
        // also run for iteration key on ADD | DELETE | Map.SET
        switch (type) {
            case "add" /* ADD */:
                // map和set才有的add
                if (!isArray(target)) {
                    // 触发 遍历事件的观察者
                    add(depsMap.get(ITERATE_KEY));
                    // 触发map独有的keys的观察者
                    if (isMap(target)) {
                        add(depsMap.get(MAP_KEY_ITERATE_KEY));
                    }
                }
                // 数组对象的add 我们触发长度事件的观察者
                else if (isIntegerKey(key)) {
                    // new index added to array -> length changes
                    add(depsMap.get('length'));
                }
                break;
            case "delete" /* DELETE */:
                // map和set的delete
                if (!isArray(target)) {
                    // 同上
                    add(depsMap.get(ITERATE_KEY));
                    // 同上
                    if (isMap(target)) {
                        add(depsMap.get(MAP_KEY_ITERATE_KEY));
                    }
                }
                break;
            case "set" /* SET */:
                // map独有的set
                if (isMap(target)) {
                    add(depsMap.get(ITERATE_KEY));
                }
                break;
        }
    }
    // 执行方法的实现
    const run = (effect) => {
        // 配置项中的 onTrigger 钩子
        if (effect.options.onTrigger) {
            effect.options.onTrigger({
                effect,
                target,
                key,
                type,
                newValue,
                oldValue,
                oldTarget
            });
        }
        // 接受vue队列调度的effect 按调度方法走
        if (effect.options.scheduler) {
            effect.options.scheduler(effect);
        }
        // 否则就直接执行就好了
        else {
            effect();
        }
    };
    // 逐一执行
    effects.forEach(run);
}

Vue(v3.0.11)源码简析之vnode的相关实现

虚拟DOM(VDOM)用来抽象实际的DOM结构,我们可以把一些信息存储在VDOM中,然后更新过程借助它来完成对比更新,是一种很方便的抽象,同时vue也加了block块的概念,增加动态节点的收集功能,提供只更新动态节点的优化功能,看下vue中是如何使用vnode的:

// 几个vnode的类型
const Fragment = Symbol('Fragment' );
const Text = Symbol('Text' );
const Comment = Symbol('Comment' );
const Static = Symbol('Static' );
// Since v-if and v-for are the two possible ways node structure can dynamically
// change, once we consider v-if branches and each v-for fragment a block, we
// can divide a template into nested blocks, and within each block the node
// structure would be stable. This allows us to skip most children diffing
// and only worry about the dynamic nodes (indicated by patch flags).
// 在单个组件的render过程中会遇到子节点也需要进行block收集的情况 这时候会形成block调用栈
const blockStack = [];
let currentBlock = null;
/**
 * Open a block.
 * This must be called before `createBlock`. It cannot be part of `createBlock`
 * because the children of the block are evaluated before `createBlock` itself
 * is called. The generated code typically looks like this:
 *
 * ```js
 * function render() {
 *   return (openBlock(),createBlock('div', null, [...]))
 * }
 * ```
 * disableTracking is true when creating a v-for fragment block, since a v-for
 * fragment always diffs its children.
 *
 * @private
 */
// 开启一个新block 准备收集动态节点
function openBlock(disableTracking = false) {
    blockStack.push((currentBlock = disableTracking ? null : []));
}
// 当前需要收集的block收集结束
function closeBlock() {
    blockStack.pop();
    currentBlock = blockStack[blockStack.length - 1] || null;
}
// Whether we should be tracking dynamic child nodes inside a block.
// Only tracks when this value is > 0
// We are not using a simple boolean because this value may need to be
// incremented/decremented by nested usage of v-once (see below)
let shouldTrack$1 = 1;
/**
 * Block tracking sometimes needs to be disabled, for example during the
 * creation of a tree that needs to be cached by v-once. The compiler generates
 * code like this:
 *
 * ``` js
 * _cache[1] || (
 *   setBlockTracking(-1),
 *   _cache[1] = createVNode(...),
 *   setBlockTracking(1),
 *   _cache[1]
 * )
 * ```
 *
 * @private
 */
// 控制是否收集动态节点 主要给缓存场景 v-once用的
function setBlockTracking(value) {
    shouldTrack$1 += value;
}
/**
 * Create a block root vnode. Takes the same exact arguments as `createVNode`.
 * A block root keeps track of dynamic nodes within the block in the
 * `dynamicChildren` array.
 *
 * @private
 */
// 和openBlock 配合使用 其实主要是createVnode而已 只是比普通的vnode多个了dynamicChildren用来收集当前block范围内所有的动态节点
// 只有这个块所有的根节点才有这个属性
function createBlock(type, props, children, patchFlag, dynamicProps) {
    const vnode = createVNode(type, props, children, patchFlag, dynamicProps, true /* isBlock: prevent a block from tracking itself */);
    // save current block children on the block vnode
    // render函数被执行的时候 作为参数的子节点列表是会先执行的 等到外层的createVNode执行后,所有的子节点如果是动态节点的话 都已经被收集到 currentBlock 中了
    vnode.dynamicChildren = currentBlock || EMPTY_ARR;
    // close block
    closeBlock();
    // a block is always going to be patched, so track it as a child of its
    // parent block
    // block总是要被更新 算动态节点 把自己也放进去
    if (shouldTrack$1 > 0 && currentBlock) {
        currentBlock.push(vnode);
    }
    // 也是返回一个vnode
    return vnode;
}
// 是vnode节点
function isVNode(value) {
    return value ? value.__v_isVNode === true : false;
}
// 同类型vnode
function isSameVNodeType(n1, n2) {
    if (n2.shapeFlag & 6 /* COMPONENT */ &&
        hmrDirtyComponents.has(n2.type)) {
        // HMR only: if the component has been hot-updated, force a reload.
        return false;
    }
    // 关注这里就好了 type 相同 字符串相等 或者 对象等于 key 存在且相等 或者同时不存在
    return n1.type === n2.type && n1.key === n2.key;
}
let vnodeArgsTransformer;
/**
 * Internal API for registering an arguments transform for createVNode
 * used for creating stubs in the test-utils
 * It is *internal* but needs to be exposed for test-utils to pick up proper
 * typings
 */
// 参数转化函数注册方法
function transformVNodeArgs(transformer) {
    vnodeArgsTransformer = transformer;
}
// 只考虑_createVNode的情况就好了
const createVNodeWithArgsTransform = (...args) => {
    return _createVNode(...(vnodeArgsTransformer
        ? vnodeArgsTransformer(args, currentRenderingInstance)
        : args));
};
const InternalObjectKey = `__vInternal`;
// 无效值 都是null 利用 != 弱判断
const normalizeKey = ({ key }) => key != null ? key : null;
// 格式化ref 注意 ref算是props中的一种 currentRenderingInstance 是我们在哪些的ref所属的vnode的所处的组件实例
const normalizeRef = ({ ref }) => {
    return (ref != null
        ? isString(ref) || isRef(ref) || isFunction(ref)
            ? { i: currentRenderingInstance, r: ref }
            : ref
        : null);
};
// 我们就认为是普通的默认处理旧可以了
const createVNode = (createVNodeWithArgsTransform
    );
// 实际vnode创建方法
function _createVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, isBlockNode = false) {
    // 注释占位符
    if (!type || type === NULL_DYNAMIC_COMPONENT) {
        if (!type) {
            warn(`Invalid vnode type when creating vnode: ${type}.`);
        }
        type = Comment;
    }
    // 如注释所言的情况 克隆新节点
    if (isVNode(type)) {
        // createVNode receiving an existing vnode. This happens in cases like
        // <component :is="vnode"/>
        // #2078 make sure to merge refs during the clone instead of overwriting it
        const cloned = cloneVNode(type, props, true /* mergeRef: true */);
        if (children) {
            // 格式化子节点 并同步到新节点上
            normalizeChildren(cloned, children);
        }
        return cloned;
    }
    // 函数式组件
    // class component normalization.
    if (isClassComponent(type)) {
        type = type.__vccOpts;
    }
    // class & style normalization.
    if (props) {
        // 这些值从组件实例的proxy this取的时候 很有可能是个代理对象的
        // class和style做下格式化处理
        // for reactive or proxy objects, we need to clone it to enable mutation.
        if (isProxy(props) || InternalObjectKey in props) {
            props = extend({}, props);
        }
        let { class: klass, style } = props;
        if (klass && !isString(klass)) {
            props.class = normalizeClass(klass);
        }
        if (isObject(style)) {
            // reactive state objects need to be cloned since they are likely to be
            // mutated
            if (isProxy(style) && !isArray(style)) {
                style = extend({}, style);
            }
            props.style = normalizeStyle(style);
        }
    }
    // encode the vnode type information into a bitmap
    // 标记vnode基础类型
    const shapeFlag = isString(type)
        ? 1 /* ELEMENT */
        : isSuspense(type)
            ? 128 /* SUSPENSE */
            : isTeleport(type)
                ? 64 /* TELEPORT */
                : isObject(type)
                    ? 4 /* STATEFUL_COMPONENT */
                    : isFunction(type)
                        ? 2 /* FUNCTIONAL_COMPONENT */
                        : 0;
    if (shapeFlag & 4 /* STATEFUL_COMPONENT */ && isProxy(type)) {
        type = toRaw(type);
        warn(`Vue received a Component which was made a reactive object. This can ` +
            `lead to unnecessary performance overhead, and should be avoided by ` +
            `marking the component with \`markRaw\` or using \`shallowRef\` ` +
            `instead of \`ref\`.`, `\nComponent that was made reactive: `, type);
    }
    // 构建vnode
    const vnode = {
        // 标记自己类型
        __v_isVNode: true,
        // 不可响应式代理
        ["__v_skip" /* SKIP */]: true,
        // 基础类型
        type,
        // 用户传入的props
        props,
        // 格式化下面2个值形式
        key: props && normalizeKey(props),
        ref: props && normalizeRef(props),
        // 如果存在设置了的 scopeid
        scopeId: currentScopeId,
        // TODO
        slotScopeIds: null,
        // 子节点数组
        children: null,
        // 如果是组件vnode 则是组件实例
        component: null,
        // 父链上的suspense控制对象
        suspense: null,
        // suspense的2个待渲染内容vnode
        ssContent: null,
        ssFallback: null,
        // 指令集合
        dirs: null,
        // transition组件下的vnode继承这个
        transition: null,
        // 宿主dom
        el: null,
        // 插入dom时参考的锚点元素
        anchor: null,
        // TODO
        target: null,
        targetAnchor: null,
        // 静态节点统计
        staticCount: 0,
        // 类型复制flag 区分元素的细分
        shapeFlag,
        // patch的是赋值flag 优化对比
        patchFlag,
        // 动态props的key集合
        dynamicProps,
        // 动态子节点集合
        dynamicChildren: null,
        // app上下文
        appContext: null
    };
    // validate key
    if (vnode.key !== vnode.key) {
        warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type);
    }
    // 格式化子节点
    normalizeChildren(vnode, children);
    // normalize suspense children
    // 对于 suspense 而言 2个插槽内容提前解析 赋值
    if (shapeFlag & 128 /* SUSPENSE */) {
        const { content, fallback } = normalizeSuspenseChildren(vnode);
        vnode.ssContent = content;
        vnode.ssFallback = fallback;
    }
    // 什么样的vnode节点要被收集到存在的block中呢?
    // 1. 目前处于可收集状态 2. 自己不是这个block的根节点 3.当前块存在 4.patchflag指明这个内容需要动态更新
    if (shouldTrack$1 > 0 &&
        // avoid a block node from tracking itself
        !isBlockNode &&
        // has current parent block
        currentBlock &&
        // presence of a patch flag indicates this node needs patching on updates.
        // component nodes also should always be patched, because even if the
        // component doesn't need to update, it needs to persist the instance on to
        // the next vnode so that it can be properly unmounted later.
        (patchFlag > 0 || shapeFlag & 6 /* COMPONENT */) &&
        // the EVENTS flag is only for hydration and if it is the only flag, the
        // vnode should not be considered dynamic due to handler caching.
        patchFlag !== 32 /* HYDRATE_EVENTS */) {
        // 收集起来
        currentBlock.push(vnode);
    }
    // 返回vnode
    return vnode;
}
// 克隆vnode
function cloneVNode(vnode, extraProps, mergeRef = false) {
    // This is intentionally NOT using spread or extend to avoid the runtime
    // key enumeration cost.
    const { props, ref, patchFlag, children } = vnode;
    // 合并待合并的props得到新props
    const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props;
    // 返回个新对象
    return {
        __v_isVNode: true,
        ["__v_skip" /* SKIP */]: true,
        type: vnode.type,
        props: mergedProps,
        key: mergedProps && normalizeKey(mergedProps),
        // 如注释所言 这种场景下合入refs中去
        ref: extraProps && extraProps.ref
            ? // #2078 in the case of <component :is="vnode" ref="extra"/>
                // if the vnode itself already has a ref, cloneVNode will need to merge
                // the refs so the single vnode can be set on multiple refs
                mergeRef && ref
                    ? isArray(ref)
                        ? ref.concat(normalizeRef(extraProps))
                        : [ref, normalizeRef(extraProps)]
                    : normalizeRef(extraProps)
            : ref,
        scopeId: vnode.scopeId,
        slotScopeIds: vnode.slotScopeIds,
        // 特定场景下才深度克隆子vnode
        children: patchFlag === -1 /* HOISTED */ && isArray(children)
            ? children.map(deepCloneVNode)
            : children,
        target: vnode.target,
        targetAnchor: vnode.targetAnchor,
        staticCount: vnode.staticCount,
        shapeFlag: vnode.shapeFlag,
        // if the vnode is cloned with extra props, we can no longer assume its
        // existing patch flag to be reliable and need to add the FULL_PROPS flag.
        // note: perserve flag for fragments since they use the flag for children
        // fast paths only.
        // 如注释所言场景下增加full props flag
        patchFlag: extraProps && vnode.type !== Fragment
            ? patchFlag === -1 // hoisted node
                ? 16 /* FULL_PROPS */
                : patchFlag | 16 /* FULL_PROPS */
            : patchFlag,
        dynamicProps: vnode.dynamicProps,
        dynamicChildren: vnode.dynamicChildren,
        appContext: vnode.appContext,
        dirs: vnode.dirs,
        transition: vnode.transition,
        // These should technically only be non-null on mounted VNodes. However,
        // they *should* be copied for kept-alive vnodes. So we just always copy
        // them since them being non-null during a mount doesn't affect the logic as
        // they will simply be overwritten.
        component: vnode.component,
        suspense: vnode.suspense,
        // 也要克隆
        ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
        ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
        el: vnode.el,
        anchor: vnode.anchor
    };
}
/**
 * Dev only, for HMR of hoisted vnodes reused in v-for
 * https://github.com/vitejs/vite/issues/2022
 */
// 深度递归克隆
function deepCloneVNode(vnode) {
    const cloned = cloneVNode(vnode);
    if (isArray(vnode.children)) {
        cloned.children = vnode.children.map(deepCloneVNode);
    }
    return cloned;
}
/**
 * @private
 */
// text node 无子节点 只有text作为文字内容
function createTextVNode(text = ' ', flag = 0) {
    return createVNode(Text, null, text, flag);
}
/**
 * @private
 */
// 一段html内容的节点 v-html产生
function createStaticVNode(content, numberOfNodes) {
    // A static vnode can contain multiple stringified elements, and the number
    // of elements is necessary for hydration.
    const vnode = createVNode(Static, null, content);
    vnode.staticCount = numberOfNodes;
    return vnode;
}
/**
 * @private
 */
// 注释节点 注意 v-else 的情况
function createCommentVNode(text = '', 
// when used as the v-else branch, the comment node must be created as a
// block to ensure correct updates.
asBlock = false) {
    return asBlock
        ? (openBlock(), createBlock(Comment, null, text))
        : createVNode(Comment, null, text);
}
// 格式化子vnode数组 保证返回的总是一个某种种类的vnode
function normalizeVNode(child) {
    if (child == null || typeof child === 'boolean') {
        // empty placeholder
        return createVNode(Comment);
    }
    else if (isArray(child)) {
        // fragment
        // 多个子节点 统一用 Fragment 包裹处理
        return createVNode(Fragment, null, child);
    }
    else if (typeof child === 'object') {
        // already vnode, this should be the most common since compiled templates
        // always produce all-vnode children arrays
        // 为挂载过的vnode直接返回即可
        return child.el === null ? child : cloneVNode(child);
    }
    else {
        // strings and numbers
        return createVNode(Text, null, String(child));
    }
}
// optimized normalization for template-compiled render fns
function cloneIfMounted(child) {
    return child.el === null ? child : cloneVNode(child);
}
// 格式化子vnode数组格式 同时更新vnode的flag
function normalizeChildren(vnode, children) {
    let type = 0;
    const { shapeFlag } = vnode;
    if (children == null) {
        children = null;
    }
    else if (isArray(children)) {
        type = 16 /* ARRAY_CHILDREN */;
    }
    // 插槽
    else if (typeof children === 'object') {
        if (shapeFlag & 1 /* ELEMENT */ || shapeFlag & 64 /* TELEPORT */) {
            // Normalize slot to plain children for plain element and Teleport
            // 上述情况 如果存在唯一的的插槽内容
            const slot = children.default;
            if (slot) {
                // _c marker is added by withCtx() indicating this is a compiled slot
                slot._c && setCompiledSlotRendering(1);
                // 运行得到slot对应的子vnode
                normalizeChildren(vnode, slot());
                slot._c && setCompiledSlotRendering(-1);
            }
            return;
        }
        // 其他组件的情况
        else {
            type = 32 /* SLOTS_CHILDREN */;
            const slotFlag = children._;
            if (!slotFlag && !(InternalObjectKey in children)) {
                // 绑定slot在子组件中运行时正确需要的当前组件实例
                children._ctx = currentRenderingInstance;
            }
            // 转发插槽的使用场景一直不清楚 TODO
            else if (slotFlag === 3 /* FORWARDED */ && currentRenderingInstance) {
                // a child component receives forwarded slots from the parent.
                // its slot type is determined by its parent's slot type.
                // 根据当前节点的情况来设置
                if (currentRenderingInstance.vnode.patchFlag & 1024 /* DYNAMIC_SLOTS */) {
                    children._ = 2 /* DYNAMIC */;
                    vnode.patchFlag |= 1024 /* DYNAMIC_SLOTS */;
                }
                else {
                    children._ = 1 /* STABLE */;
                }
            }
        }
    }
    // 也是插槽的处理方式
    else if (isFunction(children)) {
        children = { default: children, _ctx: currentRenderingInstance };
        type = 32 /* SLOTS_CHILDREN */;
    }
    else {
        children = String(children);
        // force teleport children to array so it can be moved around
        if (shapeFlag & 64 /* TELEPORT */) {
            type = 16 /* ARRAY_CHILDREN */;
            children = [createTextVNode(children)];
        }
        else {
            type = 8 /* TEXT_CHILDREN */;
        }
    }
    // 更新对应值
    vnode.children = children;
    vnode.shapeFlag |= type;
}
// 合并props对象 比较简单 分类合并就好了
function mergeProps(...args) {
    const ret = extend({}, args[0]);
    for (let i = 1; i < args.length; i++) {
        const toMerge = args[i];
        for (const key in toMerge) {
            if (key === 'class') {
                if (ret.class !== toMerge.class) {
                    ret.class = normalizeClass([ret.class, toMerge.class]);
                }
            }
            else if (key === 'style') {
                ret.style = normalizeStyle([ret.style, toMerge.style]);
            }
            else if (isOn(key)) {
                const existing = ret[key];
                const incoming = toMerge[key];
                if (existing !== incoming) {
                    ret[key] = existing
                        ? [].concat(existing, toMerge[key])
                        : incoming;
                }
            }
            else if (key !== '') {
                ret[key] = toMerge[key];
            }
        }
    }
    return ret;
}

简单聊一聊generator和async函数的模拟实现吧(4)

// 同步 generator 函数 for of 遍历自动执行
// (function() {
//     function* foo() {
//         yield 1;
//         yield 2;
//         yield 3;
//         yield 4;
//         yield 5;
//         return 6;
//     }

//     for (let v of foo()) {
//         console.log(v);
//     }
// })();

// 输出如下:

// 主要看这个辅助方法
function _createForOfIteratorHelper(o, allowArrayLike) {
    // 取出原始继承的迭代器生成方法
    var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
    if (!it) {
        // 不存在的话 自己构造模拟一个迭代器
        if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
            if (it) o = it;
            var i = 0;
            var F = function() {};
            return {
                s: F,
                n: function() {
                    if (i >= o.length) return {
                        done: true
                    };
                    return {
                        done: false,
                        value: o[i++]
                    };
                },
                e: function(e) {
                    throw e;
                },
                f: F
            };
        }
        throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
    }
    var normalCompletion = true,
    didErr = false,
    err;
    // 包裹对象
    return {
        s: function() {
            it = it.call(o);
        },
        // 最重要的就是这里
        n: function() {
            var step = it.next();
            normalCompletion = step.done;
            return step;
        },
        e: function(e) {
            didErr = true;
            err = e;
        },
        f: function() {
            try {
                if (!normalCompletion && it.
                return != null) it.
                return ();
            } finally {
                if (didErr) throw err;
            }
        }
    };
}

function _unsupportedIterableToArray(o, minLen) {
    if (!o) return;
    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
    var n = Object.prototype.toString.call(o).slice(8, -1);
    if (n === "Object" && o.constructor) n = o.constructor.name;
    if (n === "Map" || n === "Set") return Array.from(o);
    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}

function _arrayLikeToArray(arr, len) {
    if (len == null || len > arr.length) len = arr.length;
    for (var i = 0,
    arr2 = new Array(len); i < len; i++) {
        arr2[i] = arr[i];
    }
    return arr2;
}

// regeneratorRuntime 就是前文的 regeneratorRuntime 这次没有内容更新了 所以就不粘贴重复代码了哈
(function () {
    // mark和wrap的过程 之前分析过了
    var _marked = /*#__PURE__*/regeneratorRuntime.mark(foo);

    function foo() {
        return regeneratorRuntime.wrap(function foo$(_context) {
            while (1) {
                switch (_context.prev = _context.next) {
                    case 0:
                        _context.next = 2;
                        return 1;

                    case 2:
                        _context.next = 4;
                        return 2;

                    case 4:
                        _context.next = 6;
                        return 3;

                    case 6:
                        _context.next = 8;
                        return 4;

                    case 8:
                        _context.next = 10;
                        return 5;

                    case 10:
                        return _context.abrupt("return", 6);

                    case 11:
                    case "end":
                        return _context.stop();
                }
            }
        }, _marked);
    }

    // 主要是这里 对迭代器实例 包裹了一层 得到了一个辅助迭代器对象
    var _iterator = _createForOfIteratorHelper(foo()),
        _step;

    // 平平无奇的遍历...
    try {
        for (_iterator.s(); !(_step = _iterator.n()).done;) {
            var v = _step.value;
            console.log(v);
        }
    } catch (err) {
        _iterator.e(err);
    } finally {
        _iterator.f();
    }
})(); 

// 大家直接看注释就好了 for of 变成了某种 for 循环...emmm 好吧 就是这么平平无奇的结束了 for of 的介绍...

vue-router(v4.0.6)之 createRouter 方法中 createRouterMatcher 方法的实现简析

vue-router(v4.0.6)之 createRouter 方法中 createRouterMatcher 方法的实现简析

先看下它如何使用的:

// options就是我们createRouter的时候传入的配置选项
const matcher = createRouterMatcher(options.routes, options);

再看 createRouterMatcher(options.routes, options):

/**
    * Creates a Router Matcher.
    *
    * @internal
    * @param routes - array of initial routes
    * @param globalOptions - global route options
    */
function createRouterMatcher(routes, globalOptions) {
    // normalized ordered array of matchers
    // 我们配置的routes参数每条 record记录 对应生成的每个完整url(由于有别名配置 一条record最终会被处理成多个url) 对应一个matcher
    // 最终它们都被生产放到这个matchers数组中了
    const matchers = [];
    // 我们配置的拥有name属性的record route记录 对应的map结构 效率最高的索引方式
    // 比从matchers数组中找要快
    const matcherMap = new Map();
    // 合并配置参数 
    // strict:是否严格执行正则匹配 end:尾部正则是否一定要匹配 sensitive: 正则大小写敏感
    globalOptions = mergeOptions({ strict: false, end: true, sensitive: false }, globalOptions);
    // 根据name从map取出对应matcher即可
    function getRecordMatcher(name) {
        return matcherMap.get(name);
    }
    function addRoute(record, parent, originalRecord) {
        ...
    }
    function removeRoute(matcherRef) {
        ...
    }
    // 获取所有的routes对应的matchers数组
    function getRoutes() {
        return matchers;
    }
    function insertMatcher(matcher) {
        ...
    }
    function resolve(location, currentLocation) {
        ...
    }
    // add initial routes
    // 初始化调用createRouter的时候 首次添加routes集合,并对每条route生产matcher
    routes.forEach(route => addRoute(route));
    // 返回包裹对象
    return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher };
}

从代码逻辑整体结构来看,这个matcher对象对外暴露5个方法,还有一些内部的状态变量来维持这些方法正常运转。看下一些方法的具体实现:

  1. mergeOptions
function mergeOptions(defaults, partialOptions) {
    let options = {};
    // 注意 只对defaults对象上存在的字段尝试做合并
    for (let key in defaults) {
        options[key] =
            key in partialOptions ? partialOptions[key] : defaults[key];
    }
    return options;
}
  1. addRoute(record, parent, originalRecord)
    第一个参数代表着待处理的recode route记录,第二个参数是 父matcher对象, 第三个参数是 原始record记录 只有当前recode是某个recored的别名的子record情况 这个值才会存在 这时候我们产生的别名record下所有的子matcher都会被收集到 originalRecord.alias
function addRoute(record, parent, originalRecord) {
    // used later on to remove by name
    // 是否是产生的别名record的子record
    let isRootAdd = !originalRecord;
    // 格式化record
    let mainNormalizedRecord = normalizeRouteRecord(record);
    // we might be the child of an alias
    // 别名record指向它的源头record
    mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record;
    // 合并参数 globalOptions + record 就是之前描述的那几个正则控制项
    const options = mergeOptions(globalOptions, record);
    // generate an array of records to correctly handle aliases
    // 数组格式归一
    const normalizedRecords = [
        mainNormalizedRecord,
    ];
    // 对于别名数组 我们复制对应数量的别名record出来放到别名数组中
    // 注意 children 字段也被复制了 导致别名recored中存在children,虽然是浅拷贝 但是确实在下面的遍历别名数组的子节点时候需要注意
    if ('alias' in record) {
        const aliases = typeof record.alias === 'string' ? [record.alias] : record.alias;
        for (const alias of aliases) {
            normalizedRecords.push(assign({}, mainNormalizedRecord, {
                // this allows us to hold a copy of the `components` option
                // so that async components cache is hold on the original record
                // 沿用别名数组中对应原始数组中的record就好了
                components: originalRecord
                    ? originalRecord.record.components
                    : mainNormalizedRecord.components,
                // 别名的path是它的别名自己
                path: alias,
                // we might be the child of an alias
                // 沿用别名数组中对应原始数组中的record就好了
                aliasOf: originalRecord
                    ? originalRecord.record
                    : mainNormalizedRecord,
                // the aliases are always of the same kind as the original since they
                // are defined on the same record
            }));
        }
    }
    let matcher;
    let originalMatcher;
    // 遍历所有的当前节点的record(可能含别名产生的多个recored)数组
    for (const normalizedRecord of normalizedRecords) {
        let { path } = normalizedRecord;
        // Build up the path for nested routes if the child isn't an absolute
        // route. Only add the / delimiter if the child path isn't empty and if the
        // parent path doesn't have a trailing slash
        // 子record的path 拼接父节点的path 得到完整的path
        if (parent && path[0] !== '/') {
            let parentPath = parent.record.path;
            let connectingSlash = parentPath[parentPath.length - 1] === '/' ? '' : '/';
            normalizedRecord.path =
                parent.record.path + (path && connectingSlash + path);
        }
        // 使用场景限制
        if (normalizedRecord.path === '*') {
            throw new Error('Catch all routes ("*") must now be defined using a param with a custom regexp.\n' +
                'See more at https://next.router.vuejs.org/guide/migration/#removed-star-or-catch-all-routes.');
        }
        // create the object before hand so it can be passed to children
        // 一条record对应一个matcher 父子节点分别对应自己的matcher 只是子节点的path更长 对应的matcher也不同
        // createRouteRecordMatcher的实现代码比较多 稍后详细分析
        matcher = createRouteRecordMatcher(normalizedRecord, parent, options);
        // 对于子节点而言 如果path是带有 / 开头的绝对路径(基于base)
        // 那么需要校验一下父节点path中含有的params是否都包含在子节点的params中
        // 不然这是一个不符合父子path一致的父子节点路径
        if (parent && path[0] === '/')
            checkMissingParamsInAbsolutePath(matcher, parent);
        // if we are an alias we must tell the original record that we exist
        // so we can be removed
        // 如果我们当前正在处理 某个别名record R1 子数组下的某个record R2 那么此时 originalRecord 指向的是 R1的原始record下子数组中与R2索引对应的 原始子recored记录
        if (originalRecord) {
            // 原始matcher的alias存放对应别名path的matcher 其实他们都匹配一个组件渲染 只是他们的path不同而已
            originalRecord.alias.push(matcher);
            {
                // 检测动态路径参数是否符合要求
                checkSameParams(originalRecord, matcher);
            }
        }
        // 不是在处理别名record 意味着在处理原始某条record记录
        else {
            // otherwise, the first record is the original and others are aliases
            // 原始matcher赋值
            originalMatcher = originalMatcher || matcher;
            // 这行代码触发时机不确定 似乎没有必要了
            if (originalMatcher !== matcher)
                originalMatcher.alias.push(matcher);
            // remove the route if named and only for the top record (avoid in nested calls)
            // this works because the original record is the first one
            // 在下面会有再次放入map中 先先序处理 移除一次
            if (isRootAdd && record.name && !isAliasRecord(matcher))
                removeRoute(record.name);
        }
        // 深度递归 处理子节点
        if ('children' in mainNormalizedRecord) {
            let children = mainNormalizedRecord.children;
            for (let i = 0; i < children.length; i++) {
                // 第二个参数表明了 parent指的是父record对应的matcher
                // 注意第三个参数 我们在处理赋值出来的别名record的时候 把子节点也复制了 所以在遍历这些个别名子数组的时候 存在对应的 originalRecord.children[i]
                addRoute(children[i], matcher, originalRecord && originalRecord.children[i]);
            }
        }
        // if there was no original record, then the first one was not an alias and all
        // other alias (if any) need to reference this record when adding children
        originalRecord = originalRecord || matcher;
        // TODO: add normalized records for more flexibility
        // if (parent && isAliasRecord(originalRecord)) {
        //   parent.children.push(originalRecord)
        // }
        // 后续遍历完成 插入matchers数组中
        insertMatcher(matcher);
    }
    // 可以看到 加入别名record的时候返回值函数是可以支持删除它的
    return originalMatcher
        ? () => {
            // since other matchers are aliases, they should be removed by the original matcher
            removeRoute(originalMatcher);
        }
        : noop;
}

一些内部用到的辅助方法:

/**
    * Normalizes a RouteRecordRaw. Creates a copy
    *
    * @param record
    * @returns the normalized version
    */
// 返回一个标准record 对应文档中所有支持的字段
function normalizeRouteRecord(record) {
    return {
        path: record.path,
        redirect: record.redirect,
        name: record.name,
        meta: record.meta || {},
        // 是哪个record的别名 指向它
        aliasOf: undefined,
        beforeEnter: record.beforeEnter,
        // 文档中描述的props字段
        props: normalizeRecordProps(record),
        children: record.children || [],
        // 这个record渲染的vue组件实例
        instances: {},
        leaveGuards: new Set(),
        updateGuards: new Set(),
        enterCallbacks: {},
        // 标准格式
        components: 'components' in record
            ? record.components || {}
            : { default: record.component },
    };
}

/**
    * Normalize the optional `props` in a record to always be an object similar to
    * components. Also accept a boolean for components.
    * @param record
    */
// 输出标准props对象
function normalizeRecordProps(record) {
    const propsObject = {};
    // props does not exist on redirect records but we can set false directly
    const props = record.props || false;
    // 文档中描述的默认值
    if ('component' in record) {
        propsObject.default = props;
    }
    else {
        // NOTE: we could also allow a function to be applied to every component.
        // Would need user feedback for use cases
        // name区分prop传给哪个组件
        for (let name in record.components)
            propsObject[name] = typeof props === 'boolean' ? props : props[name];
    }
    return propsObject;
}

// 确保父路径的动态参数在子路径中都有 就可以了
function checkMissingParamsInAbsolutePath(record, parent) {
    for (let key of parent.keys) {
        if (!record.keys.find(isSameParam.bind(null, key)))
            return warn(`Absolute path "${record.record.path}" should have the exact same param named "${key.name}" as its parent "${parent.record.path}".`);
    }
}

// matcher中的keys中的动态参数是都是对象 含有对应的属性
function isSameParam(a, b) {
    return (a.name === b.name &&
        a.optional === b.optional &&
        a.repeatable === b.repeatable);
}

/**
    * Check if a path and its alias have the same required params
    *
    * @param a - original record
    * @param b - alias record
    */
// 检测2个存在别名关系的matcher的动态路径参数 keys 是否相互一致 文档中对此有要求
function checkSameParams(a, b) {
    for (let key of a.keys) {
        if (!key.optional && !b.keys.find(isSameParam.bind(null, key)))
            return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" should have the exact same param named "${key.name}"`);
    }
    for (let key of b.keys) {
        if (!key.optional && !a.keys.find(isSameParam.bind(null, key)))
            return warn(`Alias "${b.record.path}" and the original record: "${a.record.path}" should have the exact same param named "${key.name}"`);
    }
}

代码比较多哈,现在我们再单独分析下 createRouteRecordMatcher(normalizedRecord, parent, options):看下如何根据一个我们写的record得到一个matcher,而matcher又能做什么呢?

function createRouteRecordMatcher(record, parent, options) {
    1. 解析path得到tokens集合
    2. 根据tokens集合得到 parser对象:它包含了路径中所有的信息以及一些辅助状态和方法
    const parser = tokensToParser(tokenizePath(record.path), options);
    // warn against params with the same name
    {
        // 路径中动态参数key不得重复
        const existingKeys = new Set();
        for (const key of parser.keys) {
            if (existingKeys.has(key.name))
                warn(`Found duplicated params with name "${key.name}" for path "${record.path}". Only the last one will be available on "$route.params".`);
            existingKeys.add(key.name);
        }
    }
    // 构建返回的matcher对象
    const matcher = assign(parser, {
        record,
        parent,
        // these needs to be populated by the parent
        children: [],
        alias: [],
    });
    // 构建父子关系
    if (parent) {
        // both are aliases or both are not aliases
        // we don't want to mix them because the order is used when
        // passing originalRecord in Matcher.addRoute
        if (!matcher.record.aliasOf === !parent.record.aliasOf)
            parent.children.push(matcher);
    }
    return matcher;
}

再看下 tokenizePath(record.path) 如何解析path的:

// 根token
const ROOT_TOKEN = {
    type: 0 /* Static */,
    value: '',
};
// 正则输入范围
const VALID_PARAM_RE = /[a-zA-Z0-9_]/;
// After some profiling, the cache seems to be unnecessary because tokenizePath
// (the slowest part of adding a route) is very fast
// const tokenCache = new Map<string, Token[][]>()
// 把一段path路径 解析成一个[[token,token...],...]二维数组 每个token包含了路径中的所有信息
function tokenizePath(path) {
    if (!path)
        return [[]];
    if (path === '/')
        return [[ROOT_TOKEN]];
    // 经过前面的处理后 所有子路由的路径都变成含父路径的全路径了
    if (!path.startsWith('/')) {
        throw new Error(`Route paths should start with a "/": "${path}" should be "/${path}".`
            );
    }
    // if (tokenCache.has(path)) return tokenCache.get(path)!
    // 中间解析出错的时候 抛出错误
    function crash(message) {
        throw new Error(`ERR (${state})/"${buffer}": ${message}`);
    }
    // 状态机状态中的 静态内容 不含动态参数以及正则等 同时也是起始状态
    let state = 0 /* Static */;
    // 前一个状态
    let previousState = state;
    // tokens集合
    const tokens = [];
    // the segment will always be valid because we get into the initial state
    // with the leading /
    let segment;
    // 在 2个 / 中间的内容被称为一段 / ... /
    function finalizeSegment() {
        if (segment)
            tokens.push(segment);
        segment = [];
    }
    // index on the path
    // 循环的任一时刻准备处理的字符索引 也就是 下一个字符
    let i = 0;
    // char at index
    // 上面的 i 对应的 字符
    let char;
    // buffer of the value read
    // 目前正在读取的一串字符 读取确定了一个token的时候就把buffer消费掉了 不然就一直累加字符
    let buffer = '';
    // custom regexp for a param
    // 动态路径中 用户对参数定义的自定义正则表达式
    let customRe = '';
    // 消费目前得到的buffer内容 因为已经确定了当前token是哪种了
    function consumeBuffer() {
        if (!buffer)
            return;
        // 静态内容 比较简单 只需要值就好了
        if (state === 0 /* Static */) {
            segment.push({
                type: 0 /* Static */,
                value: buffer,
            });
        }
        // 路径中含有动态参数或者正则
        else if (state === 1 /* Param */ ||
            state === 2 /* ParamRegExp */ ||
            state === 3 /* ParamRegExpEnd */) {
            // 可重复正则的限制使用场景
            if (segment.length > 1 && (char === '*' || char === '+'))
                crash(`A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`);
            // 动态参数的token类型 被放入到segment 一段中 单个段中可能有多个参数或者多个正则
            segment.push({
                type: 1 /* Param */,
                value: buffer,
                regexp: customRe,
                repeatable: char === '*' || char === '+',
                optional: char === '*' || char === '?',
            });
        }
        else {
            crash('Invalid state to consume buffer');
        }
        // 清空之前已处理的buffer
        buffer = '';
    }
    // 累加buffer 继续处理
    function addCharToBuffer() {
        buffer += char;
    }
    // 状态机
    while (i < path.length) {
        // 待分析的字符
        char = path[i++];
        // 正则的 \\ 出现在了非 ParamRegExp 状态下 处理方案就是保持状态不变 继续累加
        if (char === '\\' && state !== 2 /* ParamRegExp */) {
            previousState = state;
            state = 4 /* EscapeNext */;
            continue;
        }
        // 状态机主逻辑
        switch (state) {
            // 当前正处于解析一段static内容的状态
            case 0 /* Static */:
                // 遇到新的一段或者刚开始的 /
                if (char === '/') {
                    if (buffer) {
                        consumeBuffer();
                    }
                    finalizeSegment();
                }
                // 动态路径参数 /:id/...
                else if (char === ':') {
                    consumeBuffer();
                    state = 1 /* Param */;
                }
                // 否则继续添加字符到buffer中
                else {
                    addCharToBuffer();
                }
                break;
            // 同上分析 累加继续
            case 4 /* EscapeNext */:
                addCharToBuffer();
                state = previousState;
                break;
            // 当前正处于解析某个动态路径参数内容的状态
            case 1 /* Param */:
                // 开始处理 用户设置的动态参数正则规则
                if (char === '(') {
                    state = 2 /* ParamRegExp */;
                }
                // /[a-zA-Z0-9_]/
                else if (VALID_PARAM_RE.test(char)) {
                    addCharToBuffer();
                }
                // 没有遇到 ( 说明遇到的是 可选正则或者 / 或者 结束没有了
                else {
                    // 消费内容
                    consumeBuffer();
                    // 段结束
                    state = 0 /* Static */;
                    // go back one character if we were not modifying
                    // 在 consumeBuffer 已分析过当前char的内容
                    if (char !== '*' && char !== '?' && char !== '+')
                        i--;
                }
                break;
            // 当前正处于解析某个动态路径参数的正则表达式内容的状态
            case 2 /* ParamRegExp */:
                // TODO: is it worth handling nested regexp? like :p(?:prefix_([^/]+)_suffix)
                // it already works by escaping the closing )
                // https://paths.esm.dev/?p=AAMeJbiAwQEcDKbAoAAkP60PG2R6QAvgNaA6AFACM2ABuQBB#
                // is this really something people need since you can also write
                // /prefix_:p()_suffix
                // 遇到上述状态对应的终止符号
                if (char === ')') {
                    // handle the escaped )
                    // 出现了不合理的内容格式 如 ....(...\\) 最后一个右括号之前应该是 \\d+ 之类的某个正则规则字符
                    if (customRe[customRe.length - 1] == '\\')
                        // 去掉 \\ 保留 (...)
                        customRe = customRe.slice(0, -1) + char;
                    else
                        state = 3 /* ParamRegExpEnd */;
                }
                // 不然就继续添加正则内容
                else {
                    customRe += char;
                }
                break;
            // 当前正处于解析完成 某个动态路径参数的正则表达式内容的状态
            case 3 /* ParamRegExpEnd */:
                // same as finalizing a param
                // 消费已知的buffer
                consumeBuffer();
                // 回到初始状态 当前段已解析完成
                state = 0 /* Static */;
                // go back one character if we were not modifying
                // 如果在动态参数参数正则后面不是 可选参数等正则 说明是 / 或者 结束了 这时候回退一个字符 应为在 consumeBuffer 的时候已经对当前char的信息做了处理了
                if (char !== '*' && char !== '?' && char !== '+')
                    i--;
                customRe = '';
                break;
            default:
                crash('Unknown state');
                break;
        }
    }
    // 不正常闭合结束
    if (state === 2 /* ParamRegExp */)
        crash(`Unfinished custom RegExp for param "${buffer}"`);
    // 消费清空最后的 /... buffer内容
    consumeBuffer();
    finalizeSegment();
    // tokenCache.set(path, tokens)
    // 返回的是  [[token,token...],....] 集合
    // 代表路径中 每段的信息 都描述在其中
    return tokens;
}

总结一下 tokenizePath 就是对单个path的 每个 / ... / 之间的内容作了解析 生成对应段的信息描述对象

然后再看下 tokensToParser(tokenizePath(record.path), options) 如何得到 parser:

// Special Regex characters that must be escaped in static tokens
const REGEX_CHARS_RE = /[.+*?^${}()[\]/\\]/g;
/**
    * Creates a path parser from an array of Segments (a segment is an array of Tokens)
    *
    * @param segments - array of segments returned by tokenizePath
    * @param extraOptions - optional options for the regexp
    * @returns a PathParser
    */
// 如上文的注释描述的参数和返回信息
function tokensToParser(segments, extraOptions) {
    // 合并参数 对正则的要求的几个条件
    const options = assign({}, BASE_PATH_PARSER_OPTIONS, extraOptions);
    // the amount of scores is the same as the length of segments except for the root segment "/"
    // 路径每个段 对应一个分数 都会被放到下面的score数组中 
    let score = [];
    // the regexp as a string
    // 这条路径最终生成的正则路径 可以通过它校验某个目标path是否匹配它
    let pattern = options.start ? '^' : '';
    // extracted keys
    // 路径中的动态参数都会放这里
    const keys = [];
    // 遍历所有段
    for (const segment of segments) {
        // the root segment needs special treatment
        // 根节点的路径是 '/' 对应的tokens数组是 [] 直接放个 [90] 进去
        // 段下面每个token对应一个自己的分数 放下面的数组中
        const segmentScores = segment.length ? [] : [90 /* Root */];
        // allow trailing slash
        // 允许在base后面加尾部 /
        if (options.strict && !segment.length)
            pattern += '/';
        // 遍历当前段里面的所有token
        for (let tokenIndex = 0; tokenIndex < segment.length; tokenIndex++) {
            const token = segment[tokenIndex];
            // resets the score if we are inside a sub segment /:a-other-:b
            // 每个token的基准分数
            let subSegmentScore = 40 /* Segment */ +
                (options.sensitive ? 0.25 /* BonusCaseSensitive */ : 0);
            // 静态路径段
            if (token.type === 0 /* Static */) {
                // prepend the slash if we are starting a new segment
                // 新开始的段 正则path前面加 / 拼接路径
                if (!tokenIndex)
                    pattern += '/';
                // /[.+*?^${}()[\]/\\]/g 非法字符需要被转译 所以替换的字符成了 \\$& 这样的 以 &开头的转译格式
                pattern += token.value.replace(REGEX_CHARS_RE, '\\$&');
                subSegmentScore += 40 /* Static */;
            }
            // token是动态的
            else if (token.type === 1 /* Param */) {
                // 取出之前分析得到的信息
                const { value, repeatable, optional, regexp } = token;
                // 添加当前这个动态参数key
                keys.push({
                    name: value,
                    repeatable,
                    optional,
                });
                // BASE_PARAM_PATTERN 默认是 '[^/]+?' 非 / 字符出现至少一次
                const re = regexp ? regexp : BASE_PARAM_PATTERN;
                // the user provided a custom regexp /:id(\\d+)
                // 存在用户自定义的正则规则
                if (re !== BASE_PARAM_PATTERN) {
                    subSegmentScore += 10 /* BonusCustomRegExp */;
                    // make sure the regexp is valid before using it
                    // 提前校验一下正则的合法性
                    try {
                        new RegExp(`(${re})`);
                    }
                    catch (err) {
                        throw new Error(`Invalid custom RegExp for param "${value}" (${re}): ` +
                            err.message);
                    }
                }
                // when we repeat we must take care of the repeating leading slash
                // 针对可选正则等规则添加额外的正则信息
                // 可重复
                let subPattern = repeatable ? `((?:${re})(?:/(?:${re}))*)` : `(${re})`;
                // prepend the slash if we are starting a new segment
                // 处理段中首个token的时候
                if (!tokenIndex)
                    subPattern =
                        // avoid an optional / if there are more segments e.g. /:p?-static
                        // or /:p?-:p2
                        // 如注释所言 /:p? 这种才走第一个分支
                        optional && segment.length < 2
                            ? `(?:/${subPattern})`
                            : '/' + subPattern;
                // 可选正则
                if (optional)
                    subPattern += '?';
                // 累加正则路径
                pattern += subPattern;
                // 路径是动态的
                subSegmentScore += 20 /* Dynamic */;
                // 提升优先级
                if (optional)
                    subSegmentScore += -8 /* BonusOptional */;
                if (repeatable)
                    subSegmentScore += -20 /* BonusRepeatable */;
                if (re === '.*')
                    subSegmentScore += -50 /* BonusWildcard */;
            }
            // 收集tokens对应的分数
            segmentScores.push(subSegmentScore);
        }
        // an empty array like /home/ -> [[{home}], []]
        // if (!segment.length) pattern += '/'
        // 收集所有段的分数
        score.push(segmentScores);
    }
    // only apply the strict bonus to the last score
    // 微下调最后一个token分数优先级
    if (options.strict && options.end) {
        const i = score.length - 1;
        score[i][score[i].length - 1] += 0.7000000000000001 /* BonusStrict */;
    }
    // TODO: dev only warn double trailing slash
    // 尾部正则
    if (!options.strict)
        pattern += '/?';
    if (options.end)
        pattern += '$';
    // allow paths like /dynamic to only match dynamic or dynamic/... but not dynamic_something_else
    else if (options.strict)
        pattern += '(?:/|$)';
    // 大小写敏感
    const re = new RegExp(pattern, options.sensitive ? '' : 'i');
    // 2个辅助方法
    // 把目标path中所有对应keys的动态参数一一解析出来 并赋值 返回params对象
    function parse(path) {
        const match = path.match(re);
        const params = {};
        if (!match)
            return null;
        for (let i = 1; i < match.length; i++) {
            const value = match[i] || '';
            const key = keys[i - 1];
            params[key.name] = value && key.repeatable ? value.split('/') : value;
        }
        return params;
    }
    // 根据已有params和段的tokens信息 还原出来完整的path
    function stringify(params) {
        let path = '';
        // for optional parameters to allow to be empty
        let avoidDuplicatedSlash = false;
        for (const segment of segments) {
            // 允许可选动态参数
            if (!avoidDuplicatedSlash || !path.endsWith('/'))
                path += '/';
            // 重置
            avoidDuplicatedSlash = false;
            // 单个段内遍历
            for (const token of segment) {
                // 静态内容直接追加
                if (token.type === 0 /* Static */) {
                    path += token.value;
                }
                else if (token.type === 1 /* Param */) {
                    const { value, repeatable, optional } = token;
                    // 取出实际值
                    const param = value in params ? params[value] : '';
                    if (Array.isArray(param) && !repeatable)
                        throw new Error(`Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`);
                    // 可重复就是数组的形式
                    const text = Array.isArray(param) ? param.join('/') : param;
                    if (!text) {
                        if (optional) {
                            // if we have more than one optional param like /:a?-static we
                            // don't need to care about the optional param
                            if (segment.length < 2) {
                                // remove the last slash as we could be at the end
                                if (path.endsWith('/'))
                                    path = path.slice(0, -1);
                                // do not append a slash on the next iteration
                                else
                                    avoidDuplicatedSlash = true;
                            }
                        }
                        else
                            throw new Error(`Missing required param "${value}"`);
                    }
                    // 追加
                    path += text;
                }
            }
        }
        return path;
    }
    // parser对象有3个关系信息和2个辅助方法
    return {
        re,
        score,
        keys,
        parse,
        stringify,
    };
}

可以看到,tokensToParser 得到了 这条path对应的正则,分数,动态参数keys和2个辅助方法,最后我们再回顾一下matcher有哪些属性:

// 构建返回的matcher对象 这些就是之前的 createRouteRecordMatcher 得到的单个path对应的matcher了
// 根据我们写的routes里面的路由得到对应的matcher 而matcher才是vue-router用来控制匹配路径的实际操控者 它内部蕴含了path的所有信息
parser = {
    re,
    score,
    keys,
    parse,
    stringify,
}
const matcher = assign(parser, {
    record,
    parent,
    // these needs to be populated by the parent
    children: [],
    alias: [],
});

总结一下 addRoute 方法做的事情有如下:

  1. 对于指定某条 route 生成对应的 matcher 插入matchers集合中
  2. 对参数和别名做了对应的处理

接下来看 createRouterMatcher 暴露的第三个方法
3. removeRoute(matcherRef)

// 带有name属性
function isRouteName(name) {
    return typeof name === 'string' || typeof name === 'symbol';
}
// 移除某个matcher
function removeRoute(matcherRef) {
    // 带有name的matcher
    if (isRouteName(matcherRef)) {
        // 根据name直接从map中取
        const matcher = matcherMap.get(matcherRef);
        if (matcher) {
            // map移除
            matcherMap.delete(matcherRef);
            // 数组移除
            matchers.splice(matchers.indexOf(matcher), 1);
            // 递归删除子matcher
            matcher.children.forEach(removeRoute);
            // 别名数组删除
            matcher.alias.forEach(removeRoute);
        }
    }
    else {
        // 否则就要遍历数组找到删除目标了 删除内容同上
        let index = matchers.indexOf(matcherRef);
        if (index > -1) {
            matchers.splice(index, 1);
            if (matcherRef.record.name)
                matcherMap.delete(matcherRef.record.name);
            matcherRef.children.forEach(removeRoute);
            matcherRef.alias.forEach(removeRoute);
        }
    }
}

接下来看 createRouterMatcher 暴露的第四个方法
4. getRoutes

// 对外暴露返回matchers数组
function getRoutes() {
    return matchers;
}

接下来看 createRouterMatcher 内部2个很重要的函数
insertMatcher:插入对应的matcher到matchers数组中

// 新解析得到的matcher插入到已存在数组中 
function insertMatcher(matcher) {
    let i = 0;
    // console.log('i is', { i })
    // 按照之前解析得到的path的score数组按照一定规则 进行排序
    // 从之类可以看到 comparePathParserScore 返回值大于0意味着需要往后排
    while (i < matchers.length &&
        comparePathParserScore(matcher, matchers[i]) >= 0)
        i++;
    // console.log('END i is', { i })
    // while (i < matchers.length && matcher.score <= matchers[i].score) i++
    // 插入即可 位置已排序确定
    matchers.splice(i, 0, matcher);
    // only add the original record to the name map
    // 添加到map中
    if (matcher.record.name && !isAliasRecord(matcher))
        matcherMap.set(matcher.record.name, matcher);
}
// 仔细看下 comparePathParserScore 如何排序的:
/**
    * Compares an array of numbers as used in PathParser.score and returns a
    * number. This function can be used to `sort` an array
    * @param a - first array of numbers
    * @param b - second array of numbers
    * @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b
    * should be sorted first
    */
// 对比2个段内的分数
function compareScoreArray(a, b) {
    let i = 0;
    // 也是对比公共长度的分数
    while (i < a.length && i < b.length) {
        // 直接取diff就好了 可以看到 待插入的a的分数越高 diff就小于0 意味着可以插入到前面
        const diff = b[i] - a[i];
        // only keep going if diff === 0
        if (diff)
            return diff;
        i++;
    }
    // if the last subsegment was Static, the shorter segments should be sorted first
    // otherwise sort the longest segment first
    // 分数相等的情况 更短的路径对最后一个段 静态情况做特殊对待 否则还是更长的插前面
    if (a.length < b.length) {
        return a.length === 1 && a[0] === 40 /* Static */ + 40 /* Segment */
            ? -1
            : 1;
    }
    else if (a.length > b.length) {
        return b.length === 1 && b[0] === 40 /* Static */ + 40 /* Segment */
            ? 1
            : -1;
    }
    return 0;
}
/**
    * Compare function that can be used with `sort` to sort an array of PathParser
    * @param a - first PathParser
    * @param b - second PathParser
    * @returns 0 if both are equal, < 0 if a should be sorted first, > 0 if b
    */
function comparePathParserScore(a, b) {
    let i = 0;
    const aScore = a.score;
    const bScore = b.score;
    // 对比公共长度的部分
    while (i < aScore.length && i < bScore.length) {
        // 先看公共长度部分的比较结果是什么 如果非 0 直接就返回对比值就完事了
        const comp = compareScoreArray(aScore[i], bScore[i]);
        // do not return if both are equal
        if (comp)
            return comp;
        i++;
    }
    // 如果公共长度部分恰好相等 分数 那就看谁更长了 因为 a 是待插入的matcher a的长度更长 返回 -1 就意味着插在当前matcher的前面了 
    // if a and b share the same score entries but b has more, sort b first
    return bScore.length - aScore.length;
    // this is the ternary version
    // return aScore.length < bScore.length
    //   ? 1
    //   : aScore.length > bScore.length
    //   ? -1
    //   : 0
}
// 可以看到,这个路径排名简单来说, 2个path每个段的分数都在对比 分数越高的 排的位置越靠前;分数相同的,长度更长的排前面

再看下内部的 resolve(location, currentLocation) 方法:根据参数 location 对象从matchers数组中 查询得到对应的匹配路由 并它信息组织成一个结构体 供调用者消费

// 根据 location 解析得到对应的 匹配对象
function resolve(location, currentLocation) {
    let matcher;
    let params = {};
    let path;
    let name;
    // 存在name属性
    if ('name' in location && location.name) {
        // 直接取到matcher即可
        matcher = matcherMap.get(location.name);
        if (!matcher)
            throw createRouterError(1 /* MATCHER_NOT_FOUND */, {
                location,
            });
        name = matcher.record.name;
        params = assign(
        // paramsFromLocation is a new object
        paramsFromLocation(currentLocation.params, 
        // only keep params that exist in the resolved location
        // TODO: only keep optional params coming from a parent record
        matcher.keys.filter(k => !k.optional).map(k => k.name)), location.params);
        // throws if cannot be stringified
        // 得到完整的去参数化 path
        path = matcher.stringify(params);
    }
    // 如果有path字段 常见的情况
    else if ('path' in location) {
        // no need to resolve the path with the matcher as it was provided
        // this also allows the user to control the encoding
        path = location.path;
        if (!path.startsWith('/')) {
            warn(`The Matcher cannot resolve relative paths but received "${path}". Unless you directly called \`matcher.resolve("${path}")\`, this is probably a bug in vue-router. Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue-router-next.`);
        }
        // 只能按照顺序 第一个匹配了就可以了 因为排好序了
        matcher = matchers.find(m => m.re.test(path));
        // matcher should have a value after the loop
        if (matcher) {
            // TODO: dev warning of unused params if provided
            // we know the matcher works because we tested the regexp
            // 从path中解析出params
            params = matcher.parse(path);
            name = matcher.record.name;
        }
        // location is a relative path
    }
    // 上面的2个都没有 就从当前route中取 分析同上面
    else {
        // match by name or path of current route
        matcher = currentLocation.name
            ? matcherMap.get(currentLocation.name)
            : matchers.find(m => m.re.test(currentLocation.path));
        if (!matcher)
            throw createRouterError(1 /* MATCHER_NOT_FOUND */, {
                location,
                currentLocation,
            });
        name = matcher.record.name;
        // since we are navigating to the same location, we don't need to pick the
        // params like when `name` is provided
        params = assign({}, currentLocation.params, location.params);
        path = matcher.stringify(params);
    }
    const matched = [];
    let parentMatcher = matcher;
    // 子路由匹配到了 把它前面所有的父record都一一插入到前面 构成一个完整的matched数组
    while (parentMatcher) {
        // reversed order so parents are at the beginning
        matched.unshift(parentMatcher.record);
        parentMatcher = parentMatcher.parent;
    }
    // 返回对象包含5个重要属性
    return {
        name,
        path,
        params,
        matched,
        meta: mergeMetaFields(matched),
    };
}

总结:终于分析完了 createRouterMatcher 得到的matcher包含哪些方法了,实现有点长,因为后面的使用场景都需要依赖这个阶段得到的matcher对象,它所包含的正则,params以及matched数组等都是很重要的信息。

Vue(v3.0.11)源码简析之props的初始化、解析及更新的相关实现

我理解的vue的中的props有3种来源,第一种是我们写在原始组件对象配置项的props,代表着这个组件可以接受的prop类型,同时我们把解析得到的信息保存在组件实例的propsOptions上,第二种是编译模板生成render函数且执行后得到vnode树,此时vnode上挂载的props是用户实际使用传入的props对象,第三个则是最终根据上面的2者得到的组件实例上的props对象,我们在业务函数中去取props的值,也就是从这个上面取到的,因此在下面的源码中,大家要分清楚再对哪个props对象在做处理噢:)

// 把编辑解析模板得到的vnode上的原始props对象 初始化构建到组件实例的props来
// isStateful 表明是在对组件进行处理
function initProps(instance, rawProps, isStateful, // result of bitwise flag comparison
isSSR = false) {
    const props = {};
    const attrs = {};
    // attrs.__vInternal = 1
    def(attrs, InternalObjectKey, 1);
    instance.propsDefaults = Object.create(null);
    // 解析设置props到实例上去
    setFullProps(instance, rawProps, props, attrs);
    // validation
    {
        // 对值和默认值做校验了
        validateProps(rawProps || {}, props, instance);
    }
    // 设置组件实例过程中 需要把处理后的props做响应式代理 以方便render的时候建立依赖收集 这里只用了浅层代理 说明只需要代理一层
    if (isStateful) {
        // stateful
        instance.props = isSSR ? props : shallowReactive(props);
    }
    else {
        // 函数组件的情况且没配置props选项 把 attrs 赋给它 所有用户传入的prop都在attrs中这时候
        if (!instance.type.props) {
            // functional w/ optional props, props === attrs
            instance.props = attrs;
        }
        else {
            // functional w/ declared props
            // 有配置props的话 就赋值就好了
            instance.props = props;
        }
    }
    // attrs 一直都有
    instance.attrs = attrs;
}
// 相对于初始化init而言的 更新过程 在重新渲染组件的时候 prop依赖的变量可能发生改变了 这时候需要更新组件实例prop上对应的值了
function updateProps(instance, rawProps, rawPrevProps, optimized) {
    // 取出实例上已存在的2类属性(已经init过了)
    const { props, attrs, vnode: { patchFlag } } = instance;
    // 未更新之前的实例的现存props对象
    const rawCurrentProps = toRaw(props);
    // 原始用户配置
    const [options] = instance.propsOptions;
    if (
    // always force full diff in dev
    // - #1942 if hmr is enabled with sfc component
    // - vite#872 non-sfc component used by sfc component
    !((instance.type.__hmrId ||
            (instance.parent && instance.parent.type.__hmrId))) &&
        (optimized || patchFlag > 0) &&
        !(patchFlag & 16 /* FULL_PROPS */)) {
        // 没有动态key的prop 在存在普通动态props的场景下 更新新值即可
        if (patchFlag & 8 /* PROPS */) {
            // Compiler-generated props & no keys change, just set the updated
            // the props.
            const propsToUpdate = instance.vnode.dynamicProps;
            for (let i = 0; i < propsToUpdate.length; i++) {
                const key = propsToUpdate[i];
                // PROPS flag guarantees rawProps to be non-null
                const value = rawProps[key];
                if (options) {
                    // attr / props separation was done on init and will be consistent
                    // in this code path, so just check if attrs have it.
                    // 更新原本对应key所在attr或者prop中的值
                    if (hasOwn(attrs, key)) {
                        attrs[key] = value;
                    }
                    else {
                        const camelizedKey = camelize(key);
                        props[camelizedKey] = resolvePropValue(options, rawCurrentProps, camelizedKey, value, instance);
                    }
                }
                else {
                    attrs[key] = value;
                }
            }
        }
    }
    else {
        // full props update.
        // 再次调用 setFullProps 去更新实例上已经存在的props和attrs
        setFullProps(instance, rawProps, props, attrs);
        // in case of dynamic props, check if we need to delete keys from
        // the props object
        let kebabKey;
        // 检测动态key的prop 在更新后key本身发生了改变
        for (const key in rawCurrentProps) {
            // 新prop不存在或者之前的某个key没有了
            if (!rawProps ||
                // for camelCase
                (!hasOwn(rawProps, key) &&
                    // it's possible the original props was passed in as kebab-case
                    // and converted to camelCase (#955)
                    ((kebabKey = hyphenate(key)) === key || !hasOwn(rawProps, kebabKey)))) {
                if (options) {
                    if (rawPrevProps &&
                        // for camelCase
                        (rawPrevProps[key] !== undefined ||
                            // for kebab-case
                            rawPrevProps[kebabKey] !== undefined)) {
                        // 新增加的动态key某个值
                        props[key] = resolvePropValue(options, rawProps || EMPTY_OBJ, key, undefined, instance);
                    }
                }
                // 直接移除 连配置项都没
                else {
                    delete props[key];
                }
            }
        }
        // in the case of functional component w/o props declaration, props and
        // attrs point to the same object so it should already have been updated.
        // 非函数组件下 attrs和props不是同一个对象 移除不需要的属性了
        if (attrs !== rawCurrentProps) {
            for (const key in attrs) {
                if (!rawProps || !hasOwn(rawProps, key)) {
                    delete attrs[key];
                }
            }
        }
    }
    // trigger updates for $attrs in case it's used in component slots
    // 触发更新 具体原因待分析 TODO
    trigger(instance, "set" /* SET */, '$attrs');
    {
        // 再验证一次 更新后
        validateProps(rawProps || {}, props, instance);
    }
}
// 根据原始prop设置新prop对象
function setFullProps(instance, rawProps, props, attrs) {
    const [options, needCastKeys] = instance.propsOptions;
    if (rawProps) {
        for (const key in rawProps) {
            const value = rawProps[key];
            // key, ref are reserved and never passed down
            // 忽略内置保留prop
            if (isReservedProp(key)) {
                continue;
            }
            // prop option names are camelized during normalization, so to support
            // kebab -> camel conversion here we need to camelize the key.
            let camelKey;
            // 用户设置了明确可接受的props的keys集合 才放入到props中
            if (options && hasOwn(options, (camelKey = camelize(key)))) {
                props[camelKey] = value;
            }
            // 非emits的其他prop都在attrs
            else if (!isEmitListener(instance.emitsOptions, key)) {
                // Any non-declared (either as a prop or an emitted event) props are put
                // into a separate `attrs` object for spreading. Make sure to preserve
                // original key casing
                attrs[key] = value;
            }
        }
    }
    // 构造没有值的prop的默认值
    if (needCastKeys) {
        // 当前已初步处理过的props集合
        const rawCurrentProps = toRaw(props);
        for (let i = 0; i < needCastKeys.length; i++) {
            const key = needCastKeys[i];
            // 继续解析赋值
            props[key] = resolvePropValue(options, rawCurrentProps, key, rawCurrentProps[key], instance);
        }
    }
}
// 解析prop的默认值
function resolvePropValue(options, props, key, value, instance) {
    const opt = options[key];
    if (opt != null) {
        // prop的default选项
        const hasDefault = hasOwn(opt, 'default');
        // default values
        // 构造默认值
        if (hasDefault && value === undefined) {
            const defaultValue = opt.default;
            // 执行默认值函数
            if (opt.type !== Function && isFunction(defaultValue)) {
                // 把默认值 存储到实例的 propsDefaults 中 记录下来 重复了可以直接调用覆盖
                const { propsDefaults } = instance;
                if (key in propsDefaults) {
                    value = propsDefaults[key];
                }
                else {
                    // 赋值 看来这个默认值函数可以执行代码中 可能会有依赖组件实例的情况
                    setCurrentInstance(instance);
                    value = propsDefaults[key] = defaultValue(props);
                    setCurrentInstance(null);
                }
            }
            // default字段本身就是普通值
            else {
                value = defaultValue;
            }
        }
        // boolean casting
        // 为布尔类型构造默认值
        if (opt[0 /* shouldCast */]) {
            // 用户没传这个key的时候 也加上这个key=false
            if (!hasOwn(props, key) && !hasDefault) {
                value = false;
            }
            // 存在Boolean 对传入的值还有2个特殊处理 这也很秀了 文档貌似没有描述?
            else if (opt[1 /* shouldCastTrue */] &&
                (value === '' || value === hyphenate(key))) {
                value = true;
            }
        }
    }
    return value;
}
// 从用户组件配置的props格式化解析props出来
function normalizePropsOptions(comp, appContext, asMixin = false) {
    // 同之前分析过的normalizeEmitsOptions deopt 存在用户调用app.mixin混入了其他配置对象
    if (!appContext.deopt && comp.__props) {
        return comp.__props;
    }
    // 原始prop对象
    const raw = comp.props;
    const normalized = {};
    const needCastKeys = [];
    // apply mixin/extends props
    let hasExtends = false;
    if (!isFunction(comp)) {
        const extendProps = (raw) => {
            hasExtends = true;
            // 同之前normalizeEmitsOptions的分析 递归解析
            const [props, keys] = normalizePropsOptions(raw, appContext, true);
            extend(normalized, props);
            if (keys)
                needCastKeys.push(...keys);
        };
        // 优先递归解析存在的全局mixins 所以可以被本组件的同名属性覆盖掉
        if (!asMixin && appContext.mixins.length) {
            appContext.mixins.forEach(extendProps);
        }
        // extends配置项
        if (comp.extends) {
            extendProps(comp.extends);
        }
        // 自己的mixins
        if (comp.mixins) {
            comp.mixins.forEach(extendProps);
        }
    }
    if (!raw && !hasExtends) {
        return (comp.__props = EMPTY_ARR);
    }
    if (isArray(raw)) {
        for (let i = 0; i < raw.length; i++) {
            // [prop1, porp2...]
            if (!isString(raw[i])) {
                warn(`props must be strings when using array syntax.`, raw[i]);
            }
            // 驼峰
            const normalizedKey = camelize(raw[i]);
            if (validatePropName(normalizedKey)) {
                // {prop1: {}...}
                normalized[normalizedKey] = EMPTY_OBJ;
            }
        }
    }
    // 对象形式配置prop时
    else if (raw) {
        if (!isObject(raw)) {
            warn(`invalid props options`, raw);
        }
        for (const key in raw) {
            const normalizedKey = camelize(key);
            if (validatePropName(normalizedKey)) {
                const opt = raw[key];
                // 统一格式 因为prop有多重格式可以用
                // type: 多个构造函数的数组 || 直接一个构造函数: [Number, String...] || String
                // 也可以是一个多key的配置对象
                const prop = (normalized[normalizedKey] =
                    isArray(opt) || isFunction(opt) ? { type: opt } : opt);
                if (prop) {
                    // 期待类型中含有布尔型
                    const booleanIndex = getTypeIndex(Boolean, prop.type);
                    // 含有String型
                    const stringIndex = getTypeIndex(String, prop.type);
                    // 注意下面2类 第一个代表对布尔值的特殊处理 第二个是string出现在布尔类型的后面的特殊处理
                    prop[0 /* shouldCast */] = booleanIndex > -1;
                    prop[1 /* shouldCastTrue */] =
                        stringIndex < 0 || booleanIndex < stringIndex;
                    // if the prop needs boolean casting or default value
                    // 需要为上述2种情况构建默认值
                    // 默认值配置项存在 但是现在这个阶段不需要执行 因为要到具体的赋值阶段也就是 初始化和更新组件实例prop的时候才需要触发赋值 现在只是解析出用户配置什么样的prop就可以了
                    if (booleanIndex > -1 || hasOwn(prop, 'default')) {
                        needCastKeys.push(normalizedKey);
                    }
                }
            }
        }
    }
    // 返回解析出来的配置项和需要再次cast构造的keys
    return (comp.__props = [normalized, needCastKeys]);
}
// 我们写的prop不能以$开头
function validatePropName(key) {
    if (key[0] !== '$') {
        return true;
    }
    else {
        warn(`Invalid prop name: "${key}" is a reserved property.`);
    }
    return false;
}
// use function string name to check type constructors
// so that it works across vms / iframes.
// 2个构造函数是否一致
function getType(ctor) {
    const match = ctor && ctor.toString().match(/^\s*function (\w+)/);
    return match ? match[1] : '';
}
function isSameType(a, b) {
    return getType(a) === getType(b);
}
// 目标type构造函数 是否存在于 类型或者类型数组中 返回可能存在的索引位置或者-1
function getTypeIndex(type, expectedTypes) {
    // 数组形式的 多个可能接受类型构造函数
    if (isArray(expectedTypes)) {
        return expectedTypes.findIndex(t => isSameType(t, type));
    }
    else if (isFunction(expectedTypes)) {
        return isSameType(expectedTypes, type) ? 0 : -1;
    }
    return -1;
}
/**
 * dev only
 */
// 逐一校验
function validateProps(rawProps, props, instance) {
    // 解析出来的值
    const resolvedValues = toRaw(props);
    // 原始用户配置的props集合
    const options = instance.propsOptions[0];
    for (const key in options) {
        let opt = options[key];
        if (opt == null)
            continue;
        validateProp(key, resolvedValues[key], opt, !hasOwn(rawProps, key) && !hasOwn(rawProps, hyphenate(key)));
    }
}
/**
 * dev only
 */
// 第四个参数代表 用户没有手动传入 第三个参数代表原始组件对象配置的某个prop
function validateProp(name, value, prop, isAbsent) {
    // 取出原始配置信息
    const { type, required, validator } = prop;
    // required!
    // 必需
    if (required && isAbsent) {
        warn('Missing required prop: "' + name + '"');
        return;
    }
    // missing but optional
    if (value == null && !prop.required) {
        return;
    }
    // type check
    // 类型检查
    if (type != null && type !== true) {
        let isValid = false;
        const types = isArray(type) ? type : [type];
        const expectedTypes = [];
        // value is valid as long as one of the specified types match
        // 只要一个匹配就好了
        for (let i = 0; i < types.length && !isValid; i++) {
            // 逐个检测即可 解析出来类型也放入信息集合中以备输出
            const { valid, expectedType } = assertType(value, types[i]);
            expectedTypes.push(expectedType || '');
            isValid = valid;
        }
        if (!isValid) {
            // 输出信息
            warn(getInvalidTypeMessage(name, value, expectedTypes));
            return;
        }
    }
    // custom validator
    // 对应文档中的验证功能
    if (validator && !validator(value)) {
        warn('Invalid prop: custom validator check failed for prop "' + name + '".');
    }
}
// 几种可以简单识别的值的类型
const isSimpleType = /*#__PURE__*/ makeMap('String,Number,Boolean,Function,Symbol,BigInt');
/**
 * dev only
 */
// 检测类型
function assertType(value, type) {
    let valid;
    const expectedType = getType(type);
    if (isSimpleType(expectedType)) {
        const t = typeof value;
        valid = t === expectedType.toLowerCase();
        // for primitive wrapper objects
        // 用是否属于实例再检测一次
        if (!valid && t === 'object') {
            valid = value instanceof type;
        }
    }
    else if (expectedType === 'Object') {
        valid = isObject(value);
    }
    else if (expectedType === 'Array') {
        valid = isArray(value);
    }
    else {
        valid = value instanceof type;
    }
    return {
        valid,
        expectedType
    };
}
/**
 * dev only
 */
// 输出指定格式的提示文案
function getInvalidTypeMessage(name, value, expectedTypes) {
    let message = `Invalid prop: type check failed for prop "${name}".` +
        ` Expected ${expectedTypes.map(capitalize).join(', ')}`;
    const expectedType = expectedTypes[0];
    const receivedType = toRawType(value);
    const expectedValue = styleValue(value, expectedType);
    const receivedValue = styleValue(value, receivedType);
    // check if we need to specify expected value
    if (expectedTypes.length === 1 &&
        isExplicable(expectedType) &&
        !isBoolean(expectedType, receivedType)) {
        message += ` with value ${expectedValue}`;
    }
    message += `, got ${receivedType} `;
    // check if we need to specify received value
    if (isExplicable(receivedType)) {
        message += `with value ${receivedValue}.`;
    }
    return message;
}
/**
 * dev only
 */
// 统一可以输出的几类类型的值格式
function styleValue(value, type) {
    if (type === 'String') {
        return `"${value}"`;
    }
    else if (type === 'Number') {
        return `${Number(value)}`;
    }
    else {
        return `${value}`;
    }
}
/**
 * dev only
 */
// 只有下面三种才输出可解释的值信息
function isExplicable(type) {
    const explicitTypes = ['string', 'number', 'boolean'];
    return explicitTypes.some(elem => type.toLowerCase() === elem);
}
/**
 * dev only
 */
function isBoolean(...args) {
    return args.some(elem => elem.toLowerCase() === 'boolean');
}

vue-router(v4.0.6)之 createRouter 方法的实现简析(3)

在之前的文章中,还有一部分createRouter所依赖的代码由于篇幅原因我们放到这里来分析:

// 注册守卫到对应record中
function registerGuard(record, name, guard) {
    const removeFromList = () => {
        record[name].delete(guard);
    };
    // 添加函数到组件钩子
    vue.onUnmounted(removeFromList);
    vue.onDeactivated(removeFromList);
    vue.onActivated(() => {
        record[name].add(guard);
    });
    record[name].add(guard);
}
/**
    * Add a navigation guard that triggers whenever the component for the current
    * location is about to be left. Similar to {@link beforeRouteLeave} but can be
    * used in any component. The guard is removed when the component is unmounted.
    *
    * @param leaveGuard - {@link NavigationGuard}
    */
// router-view的setup中使用的注册守卫方法
function onBeforeRouteLeave(leaveGuard) {
    if (!vue.getCurrentInstance()) {
        warn('getCurrentInstance() returned null. onBeforeRouteLeave() must be called at the top of a setup function');
        return;
    }
    const activeRecord = vue.inject(matchedRouteKey, {}).value;
    if (!activeRecord) {
        warn('No active route record was found. Are you missing a <router-view> component?');
        return;
    }
    registerGuard(activeRecord, 'leaveGuards', leaveGuard);
}
/**
    * Add a navigation guard that triggers whenever the current location is about
    * to be updated. Similar to {@link beforeRouteUpdate} but can be used in any
    * component. The guard is removed when the component is unmounted.
    *
    * @param updateGuard - {@link NavigationGuard}
    */
// router-view的setup中使用的注册守卫方法
function onBeforeRouteUpdate(updateGuard) {
    if (!vue.getCurrentInstance()) {
        warn('getCurrentInstance() returned null. onBeforeRouteUpdate() must be called at the top of a setup function');
        return;
    }
    // 取出当前router-view组件依赖的当前匹配route
    const activeRecord = vue.inject(matchedRouteKey, {}).value;
    if (!activeRecord) {
        warn('No active route record was found. Are you missing a <router-view> component?');
        return;
    }
    registerGuard(activeRecord, 'updateGuards', updateGuard);
}
function guardToPromiseFn(guard, to, from, record, name) {
    // keep a reference to the enterCallbackArray to prevent pushing callbacks if a new navigation took place
    // 用来收集文档中描述的 用户回调
    // 12.调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
    const enterCallbackArray = record &&
        // name is defined if record is because of the function overload
        (record.enterCallbacks[name] = record.enterCallbacks[name] || []);
    return () => new Promise((resolve, reject) => {
        // 这个就是实际执行的next实现
        const next = (valid) => {
            if (valid === false)
                reject(createRouterError(4 /* NAVIGATION_ABORTED */, {
                    from,
                    to,
                }));
            else if (valid instanceof Error) {
                reject(valid);
            }
            // 切换路由了在某个守卫中
            else if (isRouteLocation(valid)) {
                reject(createRouterError(2 /* NAVIGATION_GUARD_REDIRECT */, {
                    from: to,
                    to: valid,
                }));
            }
            else {
                if (enterCallbackArray &&
                    // since enterCallbackArray is truthy, both record and name also are
                    record.enterCallbacks[name] === enterCallbackArray &&
                    typeof valid === 'function')
                    // 收集用户的回调 组件渲染后最后执行
                    enterCallbackArray.push(valid);
                resolve();
            }
        };
        // wrapping with Promise.resolve allows it to work with both async and sync guards
        // 我们对于守卫常用的3个参数都在这里了
        const guardReturn = guard.call(record && record.instances[name], to, from, canOnlyBeCalledOnce(next, to, from) );
        let guardCall = Promise.resolve(guardReturn);
        // guard.length 代表guard函数参数的个数
        if (guard.length < 3)
            guardCall = guardCall.then(next);
        if (guard.length > 2) {
            const message = `The "next" callback was never called inside of ${guard.name ? '"' + guard.name + '"' : ''}:\n${guard.toString()}\n. If you are returning a value instead of calling "next", make sure to remove the "next" parameter from your function.`;
            if (typeof guardReturn === 'object' && 'then' in guardReturn) {
                guardCall = guardCall.then(resolvedValue => {
                    // @ts-ignore: _called is added at canOnlyBeCalledOnce
                    if (!next._called) {
                        warn(message);
                        return Promise.reject(new Error('Invalid navigation guard'));
                    }
                    return resolvedValue;
                });
                // TODO: test me!
            }
            else if (guardReturn !== undefined) {
                // @ts-ignore: _called is added at canOnlyBeCalledOnce
                if (!next._called) {
                    warn(message);
                    reject(new Error('Invalid navigation guard'));
                    return;
                }
            }
        }
        guardCall.catch(err => reject(err));
    });
}
function canOnlyBeCalledOnce(next, to, from) {
    let called = 0;
    // 闭包包裹一下next函数 控制只执行一次
    return function () {
        if (called++ === 1)
            warn(`The "next" callback was called more than once in one navigation guard when going from "${from.fullPath}" to "${to.fullPath}". It should be called exactly one time in each navigation guard. This will fail in production.`);
        // @ts-ignore: we put it in the original one because it's easier to check
        next._called = true;
        if (called === 1)
            next.apply(null, arguments);
    };
}
// 从vue组件中提取对应 guardType 类型的守卫函数
function extractComponentsGuards(matched, guardType, to, from) {
    const guards = [];
    for (const record of matched) {
        for (const name in record.components) {
            let rawComponent = record.components[name];
            // 几种错误的使用场景
            {
                // 非法组件的场景
                if (!rawComponent ||
                    (typeof rawComponent !== 'object' &&
                        typeof rawComponent !== 'function')) {
                    warn(`Component "${name}" in record with path "${record.path}" is not` +
                        ` a valid component. Received "${String(rawComponent)}".`);
                    // throw to ensure we stop here but warn to ensure the message isn't
                    // missed by the user
                    throw new Error('Invalid route component');
                }
                // 异步组件的不规范使用场景
                else if ('then' in rawComponent) {
                    // warn if user wrote import('/component.vue') instead of () =>
                    // import('./component.vue')
                    warn(`Component "${name}" in record with path "${record.path}" is a ` +
                        `Promise instead of a function that returns a Promise. Did you ` +
                        `write "import('./MyPage.vue')" instead of ` +
                        `"() => import('./MyPage.vue')" ? This will break in ` +
                        `production if not fixed.`);
                    let promise = rawComponent;
                    rawComponent = () => promise;
                }
                // defineAsyncComponent不合理使用场景
                else if (rawComponent.__asyncLoader &&
                    // warn only once per component
                    !rawComponent.__warnedDefineAsync) {
                    rawComponent.__warnedDefineAsync = true;
                    warn(`Component "${name}" in record with path "${record.path}" is defined ` +
                        `using "defineAsyncComponent()". ` +
                        `Write "() => import('./MyPage.vue')" instead of ` +
                        `"defineAsyncComponent(() => import('./MyPage.vue'))".`);
                }
            }
            // skip update and leave guards if the route component is not mounted
            // 未曾挂载过的组件 不需要执行 update and leave 2类守卫
            if (guardType !== 'beforeRouteEnter' && !record.instances[name])
                continue;
            if (isRouteComponent(rawComponent)) {
                // __vccOpts is added by vue-class-component and contain the regular options
                // 取出约定好了放置在 __vccOpts 中的配置属性
                let options = rawComponent.__vccOpts || rawComponent;
                const guard = options[guardType];
                guard && guards.push(guardToPromiseFn(guard, to, from, record, name));
            }
            // 只有尚未加载过的组件才可能是 异步组件 所以只有 beforeRouteEnter的新登场组件才有可能走下面的逻辑
            else {
                // start requesting the chunk already
                let componentPromise = rawComponent();
                if (!('catch' in componentPromise)) {
                    warn(`Component "${name}" in record with path "${record.path}" is a function that does not return a Promise. If you were passing a functional component, make sure to add a "displayName" to the component. This will break in production if not fixed.`);
                    componentPromise = Promise.resolve(componentPromise);
                }
                else {
                    // display the error if any
                    componentPromise = componentPromise.catch(console.error);
                }
                // 推入 内含一个promise的函数
                guards.push(() => componentPromise.then(resolved => {
                    if (!resolved)
                        return Promise.reject(new Error(`Couldn't resolve component "${name}" at "${record.path}"`));
                    const resolvedComponent = isESModule(resolved)
                        ? resolved.default
                        : resolved;
                    // replace the function with the resolved component
                    record.components[name] = resolvedComponent;
                    // __vccOpts is added by vue-class-component and contain the regular options
                    let options = resolvedComponent.__vccOpts || resolvedComponent;
                    const guard = options[guardType];
                    // guardToPromiseFn 包裹 保证每个守卫都是返回promise
                    return guard && guardToPromiseFn(guard, to, from, record, name)();
                }));
            }
        }
    }
    return guards;
}
/**
    * Allows differentiating lazy components from functional components and vue-class-component
    * @param component
    */
// 是vue组件的几种标志代表
function isRouteComponent(component) {
    return (typeof component === 'object' ||
        'displayName' in component ||
        'props' in component ||
        '__vccOpts' in component);
}

上面补充的主要是如何从我们写的vue组件中提取router有关的配置钩子函数,并且用包裹函数包装一层返回promise,如果依赖组件是异步组件,则添加到组件加载返回的promise的then后面去执行。

之前的2篇分析了createRouter的绝大部分方法的实现,而最终返回的router实例我们看下是什么:

// 最终返回的router实例 就是它了
let started;
// app 和 router实例相互持有对方的引用
const installedApps = new Set();
const router = {
    // 当前匹配的route (location对象类型)
    currentRoute,
    addRoute,
    removeRoute,
    hasRoute,
    getRoutes,
    resolve,
    options,
    push,
    replace,
    go,
    back: () => go(-1),
    forward: () => go(1),
    beforeEach: beforeGuards.add,
    beforeResolve: beforeResolveGuards.add,
    afterEach: afterGuards.add,
    onError: errorHandlers.add,
    isReady,
    // vue约定的方法
    install(app) {
        // this指向对象本身 就是 router实例对象
        const router = this;
        // 注册2个app级别的全局组件 实现以后再分析
        app.component('RouterLink', RouterLink);
        app.component('RouterView', RouterView);
        // 全局配置属性加上 $router 所以每个组件都可以通过 this.$router来访问实例的属性和方法
        app.config.globalProperties.$router = router;
        // 去掉ref对象的value包裹
        Object.defineProperty(app.config.globalProperties, '$route', {
            get: () => vue.unref(currentRoute),
        });
        // this initial navigation is only necessary on client, on server it doesn't
        // make sense because it will create an extra unnecessary navigation and could
        // lead to problems
        // 初始化启动一次 '/' push操作
        if (isBrowser &&
            // used for the initial navigation client side to avoid pushing
            // multiple times when the router is used in multiple apps
            !started &&
            currentRoute.value === START_LOCATION_NORMALIZED) {
            // see above
            started = true;
            push(routerHistory.location).catch(err => {
                warn('Unexpected error when starting the router:', err);
            });
        }
        // 对 currentRoute.value location类型的每个key都做响应式包裹
        // 因为router-link会需要用到某些key
        const reactiveRoute = {};
        for (let key in START_LOCATION_NORMALIZED) {
            // @ts-ignore: the key matches
            reactiveRoute[key] = vue.computed(() => currentRoute.value[key]);
        }
        // 注入3个属性 方便随时inject提取出来
        app.provide(routerKey, router);
        // 注意 reactiveRoute 本身也被响应式包裹了
        app.provide(routeLocationKey, vue.reactive(reactiveRoute));
        app.provide(routerViewLocationKey, currentRoute);
        let unmountApp = app.unmount;
        installedApps.add(app);
        // 移除时重置状态 移除事件
        app.unmount = function () {
            installedApps.delete(app);
            if (installedApps.size < 1) {
                removeHistoryListener();
                currentRoute.value = START_LOCATION_NORMALIZED;
                started = false;
                ready = false;
            }
            unmountApp();
        };
        {
            // 暂时忽略
            addDevtools(app, router, matcher);
        }
    },
};
return router;

终于看到了router实例的样子了,也分析了install的时候主要干了啥:注册全局2个组件,添加全局属性,初始化push一次,注入3个属性方便内置组件提取,添加销毁事件监听。

然后看下组合api中用到的2个方法:

/**
    * Returns the router instance. Equivalent to using `$router` inside
    * templates.
    */
function useRouter() {
    // 提取实例router对象返回
    return vue.inject(routerKey);
}
/**
    * Returns the current route location. Equivalent to using `$route` inside
    * templates.
    */
function useRoute() {
    // 提取当前匹配的route location对象返回
    return vue.inject(routeLocationKey);
}

然后我们也顺便看下滚动有关的代码实现:

// 计算el元素相对文档在offset偏移量的基础下的 相对位置信息
function getElementPosition(el, offset) {
    const docRect = document.documentElement.getBoundingClientRect();
    const elRect = el.getBoundingClientRect();
    return {
        behavior: offset.behavior,
        left: elRect.left - docRect.left - (offset.left || 0),
        top: elRect.top - docRect.top - (offset.top || 0),
    };
}
// 获取页面的滚动位置信息
const computeScrollPosition = () => ({
    left: window.pageXOffset,
    top: window.pageYOffset,
});
// 实际滚动触发的执行代码实现
function scrollToPosition(position) {
    let scrollToOptions;
    // position中存在el选项的话
    if ('el' in position) {
        let positionEl = position.el;
        const isIdSelector = typeof positionEl === 'string' && positionEl.startsWith('#');
        /**
            * `id`s can accept pretty much any characters, including CSS combinators
            * like `>` or `~`. It's still possible to retrieve elements using
            * `document.getElementById('~')` but it needs to be escaped when using
            * `document.querySelector('#\\~')` for it to be valid. The only
            * requirements for `id`s are them to be unique on the page and to not be
            * empty (`id=""`). Because of that, when passing an id selector, it should
            * be properly escaped for it to work with `querySelector`. We could check
            * for the id selector to be simple (no CSS combinators `+ >~`) but that
            * would make things inconsistent since they are valid characters for an
            * `id` but would need to be escaped when using `querySelector`, breaking
            * their usage and ending up in no selector returned. Selectors need to be
            * escaped:
            *
            * - `#1-thing` becomes `#\31 -thing`
            * - `#with~symbols` becomes `#with\\~symbols`
            *
            * - More information about  the topic can be found at
            *   https://mathiasbynens.be/notes/html5-id-class.
            * - Practical example: https://mathiasbynens.be/demo/html5-id
            */
        if (typeof position.el === 'string') {
            if (!isIdSelector || !document.getElementById(position.el.slice(1))) {
                try {
                    let foundEl = document.querySelector(position.el);
                    if (isIdSelector && foundEl) {
                        warn(`The selector "${position.el}" should be passed as "el: document.querySelector('${position.el}')" because it starts with "#".`);
                        // return to avoid other warnings
                        return;
                    }
                }
                catch (err) {
                    warn(`The selector "${position.el}" is invalid. If you are using an id selector, make sure to escape it. You can find more information about escaping characters in selectors at https://mathiasbynens.be/notes/css-escapes or use CSS.escape (https://developer.mozilla.org/en-US/docs/Web/API/CSS/escape).`);
                    // return to avoid other warnings
                    return;
                }
            }
        }
        const el = typeof positionEl === 'string'
            ? isIdSelector
                ? document.getElementById(positionEl.slice(1))
                : document.querySelector(positionEl)
            : positionEl;
        if (!el) {
            warn(`Couldn't find element using selector "${position.el}" returned by scrollBehavior.`);
            return;
        }
        scrollToOptions = getElementPosition(el, position);
    }
    else {
        scrollToOptions = position;
    }
    // 滚动到指定位置即可
    if ('scrollBehavior' in document.documentElement.style)
        window.scrollTo(scrollToOptions);
    else {
        window.scrollTo(scrollToOptions.left != null ? scrollToOptions.left : window.pageXOffset, scrollToOptions.top != null ? scrollToOptions.top : window.pageYOffset);
    }
}
// 内置的几个辅助记录位置信息的数据结构和方法
function getScrollKey(path, delta) {
    const position = history.state ? history.state.position - delta : -1;
    return position + path;
}
const scrollPositions = new Map();
function saveScrollPosition(key, scrollPosition) {
    scrollPositions.set(key, scrollPosition);
}
function getSavedScrollPosition(key) {
    const scroll = scrollPositions.get(key);
    // consume it so it's not used again
    scrollPositions.delete(key);
    return scroll;
}

上面的是基础实现方法内容,再看下是如何使用它们的:

// 我们在调用实例的push等方法的时候 由于可能会回到之前的历史记录位置 如果用户设置了滚动开启 那就需要恢复位置信息
// Scroll behavior
function handleScroll(to, from, isPush, isFirstNavigation) {
    const { scrollBehavior } = options;
    if (!isBrowser || !scrollBehavior)
        return Promise.resolve();
    // 取出保存的位置信息
    let scrollPosition = (!isPush && getSavedScrollPosition(getScrollKey(to.fullPath, 0))) ||
        ((isFirstNavigation || !isPush) &&
            history.state &&
            history.state.scroll) ||
        null;
    return vue.nextTick()
        // 用户设置的函数  
        .then(() => scrollBehavior(to, from, scrollPosition))
        // 触发滚动操作
        .then(position => position && scrollToPosition(position))
        .catch(triggerError);
}

// 在 setupListeners 方法中 我们监听了popstate事件,保存了历史滚动的位置信息呢
// 记录当时的滚动信息
if (isBrowser) {
    saveScrollPosition(getScrollKey(from.fullPath, info.delta), computeScrollPosition());
}

Vue(v3.0.11)源码简析之运行时指令辅助方法withDirectives、app实例、性能测试辅助方法及组件和异步组件定义的相关实现

标题很长,因此包含几类代码片段,不过都不多,分开仔细读一下就好了:)

/**
Runtime helper for applying directives to a vnode. Example usage:

const comp = resolveComponent('comp')
const foo = resolveDirective('foo')
const bar = resolveDirective('bar')

return withDirectives(h(comp), [
  [foo, this.x],
  [bar, this.y]
])
*/
// vue中的内置指令
const isBuiltInDirective = /*#__PURE__*/ makeMap('bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text');
function validateDirectiveName(name) {
    if (isBuiltInDirective(name)) {
        warn('Do not use built-in directive ids as custom directive id: ' + name);
    }
}
/**
 * Adds directives to a VNode.
 */
// 编译得到的render函数中 对vnode上存在运行时改变vnode指令的情况 最终会用 withDirectives 来包裹vnode 如上面的注释一样的形式 上面的内置指令只有 v-show 符合
// 用户的自定义指令都是会被视为运行时指令的
function withDirectives(vnode, directives) {
    const internalInstance = currentRenderingInstance;
    // 约束使用场景
    if (internalInstance === null) {
        warn(`withDirectives can only be used inside render functions.`);
        return vnode;
    }
    // 这个值应该是 平常用到的this
    const instance = internalInstance.proxy;
    const bindings = vnode.dirs || (vnode.dirs = []);
    for (let i = 0; i < directives.length; i++) {
        // 编译得到的指令语句中的信息都存储在directives中
        let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i];
        // dir写函数的话 默认只有2个vnode钩子
        if (isFunction(dir)) {
            dir = {
                mounted: dir,
                updated: dir
            };
        }
        // 推入指令所有需要的参数 收集起来
        bindings.push({
            dir,
            instance,
            value,
            oldValue: void 0,
            arg,
            modifiers
        });
    }
    // 还是返回这个vnode即可 只是加个指令对象而已
    return vnode;
}
// 触发vnode指定阶段name的钩子函数 在后面vnode的生命周期中会看到这个方法的应用的
function invokeDirectiveHook(vnode, prevVNode, instance, name) {
    const bindings = vnode.dirs;
    const oldBindings = prevVNode && prevVNode.dirs;
    for (let i = 0; i < bindings.length; i++) {
        const binding = bindings[i];
        // 获取旧值参数
        if (oldBindings) {
            binding.oldValue = oldBindings[i].value;
        }
        // 取出指定类型钩子
        const hook = binding.dir[name];
        if (hook) {
            // 注意最后的参数列表 对应文档中描述的 指令钩子的 参数列表
            callWithAsyncErrorHandling(hook, instance, 8 /* DIRECTIVE_HOOK */, [
                vnode.el,
                binding,
                vnode,
                prevVNode
            ]);
        }
    }
}

// 一个app实例对应一个app上下文 用来存储一些用户添加的全局配置
function createAppContext() {
    return {
        // 指向对应的app实例
        app: null,
        // 对应文档中描述的支持用户添加的自定义方法
        config: {
            isNativeTag: NO,
            performance: false,
            globalProperties: {},
            optionMergeStrategies: {},
            isCustomElement: NO,
            errorHandler: undefined,
            warnHandler: undefined
        },
        // app.mixin 添加的全局混入对象存储仓库
        mixins: [],
        // app.component 添加的全局对象存储仓库
        components: {},
        // app.directive 添加的全局指令存储仓库
        directives: {},
        // app.provide 添加的provide对象存储仓库
        provides: Object.create(null)
    };
}
let uid$1 = 0;
// 创建一个app实例的通用方法 也是返回一个闭包函数 持有 render 这个函数作为自由变量 hydrate跟SSR有关先忽略
function createAppAPI(render, hydrate) {
    // 闭包函数 rootComponent就是我们平常自己的组件配置对象 rootProps 手动添加的根props
    return function createApp(rootComponent, rootProps = null) {
        if (rootProps != null && !isObject(rootProps)) {
            warn(`root props passed to app.mount() must be an object.`);
            rootProps = null;
        }
        // 生成一个上下文
        const context = createAppContext();
        // 添加的插件对象集合
        const installedPlugins = new Set();
        // 是否挂载到宿主DOM的标志位
        let isMounted = false;
        // 实际的app实例 和context相互持有对方引用
        const app = (context.app = {
            // id
            _uid: uid$1++,
            // 原始配置对象
            _component: rootComponent,
            // 原始根props
            _props: rootProps,
            // 挂载的宿主DOM元素
            _container: null,
            // 指向上下文
            _context: context,
            // vue版本
            version,
            // 操作实例中的config仓库
            get config() {
                return context.config;
            },
            // 对应文档中的单个属性设置
            set config(v) {
                {
                    warn(`app.config cannot be replaced. Modify individual options instead.`);
                }
            },
            // 和插件约定的api
            use(plugin, ...options) {
                if (installedPlugins.has(plugin)) {
                    warn(`Plugin has already been applied to target app.`);
                }
                else if (plugin && isFunction(plugin.install)) {
                    installedPlugins.add(plugin);
                    // 调用插件约定的api 执行插件的内部逻辑即可
                    plugin.install(app, ...options);
                }
                else if (isFunction(plugin)) {
                    installedPlugins.add(plugin);
                    plugin(app, ...options);
                }
                else {
                    warn(`A plugin must either be a function or an object with an "install" ` +
                        `function.`);
                }
                // 链式调用
                return app;
            },
            // 添加全局mixin对象
            mixin(mixin) {
                {
                    if (!context.mixins.includes(mixin)) {
                        context.mixins.push(mixin);
                        // global mixin with props/emits de-optimizes props/emits
                        // normalization caching.
                        // 被添加的mixin存在props或emits deopt置位 之前分析过的对组件实例挂载 解析初始化的props和emits的时候会用到
                        if (mixin.props || mixin.emits) {
                            context.deopt = true;
                        }
                    }
                    else {
                        warn('Mixin has already been applied to target app' +
                            (mixin.name ? `: ${mixin.name}` : ''));
                    }
                }
                return app;
            },
            // 添加全局组件
            component(name, component) {
                {
                    // 组件名不能是 内置组件名字和html原始标签
                    validateComponentName(name, context.config);
                }
                if (!component) {
                    return context.components[name];
                }
                if (context.components[name]) {
                    warn(`Component "${name}" has already been registered in target app.`);
                }
                context.components[name] = component;
                return app;
            },
            // 添加全局指令
            directive(name, directive) {
                {
                    // 不能是内置指令
                    validateDirectiveName(name);
                }
                if (!directive) {
                    return context.directives[name];
                }
                if (context.directives[name]) {
                    warn(`Directive "${name}" has already been registered in target app.`);
                }
                context.directives[name] = directive;
                return app;
            },
            // 挂载到宿主dom元素上去
            mount(rootContainer, isHydrate, isSVG) {
                if (!isMounted) {
                    // 创建根对象组件类型的vnode 这种vnode并不立即给render函数去挂载到dom上 
                    // 还需要经过一些列的转化后才可以
                    const vnode = createVNode(rootComponent, rootProps);
                    // store app context on the root VNode.
                    // this will be set on the root instance on initial mount.
                    // vnode持有上下文 上下文持有app实例 这样vnode上就有所有的信息了
                    vnode.appContext = context;
                    // HMR root reload
                    {
                        context.reload = () => {
                            render(cloneVNode(vnode), rootContainer, isSVG);
                        };
                    }
                    if (isHydrate && hydrate) {
                        hydrate(vnode, rootContainer);
                    }
                    // 我们先只关注这里就好了
                    else {
                        // 调用render对象传来的render方法 根据组件vnode 宿主dom元素 进行渲染
                        // 这个方法完成了组件实例的创建 模板编译得到vnode 然后挂载到dom上 这一整个大过程
                        render(vnode, rootContainer, isSVG);
                    }
                    // 同步渲染完后 挂载标志置位
                    isMounted = true;
                    // 指向宿主dom元素
                    app._container = rootContainer;
                    // 宿主元素指向app实例
                    rootContainer.__vue_app__ = app;
                    {
                        // 忽略目前
                        devtoolsInitApp(app, version);
                    }
                    // render过程中给创建了组件实例对象 它上面有个proxy 也就是常用的this
                    // 分析见后文
                    return vnode.component.proxy;
                }
                else {
                    warn(`App has already been mounted.\n` +
                        `If you want to remount the same app, move your app creation logic ` +
                        `into a factory function and create fresh app instances for each ` +
                        `mount - e.g. \`const createMyApp = () => createApp(App)\``);
                }
            },
            // 卸载app
            unmount() {
                if (isMounted) {
                    // 还是调用render 参数为null即可
                    render(null, app._container);
                    {
                        devtoolsUnmountApp(app);
                    }
                    // 移除dom对app的引用
                    delete app._container.__vue_app__;
                }
                else {
                    warn(`Cannot unmount an app that is not mounted.`);
                }
            },
            // 提供处于可被任意深度的子组件注入的对象
            provide(key, value) {
                if (key in context.provides) {
                    warn(`App already provides property with key "${String(key)}". ` +
                        `It will be overwritten with the new value.`);
                }
                // TypeScript doesn't allow symbols as index type
                // https://github.com/Microsoft/TypeScript/issues/24587
                context.provides[key] = value;
                return app;
            }
        });
        // 返回实例对象
        return app;
    };
}
let supported;
let perf;
// 打上开始的标记
function startMeasure(instance, type) {
    if (instance.appContext.config.performance && isSupported()) {
        perf.mark(`vue-${type}-${instance.uid}`);
    }
}
// 计算开销 打上统计标记 消除开始结束标记
function endMeasure(instance, type) {
    if (instance.appContext.config.performance && isSupported()) {
        const startTag = `vue-${type}-${instance.uid}`;
        const endTag = startTag + `:end`;
        // 结束标记
        perf.mark(endTag);
        // 打上统计标记
        perf.measure(`<${formatComponentName(instance, instance.type)}> ${type}`, startTag, endTag);
        // 清除开始和结束标记 保留统计的
        perf.clearMarks(startTag);
        perf.clearMarks(endTag);
    }
}
// 测试是否支持性能测试方法 window.performance
function isSupported() {
    if (supported !== undefined) {
        return supported;
    }
    /* eslint-disable no-restricted-globals */
    if (typeof window !== 'undefined' && window.performance) {
        supported = true;
        perf = window.performance;
    }
    else {
        supported = false;
    }
    /* eslint-enable no-restricted-globals */
    return supported;
}

// implementation, close to no-op
// 格式化组件格式 归一成对象格式
function defineComponent(options) {
    return isFunction(options) ? { setup: options, name: options.name } : options;
}

// 异步组件的格式化
// 异步组件拥有统一的外部包装组件 同时这个包装组件的render返回promise加载的实际组件对象给调用者
const isAsyncWrapper = (i) => !!i.type.__asyncLoader;
// 格式化异步组件具体实现
function defineAsyncComponent(source) {
    // 统一对象格式
    if (isFunction(source)) {
        source = { loader: source };
    }
    // 异步组件配置项支持的选项都在这里了
    const { loader, loadingComponent, errorComponent, delay = 200, timeout, // undefined = never times out
    suspensible = true, onError: userOnError } = source;
    // 已经触发但是尚未结束的promise
    let pendingRequest = null;
    // 从promise中返回的组件对象
    let resolvedComp;
    // 重新执行load的次数
    let retries = 0;
    // 再次尝试加载组件
    const retry = () => {
        retries++;
        // 重新开始
        pendingRequest = null;
        return load();
    };
    // 加载异步组件方法
    const load = () => {
        let thisRequest;
        // 由于load会被多次调用 每次调用得到一个promise实例 用thisRequest在它的then用来识别自己是哪个promise
        return (pendingRequest ||
            (thisRequest = pendingRequest = loader()
                .catch(err => {
                err = err instanceof Error ? err : new Error(String(err));
                // 用用户自定的方法捕获错误
                if (userOnError) {
                    return new Promise((resolve, reject) => {
                        const userRetry = () => resolve(retry());
                        const userFail = () => reject(err);
                        // 对应文档中描述的参数
                        userOnError(err, userRetry, userFail, retries + 1);
                    });
                }
                else {
                    throw err;
                }
            })
                .then((comp) => {
                // 等待上次promise的结果就可以了
                if (thisRequest !== pendingRequest && pendingRequest) {
                    return pendingRequest;
                }
                if (!comp) {
                    warn(`Async component loader resolved to undefined. ` +
                        `If you are using retry(), make sure to return its return value.`);
                }
                // interop module default
                if (comp &&
                    (comp.__esModule || comp[Symbol.toStringTag] === 'Module')) {
                    comp = comp.default;
                }
                if (comp && !isObject(comp) && !isFunction(comp)) {
                    throw new Error(`Invalid async component load result: ${comp}`);
                }
                resolvedComp = comp;
                return comp;
            })));
    };
    return defineComponent({
        // 异步组件包装对象独有的属性 同时也是触发加载promise的实现方法
        __asyncLoader: load,
        // 统一name
        name: 'AsyncComponentWrapper',
        // setup配置项 返回值如果是函数 则作为这个组件的render函数 分析见后文
        // setup函数本身会在创建组件实例以及配置各个属性的阶段中执行
        setup() {
            // 组件实例
            const instance = currentInstance;
            // already resolved
            // 组件再次重新生成的时候这个方法还会执行 所以自由变量resolvedComp上次已经存在了
            if (resolvedComp) {
                // 返回新的render函数 调用 createInnerComp 得到vnode
                return () => createInnerComp(resolvedComp, instance);
            }
            // 加载失败的handler
            const onError = (err) => {
                // 重新开始
                pendingRequest = null;
                handleError(err, instance, 13 /* ASYNC_COMPONENT_LOADER */, !errorComponent /* do not throw in dev if user provided error component */);
            };
            // suspense-controlled or SSR.
            // 开启suspensible后 如果异步组件上面的组件中只要存在一个suspense
            // 那么异步组件自身的其他配置项将会失效 由suspense来控制异步组件加载等逻辑
            // 文档中有描述 suspense 具体如何做的见后文的分析
            if ((suspensible && instance.suspense) ||
                (false )) {
                // 运行load加载组件返回promise给组件实例 组件实例负责处理这种情况 详见后文
                return load()
                    .then(comp => {
                    // then运行返回这个render函数 同上面的情况
                    return () => createInnerComp(comp, instance);
                })
                    .catch(err => {
                    onError(err);
                    // 用户配置的失败组件
                    return () => errorComponent
                        ? createVNode(errorComponent, {
                            error: err
                        })
                        : null;
                });
            }
            // 创建响应式的几个变量 用于建立依赖收集 让promise.then执行后 render可以重新执行
            const loaded = ref(false);
            const error = ref();
            const delayed = ref(!!delay);
            // 延迟展示loading
            if (delay) {
                setTimeout(() => {
                    delayed.value = false;
                }, delay);
            }
            // 加载异步组件超时
            if (timeout != null) {
                setTimeout(() => {
                    if (!loaded.value && !error.value) {
                        const err = new Error(`Async component timed out after ${timeout}ms.`);
                        onError(err);
                        error.value = err;
                    }
                }, timeout);
            }
            // 追加load完成后 通过ref响应式trigger effect和render重新执行
            load()
                .then(() => {
                // trigger
                loaded.value = true;
            })
                .catch(err => {
                onError(err);
                // trigger
                error.value = err;
            });
            // 返回函数作为render
            return () => {
                // 初次执行没有 再次的时候可能已经成功得到了
                if (loaded.value && resolvedComp) {
                    return createInnerComp(resolvedComp, instance);
                }
                // 失败了这次
                else if (error.value && errorComponent) {
                    return createVNode(errorComponent, {
                        error: error.value
                    });
                }
                // 延迟展示loading组件
                else if (loadingComponent && !delayed.value) {
                    return createVNode(loadingComponent);
                }
            };
        }
    });
}
// 异步包装组件返回的内部组件vnode 要把ref属性继承到实际的内部组件上来 ref属性包含这个ref属于哪个父组件的信息
function createInnerComp(comp, { vnode: { ref, props, children } }) {
    const vnode = createVNode(comp, props, children);
    // ensure inner component inherits the async wrapper's ref owner
    vnode.ref = ref;
    return vnode;
}

Vue(v3.0.11)源码简析之组件实例的相关实现

vue如何根据我们的配置组件对象得到实际的vue组件实例,它上面的诸多属性时如何初始化且正确由我们的配置而一一对应赋值呢?

// 创建一个空的app上下文
const emptyAppContext = createAppContext();
let uid$2 = 0;
// 组件实例对象的创建
function createComponentInstance(vnode, parent, suspense) {
    const type = vnode.type;
    // inherit parent app context - or - if root, adopt from root vnode
    // 所有组件实例共享一个app上下文 所以可以继承全局配置项
    const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;
    // 组件实例对象字段
    const instance = {
        // id
        uid: uid$2++,
        // 既然是组件 那就是组件类型的vnode
        vnode,
        // 原始组件配置对象
        type,
        // 父组件实例
        parent,
        // 共享的app上下文
        appContext,
        // 根组件实例对象
        root: null,
        // 新的子树vnode
        next: null,
        // 子树vnode
        subTree: null,
        // render effect
        update: null,
        // render函数
        render: null,
        // this
        proxy: null,
        // this
        exposed: null,
        // render运行时的this
        withProxy: null,
        // render过程中有产生的effect会被收集到这里来
        effects: null,
        // 提供可被提取的对象集合
        provides: parent ? parent.provides : Object.create(appContext.provides),
        // get的key的类型缓存
        accessCache: null,
        // 缓存的vnode
        renderCache: [],
        // local resovled assets
        // 局部组件
        components: null,
        // 局部指令
        directives: null,
        // resolved props and emits options
        // 根据用户配置的对象 解析包含对应key的集合 用于校验实际用户的key属于其中不用来
        propsOptions: normalizePropsOptions(type, appContext),
        // 同上
        emitsOptions: normalizeEmitsOptions(type, appContext),
        // emit
        // emit方法
        emit: null,
        // 缓存once 只触发一次的方法
        emitted: null,
        // props default value
        // 存在默认值的prop配置项 缓存结果 不用多次执行同一个方法了
        propsDefaults: EMPTY_OBJ,
        // state
        // 存储用户数据的几个数据仓库
        ctx: EMPTY_OBJ,
        data: EMPTY_OBJ,
        props: EMPTY_OBJ,
        // 非prop emits之外的属性
        attrs: EMPTY_OBJ,
        // 使用组件如果里面写了插槽的 插槽内容
        slots: EMPTY_OBJ,
        // 组件上存在ref属性
        refs: EMPTY_OBJ,
        // setup上下文数据仓库
        setupState: EMPTY_OBJ,
        // setup上下文
        setupContext: null,
        // suspense related
        // suspense控制对象
        suspense,
        suspenseId: suspense ? suspense.pendingId : 0,
        // 异步依赖promise
        asyncDep: null,
        // 异步结果已解析标志位
        asyncResolved: false,
        // lifecycle hooks
        // not using enums here because it results in computed properties
        // 所处的几个状态
        isMounted: false,
        isUnmounted: false,
        isDeactivated: false,
        // 钩子们
        bc: null,
        c: null,
        bm: null,
        m: null,
        bu: null,
        u: null,
        um: null,
        bum: null,
        da: null,
        a: null,
        rtg: null,
        rtc: null,
        ec: null
    };
    {
        // ctx由此构建而来
        instance.ctx = createRenderContext(instance);
    }
    // root指向根组件实例对象
    instance.root = parent ? parent.root : instance;
    // 其实就是调用前文分析过的emit方法
    instance.emit = emit.bind(null, instance);
    return instance;
}
let currentInstance = null;
// 组件实例解析过程存在的 currentInstance 某个组件render过程的存在的 currentRenderingInstance
const getCurrentInstance = () => currentInstance || currentRenderingInstance;
const setCurrentInstance = (instance) => {
    currentInstance = instance;
};
// 组件名字不得是保留组件和原始html标签
const isBuiltInTag = /*#__PURE__*/ makeMap('slot,component');
function validateComponentName(name, config) {
    const appIsNativeTag = config.isNativeTag || NO;
    if (isBuiltInTag(name) || appIsNativeTag(name)) {
        warn('Do not use built-in or reserved HTML elements as component id: ' + name);
    }
}
// 状态组件类型 就是我们平常写的自定义组件这种
function isStatefulComponent(instance) {
    return instance.vnode.shapeFlag & 4 /* STATEFUL_COMPONENT */;
}
let isInSSRComponentSetup = false;
// 之前mountComponent用到的 导入挂载组件实例各个属性 的方法实现
function setupComponent(instance, isSSR = false) {
    isInSSRComponentSetup = isSSR;
    const { props, children } = instance.vnode;
    const isStateful = isStatefulComponent(instance);
    // 编译模板得到的vnode的用户传入的prop数据 初始化导入到组件实例上
    initProps(instance, props, isStateful, isSSR);
    // 编译模板得到的slots对象 把它的信息同步到组件实例上去
    initSlots(instance, children);
    // 还需要 setupStatefulComponent 设置其他剩余的属性
    const setupResult = isStateful
        ? setupStatefulComponent(instance, isSSR)
        : undefined;
    isInSSRComponentSetup = false;
    return setupResult;
}
// 处理完props slot之后继续解析处理剩余的属性
function setupStatefulComponent(instance, isSSR) {
    const Component = instance.type;
    {
        // 校验名字
        if (Component.name) {
            validateComponentName(Component.name, instance.appContext.config);
        }
        if (Component.components) {
            const names = Object.keys(Component.components);
            for (let i = 0; i < names.length; i++) {
                validateComponentName(names[i], instance.appContext.config);
            }
        }
        if (Component.directives) {
            const names = Object.keys(Component.directives);
            for (let i = 0; i < names.length; i++) {
                validateDirectiveName(names[i]);
            }
        }
    }
    // 0. create render proxy property access cache
    instance.accessCache = Object.create(null);
    // 1. create public instance / render proxy
    // also mark it raw so it's never observed
    // 终于得到proxy了 它就是我们的this对象了 其实是 ctx 的代理呢
    instance.proxy = new Proxy(instance.ctx, PublicInstanceProxyHandlers);
    {
        // 挂载props的key
        exposePropsOnRenderContext(instance);
    }
    // 2. call setup()
    const { setup } = Component;
    // 有setup配置项  优先处理它
    if (setup) {
        // setup需要使用第二个参数context的情况才需要创建 setup上下文
        const setupContext = (instance.setupContext =
            setup.length > 1 ? createSetupContext(instance) : null);
        // currentInstance取当前组件实例
        currentInstance = instance;
        pauseTracking();
        // setup方法执行过程中不触发依赖收集 注意2个参数 第一个是 props 不做响应式 第二个就是创建的setup上下文了
        const setupResult = callWithErrorHandling(setup, instance, 0 /* SETUP_FUNCTION */, [shallowReadonly(instance.props) , setupContext]);
        resetTracking();
        currentInstance = null;
        // 对应文档描述 setup返回值是个promise的话 
        if (isPromise(setupResult)) {
            if (isSSR) {
                // return the promise so server-renderer can wait on it
                return setupResult
                    .then((resolvedResult) => {
                    handleSetupResult(instance, resolvedResult, isSSR);
                })
                    .catch(e => {
                    handleError(e, instance, 0 /* SETUP_FUNCTION */);
                });
            }
            else {
                // async setup returned Promise.
                // bail here and wait for re-entry.
                // 指向异步依赖的promise就好了
                instance.asyncDep = setupResult;
            }
        }
        // 处理setup返回的结果
        else {
            handleSetupResult(instance, setupResult, isSSR);
        }
    }
    // 没有setup 我们继续处理剩下的配置选项
    else {
        finishComponentSetup(instance, isSSR);
    }
}
function handleSetupResult(instance, setupResult, isSSR) {
    // 返回的函数 作为组件实例的render
    if (isFunction(setupResult)) {
        // setup returned an inline render function
        {
            instance.render = setupResult;
        }
    }
    else if (isObject(setupResult)) {
        if (isVNode(setupResult)) {
            warn(`setup() should not return VNodes directly - ` +
                `return a render function instead.`);
        }
        // setup returned bindings.
        // assuming a render function compiled from template is present.
        {
            // 原始数据仓库
            instance.devtoolsRawSetupState = setupResult;
        }
        // 创建setup数据仓库 注意 setupResult 可以是一些refs对象
        instance.setupState = proxyRefs(setupResult);
        {
            // 挂载属性到ctx上 让this可获取这个key
            exposeSetupStateOnRenderContext(instance);
        }
    }
    else if (setupResult !== undefined) {
        warn(`setup() should return an object. Received: ${setupResult === null ? 'null' : typeof setupResult}`);
    }
    // 同样继续完成剩余配置项的处理
    finishComponentSetup(instance, isSSR);
}
let compile;
// dev only
// 运行时环境 compile 编译函数是存在的 见后文
const isRuntimeOnly = () => !compile;
/**
 * For runtime-dom to register the compiler.
 * Note the exported method uses any to avoid d.ts relying on the compiler types.
 */
// 后文会用到它注册运行时的dom编译函数
function registerRuntimeCompiler(_compile) {
    compile = _compile;
}
// 完成组件实例剩余属性的挂载
function finishComponentSetup(instance, isSSR) {
    const Component = instance.type;
    // template / render function normalization
    if (!instance.render) {
        // could be set from setup()
        if (compile && Component.template && !Component.render) {
            {
                startMeasure(instance, `compile`);
            }
            // 解析template 构造render函数了
            Component.render = compile(Component.template, {
                isCustomElement: instance.appContext.config.isCustomElement,
                delimiters: Component.delimiters
            });
            {
                endMeasure(instance, `compile`);
            }
        }
        instance.render = (Component.render || NOOP);
        // for runtime-compiled render functions using `with` blocks, the render
        // proxy used needs a different `has` handler which is more performant and
        // also only allows a whitelist of globals to fallthrough.
        if (instance.render._rc) {
            // withProxy 就是多一个has检测而已
            instance.withProxy = new Proxy(instance.ctx, RuntimeCompiledPublicInstanceProxyHandlers);
        }
    }
    // support for 2.x options
    {
        // applyOptions挂载其他配置属性 详情见上面的分析
        currentInstance = instance;
        pauseTracking();
        applyOptions(instance, Component);
        resetTracking();
        currentInstance = null;
    }
    // 至此组件构建完毕 所有属性赋值完成
    // warn missing template/render
    // the runtime compilation of template in SSR is done by server-render
    if (!Component.render && instance.render === NOOP && !isSSR) {
        /* istanbul ignore if */
        if (!compile && Component.template) {
            warn(`Component provided template option but ` +
                `runtime compilation is not supported in this build of Vue.` +
                (` Use "vue.global.js" instead.`
                            ) /* should not happen */);
        }
        else {
            warn(`Component is missing template or render function.`);
        }
    }
}
// attrs的代理handler
const attrHandlers = {
    get: (target, key) => {
        {
            // 标志位置位
            markAttrsAccessed();
        }
        return target[key];
    },
    // 只读
    set: () => {
        warn(`setupContext.attrs is readonly.`);
        return false;
    },
    deleteProperty: () => {
        warn(`setupContext.attrs is readonly.`);
        return false;
    }
};
// setup上下文 作为setup函数的第二个参数
function createSetupContext(instance) {
    const expose = exposed => {
        if (instance.exposed) {
            warn(`expose() should be called only once per setup().`);
        }
        instance.exposed = proxyRefs(exposed);
    };
    {
        // We use getters in dev in case libs like test-utils overwrite instance
        // properties (overwrites should not be done in prod)
        return Object.freeze({
            get attrs() {
                return new Proxy(instance.attrs, attrHandlers);
            },
            get slots() {
                return shallowReadonly(instance.slots);
            },
            get emit() {
                return (event, ...args) => instance.emit(event, ...args);
            },
            // 原来还有第四个属性
            expose
        });
    }
}
// record effects created during a component's setup() so that they can be
// stopped when the component unmounts
// setup过程中收集产生的effect 到时候卸载的时候方便统一移除
function recordInstanceBoundEffect(effect, instance = currentInstance) {
    if (instance) {
        (instance.effects || (instance.effects = [])).push(effect);
    }
}
const classifyRE = /(?:^|[-_])(\w)/g;
const classify = (str) => str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '');
// 获取组件名字
function getComponentName(Component) {
    return isFunction(Component)
        ? Component.displayName || Component.name
        : Component.name;
}
/* istanbul ignore next */
// 格式化组件名字 也会尝试从 局部组件 父组件的局部组件 以及全局组件中寻找
function formatComponentName(instance, Component, isRoot = false) {
    let name = getComponentName(Component);
    if (!name && Component.__file) {
        const match = Component.__file.match(/([^/\\]+)\.\w+$/);
        if (match) {
            name = match[1];
        }
    }
    if (!name && instance && instance.parent) {
        // try to infer the name based on reverse resolution
        const inferFromRegistry = (registry) => {
            for (const key in registry) {
                if (registry[key] === Component) {
                    return key;
                }
            }
        };
        name =
            inferFromRegistry(instance.components ||
                instance.parent.type.components) || inferFromRegistry(instance.appContext.components);
    }
    return name ? classify(name) : isRoot ? `App` : `Anonymous`;
}
// 函数式组件
function isClassComponent(value) {
    return isFunction(value) && '__vccOpts' in value;
}

// computed表达式在解析的时候 也会产生effect 需要被收集
function computed$1(getterOrOptions) {
    const c = computed(getterOrOptions);
    recordInstanceBoundEffect(c.effect);
    return c;
}

简单聊一聊 Webpack tapable 的实现(2)

  1. SyncLoopHook 同步循环钩子:只有某个tap的返回值不为undefined就一直执行它 直到它成为undefine再继续后面的
    /tapable/lib/SyncLoopHook.js
class SyncLoopHookCodeFactory extends HookCodeFactory {
	content({ onError, onDone, rethrowIfPossible }) {
        // 直接调用的是基类的 callTapsLooping
		return this.callTapsLooping({
			onError: (i, err) => onError(err),
			onDone,
			rethrowIfPossible
		});
	}
}
const hook = new SyncLoopHook();
let index = 0;
hook.tap('logPlugin', () => {console.log('被勾了'); if (index++ < 5) {return 1};});
hook.tap('logPlugin1', () => {console.log('被勾了1')} );
hook.tap('logPlugin2', () => {console.log('被勾了2')});
hook.call();

// 得到的call是这样的:
(function anonymous(
    ) {
        "use strict";
        var _context;
        var _x = this._x;
        var _loop;
        do {
            _loop = false;
            var _fn0 = _x[0];
            var _result0 = _fn0();
            if (_result0 !== undefined) {
                _loop = true;
            } else {
                var _fn1 = _x[1];
                var _result1 = _fn1();
                if (_result1 !== undefined) {
                    _loop = true;
                } else {
                    var _fn2 = _x[2];
                    var _result2 = _fn2();
                    if (_result2 !== undefined) {
                        _loop = true;
                    } else {
                        if (!_loop) {
                        }
                    }
                }
            }
        } while (_loop);

    })

从callTapsLooping代码可以看出onResult和onDone是如何追加控制连接代码到每个基础tap代码上的

  1. AsyncSeriesHook 异步tap同步执行钩子:一些本身是异步函数按照同步方式逐一执行的钩子
    /tapable/lib/AsyncSeriesHook.js
class AsyncSeriesHookCodeFactory extends HookCodeFactory {
	content({ onError, onDone }) {
        // 也是调用 基类的 callTapsSeries 根据tap本身的区别来生成不同的代码
		return this.callTapsSeries({
			onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
			onDone
		});
	}
}
const hook = new AsyncSeriesHook();
hook.tapAsync('logPlugin', (callback) => {
setTimeout(() => {
    console.log('计算路线1');
    callback();
}, 1000);
});
hook.tapAsync('logPlugin1', (callback) => {
setTimeout(() => {
    console.log('计算路线2');
    callback();
}, 2000);
});
hook.tapAsync('logPlugin2', (callback) => {
setTimeout(() => {
    console.log('计算路线3');
    callback();
}, 3000);
});
hook.callAsync(() => { console.log('最终的回调'); });

// 得到的call是这样的:
(function anonymous(_callback
    ) {
        "use strict";
        var _context;
        var _x = this._x;
        function _next1() {
            var _fn2 = _x[2];
            _fn2((function (_err2) {
                if (_err2) {
                    _callback(_err2);
                } else {
                    _callback();
                }
            }));
        }
        function _next0() {
            var _fn1 = _x[1];
            _fn1((function (_err1) {
                if (_err1) {
                    _callback(_err1);
                } else {
                    _next1();
                }
            }));
        }
        var _fn0 = _x[0];
        _fn0((function (_err0) {
            if (_err0) {
                _callback(_err0);
            } else {
                _next0();
            }
        }));

    })

仔细回顾一下 对于异步tap的callTapsSeries 和 callTap 是如何添加next,然后放到追加到后面的就明白上面的代码如何得到了

  1. AsyncSeriesBailHook 异步tap同步熔断执行:一些个异步函数 同步执行 但是如果有个返回undefined就停止执行后面的
    /tapable/lib/AsyncSeriesBailHook.js
class AsyncSeriesBailHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, resultReturns, onDone }) {
		return this.callTapsSeries({
			onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
            // 主要还是对结果有依赖 所以添加对结果的额外处理
			onResult: (i, result, next) =>
				`if(${result} !== undefined) {\n${onResult(
					result
				)}\n} else {\n${next()}}\n`,
			resultReturns,
			onDone
		});
	}
}
const hook = new AsyncSeriesBailHook();
hook.tapAsync('logPlugin', (callback) => {
setTimeout(() => {
    console.log('计算路线1');
    callback();
}, 1000);
});
hook.tapAsync('logPlugin1', (callback) => {
setTimeout(() => {
    console.log('计算路线2');
    callback(null, 1);
}, 2000);
});
hook.tapAsync('logPlugin2', (callback) => {
setTimeout(() => {
    console.log('计算路线3');
    callback();
}, 3000);
});
hook.callAsync(() => { console.log('最终的回调'); });

// 得到的call是这样的:

(function anonymous(_callback
    ) {
        "use strict";
        var _context;
        var _x = this._x;
        function _next1() {
            var _fn2 = _x[2];
            _fn2((function (_err2, _result2) {
                if (_err2) {
                    _callback(_err2);
                } else {
                    if (_result2 !== undefined) {
                        _callback(null, _result2);

                    } else {
                        _callback();
                    }
                }
            }));
        }
        function _next0() {
            var _fn1 = _x[1];
            _fn1((function (_err1, _result1) {
                if (_err1) {
                    _callback(_err1);
                } else {
                    if (_result1 !== undefined) {
                        _callback(null, _result1);

                    } else {
                        _next1();
                    }
                }
            }));
        }
        var _fn0 = _x[0];
        _fn0((function (_err0, _result0) {
            if (_err0) {
                _callback(_err0);
            } else {
                if (_result0 !== undefined) {
                    _callback(null, _result0);

                } else {
                    _next0();
                }
            }
        }));

    })

异步函数主要是添加next来包裹下一个执行的tap,然后根据结果的依赖方式来决定如何调用next,以及是否执行最终的用户callback

  1. AsyncSeriesWaterfallHook 瀑布流同步钩子 不过执行的每个都是异步函数
    /tapable/lib/AsyncSeriesWaterfallHook.js
class AsyncSeriesWaterfallHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone }) {
		return this.callTapsSeries({
			onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
            // 跟同步差不多 主要下个tap的执行用next包裹替代了
			onResult: (i, result, next) => {
				let code = "";
				code += `if(${result} !== undefined) {\n`;
				code += `${this._args[0]} = ${result};\n`;
				code += `}\n`;
				code += next();
				return code;
			},
			onDone: () => onResult(this._args[0])
		});
	}
}
const hook = new AsyncSeriesWaterfallHook(['arg1']);
hook.tapAsync('logPlugin', (callback) => {
setTimeout(() => {
    console.log('计算路线1');
    callback(null, 1);
}, 1000);
});
hook.tapAsync('logPlugin1', (res, callback) => {
setTimeout(() => {
    console.log('计算路线2');
    callback(null, 2);
}, 2000);
});
hook.tapAsync('logPlugin2', (res, callback) => {
setTimeout(() => {
    console.log('计算路线3');
    callback();
}, 3000);
});
hook.callAsync(() => { console.log('最终的回调'); });

// 得到的call是这样的:
(function anonymous(arg1, _callback
    ) {
        "use strict";
        var _context;
        var _x = this._x;
        function _next1() {
            var _fn2 = _x[2];
            _fn2(arg1, (function (_err2, _result2) {
                if (_err2) {
                    _callback(_err2);
                } else {
                    if (_result2 !== undefined) {
                        arg1 = _result2;
                    }
                    _callback(null, arg1);
                }
            }));
        }
        function _next0() {
            var _fn1 = _x[1];
            _fn1(arg1, (function (_err1, _result1) {
                if (_err1) {
                    _callback(_err1);
                } else {
                    if (_result1 !== undefined) {
                        arg1 = _result1;
                    }
                    _next1();
                }
            }));
        }
        var _fn0 = _x[0];
        _fn0(arg1, (function (_err0, _result0) {
            if (_err0) {
                _callback(_err0);
            } else {
                if (_result0 !== undefined) {
                    arg1 = _result0;
                }
                _next0();
            }
        }));

    })

跟熔断差不多,只不过对结果的处理方式不同而已

  1. AsyncSeriesLoopHook 异步函数循环执行
class AsyncSeriesLoopHookCodeFactory extends HookCodeFactory {
	content({ onError, onDone }) {
        // 直接调用 基类的 callTapsLooping
		return this.callTapsLooping({
			onError: (i, err, next, doneBreak) => onError(err) + doneBreak(true),
			onDone
		});
	}
}
let index = 0;
const hook = new AsyncSeriesLoopHook();
hook.tapAsync('logPlugin', (callback) => {
setTimeout(() => {
    console.log('计算路线1');
    if (index++ < 5) {
    callback(null, 1);
    }
}, 1000);
});
hook.tapAsync('logPlugin1', (callback) => {
setTimeout(() => {
    console.log('计算路线2');
    callback();
}, 2000);
});
hook.tapAsync('logPlugin2', (callback) => {
setTimeout(() => {
    console.log('计算路线3');
    callback();
}, 3000);
});
hook.callAsync(() => { console.log('最终的回调'); });

// 得到的call是这样的:
(function anonymous(_callback
    ) {
        "use strict";
        var _context;
        var _x = this._x;
        var _looper = (function () {
            var _loopAsync = false;
            var _loop;
            do {
                _loop = false;
                function _next1() {
                    var _fn2 = _x[2];
                    _fn2((function (_err2, _result2) {
                        if (_err2) {
                            _callback(_err2);
                        } else {
                            if (_result2 !== undefined) {
                                _loop = true;
                                if (_loopAsync) _looper();
                            } else {
                                if (!_loop) {
                                    _callback();
                                }
                            }
                        }
                    }));
                }
                function _next0() {
                    var _fn1 = _x[1];
                    _fn1((function (_err1, _result1) {
                        if (_err1) {
                            _callback(_err1);
                        } else {
                            if (_result1 !== undefined) {
                                _loop = true;
                                if (_loopAsync) _looper();
                            } else {
                                _next1();
                            }
                        }
                    }));
                }
                var _fn0 = _x[0];
                _fn0((function (_err0, _result0) {
                    if (_err0) {
                        _callback(_err0);
                    } else {
                        if (_result0 !== undefined) {
                            _loop = true;
                            if (_loopAsync) _looper();
                        } else {
                            _next0();
                        }
                    }
                }));
            } while (_loop);
            _loopAsync = true;
        });
        _looper();

    })

根同步loop差不多的格式,不过下个tap由next包裹起来了,让用户传入格式的值来判断是否跳出循环

  1. AsyncParallelHook 异步并发执行的钩子
class AsyncParallelHookCodeFactory extends HookCodeFactory {
	content({ onError, onDone }) {
        // 调用基类的即可
		return this.callTapsParallel({
			onError: (i, err, done, doneBreak) => onError(err) + doneBreak(true),
			onDone
		});
	}
}
const hook = new AsyncParallelHook();
hook.tapAsync('logPlugin', (callback) => {
setTimeout(() => {
    console.log('计算路线1');
    callback();
}, 1000);
});
hook.tapAsync('logPlugin1', (callback) => {
setTimeout(() => {
    console.log('计算路线2');
    callback();
}, 2000);
});
hook.callAsync(() => { console.log('最终的回调'); });

// 得到的call是这样的:
(function anonymous(_callback
    ) {
        "use strict";
        var _context;
        var _x = this._x;
        do {
            var _counter = 2;
            var _done = (function () {
                _callback();
            });
            if (_counter <= 0) break;
            var _fn0 = _x[0];
            _fn0((function (_err0) {
                if (_err0) {
                    if (_counter > 0) {
                        _callback(_err0);
                        _counter = 0;
                    }
                } else {
                    if (--_counter === 0) _done();
                }
            }));
            if (_counter <= 0) break;
            var _fn1 = _x[1];
            _fn1((function (_err1) {
                if (_err1) {
                    if (_counter > 0) {
                        _callback(_err1);
                        _counter = 0;
                    }
                } else {
                    if (--_counter === 0) _done();
                }
            }));
        } while (false);

    })

多个异步的并发执行,肯定是需要一个计数器来统计是否全部结束了异步函数,我们自己写也是一样的做法,用户的函数需要手动触发callback来驱动计数器减少;

  1. AsyncParallelBailHook 异步并发带有熔断效果的钩子:
    /tapable/lib/AsyncParallelBailHook
class AsyncParallelBailHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, onDone }) {
		let code = "";
		code += `var _results = new Array(${this.options.taps.length});\n`;
		code += "var _checkDone = function() {\n";
		code += "for(var i = 0; i < _results.length; i++) {\n";
		code += "var item = _results[i];\n";
		code += "if(item === undefined) return false;\n";
		code += "if(item.result !== undefined) {\n";
		code += onResult("item.result");
		code += "return true;\n";
		code += "}\n";
		code += "if(item.error) {\n";
		code += onError("item.error");
		code += "return true;\n";
		code += "}\n";
		code += "}\n";
		code += "return false;\n";
		code += "}\n";
		code += this.callTapsParallel({
			onError: (i, err, done, doneBreak) => {
				let code = "";
				code += `if(${i} < _results.length && ((_results.length = ${i +
					1}), (_results[${i}] = { error: ${err} }), _checkDone())) {\n`;
				code += doneBreak(true);
				code += "} else {\n";
				code += done();
				code += "}\n";
				return code;
			},
			onResult: (i, result, done, doneBreak) => {
				let code = "";
				code += `if(${i} < _results.length && (${result} !== undefined && (_results.length = ${i +
					1}), (_results[${i}] = { result: ${result} }), _checkDone())) {\n`;
				code += doneBreak(true);
				code += "} else {\n";
				code += done();
				code += "}\n";
				return code;
			},
			onTap: (i, run, done, doneBreak) => {
				let code = "";
				if (i > 0) {
					code += `if(${i} >= _results.length) {\n`;
					code += done();
					code += "} else {\n";
				}
				code += run();
				if (i > 0) code += "}\n";
				return code;
			},
			onDone
		});
		return code;
	}
}
const hook = new AsyncParallelBailHook();
hook.tapAsync('logPlugin', (callback) => {
setTimeout(() => {
    console.log('计算路线1');
    callback(null, 1);
}, 1000);
});
hook.tapAsync('logPlugin1', (callback) => {
setTimeout(() => {
    console.log('计算路线2');
    callback();
}, 2000);
});
hook.tapAsync('logPlugin2', (callback) => {
setTimeout(() => {
    console.log('计算路线3');
    callback();
}, 3000);
});
hook.callAsync(() => { console.log('最终的回调'); });

// 得到的call是这样的:

(function anonymous(_callback
    ) {
        "use strict";
        var _context;
        var _x = this._x;
        var _results = new Array(3);
        var _checkDone = function () {
            for (var i = 0; i < _results.length; i++) {
                var item = _results[i];
                if (item === undefined) return false;
                if (item.result !== undefined) {
                    _callback(null, item.result);
                    return true;
                }
                if (item.error) {
                    _callback(item.error);
                    return true;
                }
            }
            return false;
        }
        do {
            var _counter = 3;
            var _done = (function () {
                _callback();
            });
            if (_counter <= 0) break;
            var _fn0 = _x[0];
            _fn0((function (_err0, _result0) {
                if (_err0) {
                    if (_counter > 0) {
                        if (0 < _results.length && ((_results.length = 1), (_results[0] = { error: _err0 }), _checkDone())) {
                            _counter = 0;
                        } else {
                            if (--_counter === 0) _done();
                        }
                    }
                } else {
                    if (_counter > 0) {
                        if (0 < _results.length && (_result0 !== undefined && (_results.length = 1), (_results[0] = { result: _result0 }), _checkDone())) {
                            _counter = 0;
                        } else {
                            if (--_counter === 0) _done();
                        }
                    }
                }
            }));
            if (_counter <= 0) break;
            if (1 >= _results.length) {
                if (--_counter === 0) _done();
            } else {
                var _fn1 = _x[1];
                _fn1((function (_err1, _result1) {
                    if (_err1) {
                        if (_counter > 0) {
                            if (1 < _results.length && ((_results.length = 2), (_results[1] = { error: _err1 }), _checkDone())) {
                                _counter = 0;
                            } else {
                                if (--_counter === 0) _done();
                            }
                        }
                    } else {
                        if (_counter > 0) {
                            if (1 < _results.length && (_result1 !== undefined && (_results.length = 2), (_results[1] = { result: _result1 }), _checkDone())) {
                                _counter = 0;
                            } else {
                                if (--_counter === 0) _done();
                            }
                        }
                    }
                }));
            }
            if (_counter <= 0) break;
            if (2 >= _results.length) {
                if (--_counter === 0) _done();
            } else {
                var _fn2 = _x[2];
                _fn2((function (_err2, _result2) {
                    if (_err2) {
                        if (_counter > 0) {
                            if (2 < _results.length && ((_results.length = 3), (_results[2] = { error: _err2 }), _checkDone())) {
                                _counter = 0;
                            } else {
                                if (--_counter === 0) _done();
                            }
                        }
                    } else {
                        if (_counter > 0) {
                            if (2 < _results.length && (_result2 !== undefined && (_results.length = 3), (_results[2] = { result: _result2 }), _checkDone())) {
                                _counter = 0;
                            } else {
                                if (--_counter === 0) _done();
                            }
                        }
                    }
                }));
            }
        } while (false);

    })

稍微对比下代码,可以得知:为了知道几个并发的异步是都有一个返回了值,且这时候不再执行后面的回调,我们需要一个结果数组,每次有异步结束触发它的回调了,我们都检测这个结果数组,是否之前存在过了有值的情况,没有的话就赋值一个,后面的就可以知道不用执行了,从而实现了异步熔断的效果
虽然代码比较长这个,但是对比结果看过程,还是可以看懂的

每个异步类型的tap,还可以按照promise的方式再写一次,我们就不一一分析了,取一个 AsyncParallelHook 作为典型分析就好了;

const hook = new AsyncParallelHook();
 hook.tapPromise('logPlugin', (callback) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('计算路线1');
        resolve();
      }, 1000);
    });
  });
 hook.tapPromise('logPlugin1', (callback) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
        console.log('计算路线2');
        resolve();
      }, 2000);
    });
  });
 hook.callAsync().then(() => { console.log('最终的回调'); });;

 // 得到的call是这样的:

 (function anonymous(_callback
    ) {
        "use strict";
        var _context;
        var _x = this._x;
        do {
            var _counter = 2;
            var _done = (function () {
                _callback();
            });
            if (_counter <= 0) break;
            var _fn0 = _x[0];
            var _hasResult0 = false;
            var _promise0 = _fn0();
            if (!_promise0 || !_promise0.then)
                throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise0 + ')');
            _promise0.then((function (_result0) {
                _hasResult0 = true;
                if (--_counter === 0) _done();
            }), function (_err0) {
                if (_hasResult0) throw _err0;
                if (_counter > 0) {
                    _callback(_err0);
                    _counter = 0;
                }
            });
            if (_counter <= 0) break;
            var _fn1 = _x[1];
            var _hasResult1 = false;
            var _promise1 = _fn1();
            if (!_promise1 || !_promise1.then)
                throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise1 + ')');
            _promise1.then((function (_result1) {
                _hasResult1 = true;
                if (--_counter === 0) _done();
            }), function (_err1) {
                if (_hasResult1) throw _err1;
                if (_counter > 0) {
                    _callback(_err1);
                    _counter = 0;
                }
            });
        } while (false);

    })

其实主要就是在callback中的代码,放到then中去了而已

最后还有 HookMap和 MultiHook 2个内容,不过都比较简单,大家自己看下文档和代码就知道了:)

总结一下:tap注册的函数分 同步 异步 promise3种函数,callTap会为它们生成不同的包裹函数代码来执行它们;同时控制执行流程的函数分3种:
顺序同步执行(包含 熔断 瀑布,只是他们对结果的依赖代码不同而已),循环执行(重写了对结果的依赖代码来实现循环效果,代码同同步执行类似),异步并发执行(含熔断):通过计数器以及结果数组来实现;

以上,就是tapable的内容分析了,总体来说,它提供了一种创建指定类型的钩子对象,且钩子添加的函数可以按照指定顺序执行,
我们在自己实现的类中,添加的生命周期阶段,可以构造为某个钩子对象,然后按照需要注册对应的函数回调,等到生命周期需要触发了,
再按照指定顺序执行,这也是webpack的做法。

vue-router(v4.0.6)之 createRouter 方法的实现简析(2)

接着看上文未分析完的代码:

// 检测某个导航守卫是否改变了原计划的导航
function checkCanceledNavigation(to, from) {
    if (pendingLocation !== to) {
        return createRouterError(8 /* NAVIGATION_CANCELLED */, {
            from,
            to,
        });
    }
}
// 我们常用的个push和redirect都是调用 pushWithRedirect ,不过参数被格式化了而已
function push(to) {
    return pushWithRedirect(to);
}
function replace(to) {
    return push(assign(locationAsObject(to), { replace: true }));
}
// 待跳转的location检测是否有redirect属性
function handleRedirectRecord(to) {
    // 子路由 最终匹配到最底层的就是最后一个matched对象
    const lastMatched = to.matched[to.matched.length - 1];
    // 存在才有返回值 否则就是undefined
    if (lastMatched && lastMatched.redirect) {
        const { redirect } = lastMatched;
        let newTargetLocation = typeof redirect === 'function' ? redirect(to) : redirect;
        // 格式化待重定向location
        if (typeof newTargetLocation === 'string') {
            newTargetLocation =
                newTargetLocation.indexOf('?') > -1 ||
                    newTargetLocation.indexOf('#') > -1
                    ? (newTargetLocation = locationAsObject(newTargetLocation))
                    : { path: newTargetLocation };
        }
        // 需要提供 下面的参数之一
        if (!('path' in newTargetLocation) &&
            !('name' in newTargetLocation)) {
            warn(`Invalid redirect found:\n${JSON.stringify(newTargetLocation, null, 2)}\n when navigating to "${to.fullPath}". A redirect must contain a name or path. This will break in production.`);
            throw new Error('Invalid redirect');
        }
        // 返回新的目标location
        return assign({
            query: to.query,
            hash: to.hash,
            params: to.params,
        }, newTargetLocation);
    }
}
// 实际执行的push和redirect内部方法
function pushWithRedirect(to, redirectedFrom) {
    // 调用实例的resolve得到匹配信息 同时 pendingLocation 置位
    // targetLocation: 待跳转目标location
    const targetLocation = (pendingLocation = resolve(to));
    // 当前值变成了历史值
    const from = currentRoute.value;
    const data = to.state;
    const force = to.force;
    // to could be a string where `replace` is a function
    const replace = to.replace === true;
    // 上面的 方法 尝试进行一次重定向解析
    const shouldRedirect = handleRedirectRecord(targetLocation);
    // 如果需要的话 重新调用一次 再次调用的时候 不会有redirect字段了
    if (shouldRedirect)
        return pushWithRedirect(assign(locationAsObject(shouldRedirect), {
            state: data,
            force,
            replace,
        }), 
        // keep original redirectedFrom if it exists
        redirectedFrom || targetLocation);
    // if it was a redirect we already called `pushWithRedirect` above
    // 或许是重定向回来的 统一以 toLocation 代表
    const toLocation = targetLocation;
    // 指向原来的
    toLocation.redirectedFrom = redirectedFrom;
    let failure;
    // 非强制更新的场景下 待跳转location和当前又一样
    if (!force && isSameRouteLocation(stringifyQuery$1, from, targetLocation)) {
        failure = createRouterError(16 /* NAVIGATION_DUPLICATED */, { to: toLocation, from });
        // trigger scroll to allow scrolling to the same anchor
        handleScroll(from, from, 
        // this is a push, the only way for it to be triggered from a
        // history.listen is with a redirect, which makes it become a push
        true, 
        // This cannot be the first navigation because the initial location
        // cannot be manually navigated to
        false);
    }
    // 最后的实际调用方法
    // 非错误场景下 调用的其实是 navigate(toLocation, from) 而它返回的是一个promise
    return (failure ? Promise.resolve(failure) : navigate(toLocation, from))
        // 捕获navigate执行过程中的错误
        .catch((error) => isNavigationFailure(error)
        ? error
        : // reject any unknown error
            triggerError(error))
        .then((failure) => {
        // 处理navigate之后的结果
        if (failure) {
            // 无限重定向的错误情况
            if (isNavigationFailure(failure, 2 /* NAVIGATION_GUARD_REDIRECT */)) {
                if (// we are redirecting to the same location we were already at
                    isSameRouteLocation(stringifyQuery$1, resolve(failure.to), toLocation) &&
                    // and we have done it a couple of times
                    redirectedFrom &&
                    // @ts-ignore
                    (redirectedFrom._count = redirectedFrom._count
                        ? // @ts-ignore
                            redirectedFrom._count + 1
                        : 1) > 10) {
                    warn(`Detected an infinite redirection in a navigation guard when going from "${from.fullPath}" to "${toLocation.fullPath}". Aborting to avoid a Stack Overflow. This will break in production if not fixed.`);
                    return Promise.reject(new Error('Infinite redirect in navigation guard'));
                }
                return pushWithRedirect(
                // keep options
                assign(locationAsObject(failure.to), {
                    state: data,
                    force,
                    replace,
                }), 
                // preserve the original redirectedFrom if any
                redirectedFrom || toLocation);
            }
        }
        // 最终成功后的收尾方法
        else {
            // if we fail we don't finalize the navigation
            failure = finalizeNavigation(toLocation, from, true, replace, data);
        }
        // 触发after导航守卫
        triggerAfterEach(toLocation, from, failure);
        return failure;
    });
}

下面是刚刚用到的一些辅助方法,比较简单


// 判断2个location对象其实是不是一样
// 对各个属性的字段都有值相等的要求
function isSameRouteLocation(stringifyQuery, a, b) {
    let aLastIndex = a.matched.length - 1;
    let bLastIndex = b.matched.length - 1;
    return (aLastIndex > -1 &&
        aLastIndex === bLastIndex &&
        isSameRouteRecord(a.matched[aLastIndex], b.matched[bLastIndex]) &&
        isSameRouteLocationParams(a.params, b.params) &&
        stringifyQuery(a.query) === stringifyQuery(b.query) &&
        a.hash === b.hash);
}
/**
    * Check if two `RouteRecords` are equal. Takes into account aliases: they are
    * considered equal to the `RouteRecord` they are aliasing.
    *
    * @param a - first {@link RouteRecord}
    * @param b - second {@link RouteRecord}
    */
function isSameRouteRecord(a, b) {
    // since the original record has an undefined value for aliasOf
    // but all aliases point to the original record, this will always compare
    // the original record
    return (a.aliasOf || a) === (b.aliasOf || b);
}
function isSameRouteLocationParams(a, b) {
    if (Object.keys(a).length !== Object.keys(b).length)
        return false;
    for (let key in a) {
        if (!isSameRouteLocationParamsValue(a[key], b[key]))
            return false;
    }
    return true;
}
function isSameRouteLocationParamsValue(a, b) {
    return Array.isArray(a)
        ? isEquivalentArray(a, b)
        : Array.isArray(b)
            ? isEquivalentArray(b, a)
            : a === b;
}

const NavigationFailureSymbol = /*#__PURE__*/ PolySymbol('navigation failure' );

// 一些错误信息的标识
const NavigationFailureSymbol = /*#__PURE__*/ PolySymbol('navigation failure' );
/**
    * Enumeration with all possible types for navigation failures. Can be passed to
    * {@link isNavigationFailure} to check for specific failures.
    */
exports.NavigationFailureType = void 0;
(function (NavigationFailureType) {
    /**
        * An aborted navigation is a navigation that failed because a navigation
        * guard returned `false` or called `next(false)`
        */
    NavigationFailureType[NavigationFailureType["aborted"] = 4] = "aborted";
    /**
        * A cancelled navigation is a navigation that failed because a more recent
        * navigation finished started (not necessarily finished).
        */
    NavigationFailureType[NavigationFailureType["cancelled"] = 8] = "cancelled";
    /**
        * A duplicated navigation is a navigation that failed because it was
        * initiated while already being at the exact same location.
        */
    NavigationFailureType[NavigationFailureType["duplicated"] = 16] = "duplicated";
})(exports.NavigationFailureType || (exports.NavigationFailureType = {}));
// DEV only debug messages
const ErrorTypeMessages = {
    [1 /* MATCHER_NOT_FOUND */]({ location, currentLocation }) {
        return `No match for\n ${JSON.stringify(location)}${currentLocation
            ? '\nwhile being at\n' + JSON.stringify(currentLocation)
            : ''}`;
    },
    [2 /* NAVIGATION_GUARD_REDIRECT */]({ from, to, }) {
        return `Redirected from "${from.fullPath}" to "${stringifyRoute(to)}" via a navigation guard.`;
    },
    [4 /* NAVIGATION_ABORTED */]({ from, to }) {
        return `Navigation aborted from "${from.fullPath}" to "${to.fullPath}" via a navigation guard.`;
    },
    [8 /* NAVIGATION_CANCELLED */]({ from, to }) {
        return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new navigation.`;
    },
    [16 /* NAVIGATION_DUPLICATED */]({ from, to }) {
        return `Avoided redundant navigation to current location: "${from.fullPath}".`;
    },
};
// 错误信息的一些辅助方法 比较简单
function createRouterError(type, params) {
    {
        return assign(new Error(ErrorTypeMessages[type](params)), {
            type,
            [NavigationFailureSymbol]: true,
        }, params);
    }
}
function isNavigationFailure(error, type) {
    return (error instanceof Error &&
        NavigationFailureSymbol in error &&
        (type == null || !!(error.type & type)));
}
const propertiesToLog = ['params', 'query', 'hash'];
function stringifyRoute(to) {
    if (typeof to === 'string')
        return to;
    if ('path' in to)
        return to.path;
    const location = {};
    for (const key of propertiesToLog) {
        if (key in to)
            location[key] = to[key];
    }
    return JSON.stringify(location, null, 2);
}

接着往下看:

/**
* Helper to reject and skip all navigation guards if a new navigation happened
* @param to
* @param from
*/
// 发下原目标location已被修改 那剩下的导航守卫们就不需要执行了 promise链可以reject掉了 
function checkCanceledNavigationAndReject(to, from) {
    const error = checkCanceledNavigation(to, from);
    return error ? Promise.reject(error) : Promise.resolve();
}
// TODO: refactor the whole before guards by internally using router.beforeEach
// 导航的实际实现
function navigate(to, from) {
    // 待处理的守卫集合
    let guards;
    // 根据2次路径的对比 把所有的records分3份
    // 待离开 待更新 待进入
    const [leavingRecords, updatingRecords, enteringRecords,] = extractChangingRecords(to, from);
    // all components here have been resolved once because we are leaving
    // 刚解析出来的是待离开 是从父到子的顺序排放的
    // 实际执行需要从子开始离开
    // 从组件中解析解析出 beforeRouteLeave 对应的守卫函数
    guards = extractComponentsGuards(leavingRecords.reverse(), 'beforeRouteLeave', to, from);
    // leavingRecords is already reversed
    // 组件解析过程的时候 把单独注册添加的离开守卫也加入
    for (const record of leavingRecords) {
        record.leaveGuards.forEach(guard => {
            guards.push(guardToPromiseFn(guard, to, from));
        });
    }
    // 添加 检测是否导航目标地址发生了改变 的守卫
    const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(null, to, from);
    guards.push(canceledNavigationCheck);
    // run the queue of per route beforeRouteLeave guards
    // 驱动执行守卫列表
    // 链式调用 promise.then 一个一个执行
    // 以下执行顺序按照文档中描述的执行顺序执行
    /**
        *  1.导航被触发。
        2.在失活的组件里调用 beforeRouteLeave 守卫。
        3.调用全局的 beforeEach 守卫。
        4.在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
        5.在路由配置里调用 beforeEnter。
        6.解析异步路由组件。
        7.在被激活的组件里调用 beforeRouteEnter。
        8.调用全局的 beforeResolve 守卫(2.5+)。
        9.导航被确认。
        10.调用全局的 afterEach 钩子。
        11.触发 DOM 更新。
        12.调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
        */
    // 执行步骤 2
    return (runGuardQueue(guards)
        .then(() => {
        // check global guards beforeEach
        // 执行完后清空守卫集合 添加下一阶段的守卫们
        guards = [];
        for (const guard of beforeGuards.list()) {
            guards.push(guardToPromiseFn(guard, to, from));
        }
        // 每次都需要检查是否改变了目标location
        guards.push(canceledNavigationCheck);
        // 执行步骤 3
        return runGuardQueue(guards);
    })
        .then(() => {
        // check in components beforeRouteUpdate
        guards = extractComponentsGuards(updatingRecords, 'beforeRouteUpdate', to, from);
        for (const record of updatingRecords) {
            record.updateGuards.forEach(guard => {
                guards.push(guardToPromiseFn(guard, to, from));
            });
        }
        guards.push(canceledNavigationCheck);
        // run the queue of per route beforeEnter guards
        // 执行步骤 4
        return runGuardQueue(guards);
    })
        .then(() => {
        // check the route beforeEnter
        guards = [];
        for (const record of to.matched) {
            // do not trigger beforeEnter on reused views
            if (record.beforeEnter && from.matched.indexOf(record) < 0) {
                if (Array.isArray(record.beforeEnter)) {
                    for (const beforeEnter of record.beforeEnter)
                        guards.push(guardToPromiseFn(beforeEnter, to, from));
                }
                else {
                    guards.push(guardToPromiseFn(record.beforeEnter, to, from));
                }
            }
        }
        guards.push(canceledNavigationCheck);
        // run the queue of per route beforeEnter guards
        // 执行步骤 5
        return runGuardQueue(guards);
    })
        .then(() => {
        // NOTE: at this point to.matched is normalized and does not contain any () => Promise<Component>
        // clear existing enterCallbacks, these are added by extractComponentsGuards
        to.matched.forEach(record => (record.enterCallbacks = {}));
        // check in-component beforeRouteEnter
        // 执行步骤 6
        guards = extractComponentsGuards(enteringRecords, 'beforeRouteEnter', to, from);
        guards.push(canceledNavigationCheck);
        // run the queue of per route beforeEnter guards
        // 执行步骤 7
        return runGuardQueue(guards);
    })
        .then(() => {
        // check global guards beforeResolve
        guards = [];
        for (const guard of beforeResolveGuards.list()) {
            guards.push(guardToPromiseFn(guard, to, from));
        }
        guards.push(canceledNavigationCheck);
        // 执行步骤 8
        return runGuardQueue(guards);
    })
        // catch any navigation canceled
        .catch(err => isNavigationFailure(err, 8 /* NAVIGATION_CANCELLED */)
        ? err
        : Promise.reject(err)));
}
// 执行 全局afterEach钩子
function triggerAfterEach(to, from, failure) {
    // navigation is confirmed, call afterGuards
    // TODO: wrap with error handlers
    for (const guard of afterGuards.list())
        guard(to, from, failure);
}
/**
    * - Cleans up any navigation guards
    * - Changes the url if necessary
    * - Calls the scrollBehavior
    */
// 最终改变url和当前路由值的方法实现
function finalizeNavigation(toLocation, from, isPush, replace, data) {
    // a more recent navigation took place
    // 再次检查
    const error = checkCanceledNavigation(toLocation, from);
    if (error)
        return error;
    // only consider as push if it's not the first navigation
    const isFirstNavigation = from === START_LOCATION_NORMALIZED;
    const state = !isBrowser ? {} : history.state;
    // change URL only if the user did a push/replace and if it's not the initial navigation because
    // it's just reflecting the url
    // 用户调用的引起的push、redirect调用
    if (isPush) {
        // on the initial navigation, we want to reuse the scroll position from
        // history state if it exists
        // 使用history的push 修改浏览器url
        if (replace || isFirstNavigation)
            routerHistory.replace(toLocation.fullPath, assign({
                scroll: isFirstNavigation && state && state.scroll,
            }, data));
        else
            routerHistory.push(toLocation.fullPath, data);
    }
    // accept current navigation
    // 最终改变了我们的响应式对象 currentRoute 的 value 而这个操作将会引起dom视图的更新 也就是 执行步骤11
    currentRoute.value = toLocation;
    // 处理滚动情况因为滚到到曾经访问过的路径的话 可能需要滚动到历史位置
    handleScroll(toLocation, from, isPush, isFirstNavigation);
    // 标记ready 首次才有意义
    markAsReady();
}
// 逐个执行守卫 promise.then
function runGuardQueue(guards) {
    return guards.reduce((promise, guard) => promise.then(() => guard()), Promise.resolve());
}
// 对比2个路径 解析3种守卫出来
function extractChangingRecords(to, from) {
    const leavingRecords = [];
    const updatingRecords = [];
    const enteringRecords = [];
    // 取最大值
    // 注意顺序 按照matched数组 从父到子 从前往后正常解析的
    const len = Math.max(from.matched.length, to.matched.length);
    for (let i = 0; i < len; i++) {
        const recordFrom = from.matched[i];
        if (recordFrom) {
            if (to.matched.find(record => isSameRouteRecord(record, recordFrom)))
                updatingRecords.push(recordFrom);
            else
                leavingRecords.push(recordFrom);
        }
        const recordTo = to.matched[i];
        if (recordTo) {
            // the type doesn't matter because we are comparing per reference
            if (!from.matched.find(record => isSameRouteRecord(record, recordTo))) {
                enteringRecords.push(recordTo);
            }
        }
    }
    return [leavingRecords, updatingRecords, enteringRecords];
}

然后还有一个 setupListeners 方法:

let removeHistoryListener;
// attach listener to history to trigger navigations
function setupListeners() {
    // 利用history的监听的popstate事件来触发
    // 也就是说 当我们通过点击浏览器的前进回退按钮的时候 我们也需要手动触发我们的导航内部逻辑
    removeHistoryListener = routerHistory.listen((to, _from, info) => {
        // cannot be a redirect route because it was in history
        let toLocation = resolve(to);
        // due to dynamic routing, and to hash history with manual navigation
        // (manually changing the url or calling history.hash = '#/somewhere'),
        // there could be a redirect record in history
        const shouldRedirect = handleRedirectRecord(toLocation);
        if (shouldRedirect) {
            pushWithRedirect(assign(shouldRedirect, { replace: true }), toLocation).catch(noop);
            return;
        }
        pendingLocation = toLocation;
        const from = currentRoute.value;
        // 分析同上面
        // TODO: should be moved to web history?
        // 记录当时的滚动信息
        if (isBrowser) {
            saveScrollPosition(getScrollKey(from.fullPath, info.delta), computeScrollPosition());
        }
        // 响应浏览器popstate事件 我们手动驱动导航逻辑触发去更新当前route且更新dom组件视图
        navigate(toLocation, from)
            .catch((error) => {
            if (isNavigationFailure(error, 4 /* NAVIGATION_ABORTED */ | 8 /* NAVIGATION_CANCELLED */)) {
                return error;
            }
            if (isNavigationFailure(error, 2 /* NAVIGATION_GUARD_REDIRECT */)) {
                // Here we could call if (info.delta) routerHistory.go(-info.delta,
                // false) but this is bug prone as we have no way to wait the
                // navigation to be finished before calling pushWithRedirect. Using
                // a setTimeout of 16ms seems to work but there is not guarantee for
                // it to work on every browser. So Instead we do not restore the
                // history entry and trigger a new navigation as requested by the
                // navigation guard.
                // the error is already handled by router.push we just want to avoid
                // logging the error
                pushWithRedirect(error.to, toLocation
                // avoid an uncaught rejection, let push call triggerError
                ).catch(noop);
                // avoid the then branch
                return Promise.reject();
            }
            // do not restore history on unknown direction
            if (info.delta)
                routerHistory.go(-info.delta, false);
            // unrecognized error, transfer to the global handler
            return triggerError(error);
        })
            .then((failure) => {
            failure =
                failure ||
                    finalizeNavigation(
                    // after navigation, all matched components are resolved
                    toLocation, from, false);
            // revert the navigation
            if (failure && info.delta)
                routerHistory.go(-info.delta, false);
            triggerAfterEach(toLocation, from, failure);
        })
            .catch(noop);
    });
}
// Initialization and Errors
let readyHandlers = useCallbacks();
let errorHandlers = useCallbacks();
let ready;
/**
    * Trigger errorHandlers added via onError and throws the error as well
    * @param error - error to throw
    * @returns the error as a rejected promise
    */
// 触发用户设置的err钩子
function triggerError(error) {
    markAsReady(error);
    errorHandlers.list().forEach(handler => handler(error));
    return Promise.reject(error);
}
// 感觉这个作用就是延迟1个tick而已...
function isReady() {
    if (ready && currentRoute.value !== START_LOCATION_NORMALIZED)
        return Promise.resolve();
    return new Promise((resolve, reject) => {
        readyHandlers.add([resolve, reject]);
    });
}
/**
    * Mark the router as ready, resolving the promised returned by isReady(). Can
    * only be called once, otherwise does nothing.
    * @param err - optional error
    */
// 只调用一次的标记已经ready 初始化完成
function markAsReady(err) {
    if (ready)
        return;
    ready = true;
    // 设置一个监听事件
    setupListeners();
    // 触发钩子
    readyHandlers
        .list()
        .forEach(([resolve, reject]) => (err ? reject(err) : resolve()));
    readyHandlers.reset();
}
// 简单的调用history的方法
const go = (delta) => routerHistory.go(delta);

滚动有关的代码以及从组件中抽取钩子的代码,下篇再分析;
总结一下:目前分析到的push等实例方法,展示了如何使用resolve方法来解析路径,然后构造对应的守卫数组,按照指定顺序执行,然后更新响应式对象的值,
更新浏览器url;同时也利用history的popstate事件来监听浏览器的url的改变,再次手动驱动我们的代码执行;

Vue(v3.0.11)源码简析之组件配置参数如何应用到vue组件实例的相关实现

我们平常会用对象来配置组件,中间一些配置选项参数是如何同步映射到vue的组件实例呢,原始对象和组件实例上的属性存在怎么样的对应关系呢?这一切都要看vue是怎么实现这些参数应用的:

// 组合api中使用的提供可被子组件提取的变量
function provide(key, value) {
    if (!currentInstance) {
        {
            warn(`provide() can only be used inside setup().`);
        }
    }
    else {
        let provides = currentInstance.provides;
        // by default an instance inherits its parent's provides object
        // but when it needs to provide values of its own, it creates its
        // own provides object using parent provides object as prototype.
        // this way in `inject` we can simply look up injections from direct
        // parent and let the prototype chain do the work.
        // 如注释所言 当父组件有provides 子组件继承同个对象 但是当子组件自己调用provide提供的时候
        // 为了保证从当前子组件找到现在新的和之前的所有provide变量 把之前的作为原型对象
        const parentProvides = currentInstance.parent && currentInstance.parent.provides;
        if (parentProvides === provides) {
            provides = currentInstance.provides = Object.create(parentProvides);
        }
        // TS doesn't allow symbol as index type
        provides[key] = value;
    }
}
// 提取provide的对应key的value
function inject(key, defaultValue, treatDefaultAsFactory = false) {
    // fallback to `currentRenderingInstance` so that this can be called in
    // a functional component
    const instance = currentInstance || currentRenderingInstance;
    if (instance) {
        // #2400
        // to support `app.use` plugins,
        // fallback to appContext's `provides` if the intance is at root
        // 找到合适的provide来源
        const provides = instance.parent == null
            ? instance.vnode.appContext && instance.vnode.appContext.provides
            : instance.parent.provides;
        if (provides && key in provides) {
            // TS doesn't allow symbol as index type
            return provides[key];
        }
        // 默认值 对应文档所言
        else if (arguments.length > 1) {
            return treatDefaultAsFactory && isFunction(defaultValue)
                ? defaultValue()
                : defaultValue;
        }
        else {
            warn(`injection "${String(key)}" not found.`);
        }
    }
    else {
        warn(`inject() can only be used inside setup() or functional components.`);
    }
}

// 生成对应type的重复key检测函数
function createDuplicateChecker() {
    const cache = Object.create(null);
    return (type, key) => {
        if (cache[key]) {
            warn(`${type} property "${key}" is already defined in ${cache[key]}.`);
        }
        else {
            cache[key] = type;
        }
    };
}
// 是否缓存获取this上的key的类型 使用见下文
let shouldCacheAccess = true;
// 把用户设置的options的选择 应用到组件实例上来
function applyOptions(instance, options, deferredData = [], deferredWatch = [], deferredProvide = [], asMixin = false) {
    // 以下就是所有支持的配置选项 setup在之前已经处理过了
    const { 
    // composition
    mixins, extends: extendsOptions, 
    // state
    data: dataOptions, computed: computedOptions, methods, watch: watchOptions, provide: provideOptions, inject: injectOptions, 
    // assets
    components, directives, 
    // lifecycle
    beforeMount, mounted, beforeUpdate, updated, activated, deactivated, beforeDestroy, beforeUnmount, destroyed, unmounted, render, renderTracked, renderTriggered, errorCaptured, 
    // public API
    // 这个居然在文档中找不到对应的解释哈 从代码看 是导出指定变量给用户可用
    expose } = options;
    // 我们常用的this
    const publicThis = instance.proxy;
    // 上下文 数据仓库
    const ctx = instance.ctx;
    // 全局mixins
    const globalMixins = instance.appContext.mixins;
    // mixin中可以配置render
    if (asMixin && render && instance.render === NOOP) {
        instance.render = render;
    }
    // applyOptions is called non-as-mixin once per instance
    if (!asMixin) {
        shouldCacheAccess = false;
        // 钩子
        callSyncHook('beforeCreate', "bc" /* BEFORE_CREATE */, options, instance, globalMixins);
        shouldCacheAccess = true;
        // global mixins are applied first
        // 先加载全局的
        applyMixins(instance, globalMixins, deferredData, deferredWatch, deferredProvide);
    }
    // extending a base component...
    // 挂载extent对象 也是个组件形式
    if (extendsOptions) {
        applyOptions(instance, extendsOptions, deferredData, deferredWatch, deferredProvide, true);
    }
    // local mixins
    // 再加载自己独有的 可覆盖全局的 然后最终自己的配置覆盖局部的
    if (mixins) {
        applyMixins(instance, mixins, deferredData, deferredWatch, deferredProvide);
    }
    const checkDuplicateProperties = createDuplicateChecker() ;
    {
        // props重名key检查
        const [propsOptions] = instance.propsOptions;
        if (propsOptions) {
            for (const key in propsOptions) {
                checkDuplicateProperties("Props" /* PROPS */, key);
            }
        }
    }
    // options initialization order (to be consistent with Vue 2):
    // - props (already done outside of this function)
    // - inject
    // - methods
    // - data (deferred since it relies on `this` access)
    // - computed
    // - watch (deferred since it relies on `this` access)
    // 按照以上顺序逐一挂载 props在外部已挂载 详情见后文
    if (injectOptions) {
        if (isArray(injectOptions)) {
            for (let i = 0; i < injectOptions.length; i++) {
                const key = injectOptions[i];
                // 挂载
                ctx[key] = inject(key);
                {
                    checkDuplicateProperties("Inject" /* INJECT */, key);
                }
            }
        }
        else {
            // 对象形式的inject
            for (const key in injectOptions) {
                const opt = injectOptions[key];
                if (isObject(opt)) {
                    ctx[key] = inject(opt.from || key, opt.default, true /* treat default function as factory */);
                }
                else {
                    ctx[key] = inject(opt);
                }
                {
                    checkDuplicateProperties("Inject" /* INJECT */, key);
                }
            }
        }
    }
    if (methods) {
        for (const key in methods) {
            const methodHandler = methods[key];
            if (isFunction(methodHandler)) {
                // In dev mode, we use the `createRenderContext` function to define methods to the proxy target,
                // and those are read-only but reconfigurable, so it needs to be redefined here
                {
                    // 挂载方法 注意this的绑定
                    Object.defineProperty(ctx, key, {
                        value: methodHandler.bind(publicThis),
                        configurable: true,
                        enumerable: true,
                        writable: true
                    });
                }
                {
                    checkDuplicateProperties("Methods" /* METHODS */, key);
                }
            }
            else {
                warn(`Method "${key}" has type "${typeof methodHandler}" in the component definition. ` +
                    `Did you reference the function correctly?`);
            }
        }
    }
    // 最终合并完mixin后一起处理
    if (!asMixin) {
        if (deferredData.length) {
            // 挂载 mixins中的 data 的字段 到实例上的data上
            deferredData.forEach(dataFn => resolveData(instance, dataFn, publicThis));
        }
        // 处理自己的data到组件实例的data
        if (dataOptions) {
            // @ts-ignore dataOptions is not fully type safe
            resolveData(instance, dataOptions, publicThis);
        }
        {
            // 组件实例的data被前面处理成响应式的了
            const rawData = toRaw(instance.data);
            for (const key in rawData) {
                // 重名
                checkDuplicateProperties("Data" /* DATA */, key);
                // expose data on ctx during dev
                if (key[0] !== '$' && key[0] !== '_') {
                    // 挂载到ctx仓库
                    Object.defineProperty(ctx, key, {
                        configurable: true,
                        enumerable: true,
                        // 返回原始对象的值
                        get: () => rawData[key],
                        set: NOOP
                    });
                }
            }
        }
    }
    // 作为mixin合入的时候先推入延迟数组中 之后一起处理
    else if (dataOptions) {
        deferredData.push(dataOptions);
    }
    // computed对象挂载
    if (computedOptions) {
        for (const key in computedOptions) {
            const opt = computedOptions[key];
            const get = isFunction(opt)
                // getter 也绑定this
                ? opt.bind(publicThis, publicThis)
                : isFunction(opt.get)
                    ? opt.get.bind(publicThis, publicThis)
                    : NOOP;
            if (get === NOOP) {
                warn(`Computed property "${key}" has no getter.`);
            }
            const set = !isFunction(opt) && isFunction(opt.set)
                ? opt.set.bind(publicThis)
                : () => {
                        warn(`Write operation failed: computed property "${key}" is readonly.`);
                    }
                    ;
            // 构建computed响应式对象
            const c = computed$1({
                get,
                set
            });
            Object.defineProperty(ctx, key, {
                enumerable: true,
                configurable: true,
                // 返回getter的值
                get: () => c.value,
                set: v => (c.value = v)
            });
            {
                checkDuplicateProperties("Computed" /* COMPUTED */, key);
            }
        }
    }
    // mixin的watcher先收集
    if (watchOptions) {
        deferredWatch.push(watchOptions);
    }
    // 一起处理
    if (!asMixin && deferredWatch.length) {
        deferredWatch.forEach(watchOptions => {
            for (const key in watchOptions) {
                // 依次创建对应的watcher effect
                createWatcher(watchOptions[key], ctx, publicThis, key);
            }
        });
    }
    // mixin的先收集
    if (provideOptions) {
        deferredProvide.push(provideOptions);
    }
    // 统一处理 provide
    if (!asMixin && deferredProvide.length) {
        deferredProvide.forEach(provideOptions => {
            const provides = isFunction(provideOptions)
                ? provideOptions.call(publicThis)
                : provideOptions;
            Reflect.ownKeys(provides).forEach(key => {
                // 挂载key value到当前组件实例的provide对象中
                provide(key, provides[key]);
            });
        });
    }
    // asset options.
    // To reduce memory usage, only components with mixins or extends will have
    // resolved asset registry attached to instance.
    // 合并对应的对象
    if (asMixin) {
        if (components) {
            extend(instance.components ||
                (instance.components = extend({}, instance.type.components)), components);
        }
        if (directives) {
            extend(instance.directives ||
                (instance.directives = extend({}, instance.type.directives)), directives);
        }
    }
    // lifecycle options
    // 钩子
    if (!asMixin) {
        callSyncHook('created', "c" /* CREATED */, options, instance, globalMixins);
    }
    // 注册对应的钩子到钩子key下
    if (beforeMount) {
        onBeforeMount(beforeMount.bind(publicThis));
    }
    if (mounted) {
        onMounted(mounted.bind(publicThis));
    }
    if (beforeUpdate) {
        onBeforeUpdate(beforeUpdate.bind(publicThis));
    }
    if (updated) {
        onUpdated(updated.bind(publicThis));
    }
    if (activated) {
        onActivated(activated.bind(publicThis));
    }
    if (deactivated) {
        onDeactivated(deactivated.bind(publicThis));
    }
    if (errorCaptured) {
        onErrorCaptured(errorCaptured.bind(publicThis));
    }
    if (renderTracked) {
        onRenderTracked(renderTracked.bind(publicThis));
    }
    if (renderTriggered) {
        onRenderTriggered(renderTriggered.bind(publicThis));
    }
    if (beforeDestroy) {
        warn(`\`beforeDestroy\` has been renamed to \`beforeUnmount\`.`);
    }
    if (beforeUnmount) {
        onBeforeUnmount(beforeUnmount.bind(publicThis));
    }
    if (destroyed) {
        warn(`\`destroyed\` has been renamed to \`unmounted\`.`);
    }
    if (unmounted) {
        onUnmounted(unmounted.bind(publicThis));
    }
    if (isArray(expose)) {
        if (!asMixin) {
            if (expose.length) {
                // exposed是refs的代理
                const exposed = instance.exposed || (instance.exposed = proxyRefs({}));
                expose.forEach(key => {
                    // 响应式ref
                    exposed[key] = toRef(publicThis, key);
                });
            }
            else if (!instance.exposed) {
                instance.exposed = EMPTY_OBJ;
            }
        }
        else {
            warn(`The \`expose\` option is ignored when used in mixins.`);
        }
    }
}
// 同步执行钩子函数 注意 mixin中的 先执行
function callSyncHook(name, type, options, instance, globalMixins) {
    for (let i = 0; i < globalMixins.length; i++) {
        callHookWithMixinAndExtends(name, type, globalMixins[i], instance);
    }
    callHookWithMixinAndExtends(name, type, options, instance);
}
// 先执行 extend 再 mixin的 再自己的
function callHookWithMixinAndExtends(name, type, options, instance) {
    const { extends: base, mixins } = options;
    const selfHook = options[name];
    if (base) {
        callHookWithMixinAndExtends(name, type, base, instance);
    }
    if (mixins) {
        for (let i = 0; i < mixins.length; i++) {
            callHookWithMixinAndExtends(name, type, mixins[i], instance);
        }
    }
    if (selfHook) {
        callWithAsyncErrorHandling(selfHook.bind(instance.proxy), instance, type);
    }
}
// mixin数组中的依次挂载
function applyMixins(instance, mixins, deferredData, deferredWatch, deferredProvide) {
    for (let i = 0; i < mixins.length; i++) {
        // 注意最后个参数
        applyOptions(instance, mixins[i], deferredData, deferredWatch, deferredProvide, true);
    }
}
// 解析data并且挂载
function resolveData(instance, dataFn, publicThis) {
    if (!isFunction(dataFn)) {
        warn(`The data option must be a function. ` +
            `Plain object usage is no longer supported.`);
    }
    shouldCacheAccess = false;
    // 得到data对象
    const data = dataFn.call(publicThis, publicThis);
    shouldCacheAccess = true;
    if (isPromise(data)) {
        warn(`data() returned a Promise - note data() cannot be async; If you ` +
            `intend to perform data fetching before component renders, use ` +
            `async setup() + <Suspense>.`);
    }
    if (!isObject(data)) {
        warn(`data() should return an object.`);
    }
    else if (instance.data === EMPTY_OBJ) {
        // 构建响应式data
        instance.data = reactive(data);
    }
    else {
        // existing data: this is a mixin or extends.
        // 多个data 之后的合入即可
        extend(instance.data, data);
    }
}
// 创建watcher对象
function createWatcher(raw, ctx, publicThis, key) {
    // 可以写 'a.b.c' 这样的path
    const getter = key.includes('.')
        ? createPathGetter(publicThis, key)
        : () => publicThis[key];
    // 'key1': 'handler1'
    if (isString(raw)) {
        const handler = ctx[raw];
        if (isFunction(handler)) {
            watch(getter, handler);
        }
        else {
            warn(`Invalid watch handler specified by key "${raw}"`, handler);
        }
    }
    // 'key2': handler2
    else if (isFunction(raw)) {
        watch(getter, raw.bind(publicThis));
    }
    // 'key3': {...}
    else if (isObject(raw)) {
        if (isArray(raw)) {
            // 逐个watcher
            raw.forEach(r => createWatcher(r, ctx, publicThis, key));
        }
        else {
            const handler = isFunction(raw.handler)
                ? raw.handler.bind(publicThis)
                : ctx[raw.handler];
            if (isFunction(handler)) {
                watch(getter, handler, raw);
            }
            else {
                warn(`Invalid watch handler specified by key "${raw.handler}"`, handler);
            }
        }
    }
    else {
        warn(`Invalid watch option: "${key}"`, raw);
    }
}
// 根据path获取最终的值
function createPathGetter(ctx, path) {
    const segments = path.split('.');
    return () => {
        let cur = ctx;
        for (let i = 0; i < segments.length && cur; i++) {
            cur = cur[segments[i]];
        }
        return cur;
    };
}
// 合并所有配置参数 返回给调用者
function resolveMergedOptions(instance) {
    const raw = instance.type;
    const { __merged, mixins, extends: extendsOptions } = raw;
    if (__merged)
        return __merged;
    // 跟之前的差不多 同样的合并顺序
    const globalMixins = instance.appContext.mixins;
    if (!globalMixins.length && !mixins && !extendsOptions)
        return raw;
    const options = {};
    globalMixins.forEach(m => mergeOptions(options, m, instance));
    // 合并属性
    mergeOptions(options, raw, instance);
    return (raw.__merged = options);
}
// 实际合并配置参数对象的实现
function mergeOptions(to, from, instance) {
    const strats = instance.appContext.config.optionMergeStrategies;
    const { mixins, extends: extendsOptions } = from;
    extendsOptions && mergeOptions(to, extendsOptions, instance);
    mixins &&
        mixins.forEach((m) => mergeOptions(to, m, instance));
    for (const key in from) {
        if (strats && hasOwn(strats, key)) {
            to[key] = strats[key](to[key], from[key], instance.proxy, key);
        }
        else {
            to[key] = from[key];
        }
    }
}

简单聊一聊 Webpack tapable 的实现(1)

看下源码的入口文件 tapable/lib/index.js:

exports.__esModule = true;
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
exports.HookMap = require("./HookMap");
exports.MultiHook = require("./MultiHook");

导出的对象对应文档介绍所描述的几种hook类型,我们一个个看他们的具体实现吧 :)

我们先从使用的角度体验下它实现的功能:
首先npm初始化一个项目:

npm init
npm install --save tapable

然后自己随意写个js文件,开头写:

const {
	SyncHook,
	SyncBailHook,
	SyncWaterfallHook,
	SyncLoopHook,
	AsyncParallelHook,
	AsyncParallelBailHook,
	AsyncSeriesHook,
	AsyncSeriesBailHook,
	AsyncSeriesWaterfallHook,
  AsyncSeriesLoopHook
 } = require("tapable");

在node环境下为了可以调试代码,我们可以给vscode添加配置:luanch.json,具体的操作搜索下vscode如何debug调试node环境下的js库代码 就可以了:)

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}\\src\\tap.js"
        }
    ]
}

下面再逐渐添加对应的hook分析内容:

  1. SyncHook 同步钩子
// 创建钩子对象
const hook = new SyncHook();
// 注册name对应的回调函数
hook.tap('logPlugin', () => console.log('被勾了'));
hook.tap('logPlugin1', () => console.log('被勾了1'));
hook.tap('logPlugin2', () => console.log('被勾了2'));
// 按照一定顺序 触发执行所有注册的钩子回调函数
hook.call();

// 最终的结果会按照顺序输出打印的内容

同步钩子的作用就是按照注册的顺序依次执行同步钩子函数,我们可以先简单的设想一下,
我们自己实现这样的功能会怎么做?用个数组存起来,然后依次执行不就完事了?
是的,理论上就是这么简单:)
我们去看下源码咋做的:
tapable/lib/SyncHook.js:

const Hook = require("./Hook");
const HookCodeFactory = require("./HookCodeFactory");

// SyncHookCodeFactory 工厂类继承自 HookCodeFactory 基类 并且实现了自己的 content 方法(虽然还是调用基类上的callTapsSeries,不过不同的hook类对content的实现并不相同)
class SyncHookCodeFactory extends HookCodeFactory {
	content({ onError, onDone, rethrowIfPossible }) {
		return this.callTapsSeries({
			onError: (i, err) => onError(err),
			onDone,
			rethrowIfPossible
		});
	}
}

// 提前创建 生成hook执行代码的工厂函数实例
const factory = new SyncHookCodeFactory();

const TAP_ASYNC = () => {
	throw new Error("tapAsync is not supported on a SyncHook");
};

const TAP_PROMISE = () => {
	throw new Error("tapPromise is not supported on a SyncHook");
};

// hook实例的compile方法就是由工厂实例factory来负责的
const COMPILE = function(options) {
	factory.setup(this, options);
	return factory.create(options);
};

// Synchook的构造函数
function SyncHook(args = [], name = undefined) {
	// 返回基类hook实例 
	const hook = new Hook(args, name);
	hook.constructor = SyncHook;
	// 重写部分方法
	hook.tapAsync = TAP_ASYNC;
	hook.tapPromise = TAP_PROMISE;
	hook.compile = COMPILE;
	return hook;
}

SyncHook.prototype = null;

module.exports = SyncHook;

内容不多,其实SyncHook实例其实是重写了部分方法的基类的hook实例,同时重要的compile方法是由HookCodeFactory实例实现的,
所以我们重点看下这2个文件的代码:
tapable/lib/Hook.js:

const util = require("util");

// 只警告一次
const deprecateContext = util.deprecate(() => {},
"Hook.context is deprecated and will be removed");

// 几个call方法的代理函数 同时也是默认值 他们都是调用Hook实例的_createCall根据不同类型得到实际的call函数
// 我们实际触发的也是这个生成创建的函数
const CALL_DELEGATE = function(...args) {
	this.call = this._createCall("sync");
	return this.call(...args);
};
const CALL_ASYNC_DELEGATE = function(...args) {
	this.callAsync = this._createCall("async");
	return this.callAsync(...args);
};
const PROMISE_DELEGATE = function(...args) {
	this.promise = this._createCall("promise");
	return this.promise(...args);
};

// 基类钩子实现
class Hook {
	constructor(args = [], name = undefined) {
		// 用户声明可以用于钩子回调函数可接受的形参name集合
		this._args = args;
		// name标识
		this.name = name;
		// 钩子回调函数的包装对象的集合
		this.taps = [];
		// 拦截器集合
		this.interceptors = [];
		// 默认的call 用于回退
		this._call = CALL_DELEGATE;
		// 实际每次compile得到的新函数
		this.call = CALL_DELEGATE;
		// 下面2个同上
		this._callAsync = CALL_ASYNC_DELEGATE;
		this.callAsync = CALL_ASYNC_DELEGATE;
		this._promise = PROMISE_DELEGATE;
		this.promise = PROMISE_DELEGATE;
		// 实际的钩子执行的回调函数fn集合
		this._x = undefined;

		// 实例也有下面4个方法的实现 根据具体类 具体覆盖即可
		this.compile = this.compile;
		this.tap = this.tap;
		this.tapAsync = this.tapAsync;
		this.tapPromise = this.tapPromise;
	}

	// 基类自己不实现 交给子类各自实现
	compile(options) {
		throw new Error("Abstract: should be overridden");
	}

	// 根据type构造不同的call函数
	_createCall(type) {
		return this.compile({
			taps: this.taps,
			interceptors: this.interceptors,
			args: this._args,
			type: type
		});
	}

	// 注册单个钩子fn
	_tap(type, options, fn) {
		if (typeof options === "string") {
			options = {
				name: options.trim()
			};
		} else if (typeof options !== "object" || options === null) {
			throw new Error("Invalid tap options");
		}
		if (typeof options.name !== "string" || options.name === "") {
			throw new Error("Missing name for tap");
		}
		if (typeof options.context !== "undefined") {
			deprecateContext();
		}
		// options就是fn的包装对象
		options = Object.assign({ type, fn }, options);
		// 对于新注册一个钩子回调 如果在此之前存在已经注册过的拦截器 那需要对这个新的钩子回调 补上应用拦截器register过滤
		options = this._runRegisterInterceptors(options);
		// 最后插入到钩子回调集合中
		this._insert(options);
	}

	// 3类不同的注册钩子方式
	tap(options, fn) {
		this._tap("sync", options, fn);
	}

	tapAsync(options, fn) {
		this._tap("async", options, fn);
	}

	tapPromise(options, fn) {
		this._tap("promise", options, fn);
	}

	// 对新加入的钩子 应用之前存在的拦截器的register方法
	_runRegisterInterceptors(options) {
		for (const interceptor of this.interceptors) {
			if (interceptor.register) {
				const newOptions = interceptor.register(options);
				if (newOptions !== undefined) {
					options = newOptions;
				}
			}
		}
		return options;
	}

	// 构造一个包含hook实例的公共方法的对象给MultiHook使用
	withOptions(options) {
		const mergeOptions = opt =>
			Object.assign({}, options, typeof opt === "string" ? { name: opt } : opt);

		return {
			name: this.name,
			tap: (opt, fn) => this.tap(mergeOptions(opt), fn),
			tapAsync: (opt, fn) => this.tapAsync(mergeOptions(opt), fn),
			tapPromise: (opt, fn) => this.tapPromise(mergeOptions(opt), fn),
			intercept: interceptor => this.intercept(interceptor),
			isUsed: () => this.isUsed(),
			withOptions: opt => this.withOptions(mergeOptions(opt))
		};
	}

	// 是否被使用过
	isUsed() {
		return this.taps.length > 0 || this.interceptors.length > 0;
	}

	// 注册拦截器
	intercept(interceptor) {
		// 重置下call方法 因为需要重新编译了 拦截器发生了改变
		this._resetCompilation();
		this.interceptors.push(Object.assign({}, interceptor));
		if (interceptor.register) {
			for (let i = 0; i < this.taps.length; i++) {
				// 新的拦截器 对已存在的tap需要补一下register应用
				this.taps[i] = interceptor.register(this.taps[i]);
			}
		}
	}

	// 重置
	_resetCompilation() {
		this.call = this._call;
		this.callAsync = this._callAsync;
		this.promise = this._promise;
	}

	// 插入tap到合适的位置
	_insert(item) {
		// 重置 有新的tap加入了 需要重新生成call方法
		this._resetCompilation();
		// before中存在的是 新tap要放到他们之前的tap集合
		let before;
		if (typeof item.before === "string") {
			before = new Set([item.before]);
		} else if (Array.isArray(item.before)) {
			before = new Set(item.before);
		}
		// stage越小优先级越高
		let stage = 0;
		if (typeof item.stage === "number") {
			stage = item.stage;
		}
		let i = this.taps.length;
		while (i > 0) {
			i--;
			const x = this.taps[i];
			// 往后挪一个位置出来
			this.taps[i + 1] = x;
			const xStage = x.stage || 0;
			if (before) {
				if (before.has(x.name)) {
					before.delete(x.name);
					continue;
				}
				if (before.size > 0) {
					continue;
				}
			}
			if (xStage > stage) {
				continue;
			}
			// 上述任一逻辑触发的continue会让倒序遍历继续往前 继续前面的往后挪一个位置 知道上面的条件都不符合了 这时候位置就找到了插入位置
			// 可以终止遍历了
			// 多减去的i恢复+1
			i++;
			break;
		}
		// 这个挪出来的位置放新元素
		this.taps[i] = item;
	}
}

Object.setPrototypeOf(Hook.prototype, null);

module.exports = Hook;

Hook基类主要负责是新钩子函数的添加,从实现上看跟我们的预设的数组存储还是很类似的,不过它进行了一层数据包装;以及提供对外的call函数,
具体调用的子类实现的compile得到一个函数方法,这个我们看 HookCodeFactory 代码工厂的实现:

// 生成hook的call函数代码的工厂类
class HookCodeFactory {
	constructor(config) {
		this.config = config;
		this.options = undefined;
		this._args = undefined;
	}

	// hook实例调用 compile 触发的 HookCodeFactory实例的create
	create(options) {
		this.init(options);
		let fn;
		// 分3种回调函数 对应生成不同的call函数
		switch (this.options.type) {
			case "sync":
				fn = new Function(
					// 参数列表
					this.args(),
					// 代码片段
					'"use strict";\n' +
						// 一些公共代码片段
						this.header() +
						// 拦截器在对应钩子执行的时候也需要生成调用代码
						this.contentWithInterceptors({
							onError: err => `throw ${err};\n`,
							onResult: result => `return ${result};\n`,
							resultReturns: true,
							onDone: () => "",
							rethrowIfPossible: true
						})
				);
				break;
			case "async":
				fn = new Function(
					// 对应异步函数类型的用户函数 总是需要在最后给用户传递一个callback函数,用户调用它来通知我们这个异步结束了
					this.args({
						after: "_callback"
					}),
					'"use strict";\n' +
						this.header() +
						// 几种结束场景 都是以我们实现的callback调用结束的
						this.contentWithInterceptors({
							onError: err => `_callback(${err});\n`,
							onResult: result => `_callback(null, ${result});\n`,
							onDone: () => "_callback();\n"
						})
				);
				break;
			case "promise":
				let errorHelperUsed = false;
				// 提前得到主体函数内容
				const content = this.contentWithInterceptors({
					onError: err => {
						errorHelperUsed = true;
						return `_error(${err});\n`;
					},
					onResult: result => `_resolve(${result});\n`,
					onDone: () => "_resolve();\n"
				});
				let code = "";
				code += '"use strict";\n';
				code += this.header();
				// 调用的时候需要以 someMethod().then(...) 这样的方式使用 那肯定这个方法就是个promise了
				code += "return new Promise((function(_resolve, _reject) {\n";
				// 添加错误处理函数
				if (errorHelperUsed) {
					code += "var _sync = true;\n";
					code += "function _error(_err) {\n";
					code += "if(_sync)\n";
					code +=
						"_resolve(Promise.resolve().then((function() { throw _err; })));\n";
					code += "else\n";
					code += "_reject(_err);\n";
					code += "};\n";
				}
				// 追加主体函数代码
				code += content;
				if (errorHelperUsed) {
					code += "_sync = false;\n";
				}
				code += "}));\n";
				fn = new Function(this.args(), code);
				break;
		}
		this.deinit();
		// 得到了对应的call函数 可以按照生成的代码去执行了 实际的主体实现都在contentWithInterceptors中
		return fn;
	}

	//  hook实例调用 compile 触发的setup hook实例的_x数组存储 taps中用户原始设置的fn回调
	setup(instance, options) {
		instance._x = options.taps.map(t => t.fn);
	}

	/**
	 * @param {{ type: "sync" | "promise" | "async", taps: Array<Tap>, interceptors: Array<Interceptor> }} options
	 */
	// _args 用户回调可接受形参列表
	init(options) {
		this.options = options;
		this._args = options.args.slice();
	}

	// 清除设置的值
	deinit() {
		this.options = undefined;
		this._args = undefined;
	}

	// 生成code代码的主体调用
	contentWithInterceptors(options) {
		// 有拦截器的情况
		if (this.options.interceptors.length > 0) {
			const onError = options.onError;
			const onResult = options.onResult;
			const onDone = options.onDone;
			let code = "";
			for (let i = 0; i < this.options.interceptors.length; i++) {
				const interceptor = this.options.interceptors[i];
				if (interceptor.call) {
					// call阶段需要执行的拦截器代码
					code += `${this.getInterceptor(i)}.call(${this.args({
						before: interceptor.context ? "_context" : undefined
					})});\n`;
				}
			}
			// 实际的主体的代码是由子类实例自己实现的的content方法来生成的 各个不同代码工厂子类有着自己的实现 从而实现不同的钩子对象类型
			// 当然 下面的是为了追加对应阶段拦截器的方法调用代码
			code += this.content(
				Object.assign(options, {
					onError:
						onError &&
						(err => {
							let code = "";
							for (let i = 0; i < this.options.interceptors.length; i++) {
								const interceptor = this.options.interceptors[i];
								if (interceptor.error) {
									code += `${this.getInterceptor(i)}.error(${err});\n`;
								}
							}
							code += onError(err);
							return code;
						}),
					onResult:
						onResult &&
						(result => {
							let code = "";
							for (let i = 0; i < this.options.interceptors.length; i++) {
								const interceptor = this.options.interceptors[i];
								if (interceptor.result) {
									code += `${this.getInterceptor(i)}.result(${result});\n`;
								}
							}
							code += onResult(result);
							return code;
						}),
					onDone:
						onDone &&
						(() => {
							let code = "";
							for (let i = 0; i < this.options.interceptors.length; i++) {
								const interceptor = this.options.interceptors[i];
								if (interceptor.done) {
									code += `${this.getInterceptor(i)}.done();\n`;
								}
							}
							code += onDone();
							return code;
						})
				})
			);
			return code;
		// 没有拦截器就是直接调用子类工厂的实例content了
		} else {
			return this.content(options);
		}
	}

	// 一些通用的变量声明代码
	header() {
		let code = "";
		// 需要上下文对象传递数据
		if (this.needContext()) {
			code += "var _context = {};\n";
		} else {
			code += "var _context;\n";
		}
		code += "var _x = this._x;\n";
		if (this.options.interceptors.length > 0) {
			code += "var _taps = this.taps;\n";
			code += "var _interceptors = this.interceptors;\n";
		}
		return code;
	}

	// 任意一个tap钩子表示自己需要context存储一些数据
	needContext() {
		for (const tap of this.options.taps) if (tap.context) return true;
		return false;
	}

	// 对于单个tap 它的包裹函数代码实现
	callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
		let code = "";
		let hasTapCached = false;
		for (let i = 0; i < this.options.interceptors.length; i++) {
			const interceptor = this.options.interceptors[i];
			// 拦截器中tap生成的阶段
			if (interceptor.tap) {
				if (!hasTapCached) {
					code += `var _tap${tapIndex} = ${this.getTap(tapIndex)};\n`;
					hasTapCached = true;
				}
				// 追加tap拦截器代码执行片段
				code += `${this.getInterceptor(i)}.tap(${
					interceptor.context ? "_context, " : ""
				}_tap${tapIndex});\n`;
			}
		}
		// 取出用户函数
		code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
		const tap = this.options.taps[tapIndex];
		// 请特别注意onDone和onResult参数 在3种不同类型的场景下 它得到的是什么值
		switch (tap.type) {
			// 同步类型tap的比较简单
			case "sync":
				if (!rethrowIfPossible) {
					code += `var _hasError${tapIndex} = false;\n`;
					code += "try {\n";
				}
				// 执行fn代码
				if (onResult) {
					code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
						before: tap.context ? "_context" : undefined
					})});\n`;
				} else {
					code += `_fn${tapIndex}(${this.args({
						before: tap.context ? "_context" : undefined
					})});\n`;
				}
				// 错误处理
				if (!rethrowIfPossible) {
					code += "} catch(_err) {\n";
					code += `_hasError${tapIndex} = true;\n`;
					code += onError("_err");
					code += "}\n";
					code += `if(!_hasError${tapIndex}) {\n`;
				}
				// 最外层子类的content如果传了onResult函数的话就需要执行
				if (onResult) {
					code += onResult(`_result${tapIndex}`);
				}
				// 追加onDone代码 回顾synchook的代码中的参数
				if (onDone) {
					code += onDone();
				}
				if (!rethrowIfPossible) {
					code += "}\n";
				}
				break;
			// 异步类型主要就是必须生成 最后的那一个callback函数参数
			case "async":
				let cbCode = "";
				if (onResult)
					cbCode += `(function(_err${tapIndex}, _result${tapIndex}) {\n`;
				else cbCode += `(function(_err${tapIndex}) {\n`;
				cbCode += `if(_err${tapIndex}) {\n`;
				cbCode += onError(`_err${tapIndex}`);
				cbCode += "} else {\n";
				if (onResult) {
					cbCode += onResult(`_result${tapIndex}`);
				}
				if (onDone) {
					cbCode += onDone();
				}
				cbCode += "}\n";
				cbCode += "})";
				// cbCode代码作为最后个参数合入
				code += `_fn${tapIndex}(${this.args({
					before: tap.context ? "_context" : undefined,
					after: cbCode
				})});\n`;
				break;
			// 对于promise类型 我们接受的用户函数是一个返回promise的普通函数
			case "promise":
				code += `var _hasResult${tapIndex} = false;\n`;
				code += `var _promise${tapIndex} = _fn${tapIndex}(${this.args({
					before: tap.context ? "_context" : undefined
				})});\n`;
				code += `if (!_promise${tapIndex} || !_promise${tapIndex}.then)\n`;
				code += `  throw new Error('Tap function (tapPromise) did not return promise (returned ' + _promise${tapIndex} + ')');\n`;
				code += `_promise${tapIndex}.then((function(_result${tapIndex}) {\n`;
				code += `_hasResult${tapIndex} = true;\n`;
				if (onResult) {
					code += onResult(`_result${tapIndex}`);
				}
				if (onDone) {
					code += onDone();
				}
				code += `}), function(_err${tapIndex}) {\n`;
				code += `if(_hasResult${tapIndex}) throw _err${tapIndex};\n`;
				code += onError(`_err${tapIndex}`);
				code += "});\n";
				break;
		}
		return code;
	}

	// SyncHook的content就是直接调用它了
	// 串行执行的taps
	callTapsSeries({
		onError,
		onResult,
		resultReturns,
		onDone,
		doneReturns,
		rethrowIfPossible
	}) {
		if (this.options.taps.length === 0) return onDone();
		// 如果存在某个异步tap
		const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");
		// 存在需要返回值的情况
		const somethingReturns = resultReturns || doneReturns;
		let code = "";
		let current = onDone;
		let unrollCounter = 0;
		// 从后往前遍历 这个顺序很有意思 意味着 后的一个函数的代码会被它的前一个函数代码用到 这在串行异步的场景中很有用
		for (let j = this.options.taps.length - 1; j >= 0; j--) {
			const i = j;
			// 不是第一次调用 且 当前tap是异步的 或者 同步tap出现超过20次了
			const unroll =
				current !== onDone &&
				(this.options.taps[i].type !== "sync" || unrollCounter++ > 20);
			if (unroll) {
				unrollCounter = 0;
				// 创建一个next i 函数 中间的代码是注册在它后面的tap代码
				code += `function _next${i}() {\n`;
				code += current();
				code += `}\n`;
				current = () => `${somethingReturns ? "return " : ""}_next${i}();\n`;
			}
			// 注意这个赋值 当前正在处理的tap以排在数组中它后面的tap生成的current作为done函数 追加在当前tap代码的后面
			const done = current;
			const doneBreak = skipDone => {
				if (skipDone) return "";
				return onDone();
			};
			// 生成单个tap的代码
			const content = this.callTap(i, {
				onError: error => onError(i, error, done, doneBreak),
				onResult:
					onResult &&
					(result => {
						// 子类工厂实例content中所接受的几个参数
						return onResult(i, result, done, doneBreak);
					}),
				// 对于串行的tap 注意这个ondone的值 返回的是前一个处理的tap 然后追加在自己的后面
				onDone: !onResult && done,
				// 注意抛出错误的条件
				rethrowIfPossible:
					rethrowIfPossible && (firstAsync < 0 || i < firstAsync)
			});
			// 返回当前tap的代码
			current = () => content;
		}
		// 最后的current函数返回值中保存着第一个tap的代码 也就是它后面的所有的tap代码集合
		code += current();
		return code;
	}

	// 带有循环效果的taps 某个tap如果返回值不为undefined 就一直执行它 知道它成为undefined 才继续下面的tap
	callTapsLooping({ onError, onDone, rethrowIfPossible }) {
		if (this.options.taps.length === 0) return onDone();
		const syncOnly = this.options.taps.every(t => t.type === "sync");
		let code = "";
		if (!syncOnly) {
			code += "var _looper = (function() {\n";
			code += "var _loopAsync = false;\n";
		}
		code += "var _loop;\n";
		code += "do {\n";
		code += "_loop = false;\n";
		for (let i = 0; i < this.options.interceptors.length; i++) {
			const interceptor = this.options.interceptors[i];
			if (interceptor.loop) {
				code += `${this.getInterceptor(i)}.loop(${this.args({
					before: interceptor.context ? "_context" : undefined
				})});\n`;
			}
		}
		// loop 也调用了 callTapsSeries 不过onResult和onDone重写过了
		code += this.callTapsSeries({
			onError,
			onResult: (i, result, next, doneBreak) => {
				let code = "";
				code += `if(${result} !== undefined) {\n`;
				code += "_loop = true;\n";
				if (!syncOnly) code += "if(_loopAsync) _looper();\n";
				code += doneBreak(true);
				code += `} else {\n`;
				code += next();
				code += `}\n`;
				return code;
			},
			onDone:
				onDone &&
				(() => {
					let code = "";
					code += "if(!_loop) {\n";
					code += onDone();
					code += "}\n";
					return code;
				}),
			rethrowIfPossible: rethrowIfPossible && syncOnly
		});
		code += "} while(_loop);\n";
		if (!syncOnly) {
			code += "_loopAsync = true;\n";
			code += "});\n";
			code += "_looper();\n";
		}
		return code;
	}

	// 并行tap的主体代码生成
	callTapsParallel({
		onError,
		onResult,
		onDone,
		rethrowIfPossible,
		onTap = (i, run) => run()
	}) {
		// 只有一个并行的tap 就退化成了一个串行一样的情况了
		if (this.options.taps.length <= 1) {
			return this.callTapsSeries({
				onError,
				onResult,
				onDone,
				rethrowIfPossible
			});
		}
		let code = "";
		code += "do {\n";
		// 对于并行的异步tap 都需要一个计数器来判断所有的异步任务是否已经全部结束了
		code += `var _counter = ${this.options.taps.length};\n`;
		if (onDone) {
			code += "var _done = (function() {\n";
			code += onDone();
			code += "});\n";
		}
		for (let i = 0; i < this.options.taps.length; i++) {
			const done = () => {
				if (onDone) return "if(--_counter === 0) _done();\n";
				else return "--_counter;";
			};
			const doneBreak = skipDone => {
				if (skipDone || !onDone) return "_counter = 0;\n";
				else return "_counter = 0;\n_done();\n";
			};
			code += "if(_counter <= 0) break;\n";
			code += onTap(
				i,
				() =>
					this.callTap(i, {
						onError: error => {
							let code = "";
							code += "if(_counter > 0) {\n";
							code += onError(i, error, done, doneBreak);
							code += "}\n";
							return code;
						},
						onResult:
							onResult &&
							(result => {
								let code = "";
								code += "if(_counter > 0) {\n";
								code += onResult(i, result, done, doneBreak);
								code += "}\n";
								return code;
							}),
						onDone:
							!onResult &&
							(() => {
								return done();
							}),
						rethrowIfPossible
					}),
				done,
				doneBreak
			);
		}
		code += "} while(false);\n";
		return code;
	}

	// 形参列表
	args({ before, after } = {}) {
		let allArgs = this._args;
		// 根据参数 合并需要添加的前后形参
		if (before) allArgs = [before].concat(allArgs);
		if (after) allArgs = allArgs.concat(after);
		if (allArgs.length === 0) {
			return "";
		} else {
			return allArgs.join(", ");
		}
	}

	// 下面3个都是 从hook实例属性中取出对应类型实际的函数引用 的代码code
	getTapFn(idx) {
		return `_x[${idx}]`;
	}

	getTap(idx) {
		return `_taps[${idx}]`;
	}

	getInterceptor(idx) {
		return `_interceptors[${idx}]`;
	}
}

module.exports = HookCodeFactory;

从代码中可以看到,HookCodeFactory 基类实现了通用部分的代码生成函数,同时也保留了给外部的子类实现自己的content,通过传入的参数来控制部分关键流程逻辑的
代码生成逻辑,从而实现不同的子类实现不同的控制效果;而通用的每个类型的tap的包裹函数则在基类中实现了,我们先看下SyncHook的生成的代码的样子:

// 创建钩子对象
const hook = new SyncHook();
// 注册name对应的回调函数
hook.tap('logPlugin', () => console.log('被勾了'));
hook.tap('logPlugin1', () => console.log('被勾了1'));
hook.tap('logPlugin2', () => console.log('被勾了2'));
// 按照一定顺序 触发执行所有注册的钩子回调函数
hook.call();
// 得到的call是这样的:

(function anonymous(
) {
    "use strict";
    var _context;
    var _x = this._x;
    var _fn0 = _x[0];
    _fn0();
    var _fn1 = _x[1];
    _fn1();
    var _fn2 = _x[2];
    _fn2();
})

可以回忆下之前的 callTapsSeries 是如何反向遍历如何一步一步生成
var _fn2 = _x[2];_fn2(); 这样的代码片段的

  1. SyncBailHook 同步熔断钩子: 只要中间一个返回值不是undefined 就取消后面的tap了
    tapable/lib/SyncBailHook.js
// 同 SyncHook 几乎没有区别 只看不同的部分
// content实现不同 从而导致生成的代码片段不同
class SyncBailHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, resultReturns, onDone, rethrowIfPossible }) {
        // 也是直接调用 基类的 callTapsSeries 得到串行taps代码 不过由于 自己实现了 onResult 导致了不同的代码产生
		return this.callTapsSeries({
			onError: (i, err) => onError(err),
            // 注意这个next 得到的是上一个处理的tap的代码
			onResult: (i, result, next) =>
				`if(${result} !== undefined) {\n${onResult(
					result
				)};\n} else {\n${next()}}\n`,
			resultReturns,
			onDone,
			rethrowIfPossible
		});
	}
}

创造一个实例看下:

const hook = new SyncBailHook();
hook.tap('logPlugin', () => console.log('被勾了'));
hook.tap('logPlugin1', () => {console.log('被勾了1'); return 1;} );
hook.tap('logPlugin2', () => console.log('被勾了2'));
hook.call();

// 得到的call是这样的:

(function anonymous(
    ) {
        "use strict";
        var _context;
        var _x = this._x;
        var _fn0 = _x[0];
        var _result0 = _fn0();
        if (_result0 !== undefined) {
            return _result0;
            ;
        } else {
            var _fn1 = _x[1];
            var _result1 = _fn1();
            if (_result1 !== undefined) {
                return _result1;
                ;
            } else {
                var _fn2 = _x[2];
                var _result2 = _fn2();
                if (_result2 !== undefined) {
                    return _result2;
                    ;
                } else {
                }
            }
        }
    })

可以看到,逆序生成导致了先得到 2 再是1 然后是 0,同SyncHook的区别就是在于对于onResult参数的传入,导致了连接2个tap的代码片段更改了,从而实现了熔断的效果

  1. SyncWaterfallHook 同步瀑布流钩子:上一个tap的返回值会传递给下一个tap
    tapable/lib/SyncWaterfallHook.js
class SyncWaterfallHookCodeFactory extends HookCodeFactory {
	content({ onError, onResult, resultReturns, rethrowIfPossible }) {
		return this.callTapsSeries({
			onError: (i, err) => onError(err),
            // 跟SyncBailHook差不多 传入了自己的onResult
			onResult: (i, result, next) => {
				let code = "";
				code += `if(${result} !== undefined) {\n`;
				code += `${this._args[0]} = ${result};\n`;
				code += `}\n`;
				code += next();
				return code;
			},
            // onDone也重写了
			onDone: () => onResult(this._args[0]),
			doneReturns: resultReturns,
			rethrowIfPossible
		});
	}
}
const hook = new SyncWaterfallHook(['arg1']);
hook.tap('logPlugin', () => {console.log('被勾了'); return 0;});
hook.tap('logPlugin1', () => {console.log('被勾了1'); return 1;} );
hook.tap('logPlugin2', () => {console.log('被勾了2'); return 2;});
hook.call();

// 得到的call是这样的:

(function anonymous(arg1
    ) {
        "use strict";
        var _context;
        var _x = this._x;
        var _fn0 = _x[0];
        var _result0 = _fn0(arg1);
        if (_result0 !== undefined) {
            arg1 = _result0;
        }
        var _fn1 = _x[1];
        var _result1 = _fn1(arg1);
        if (_result1 !== undefined) {
            arg1 = _result1;
        }
        var _fn2 = _x[2];
        var _result2 = _fn2(arg1);
        if (_result2 !== undefined) {
            arg1 = _result2;
        }
        return arg1;

    })

可以看到瀑布流就是修改了第一个参数,其余的和普通的SyncHook几乎一样

Vue(v3.0.11)源码简析之模板编译generate的相关实现

generate负责生成最后的render函数代码字符串:

// generate上下文:公共方法以及存储中间状态
const PURE_ANNOTATION = `/*#__PURE__*/`;
function createCodegenContext(ast, { mode = 'function', prefixIdentifiers = mode === 'module', sourceMap = false, filename = `template.vue.html`, scopeId = null, optimizeImports = false, runtimeGlobalName = `Vue`, runtimeModuleName = `vue`, ssr = false }) {
    // render代码生成上下文
    const context = {
        // 模式 我们就按默认的普通函数模式就好了 'function'
        mode,
        // function 下为false
        prefixIdentifiers,
        // 开启sourcemap
        sourceMap,
        // 按默认值处理就好
        filename,
        // 递归调用的作用域深度标识
        scopeId,
        // TODO
        optimizeImports,
        // 运行时导入依赖方法的源头变量名
        runtimeGlobalName,
        runtimeModuleName,
        ssr,
        // 原始字符串html
        source: ast.loc.source,
        // 最重要的就是最终产出 code
        code: ``,
        // 位置信息
        column: 1,
        line: 1,
        offset: 0,
        // 缩进信息 代码格式
        indentLevel: 0,
        // 为true的时候 添加 PURE_ANNOTATION 信息
        pure: false,
        // TODO
        map: undefined,
        // render函数中使用的helper方法名字
        helper(key) {
            return `_${helperNameMap[key]}`;
        },
        // 拼接code代码
        push(code, node) {
            context.code += code;
        },
        // 控制缩进
        indent() {
            newline(++context.indentLevel);
        },
        deindent(withoutNewLine = false) {
            if (withoutNewLine) {
                --context.indentLevel;
            }
            else {
                newline(--context.indentLevel);
            }
        },
        // 另起一行
        newline() {
            newline(context.indentLevel);
        }
    };
    // 另起一行 适配缩进
    function newline(n) {
        // 缩进是2个空格 :)
        context.push('\n' + `  `.repeat(n));
    }
    return context;
}
// transform完成之后 生成render函数的code码
function generate(ast, options = {}) {
    // 得到生成上下文对象
    const context = createCodegenContext(ast, options);
    // 触发钩子
    if (options.onContextCreated)
        options.onContextCreated(context);
    const { mode, push, prefixIdentifiers, indent, deindent, newline, scopeId, ssr } = context;
    // 是否需要添加helper导入的信息
    const hasHelpers = ast.helpers.length > 0;
    // 当前render自成 一个块 拥有自己的上下文 function下我们总是true
    const useWithBlock = !prefixIdentifiers && mode !== 'module';
    // preambles
    // in setup() inline mode, the preamble is generated in a sub context
    // and returned separately.

    // 提前预处理静态提升的节点
    const preambleContext = context;
    {
        genFunctionPreamble(ast, preambleContext);
    }
    // enter render function
    // 我们就按 render 函数名字处理就好了
    const functionName = ssr ? `ssrRender` : `render`;
    // 浏览器环境下我们用第二个的参数 上下文和缓存就可以了
    const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache'];
    const signature = args.join(', ');
    {
        push(`function ${functionName}(${signature}) {`);
    }
    indent();
    if (useWithBlock) {
        push(`with (_ctx) {`);
        indent();
        // function mode const declarations should be inside with block
        // also they should be renamed to avoid collision with user properties
        // 引入所有的helpers辅助方法
        if (hasHelpers) {
            push(`const { ${ast.helpers
              .map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
              .join(', ')} } = _Vue`);
            push(`\n`);
            newline();
        }
    }
    // generate asset resolution statements
    // 引入componet解析辅助方法
    if (ast.components.length) {
        genAssets(ast.components, 'component', context);
        if (ast.directives.length || ast.temps > 0) {
            newline();
        }
    }
    // 引入指令解析辅助方法
    if (ast.directives.length) {
        genAssets(ast.directives, 'directive', context);
        if (ast.temps > 0) {
            newline();
        }
    }
    // TODO 从代码上看 似乎是一些临时变量 但是作用目前不明
    if (ast.temps > 0) {
        push(`let `);
        for (let i = 0; i < ast.temps; i++) {
            push(`${i > 0 ? `, ` : ``}_temp${i}`);
        }
    }
    if (ast.components.length || ast.directives.length || ast.temps) {
        push(`\n`);
        newline();
    }
    // generate the VNode tree expression
    if (!ssr) {
        push(`return `);
    }
    // 主体生成vnode代码
    if (ast.codegenNode) {
        // 这个函数递归调用自己 完成所有节点的遍历 生成完整的代码
        genNode(ast.codegenNode, context);
    }
    else {
        push(`null`);
    }
    // 闭合函数尾部
    if (useWithBlock) {
        deindent();
        push(`}`);
    }
    deindent();
    push(`}`);
    // 返回给调用者的信息
    return {
        ast,
        code: context.code,
        preamble: ``,
        // SourceMapGenerator does have toJSON() method but it's not in the types
        map: context.map ? context.map.toJSON() : undefined
    };
}
// 预处理
function genFunctionPreamble(ast, context) {
    const { ssr, prefixIdentifiers, push, newline, runtimeModuleName, runtimeGlobalName } = context;
    const VueBinding = runtimeGlobalName;
    const aliasHelper = (s) => `${helperNameMap[s]}: _${helperNameMap[s]}`;
    // Generate const declaration for helpers
    // In prefix mode, we place the const declaration at top so it's done
    // only once; But if we not prefixing, we place the declaration inside the
    // with block so it doesn't incur the `in` check cost for every helper access.
    // 提前引入静态提升的辅助方法
    if (ast.helpers.length > 0) {
        {
            // "with" mode.
            // save Vue in a separate variable to avoid collision
            push(`const _Vue = ${VueBinding}\n`);
            // in "with" mode, helpers are declared inside the with block to avoid
            // has check cost, but hoists are lifted out of the function - we need
            // to provide the helper here.
            if (ast.hoists.length) {
                const staticHelpers = [
                    CREATE_VNODE,
                    CREATE_COMMENT,
                    CREATE_TEXT,
                    CREATE_STATIC
                ]
                    .filter(helper => ast.helpers.includes(helper))
                    .map(aliasHelper)
                    .join(', ');
                push(`const { ${staticHelpers} } = _Vue\n`);
            }
        }
    }
    // 生成静态内容
    genHoists(ast.hoists, context);
    newline();
    push(`return `);
}
// 生成组件和指令的辅助方法调用结构
function genAssets(assets, type, { helper, push, newline }) {
    const resolver = helper(type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE);
    for (let i = 0; i < assets.length; i++) {
        let id = assets[i];
        // potential component implicit self-reference inferred from SFC filename
        const maybeSelfReference = id.endsWith('__self');
        if (maybeSelfReference) {
            id = id.slice(0, -6);
        }
        // 添加解析 组件/指令的 代码
        push(`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${maybeSelfReference ? `, true` : ``})`);
        if (i < assets.length - 1) {
            newline();
        }
    }
}
// 把提取的静态内容一次性引入
function genHoists(hoists, context) {
    if (!hoists.length) {
        return;
    }
    context.pure = true;
    const { push, newline, helper, scopeId, mode } = context;
    newline();
    // 逐一得到静态节点
    hoists.forEach((exp, i) => {
        if (exp) {
            push(`const _hoisted_${i + 1} = `);
            // 调用 genNode 完成处理
            genNode(exp, context);
            newline();
        }
    });
    context.pure = false;
}
// 内容可以被视为text的情况 直接把内容加入代码中即可
function isText$1(n) {
    return (isString(n) ||
        n.type === 4 /* SIMPLE_EXPRESSION */ ||
        n.type === 2 /* TEXT */ ||
        n.type === 5 /* INTERPOLATION */ ||
        n.type === 8 /* COMPOUND_EXPRESSION */);
}
function genNodeListAsArray(nodes, context) {
    // 需要另起一行的情况
    const multilines = nodes.length > 3 ||
        (nodes.some(n => isArray(n) || !isText$1(n)));
    context.push(`[`);
    multilines && context.indent();
    // 继续解析子内容
    genNodeList(nodes, context, multilines);
    multilines && context.deindent();
    context.push(`]`);
}
// nodes中含有多个类型的参数 tag, props, children, patchFlag, dynamicProps
function genNodeList(nodes, context, multilines = false, comma = true) {
    const { push, newline } = context;
    for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i];
        // tag
        if (isString(node)) {
            push(node);
        }
        // children dynamicProps
        else if (isArray(node)) {
            genNodeListAsArray(node, context);
        }
        // 其他的
        else {
            genNode(node, context);
        }
        // 格式
        if (i < nodes.length - 1) {
            if (multilines) {
                comma && push(',');
                newline();
            }
            else {
                comma && push(', ');
            }
        }
    }
}
// 生成node的code码主方法
function genNode(node, context) {
    // 2种递归出口
    // node为字符串 textnode
    if (isString(node)) {
        context.push(node);
        return;
    }
    // 辅助方式产生vnode的场景 node是方法名
    if (isSymbol(node)) {
        context.push(context.helper(node));
        return;
    }
    // 分情况处理
    switch (node.type) {
        // 这几种节点上必定存在着codegenNode信息 我们直接用就是了
        case 1 /* ELEMENT */:
        case 9 /* IF */:
        case 11 /* FOR */:
            assert(node.codegenNode != null, `Codegen node is missing for element/if/for node. ` +
                    `Apply appropriate transforms first.`);
            genNode(node.codegenNode, context);
            break;
        case 2 /* TEXT */:
            genText(node, context);
            break;
        case 4 /* SIMPLE_EXPRESSION */:
            genExpression(node, context);
            break;
        case 5 /* INTERPOLATION */:
            genInterpolation(node, context);
            break;
        case 12 /* TEXT_CALL */:
            genNode(node.codegenNode, context);
            break;
        case 8 /* COMPOUND_EXPRESSION */:
            genCompoundExpression(node, context);
            break;
        case 3 /* COMMENT */:
            genComment(node, context);
            break;
        case 13 /* VNODE_CALL */:
            genVNodeCall(node, context);
            break;
        case 14 /* JS_CALL_EXPRESSION */:
            genCallExpression(node, context);
            break;
        case 15 /* JS_OBJECT_EXPRESSION */:
            genObjectExpression(node, context);
            break;
        case 17 /* JS_ARRAY_EXPRESSION */:
            genArrayExpression(node, context);
            break;
        case 18 /* JS_FUNCTION_EXPRESSION */:
            genFunctionExpression(node, context);
            break;
        case 19 /* JS_CONDITIONAL_EXPRESSION */:
            genConditionalExpression(node, context);
            break;
        case 20 /* JS_CACHE_EXPRESSION */:
            genCacheExpression(node, context);
            break;
        // SSR only types
        case 21 /* JS_BLOCK_STATEMENT */:
            break;
        case 22 /* JS_TEMPLATE_LITERAL */:
            break;
        case 23 /* JS_IF_STATEMENT */:
            break;
        case 24 /* JS_ASSIGNMENT_EXPRESSION */:
            break;
        case 25 /* JS_SEQUENCE_EXPRESSION */:
            break;
        case 26 /* JS_RETURN_STATEMENT */:
            break;
        /* istanbul ignore next */
        case 10 /* IF_BRANCH */:
            // noop
            break;
        default:
            {
                assert(false, `unhandled codegen node type: ${node.type}`);
                // make sure we exhaust all possible types
                const exhaustiveCheck = node;
                return exhaustiveCheck;
            }
    }
}
// text 节点内容 直接加入代码中就好了
function genText(node, context) {
    context.push(JSON.stringify(node.content), node);
}
// 表达式内容
function genExpression(node, context) {
    const { content, isStatic } = node;
    // 静态的 加入字符串就好了 动态的 保留变量字符串 render调用时候作为变量触发get
    context.push(isStatic ? JSON.stringify(content) : content, node);
}
// 插值 递归调用解析插值的content
function genInterpolation(node, context) {
    const { push, helper, pure } = context;
    if (pure)
        push(PURE_ANNOTATION);
    push(`${helper(TO_DISPLAY_STRING)}(`);
    // 递归解析它内部的插槽表达式
    genNode(node.content, context);
    push(`)`);
}
// 组合表达式 递归处理子节点 
function genCompoundExpression(node, context) {
    for (let i = 0; i < node.children.length; i++) {
        const child = node.children[i];
        // 遍历子节点 类型可以有多种
        if (isString(child)) {
            context.push(child);
        }
        else {
            genNode(child, context);
        }
    }
}
// 对象表达式的key值代码
function genExpressionAsPropertyKey(node, context) {
    const { push } = context;
    if (node.type === 8 /* COMPOUND_EXPRESSION */) {
        push(`[`);
        genCompoundExpression(node, context);
        push(`]`);
    }
    else if (node.isStatic) {
        // only quote keys if necessary
        const text = isSimpleIdentifier(node.content)
            ? node.content
            : JSON.stringify(node.content);
        push(text, node);
    }
    else {
        push(`[${node.content}]`, node);
    }
}
// 生成注释节点
function genComment(node, context) {
    {
        const { push, helper, pure } = context;
        if (pure) {
            push(PURE_ANNOTATION);
        }
        // 生成注释节点代码添加
        push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node);
    }
}
// 最重要的是vnodecall的生成 得到vnode节点
function genVNodeCall(node, context) {
    const { push, helper, pure } = context;
    const { tag, props, children, patchFlag, dynamicProps, directives, isBlock, disableTracking } = node;
    // 运行时指令存在 先用指令代码包裹起来 到时候render执行的时候 注入指令
    if (directives) {
        push(helper(WITH_DIRECTIVES) + `(`);
    }
    // 添加block信息
    if (isBlock) {
        push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `);
    }
    if (pure) {
        push(PURE_ANNOTATION);
    }
    // 带block的vnode还是普通vnode的代码
    push(helper(isBlock ? CREATE_BLOCK : CREATE_VNODE) + `(`, node);
    // 递归处理子节点
    genNodeList(genNullableArgs([tag, props, children, patchFlag, dynamicProps]), context);
    // 处理完成
    push(`)`);
    if (isBlock) {
        push(`)`);
    }
    // 最后把指令的名字也加入到dynamicProps参数列表中
    if (directives) {
        push(`, `);
        genNode(directives, context);
        push(`)`);
    }
}
// 只传入非null参数列表
function genNullableArgs(args) {
    let i = args.length;
    while (i--) {
        if (args[i] != null)
            break;
    }
    return args.slice(0, i + 1).map(arg => arg || `null`);
}
// JavaScript 函数调用才能得到vnode的代码
function genCallExpression(node, context) {
    const { push, helper, pure } = context;
    // 函数名
    const callee = isString(node.callee) ? node.callee : helper(node.callee);
    if (pure) {
        push(PURE_ANNOTATION);
    }
    push(callee + `(`, node);
    // 函数参数 可以是任意类型
    genNodeList(node.arguments, context);
    push(`)`);
}
// 输出对象表达式代码
function genObjectExpression(node, context) {
    const { push, indent, deindent, newline } = context;
    const { properties } = node;
    // 空值情况
    if (!properties.length) {
        push(`{}`, node);
        return;
    }
    const multilines = properties.length > 1 ||
        (properties.some(p => p.value.type !== 4 /* SIMPLE_EXPRESSION */));
    push(multilines ? `{` : `{ `);
    multilines && indent();
    for (let i = 0; i < properties.length; i++) {
        const { key, value } = properties[i];
        // key
        genExpressionAsPropertyKey(key, context);
        push(`: `);
        // value
        genNode(value, context);
        if (i < properties.length - 1) {
            // will only reach this if it's multilines
            push(`,`);
            newline();
        }
    }
    multilines && deindent();
    push(multilines ? `}` : ` }`);
}
// 数组表达式
function genArrayExpression(node, context) {
    // 内容总是存储在 elements 列表中
    genNodeListAsArray(node.elements, context);
}
// 函数表达式代码
function genFunctionExpression(node, context) {
    const { push, indent, deindent, scopeId, mode } = context;
    const { params, returns, body, newline, isSlot } = node;
    // slot节点 需要改变作用域 调用父节点的作用域
    if (isSlot) {
        // wrap slot functions with owner context
        push(`_${helperNameMap[WITH_CTX]}(`);
    }
    push(`(`, node);
    // 解析参数列表
    if (isArray(params)) {
        genNodeList(params, context);
    }
    else if (params) {
        genNode(params, context);
    }
    // 完善函数代码
    push(`) => `);
    if (newline || body) {
        push(`{`);
        indent();
    }
    // 返回值
    if (returns) {
        if (newline) {
            push(`return `);
        }
        if (isArray(returns)) {
            genNodeListAsArray(returns, context);
        }
        else {
            genNode(returns, context);
        }
    }
    else if (body) {
        genNode(body, context);
    }
    if (newline || body) {
        deindent();
        push(`}`);
    }
    if (isSlot) {
        push(`)`);
    }
}
// 三元条件表达式
function genConditionalExpression(node, context) {
    const { test, consequent, alternate, newline: needNewline } = node;
    const { push, indent, deindent, newline } = context;
    if (test.type === 4 /* SIMPLE_EXPRESSION */) {
        const needsParens = !isSimpleIdentifier(test.content);
        needsParens && push(`(`);
        // 测试条件值代码
        genExpression(test, context);
        needsParens && push(`)`);
    }
    else {
        push(`(`);
        // 其他复杂情况
        genNode(test, context);
        push(`)`);
    }
    needNewline && indent();
    context.indentLevel++;
    needNewline || push(` `);
    push(`? `);
    // 递归调用解析 成功值
    genNode(consequent, context);
    context.indentLevel--;
    needNewline && newline();
    needNewline || push(` `);
    push(`: `);
    // 失败场景也是一个条件节点 说明存在嵌套的三元表达式
    const isNested = alternate.type === 19 /* JS_CONDITIONAL_EXPRESSION */;
    if (!isNested) {
        context.indentLevel++;
    }
    // 递归调用解析失败场景表达式 继续生存三元表达式
    genNode(alternate, context);
    if (!isNested) {
        context.indentLevel--;
    }
    needNewline && deindent(true /* without newline */);
}
// 缓存表达式
function genCacheExpression(node, context) {
    const { push, helper, indent, deindent, newline } = context;
    push(`_cache[${node.index}] || (`);
    if (node.isVNode) {
        indent();
        // 禁止追踪响应式
        push(`${helper(SET_BLOCK_TRACKING)}(-1),`);
        newline();
    }
    push(`_cache[${node.index}] = `);
    genNode(node.value, context);
    if (node.isVNode) {
        push(`,`);
        newline();
        // 禁止追踪响应式
        push(`${helper(SET_BLOCK_TRACKING)}(1),`);
        newline();
        push(`_cache[${node.index}]`);
        deindent();
    }
    push(`)`);
}

简单聊一聊 express.js 的中间件的实现吧

从最简单的例子分析:

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

我们可以注意到express其实是帮我们做了一个我们web开发都要做的,但是很重要的一个事情,那就是绑定url和对这个url的处理函数,这其实也是我们开发web服务器都要面对的问题;express替绑实现url绑定回调的过程,我们只负责回调的业务方法实现,这就节省了很多的时间,不然我们还得自己解析url,建立绑定关系,每次请求到来,匹配寻找,现在都省了;那我们看下这个简单的例子,express是怎么实现的:

/**
 * Expose `createApplication()`.
 */

// 导出的express函数
exports = module.exports = createApplication;

/**
 * Create an express application.
 *
 * @return {Function}
 * @api public
 */

function createApplication() {
  // 注意app是个函数 它的几个参数是我们创建http服务的时候需要被使用的
  // 而实际处理每个请求的实现是由 app.handle(req, res, next) 来完成的
  var app = function(req, res, next) {
    app.handle(req, res, next);
  };

  // 原型对象上的属性导入到app上
  mixin(app, EventEmitter.prototype, false);
  mixin(app, proto, false);

  // 我们自己实现的req res对象上面有很多express实现的方法
  // 通过 request response 的原型对象来引用它们的方法
  // expose the prototype that will get set on requests
  app.request = Object.create(req, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  // expose the prototype that will get set on responses
  app.response = Object.create(res, {
    app: { configurable: true, enumerable: true, writable: true, value: app }
  })

  // 初始化一些配置
  app.init();
  return app;
}

// 可以看到express()的过程是创建一个app函数用于承载每个请求的处理,具体请求处理实现由原型上的app.handle实现,同时我们也挂载了一些预先实现的方法,以及调用了原型上的init

我们可以先看下app原型对象上的 listen方法其实:

// 注意listen里面的 http.createServer(this) this是app实例 是个函数 它的参数arguments就是req res next 这几个
app.listen = function listen() {
  var server = http.createServer(this);
  return server.listen.apply(server, arguments);
};

然后分析几个比较重要的实现:

// 初始化调用的init
app.init = function init() {
  var router = null;

  // 存储内部变量 都挂载在实例app上
  this.cache = {};
  this.engines = {};
  this.settings = {};

  this.defaultConfiguration();

  // 延迟初始化一次的router实例
  // Setup getting to lazily add base router
  Object.defineProperty(this, 'router', {
    configurable: true,
    enumerable: true,
    get: function getrouter() {
      if (router === null) {
        router = new Router({
          caseSensitive: this.enabled('case sensitive routing'),
          strict: this.enabled('strict routing')
        });
      }

      return router;
    }
  });
};

init的过程就是一些默认配置的初始化,没啥好讲的;

// 处理每个请求的实现就是在这里咯
app.handle = function handle(req, res, callback) {
  // final handler
  // 准备一个默认的next 用来结束请求 响应默认的文档或者错误信息
  var done = callback || finalhandler(req, res, {
    env: this.get('env'),
    onerror: logerror.bind(this)
  });

  // set powered by header
  if (this.enabled('x-powered-by')) {
    res.setHeader('X-Powered-By', 'Express');
  }

  // 设置循环引用
  // set circular references
  req.res = res;
  res.req = req;

  // 让我们实现的req res上的方法也可以直接被req res使用
  // alter the prototypes
  setPrototypeOf(req, this.request)
  setPrototypeOf(res, this.response)

  // setup locals
  if (!res.locals) {
    res.locals = Object.create(null);
  }

  // 最终还是router实例的handle来处理
  this.router.handle(req, res, done);
};

/**
 * Proxy `Router#use()` to add middleware to the app router.
 * See Router#use() documentation for details.
 *
 * If the _fn_ parameter is an express app, then it will be
 * mounted at the _route_ specified.
 *
 * @public
 */

// 我们常用的use对指定路径或者 / 添加中间件 这个比较重要
app.use = function use(fn) {
  var offset = 0;
  var path = '/';

  // default path to '/'
  // disambiguate app.use([fn])
  // 处理不同格式的参数
  if (typeof fn !== 'function') {
    var arg = fn;

    while (Array.isArray(arg) && arg.length !== 0) {
      arg = arg[0];
    }

    // first arg is the path
    if (typeof arg !== 'function') {
      offset = 1;
      path = fn;
    }
  }

  // 展开成一维数组即可
  var fns = flatten(slice.call(arguments, offset));

  if (fns.length === 0) {
    throw new TypeError('app.use() requires a middleware function')
  }

  // get router
  var router = this.router;

  fns.forEach(function (fn) {
    // non-express app
    // 普通的场景的话就是 直接 router.use(path, fn) 来建立绑定关系即可
    // 但是fn是子express app的话就是调用 fn.handle 子app的handle方法处理请求了
    if (!fn || !fn.handle || !fn.set) {
      return router.use(path, fn);
    }

    debug('.use app under %s', path);
    // 保存挂载路径和父子app关系
    fn.mountpath = path;
    fn.parent = this;

    // restore .app property on req and res
    router.use(path, function mounted_app(req, res, next) {
      var orig = req.app;
      fn.handle(req, res, function (err) {
        setPrototypeOf(req, orig.request)
        setPrototypeOf(res, orig.response)
        next(err);
      });
    });

    // mounted an app
    // 触发子app上注册的挂载事件
    fn.emit('mount', this);
  }, this);

  return this;
};

可以看到,app实例上的方法其实都是router实例的方法的代理,真正的实现还是要看router;

// get post 这样的 app.get(...) 调用的实际实现
methods.forEach(function(method){
  app[method] = function(path){
    if (method === 'get' && arguments.length === 1) {
      // app.get(setting)
      return this.set(path);
    }

    // 其实还是调用 path对应route实例的对应method
    var route = this.route(path);
    route[method].apply(route, slice.call(arguments, 1));
    return this;
  };
});

methods中存放着所有的http请求方法的key,它调用的是route实例的对应方法key的实现,详情见route实例;
所以,重点还是要看Router和Route的实现,他们是如何维持路径和中间件的关系,以及如何查找正确的中间件集合并依次执行呢?

/**
 * Expose `Router`.
 */

module.exports = Router

/**
 * Expose `Route`.
 */

module.exports.Route = Route

/**
 * Initialize a new `Router` with the given `options`.
 *
 * @param {object} [options]
 * @return {Router} which is a callable function
 * @public
 */

function Router(options) {
  // 只能通过new方式调用
  if (!(this instanceof Router)) {
    return new Router(options)
  }

  var opts = options || {}

  // 返回的也是一个函数对象
  function router(req, res, next) {
    router.handle(req, res, next)
  }

  // 继承Router的原型链
  // inherit from the correct prototype
  setPrototypeOf(router, this)

  // 内部维持的几个状态
  router.caseSensitive = opts.caseSensitive
  router.mergeParams = opts.mergeParams
  router.params = {}
  router.strict = opts.strict
  router.stack = []

  return router
}

这就是Router的实例,再看原型上的方法:

Router.prototype.use = function use(handler) {
  var offset = 0
  var path = '/'

  // default path to '/'
  // disambiguate router.use([handler])
  if (typeof handler !== 'function') {
    var arg = handler

    while (Array.isArray(arg) && arg.length !== 0) {
      arg = arg[0]
    }

    // first arg is the path
    if (typeof arg !== 'function') {
      offset = 1
      path = handler
    }
  }

  var callbacks = flatten(slice.call(arguments, offset))

  if (callbacks.length === 0) {
    throw new TypeError('argument handler is required')
  }

  for (var i = 0; i < callbacks.length; i++) {
    var fn = callbacks[i]

    if (typeof fn !== 'function') {
      throw new TypeError('argument handler must be a function')
    }

    // add the middleware
    debug('use %o %s', path, fn.name || '<anonymous>')

    var layer = new Layer(path, {
      sensitive: this.caseSensitive,
      strict: false,
      end: false
    }, fn)

    layer.route = undefined

    this.stack.push(layer)
  }

  return this
}

从这个use方法可以得知,Router对每个url和fn回调都构造了一个layer实例来维持它们的关系,然后把所有的layer都存放在Router实例的stacks中,我们看下layer的实现:

function Layer(path, options, fn) {
  if (!(this instanceof Layer)) {
    return new Layer(path, options, fn)
  }

  debug('new %o', path)
  var opts = options || {}

  this.handle = fn
  this.name = fn.name || '<anonymous>'
  this.params = undefined
  this.path = undefined
  // 关键的url对应正则 匹配的关键依赖
  this.regexp = pathRegexp(path, this.keys = [], opts)

  // set fast path flags
  this.regexp.fast_star = path === '*'
  this.regexp.fast_slash = path === '/' && opts.end === false
}

// 每个layer处理自己的url对应的回调 然后默默调用Router传给它的next 去执行stack中的下个layer
Layer.prototype.handle_request = function handle(req, res, next) {
  var fn = this.handle

  if (fn.length > 3) {
    // not a standard request handler
    return next()
  }

  try {
    // invoke function
    var ret = fn(req, res, next)

    // wait for returned promise
    if (isPromise(ret)) {
      ret.then(null, function (error) {
        next(error || new Error('Rejected promise'))
      })
    }
  } catch (err) {
    next(err)
  }
}

回调Router我们继续看真正处理请求的 Router.prototype.handle 的最终实现:

Router.prototype.handle = function handle(req, res, callback) {
  if (!callback) {
    throw new TypeError('argument callback is required')
  }

  debug('dispatching %s %s', req.method, req.url)

  // layer处理的索引
  var idx = 0
  var methods
  var protohost = getProtohost(req.url) || ''
  var removed = ''
  var self = this
  var slashAdded = false
  var paramcalled = {}

  // middleware and routes
  var stack = this.stack

  // manage inter-router variables
  var parentParams = req.params
  var parentUrl = req.baseUrl || ''
  var done = restore(callback, req, 'baseUrl', 'next', 'params')

  // setup next layer
  req.next = next

  // for options requests, respond with a default if nothing else responds
  if (req.method === 'OPTIONS') {
    methods = []
    done = wrap(done, generateOptionsResponder(res, methods))
  }

  // setup basic req values
  req.baseUrl = parentUrl
  req.originalUrl = req.originalUrl || req.url

  next()

  // 从stack中寻找匹配的中间件去执行
  function next(err) {
    var layerError = err === 'route'
      ? null
      : err

    // remove added slash
    if (slashAdded) {
      req.url = req.url.substr(1)
      slashAdded = false
    }

    // restore altered req.url
    if (removed.length !== 0) {
      req.baseUrl = parentUrl
      req.url = protohost + removed + req.url.substr(protohost.length)
      removed = ''
    }

    // signal to exit router
    if (layerError === 'router') {
      setImmediate(done, null)
      return
    }

    // no more matching layers
    if (idx >= stack.length) {
      setImmediate(done, layerError)
      return
    }

    // 关键path url
    // get pathname of request
    var path = getPathname(req)

    if (path == null) {
      return done(layerError)
    }

    // find next matching layer
    var layer
    var match
    var route

    while (match !== true && idx < stack.length) {
      // 自加+1 前进一个
      layer = stack[idx++]
      // 找到匹配的layer
      match = matchLayer(layer, path)
      route = layer.route

      if (typeof match !== 'boolean') {
        // hold on to layerError
        layerError = layerError || match
      }

      if (match !== true) {
        continue
      }

      if (!route) {
        // process non-route handlers normally
        continue
      }

      if (layerError) {
        // routes do not match with a pending error
        match = false
        continue
      }

      var method = req.method
      var has_method = route._handles_method(method)

      // build up automatic options response
      if (!has_method && method === 'OPTIONS' && methods) {
        methods.push.apply(methods, route._methods())
      }

      // don't even bother matching route
      if (!has_method && method !== 'HEAD') {
        match = false
        continue
      }
    }

    // no match
    if (match !== true) {
      return done(layerError)
    }

    // store route for dispatch on change
    if (route) {
      req.route = route
    }

    // Capture one-time layer values
    req.params = self.mergeParams
      ? mergeParams(layer.params, parentParams)
      : layer.params
    var layerPath = layer.path

    // 填充params 动态路径的参数
    // this should be done for the layer
    self.process_params(layer, paramcalled, req, res, function (err) {
      if (err) {
        return next(layerError || err)
      }

      // 其实都是调用 layer.handle_request 去执行用户设置的中间件方法了
      // handle_request最后又会调用 next 去寻找下一个
      if (route) {
        return layer.handle_request(req, res, next)
      }

      trim_prefix(layer, layerError, layerPath, path)
    })
  }

  function trim_prefix(layer, layerError, layerPath, path) {
    if (layerPath.length !== 0) {
      // Validate path breaks on a path separator
      var c = path[layerPath.length]
      if (c && c !== '/') {
        next(layerError)
        return
      }

      // Trim off the part of the url that matches the route
      // middleware (.use stuff) needs to have the path stripped
      debug('trim prefix (%s) from url %s', layerPath, req.url)
      removed = layerPath
      req.url = protohost + req.url.substr(protohost.length + removed.length)

      // Ensure leading slash
      if (!protohost && req.url[0] !== '/') {
        req.url = '/' + req.url
        slashAdded = true
      }

      // Setup base URL (no trailing slash)
      req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
        ? removed.substring(0, removed.length - 1)
        : removed)
    }

    debug('%s %s : %s', layer.name, layerPath, req.originalUrl)

    if (layerError) {
      layer.handle_error(layerError, req, res, next)
    } else {
      layer.handle_request(req, res, next)
    }
  }
}

从简单上来说,Router的实例存储所有关于控制着url和中间件关联关系的layer到自己的stack数组中,我们如果没有指定method,直接用use注册中间件,只需要layer的自身的match正则匹配就可以决定是否对这个url应用这个中间件了;我们如果指定了了method,如app.get('/a', ...) 这样的,除了上面的处理照旧之外,其实还会得到一个关联的route实例,它也有个stack用来存储我们设置的针对这个url和指定mehtod的layer,当method和url匹配时候,Route.prototype.dispatch会去调度执行它的stacks下面的中间价。
总体来说,关键的实现可以总结为:路径和方法,回调3个信息维持一个layer数据结构体,存储在stacks中,每个请求到来后,顺序遍历调用正则匹配找到匹配的中间件,一一顺序执行,同时内部的调度函数next被传给我们的中间件函数了,所以我们手动调度next实现stack的前进。

Vue(v3.0.11)源码简析之Suspense组件的相关实现

Suspense组件接受2个插槽用来渲染内容,一个是可以包含异步组件,一个是默认展示内容;对于这个组件的处理呢,2个插槽的内容vnode是提前解析得到了,所以在patch Suspense组件vnode的时候,可以根据内置逻辑来控制展示哪个vnode,等到异步组件更新了,在解析异步组件的时候由Suspense对象注册的then事件触发,我们就可以快乐的更新Suspense组件渲染的新内容了。

const isSuspense = (type) => type.__isSuspense;
// Suspense exposes a component-like API, and is treated like a component
// in the compiler, but internally it's a special built-in type that hooks
// directly into the renderer.
// Suspense组件实际配置
const SuspenseImpl = {
    name: 'Suspense',
    // In order to make Suspense tree-shakable, we need to avoid importing it
    // directly in the renderer. The renderer checks for the __isSuspense flag
    // on a vnode's type and calls the `process` method, passing in renderer
    // internals.
    __isSuspense: true,
    // patch一个Suspense组件vnode的时候 调用的是自己的process实现
    process(n1, n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, 
    // platform-specific impl passed from renderer
    rendererInternals) {
        // 分情况挂载和更新
        if (n1 == null) {
            mountSuspense(n2, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, rendererInternals);
        }
        else {
            patchSuspense(n1, n2, container, anchor, parentComponent, isSVG, slotScopeIds, optimized, rendererInternals);
        }
    },
    // 先忽略
    hydrate: hydrateSuspense,
    create: createSuspenseBoundary
};
// Force-casted public typing for h and TSX props inference
const Suspense = (SuspenseImpl
    );
// 挂载Suspense组件vnode
function mountSuspense(vnode, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, rendererInternals) {
    // 取出2个来自render的辅助方法
    const { p: patch, o: { createElement } } = rendererInternals;
    // 内置的一个临时包裹元素 用于包裹异步结果ssContent
    const hiddenContainer = createElement('div');
    // 构建suspense控制对象
    const suspense = (vnode.suspense = createSuspenseBoundary(vnode, parentSuspense, parentComponent, container, hiddenContainer, anchor, isSVG, slotScopeIds, optimized, rendererInternals));
    // start mounting the content subtree in an off-dom container
    // 先尝试挂载ssContent内容vnode到临时元素下
    patch(null, (suspense.pendingBranch = vnode.ssContent), hiddenContainer, null, parentComponent, suspense, isSVG, slotScopeIds);
    // now check if we have encountered any async deps
    // 上面patch的结果 如果存在异步依赖组件 这个deps大于0
    if (suspense.deps > 0) {
        // has async
        // mount the fallback tree
        // 那就先挂载回退情况的vnode到真实container下 不需要suspense控制对象
        patch(null, vnode.ssFallback, container, anchor, parentComponent, null, // fallback tree will not have suspense context
        isSVG, slotScopeIds);
        // activeBranch置位 也是vnode其实
        setActiveBranch(suspense, vnode.ssFallback);
    }
    else {
        // Suspense has no async deps. Just resolve.
        // 直接解析 渲染结果
        suspense.resolve();
    }
}
// Suspense组件vnode的更新场景
function patchSuspense(n1, n2, container, anchor, parentComponent, isSVG, slotScopeIds, optimized, { p: patch, um: unmount, o: { createElement } }) {
    const suspense = (n2.suspense = n1.suspense);
    suspense.vnode = n2;
    n2.el = n1.el;
    // 取出新的vnode结果
    const newBranch = n2.ssContent;
    const newFallback = n2.ssFallback;
    // 之前的状态
    const { activeBranch, pendingBranch, isInFallback, isHydrating } = suspense;
    // 之前的异步已经得到但是还没渲染
    if (pendingBranch) {
        // 直接取新的
        suspense.pendingBranch = newBranch;
        if (isSameVNodeType(newBranch, pendingBranch)) {
            // same root type but content may have changed.
            // 对比更新2个vnode 结果更新到临时节点上
            patch(pendingBranch, newBranch, suspense.hiddenContainer, null, parentComponent, suspense, isSVG, slotScopeIds, optimized);
            // 无异步依赖 解析结果渲染
            if (suspense.deps <= 0) {
                suspense.resolve();
            }
            // 用新的回退情况更新就的回退情况
            else if (isInFallback) {
                patch(activeBranch, newFallback, container, anchor, parentComponent, null, // fallback tree will not have suspense context
                isSVG, slotScopeIds, optimized);
                setActiveBranch(suspense, newFallback);
            }
        }
        else {
            // toggled before pending tree is resolved
            // 每次要进行一次解析组件的操作 这个id就自增
            suspense.pendingId++;
            if (isHydrating) {
                // if toggled before hydration is finished, the current DOM tree is
                // no longer valid. set it as the active branch so it will be unmounted
                // when resolved
                suspense.isHydrating = false;
                suspense.activeBranch = pendingBranch;
            }
            // 移除旧的直接
            else {
                unmount(pendingBranch, parentComponent, suspense);
            }
            // increment pending ID. this is used to invalidate async callbacks
            // reset suspense state
            // 重置状态
            suspense.deps = 0;
            // discard effects from pending branch
            suspense.effects.length = 0;
            // discard previous container
            suspense.hiddenContainer = createElement('div');
            if (isInFallback) {
                // already in fallback state
                patch(null, newBranch, suspense.hiddenContainer, null, parentComponent, suspense, isSVG, slotScopeIds, optimized);
                if (suspense.deps <= 0) {
                    suspense.resolve();
                }
                else {
                    patch(activeBranch, newFallback, container, anchor, parentComponent, null, // fallback tree will not have suspense context
                    isSVG, slotScopeIds, optimized);
                    setActiveBranch(suspense, newFallback);
                }
            }
            else if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {
                // toggled "back" to current active branch
                patch(activeBranch, newBranch, container, anchor, parentComponent, suspense, isSVG, slotScopeIds, optimized);
                // force resolve
                // 注意这个参数 不想需要patch了
                suspense.resolve(true);
            }
            else {
                // switched to a 3rd branch
                patch(null, newBranch, suspense.hiddenContainer, null, parentComponent, suspense, isSVG, slotScopeIds, optimized);
                if (suspense.deps <= 0) {
                    suspense.resolve();
                }
            }
        }
    }
    // 没有待处理的pendingBranch
    else {
        if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {
            // root did not change, just normal patch
            patch(activeBranch, newBranch, container, anchor, parentComponent, suspense, isSVG, slotScopeIds, optimized);
            setActiveBranch(suspense, newBranch);
        }
        else {
            // root node toggled
            // invoke @pending event
            const onPending = n2.props && n2.props.onPending;
            if (isFunction(onPending)) {
                onPending();
            }
            // mount pending branch in off-dom container
            suspense.pendingBranch = newBranch;
            suspense.pendingId++;
            patch(null, newBranch, suspense.hiddenContainer, null, parentComponent, suspense, isSVG, slotScopeIds, optimized);
            if (suspense.deps <= 0) {
                // incoming branch has no async deps, resolve now.
                suspense.resolve();
            }
            // 延迟渲染默认内容
            else {
                const { timeout, pendingId } = suspense;
                if (timeout > 0) {
                    setTimeout(() => {
                        if (suspense.pendingId === pendingId) {
                            suspense.fallback(newFallback);
                        }
                    }, timeout);
                }
                else if (timeout === 0) {
                    suspense.fallback(newFallback);
                }
            }
        }
    }
}
let hasWarned = false;
// 创造一个Suspense对象 内含主要的实现
function createSuspenseBoundary(vnode, parent, parentComponent, container, hiddenContainer, anchor, isSVG, slotScopeIds, optimized, rendererInternals, isHydrating = false) {
    /* istanbul ignore if */
    // 实验阶段的组件
    if (!hasWarned) {
        hasWarned = true;
        // @ts-ignore `console.info` cannot be null error
        console[console.info ? 'info' : 'log'](`<Suspense> is an experimental feature and its API will likely change.`);
    }
    // 取出来自render的辅助方法
    const { p: patch, m: move, um: unmount, n: next, o: { parentNode, remove } } = rendererInternals;
    const timeout = toNumber(vnode.props && vnode.props.timeout);
    // 实际的suspense控制对象
    const suspense = {
        // 组件vnode
        vnode,
        // 父suspense对象
        parent,
        // 父组件实例
        parentComponent,
        isSVG,
        // 宿主dom
        container,
        // 临时dom
        hiddenContainer,
        // 锚点
        anchor,
        // 异步依赖数
        deps: 0,
        // 当前等待准备解析哪个异步的结果
        pendingId: 0,
        // 延迟渲染默认内容
        timeout: typeof timeout === 'number' ? timeout : -1,
        // 当前展示的vnode
        activeBranch: null,
        // 准备渲染的vnode
        pendingBranch: null,
        // 当前是否渲染的是默认内容
        isInFallback: true,
        isHydrating,
        isUnmounted: false,
        // 收集的子effect集合
        effects: [],
        // 解析待渲染的vnode 完成渲染工作 resume代表强制解析 之前已经patch过了 无需再操作dom
        resolve(resume = false) {
            {
                if (!resume && !suspense.pendingBranch) {
                    throw new Error(`suspense.resolve() is called without a pending branch.`);
                }
                if (suspense.isUnmounted) {
                    throw new Error(`suspense.resolve() is called on an already unmounted suspense boundary.`);
                }
            }
            const { vnode, activeBranch, pendingBranch, pendingId, effects, parentComponent, container } = suspense;
            if (suspense.isHydrating) {
                suspense.isHydrating = false;
            }
            // resume代表强制解析 之前已经patch过了 无需再操作dom
            else if (!resume) {
                const delayEnter = activeBranch &&
                    pendingBranch.transition &&
                    pendingBranch.transition.mode === 'out-in';
                if (delayEnter) {
                    activeBranch.transition.afterLeave = () => {
                        if (pendingId === suspense.pendingId) {
                            move(pendingBranch, container, anchor, 0 /* ENTER */);
                        }
                    };
                }
                // this is initial anchor on mount
                let { anchor } = suspense;
                // unmount current active tree
                // 先移除之前挂载的内容
                if (activeBranch) {
                    // if the fallback tree was mounted, it may have been moved
                    // as part of a parent suspense. get the latest anchor for insertion
                    anchor = next(activeBranch);
                    unmount(activeBranch, parentComponent, suspense, true);
                }
                // 移动临时节点的内容到目标元素下来
                if (!delayEnter) {
                    // move content from off-dom container to actual container
                    move(pendingBranch, container, anchor, 0 /* ENTER */);
                }
            }
            // 新的activebranch置位
            setActiveBranch(suspense, pendingBranch);
            // 其他状态修改
            suspense.pendingBranch = null;
            suspense.isInFallback = false;
            // flush buffered effects
            // check if there is a pending parent suspense
            let parent = suspense.parent;
            let hasUnresolvedAncestor = false;
            // 父链上还存在suspense的话 effects 一直合并上溯到根suspense里面去
            while (parent) {
                if (parent.pendingBranch) {
                    // found a pending parent suspense, merge buffered post jobs
                    // into that parent
                    parent.effects.push(...effects);
                    hasUnresolvedAncestor = true;
                    break;
                }
                parent = parent.parent;
            }
            // no pending parent suspense, flush all jobs
            if (!hasUnresolvedAncestor) {
                queuePostFlushCb(effects);
            }
            suspense.effects = [];
            // invoke @resolve event
            // 钩子
            const onResolve = vnode.props && vnode.props.onResolve;
            if (isFunction(onResolve)) {
                onResolve();
            }
        },
        // 渲染默认内容
        fallback(fallbackVNode) {
            // 等待异步下才有语义
            if (!suspense.pendingBranch) {
                return;
            }
            const { vnode, activeBranch, parentComponent, container, isSVG } = suspense;
            // invoke @fallback event
            // 钩子
            const onFallback = vnode.props && vnode.props.onFallback;
            if (isFunction(onFallback)) {
                onFallback();
            }
            const anchor = next(activeBranch);
            // 挂载渲染默认内容
            const mountFallback = () => {
                if (!suspense.isInFallback) {
                    return;
                }
                // mount the fallback tree
                patch(null, fallbackVNode, container, anchor, parentComponent, null, // fallback tree will not have suspense context
                isSVG, slotScopeIds, optimized);
                setActiveBranch(suspense, fallbackVNode);
            };
            const delayEnter = fallbackVNode.transition && fallbackVNode.transition.mode === 'out-in';
            if (delayEnter) {
                activeBranch.transition.afterLeave = mountFallback;
            }
            // unmount current active branch
            unmount(activeBranch, parentComponent, null, // no suspense so unmount hooks fire now
            true // shouldRemove
            );
            suspense.isInFallback = true;
            if (!delayEnter) {
                mountFallback();
            }
        },
        // 移动异步组件的结果子树到目标dom下
        move(container, anchor, type) {
            suspense.activeBranch &&
                move(suspense.activeBranch, container, anchor, type);
            suspense.container = container;
        },
        // 下个元素 获取锚点元素
        next() {
            return suspense.activeBranch && next(suspense.activeBranch);
        },
        // 存在异步结果的时候 我们会在setup之后执行它 添加结果监听的事情函数
        registerDep(instance, setupRenderEffect) {
            const isInPendingSuspense = !!suspense.pendingBranch;
            // suspense下放了多个异步组件
            if (isInPendingSuspense) {
                suspense.deps++;
            }
            const hydratedEl = instance.vnode.el;
            instance
                .asyncDep.catch(err => {
                handleError(err, instance, 0 /* SETUP_FUNCTION */);
            })
                .then(asyncSetupResult => {
                // retry when the setup() promise resolves.
                // component may have been unmounted before resolve.
                if (instance.isUnmounted ||
                    suspense.isUnmounted ||
                    suspense.pendingId !== instance.suspenseId) {
                    return;
                }
                // retry from this component
                // 正常得到异步结果
                instance.asyncResolved = true;
                const { vnode } = instance;
                {
                    pushWarningContext(vnode);
                }
                // 处理异步组件 挂载组件配置属性
                handleSetupResult(instance, asyncSetupResult, false);
                if (hydratedEl) {
                    // vnode may have been replaced if an update happened before the
                    // async dep is resolved.
                    vnode.el = hydratedEl;
                }
                // 之前放置的占位元素
                const placeholder = !hydratedEl && instance.subTree.el;
                // 重新设置组件实例的effect 重新生成异步组件的子树 实际渲染内容
                setupRenderEffect(instance, vnode, 
                // component may have been moved before resolve.
                // if this is not a hydration, instance.subTree will be the comment
                // placeholder.
                parentNode(hydratedEl || instance.subTree.el), 
                // anchor will not be used if this is hydration, so only need to
                // consider the comment placeholder case.
                hydratedEl ? null : next(instance.subTree), suspense, isSVG, optimized);
                // 移除之前的占位符
                if (placeholder) {
                    remove(placeholder);
                }
                updateHOCHostEl(instance, vnode.el);
                {
                    popWarningContext();
                }
                // only decrease deps count if suspense is not already resolved
                // 所有异步依赖结束后 解析完成
                if (isInPendingSuspense && --suspense.deps === 0) {
                    suspense.resolve();
                }
            });
        },
        // 卸载
        unmount(parentSuspense, doRemove) {
            suspense.isUnmounted = true;
            if (suspense.activeBranch) {
                unmount(suspense.activeBranch, parentComponent, parentSuspense, doRemove);
            }
            if (suspense.pendingBranch) {
                unmount(suspense.pendingBranch, parentComponent, parentSuspense, doRemove);
            }
        }
    };
    return suspense;
}
// 忽略
function hydrateSuspense(node, vnode, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, rendererInternals, hydrateNode) {
    /* eslint-disable no-restricted-globals */
    const suspense = (vnode.suspense = createSuspenseBoundary(vnode, parentSuspense, parentComponent, node.parentNode, document.createElement('div'), null, isSVG, slotScopeIds, optimized, rendererInternals, true /* hydrating */));
    // there are two possible scenarios for server-rendered suspense:
    // - success: ssr content should be fully resolved
    // - failure: ssr content should be the fallback branch.
    // however, on the client we don't really know if it has failed or not
    // attempt to hydrate the DOM assuming it has succeeded, but we still
    // need to construct a suspense boundary first
    const result = hydrateNode(node, (suspense.pendingBranch = vnode.ssContent), parentComponent, suspense, slotScopeIds, optimized);
    if (suspense.deps === 0) {
        suspense.resolve();
    }
    return result;
    /* eslint-enable no-restricted-globals */
}
// 格式化Suspense的2个子节点内容并返回
function normalizeSuspenseChildren(vnode) {
    const { shapeFlag, children } = vnode;
    let content;
    let fallback;
    // 含有2个标准slot或者一个conent 另一个默认是null
    if (shapeFlag & 32 /* SLOTS_CHILDREN */) {
        content = normalizeSuspenseSlot(children.default);
        fallback = normalizeSuspenseSlot(children.fallback);
    }
    else {
        content = normalizeSuspenseSlot(children);
        fallback = normalizeVNode(null);
    }
    return {
        content,
        fallback
    };
}
// 运行slot生成函数得到对应的vnode
function normalizeSuspenseSlot(s) {
    if (isFunction(s)) {
        s = s();
    }
    if (isArray(s)) {
        const singleChild = filterSingleRoot(s);
        if (!singleChild) {
            warn(`<Suspense> slots expect a single root node.`);
        }
        s = singleChild;
    }
    return normalizeVNode(s);
}
// suspense控制对象存在的情况下 收集effect
function queueEffectWithSuspense(fn, suspense) {
    if (suspense && suspense.pendingBranch) {
        if (isArray(fn)) {
            suspense.effects.push(...fn);
        }
        else {
            suspense.effects.push(fn);
        }
    }
    else {
        queuePostFlushCb(fn);
    }
}
// branch这个vnode已经挂载过了 activeBranch指向它
function setActiveBranch(suspense, branch) {
    suspense.activeBranch = branch;
    const { vnode, parentComponent } = suspense;
    const el = (vnode.el = branch.el);
    // in case suspense is the root node of a component,
    // recursively update the HOC el
    if (parentComponent && parentComponent.subTree === vnode) {
        parentComponent.vnode.el = el;
        updateHOCHostEl(parentComponent, el);
    }
}

简单聊一聊axios的实现原理吧

以前做vue项目的时候,http请求的封装一般是基于axios来做的,它提供了一些比较通用的功能,
如请求拦截器,请求头设置,promise调用链等等,这些都是工作需要的,现在看一看它的主体实现框架吧:)

// Create the default instance to be exported
var axios = createInstance(defaults);

module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;

基于默认配置项创建的实例:

function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  utils.extend(instance, context);

  return instance;
}

构造的 Axios 实例 context 作为下面绑定方法的this,去看看 Axios 函数是怎么实现的:

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

实例本身到是很简单,看看它的原型对象上可被继承的方法有哪些去:

// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: (config || {}).data
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

哦豁,这些个就是比较重要的常用方法的入口,调用的其实都是request方法而已:

Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  config = mergeConfig(this.defaults, config);

  // Set config.method
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }

  // Hook up interceptors middleware
  var chain = [dispatchRequest, undefined];
  var promise = Promise.resolve(config);

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

配置对象的合并我们先忽略,看下对拦截器的处理,promise链的实现有点意思,先用数组存起来,然后依次追加resolve和reject到前一个promise实例的then中,
而第一个promise实例则是来自于 dispatchRequest 产生的:

module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // Ensure headers exist
  config.headers = config.headers || {};

  // Transform request data
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // Flatten headers
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers
  );

  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  var adapter = config.adapter || defaults.adapter;

  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);

    // Transform response data
    response.data = transformData(
      response.data,
      response.headers,
      config.transformResponse
    );

    return response;
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) {
      throwIfCancellationRequested(config);

      // Transform response data
      if (reason && reason.response) {
        reason.response.data = transformData(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

    return Promise.reject(reason);
  });
};

触发事件比较关键的 adapter(config).then 这一行,决定了 adapter 返回的也是promise实例呢,看一下:

function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}

我们关注浏览器的XMLHttpRequest的实现:

module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    var requestHeaders = config.headers;

    if (utils.isFormData(requestData)) {
      delete requestHeaders['Content-Type']; // Let the browser set it
    }

    var request = new XMLHttpRequest();

    // HTTP basic authentication
    if (config.auth) {
      var username = config.auth.username || '';
      var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
      requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
    }

    var fullPath = buildFullPath(config.baseURL, config.url);
    request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);

    // Set the request timeout in MS
    request.timeout = config.timeout;

    // Listen for ready state
    request.onreadystatechange = function handleLoad() {
      if (!request || request.readyState !== 4) {
        return;
      }

      // The request errored out and we didn't get a response, this will be
      // handled by onerror instead
      // With one exception: request that using file: protocol, most browsers
      // will return status as 0 even though it's a successful request
      if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
        return;
      }

      // Prepare the response
      var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;
      var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;
      var response = {
        data: responseData,
        status: request.status,
        statusText: request.statusText,
        headers: responseHeaders,
        config: config,
        request: request
      };

      settle(resolve, reject, response);

      // Clean up request
      request = null;
    };

    // Handle browser request cancellation (as opposed to a manual cancellation)
    request.onabort = function handleAbort() {
      if (!request) {
        return;
      }

      reject(createError('Request aborted', config, 'ECONNABORTED', request));

      // Clean up request
      request = null;
    };

    // Handle low level network errors
    request.onerror = function handleError() {
      // Real errors are hidden from us by the browser
      // onerror should only fire if it's a network error
      reject(createError('Network Error', config, null, request));

      // Clean up request
      request = null;
    };

    // Handle timeout
    request.ontimeout = function handleTimeout() {
      var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
      if (config.timeoutErrorMessage) {
        timeoutErrorMessage = config.timeoutErrorMessage;
      }
      reject(createError(timeoutErrorMessage, config, 'ECONNABORTED',
        request));

      // Clean up request
      request = null;
    };

    // Add xsrf header
    // This is only done if running in a standard browser environment.
    // Specifically not if we're in a web worker, or react-native.
    if (utils.isStandardBrowserEnv()) {
      // Add xsrf header
      var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ?
        cookies.read(config.xsrfCookieName) :
        undefined;

      if (xsrfValue) {
        requestHeaders[config.xsrfHeaderName] = xsrfValue;
      }
    }

    // Add headers to the request
    if ('setRequestHeader' in request) {
      utils.forEach(requestHeaders, function setRequestHeader(val, key) {
        if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {
          // Remove Content-Type if data is undefined
          delete requestHeaders[key];
        } else {
          // Otherwise add header to the request
          request.setRequestHeader(key, val);
        }
      });
    }

    // Add withCredentials to request if needed
    if (!utils.isUndefined(config.withCredentials)) {
      request.withCredentials = !!config.withCredentials;
    }

    // Add responseType to request if needed
    if (config.responseType) {
      try {
        request.responseType = config.responseType;
      } catch (e) {
        // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
        // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
        if (config.responseType !== 'json') {
          throw e;
        }
      }
    }

    // Handle progress if needed
    if (typeof config.onDownloadProgress === 'function') {
      request.addEventListener('progress', config.onDownloadProgress);
    }

    // Not all browsers support upload events
    if (typeof config.onUploadProgress === 'function' && request.upload) {
      request.upload.addEventListener('progress', config.onUploadProgress);
    }

    if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }

    if (!requestData) {
      requestData = null;
    }

    // Send the request
    request.send(requestData);
  });
};

这个就是比较熟悉的 promise包裹的 XMLHttpRequest请求的封装了,值得一提的是 取消请求的实现:

if (config.cancelToken) {
    // Handle cancellation
    config.cancelToken.promise.then(function onCanceled(cancel) {
    if (!request) {
        return;
    }

    request.abort();
    reject(cancel);
    // Clean up request
    request = null;
    });
}

对应文档中所描述的的请求取消的功能。

总结:整体来看,axios封装了工作中比较常用的功能,整体实现也比较简单,学习下封装 XML请求倒也不错:)

简单聊一聊js中继承的几种模式吧



// 1.原型链继承

{
    function Parent () {
        this.name = 'kevin';
    }
    
    Parent.prototype.getName = function () {
        console.log(this.name);
    }
    
    function Child () {
    
    }
    
    Child.prototype = new Parent();
    
    var child1 = new Child();
    
    console.log(child1.getName()) // kevin
}

// 缺点: 1.引用类型的属性被所有实例共享 2. 在创建 Child 的实例时,不能向Parent传参

// 2.借用构造函数(经典继承)

{
    function Parent () {
        this.names = ['kevin', 'daisy'];
    }
    
    function Child () {
        Parent.call(this);
    }
    
    var child1 = new Child();
    
    child1.names.push('yayu');
    
    console.log(child1.names); // ["kevin", "daisy", "yayu"]
    
    var child2 = new Child();
    
    console.log(child2.names); // ["kevin", "daisy"]
}

// 缺点 方法都在构造函数中定义,每次创建实例都会创建一遍方法。

// 3. 组合继承

{
    function Parent (name) {
        this.name = name;
        this.colors = ['red', 'blue', 'green'];
    }
    
    Parent.prototype.getName = function () {
        console.log(this.name)
    }
    
    function Child (name, age) {
    
        Parent.call(this, name);
        
        this.age = age;
    
    }
    
    Child.prototype = new Parent();
    Child.prototype.constructor = Child;
    
    var child1 = new Child('kevin', '18');
    
    child1.colors.push('black');
    
    console.log(child1.name); // kevin
    console.log(child1.age); // 18
    console.log(child1.colors); // ["red", "blue", "green", "black"]
    
    var child2 = new Child('daisy', '20');
    
    console.log(child2.name); // daisy
    console.log(child2.age); // 20
    console.log(child2.colors); // ["red", "blue", "green"]
}

// 常见的就是这种组合继承

// 4.原型式继承

{
    function createObj(o) {
        function F(){}
        F.prototype = o;
        return new F();
    }
}

// 包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样。

// 5. 寄生式继承

{
    function createObj (o) {
        var clone = Object.create(o);
        clone.sayName = function () {
            console.log('hi');
        }
        return clone;
    }
}

// 缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。

// 6. 寄生组合式继承
// 组合继承最大的缺点是会调用两次父构造函数。所以,在上面的组合继承例子中,如果我们打印 child1 对象,我们会发现 Child.prototype 和 child1 都有一个属性为colors,属性值为['red', 'blue', 'green']。

{
    function Parent (name) {
        this.name = name;
        this.colors = ['red', 'blue', 'green'];
    }
    
    Parent.prototype.getName = function () {
        console.log(this.name)
    }
    
    function Child (name, age) {
        Parent.call(this, name);
        this.age = age;
    }
    
    // 关键的三步
    var F = function () {};
    
    F.prototype = Parent.prototype;
    
    Child.prototype = new F();
    
    
    var child1 = new Child('kevin', '18');
    
    console.log(child1); 
}

vuex(v4.0.0) 源码注释简析

/*!
 * vuex v4.0.0
 * (c) 2021 Evan You
 * @license MIT
 */
var Vuex = (function (vue) {
    'use strict';
  
    // 供组合api使用 provide和reject的时候使用的key值
    var storeKey = 'store';
  
    // 取出之前provide的值
    function useStore (key) {
      if ( key === void 0 ) key = null;
  
      return vue.inject(key !== null ? key : storeKey)
    }
  
    // 全局顶级对象
    var target = typeof window !== 'undefined'
      ? window
      : typeof global !== 'undefined'
        ? global
        : {};
    var devtoolHook = target.__VUE_DEVTOOLS_GLOBAL_HOOK__;
  
    // 先忽略
    function devtoolPlugin (store) {
      if (!devtoolHook) { return }
  
      store._devtoolHook = devtoolHook;
  
      devtoolHook.emit('vuex:init', store);
  
      devtoolHook.on('vuex:travel-to-state', function (targetState) {
        store.replaceState(targetState);
      });
  
      store.subscribe(function (mutation, state) {
        devtoolHook.emit('vuex:mutation', mutation, state);
      }, { prepend: true });
  
      store.subscribeAction(function (action, state) {
        devtoolHook.emit('vuex:action', action, state);
      }, { prepend: true });
    }
  
    /**
     * Get the first item that pass the test
     * by second argument function
     *
     * @param {Array} list
     * @param {Function} f
     * @return {*}
     */
    // 指定对比函数 f 在数组list中顺序找到第一个符合要求的元素
    function find (list, f) {
      return list.filter(f)[0]
    }
  
    /**
     * Deep copy the given object considering circular structure.
     * This function caches all nested objects and its copies.
     * If it detects circular structure, use cached copy to avoid infinite loop.
     *
     * @param {*} obj
     * @param {Array<Object>} cache
     * @return {*}
     */
    // 深克隆对象 支持存在循环引用自身的场景
    function deepCopy (obj, cache) {
      // 用来存储已经出现过的对象
      if ( cache === void 0 ) cache = [];
  
      // 递归出口 普通值对象直接返回即可
      // just return if obj is immutable value
      if (obj === null || typeof obj !== 'object') {
        return obj
      }
  
      // 缓存数组中找到曾经出现过的对象
      // if obj is hit, it is in circular structure
      var hit = find(cache, function (c) { return c.original === obj; });
      if (hit) {
        // 返回copy对象 终止无限递归
        return hit.copy
      }
  
      // 准备引用对象的拷贝体
      var copy = Array.isArray(obj) ? [] : {};
      // put the copy into cache at first
      // because we want to refer it in recursive deepCopy
      // 设置缓存
      cache.push({
        original: obj,
        copy: copy
      });
  
      // 递归克隆
      Object.keys(obj).forEach(function (key) {
        copy[key] = deepCopy(obj[key], cache);
      });
  
      return copy
    }
  
    /**
     * forEach for object
     */
    // 对数组每个元素做一次 fn(val, key)操作
    function forEachValue (obj, fn) {
      Object.keys(obj).forEach(function (key) { return fn(obj[key], key); });
    }
  
    // 对象判断
    function isObject (obj) {
      return obj !== null && typeof obj === 'object'
    }
  
    // promise判断
    function isPromise (val) {
      return val && typeof val.then === 'function'
    }
  
    // 断言
    function assert (condition, msg) {
      if (!condition) { throw new Error(("[vuex] " + msg)) }
    }
  
    // 去参数化的偏函数
    function partial (fn, arg) {
      return function () {
        return fn(arg)
      }
    }
  
    // Base data struct for store's module, package with some attribute and method
    // rawModule 是我们自己写的module原始配置对象 对它做一层包裹 添加一些辅助属性
    var Module = function Module (rawModule, runtime) {
      // 区分 在初始化的时候添加的 还是在运行过程动态添加的module
      this.runtime = runtime;
      // Store some children item
      // 子module存储对象
      this._children = Object.create(null);
      // Store the origin module object which passed by programmer
      // 保留原始module信息
      this._rawModule = rawModule;
      var rawState = rawModule.state;
  
      // Store the origin module's state
      // 设置当前module的state对象
      this.state = (typeof rawState === 'function' ? rawState() : rawState) || {};
    };
  
    // namespaced 访问器
    var prototypeAccessors = { namespaced: { configurable: true } };
  
    // 返回原始module的 namespaced 属性
    prototypeAccessors.namespaced.get = function () {
      return !!this._rawModule.namespaced
    };
  
    // module对子module的 增 删 取 查 操作
    Module.prototype.addChild = function addChild (key, module) {
      this._children[key] = module;
    };
  
    Module.prototype.removeChild = function removeChild (key) {
      delete this._children[key];
    };
  
    Module.prototype.getChild = function getChild (key) {
      return this._children[key]
    };
  
    Module.prototype.hasChild = function hasChild (key) {
      return key in this._children
    };
  
    // 用新module 替换之前的_rawModule以及相应的属性
    Module.prototype.update = function update (rawModule) {
      this._rawModule.namespaced = rawModule.namespaced;
      if (rawModule.actions) {
        this._rawModule.actions = rawModule.actions;
      }
      if (rawModule.mutations) {
        this._rawModule.mutations = rawModule.mutations;
      }
      if (rawModule.getters) {
        this._rawModule.getters = rawModule.getters;
      }
    };
  
    // 对module的几个对象属性做遍历fn处理
    Module.prototype.forEachChild = function forEachChild (fn) {
      forEachValue(this._children, fn);
    };
  
    Module.prototype.forEachGetter = function forEachGetter (fn) {
      if (this._rawModule.getters) {
        forEachValue(this._rawModule.getters, fn);
      }
    };
  
    Module.prototype.forEachAction = function forEachAction (fn) {
      if (this._rawModule.actions) {
        forEachValue(this._rawModule.actions, fn);
      }
    };
  
    Module.prototype.forEachMutation = function forEachMutation (fn) {
      if (this._rawModule.mutations) {
        forEachValue(this._rawModule.mutations, fn);
      }
    };
  
    // 设置 namespaced 属性
    Object.defineProperties( Module.prototype, prototypeAccessors );
  
    // module集合控制器
    var ModuleCollection = function ModuleCollection (rawRootModule) {
      // register root module (Vuex.Store options)
      // new的时候 执行注册逻辑 遍历module树
      this.register([], rawRootModule, false);
    };
  
    // 根据path数组 查找 module树 返回的是目标module
    ModuleCollection.prototype.get = function get (path) {
      // root 是moudlue树 根节点
      return path.reduce(function (module, key) {
        return module.getChild(key)
      }, this.root)
    };
  
    // 根据path 生成对应的 namespace 值 [a,b,c] => a/b/c 在开启了namespace的情况下
    ModuleCollection.prototype.getNamespace = function getNamespace (path) {
      var module = this.root;
      return path.reduce(function (namespace, key) {
        module = module.getChild(key);
        return namespace + (module.namespaced ? key + '/' : '')
      }, '')
    };
  
    // 递归更新 root根module
    ModuleCollection.prototype.update = function update$1 (rawRootModule) {
      update([], this.root, rawRootModule);
    };
  
    // 注册module 的实际实现 构造module树
    // 其实注册的过程就是根据我们设置的原始module层级结构 建立一个module树而已
    ModuleCollection.prototype.register = function register (path, rawModule, runtime) {
        // module集合器的实例对象
        var this$1 = this;
        if ( runtime === void 0 ) runtime = true;
  
      {
        // 对module中各个属性的类型做断言判断
        assertRawModule(path, rawModule);
      }
      
      // 构造一个新的module包裹体
      var newModule = new Module(rawModule, runtime);
      if (path.length === 0) {
        // 根module节点
        this.root = newModule;
      } else {
        // 简单的建立起父子关系
        var parent = this.get(path.slice(0, -1));
        parent.addChild(path[path.length - 1], newModule);
      }
  
      // register nested modules
      // 递归遍历注册子module 构成一个module包裹体的 树形数据
      if (rawModule.modules) {
        forEachValue(rawModule.modules, function (rawChildModule, key) {
          this$1.register(path.concat(key), rawChildModule, runtime);
        });
      }
    };
  
    // 从module树中移除path对应的module
    ModuleCollection.prototype.unregister = function unregister (path) {
      // 找到父子节点
      var parent = this.get(path.slice(0, -1));
      var key = path[path.length - 1];
      var child = parent.getChild(key);
  
      if (!child) {
        {
          console.warn(
            "[vuex] trying to unregister module '" + key + "', which is " +
            "not registered"
          );
        }
        return
      }
  
      if (!child.runtime) {
        return
      }
  
      parent.removeChild(key);
    };
  
    // 查找path对应的module是否存在
    ModuleCollection.prototype.isRegistered = function isRegistered (path) {
      var parent = this.get(path.slice(0, -1));
      var key = path[path.length - 1];
  
      if (parent) {
        return parent.hasChild(key)
      }
  
      return false
    };
  
    // 更新module树中内容
    function update (path, targetModule, newModule) {
      {
        // 对module中各个属性的类型做断言判断
        assertRawModule(path, newModule);
      }
  
      // 更新单个节点
      // update target module
      targetModule.update(newModule);
  
      // update nested modules
      // 递归更新子module
      if (newModule.modules) {
        for (var key in newModule.modules) {
          if (!targetModule.getChild(key)) {
            {
              console.warn(
                "[vuex] trying to add a new module '" + key + "' on hot reloading, " +
                'manual reload is needed'
              );
            }
            return
          }
          // 递归
          update(
            path.concat(key),
            targetModule.getChild(key),
            newModule.modules[key]
          );
        }
      }
    }
  
    // 几种类型的断言实现
    var functionAssert = {
      assert: function (value) { return typeof value === 'function'; },
      expected: 'function'
    };
  
    var objectAssert = {
      assert: function (value) { return typeof value === 'function' ||
        (typeof value === 'object' && typeof value.handler === 'function'); },
      expected: 'function or object with "handler" function'
    };
  
    // 对于下面几种属性 我们期望他们的类型
    var assertTypes = {
      getters: functionAssert,
      mutations: functionAssert,
      actions: objectAssert
    };
  
    // 遍历检测是否类型符合预期
    function assertRawModule (path, rawModule) {
      Object.keys(assertTypes).forEach(function (key) {
        if (!rawModule[key]) { return }
  
        var assertOptions = assertTypes[key];
  
        forEachValue(rawModule[key], function (value, type) {
          assert(
            assertOptions.assert(value),
            makeAssertionMessage(path, key, type, value, assertOptions.expected)
          );
        });
      });
    }
  
    // 生成提示消息
    function makeAssertionMessage (path, key, type, value, expected) {
      var buf = key + " should be " + expected + " but \"" + key + "." + type + "\"";
      if (path.length > 0) {
        buf += " in module \"" + (path.join('.')) + "\"";
      }
      buf += " is " + (JSON.stringify(value)) + ".";
      return buf
    }
  
    // 外部调用入口
    function createStore (options) {
      return new Store(options)
    }
  
    // store的实际实现
    // 注意实例store上有哪些内部属性
    var Store = function Store (options) {
      // 指向store的实际实现
      var this$1 = this;
      if ( options === void 0 ) options = {};
  
      {
        assert(typeof Promise !== 'undefined', "vuex requires a Promise polyfill in this browser.");
        // 只能通过 new State 方式创建
        assert(this instanceof Store, "store must be called with the new operator.");
      }
  
      var plugins = options.plugins; if ( plugins === void 0 ) plugins = [];
      var strict = options.strict; if ( strict === void 0 ) strict = false;
  
      // 内部属性
      // store internal state
      // 正在处于提交状态
      this._committing = false;
      // 存放所有的异步actions
      this._actions = Object.create(null);
      //存放异步action的before钩子们
      this._actionSubscribers = [];
      // 存放所有的 mutations
      this._mutations = Object.create(null);
      // 存放所有的 getters
      this._wrappedGetters = Object.create(null);
      // 构造的module树
      this._modules = new ModuleCollection(options);
      // module命名空间缓存
      this._modulesNamespaceMap = Object.create(null);
      // 存放mutations提交之后的钩子
      this._subscribers = [];
      // 在注册getters时候用到的缓存
      this._makeLocalGettersCache = Object.create(null);
  
      // bind commit and dispatch to self
      var store = this;
      var ref = this;
      // 原实例方法引用
      var dispatch = ref.dispatch;
      var commit = ref.commit;
      // 重写 固定this指向 store实例
      this.dispatch = function boundDispatch (type, payload) {
        return dispatch.call(store, type, payload)
      };
      this.commit = function boundCommit (type, payload, options) {
        return commit.call(store, type, payload, options)
      };
  
      // strict mode
      this.strict = strict;
  
      // 根节点module的state对象 就是我们书写的配置项中state函数返回的对象
      var state = this._modules.root.state;
  
      // init root module.
      // this also recursively registers all sub-modules
      // and collects all module getters inside this._wrappedGetters
      // 注册挂载module上各个属性到上面的对应私有属性中
      // 重点看install过程的实现
      installModule(this, state, [], this._modules.root);
  
      // initialize the store state, which is responsible for the reactivity
      // (also registers _wrappedGetters as computed properties)
      // 初始化重置state
      resetStoreState(this, state);
  
      // apply plugins
      plugins.forEach(function (plugin) { return plugin(this$1); });
  
      var useDevtools = options.devtools !== undefined ? options.devtools : /* Vue.config.devtools */ true;
      if (useDevtools) {
        devtoolPlugin(this);
      }
    };
  
    // module的state的存取器
    var prototypeAccessors$1 = { state: { configurable: true } };
  
    // vue要用到的install方法
    Store.prototype.install = function install (app, injectKey) {
      // 注入
      app.provide(injectKey || storeKey, this);
      // 添加全局对象 方便我们获取 store实例
      app.config.globalProperties.$store = this;
    };
  
    // 我们平常用到的state取值就是来自这里的 this.$store.state
    prototypeAccessors$1.state.get = function () {
      return this._state.data
    };
  
    prototypeAccessors$1.state.set = function (v) {
      {
        assert(false, "use store.replaceState() to explicit replace store state.");
      }
    };
  
    // 我们常用到的实例上的commit方法
    Store.prototype.commit = function commit (_type, _payload, _options) {
        var this$1 = this;
  
      // check object-style commit
      // 格式化入参
      var ref = unifyObjectStyle(_type, _payload, _options);
        var type = ref.type;
        var payload = ref.payload;
        var options = ref.options;
  
      var mutation = { type: type, payload: payload };
      // 取出对应方法集合
      var entry = this._mutations[type];
      if (!entry) {
        {
          console.error(("[vuex] unknown mutation type: " + type));
        }
        return
      }
      // 包裹提交执行
      this._withCommit(function () {
        entry.forEach(function commitIterator (handler) {
          handler(payload);
        });
      });
  
      // commit之后触发 _subscribers 中的观察者们
      this._subscribers
        .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
        .forEach(function (sub) { return sub(mutation, this$1.state); });
  
      if (
        
        options && options.silent
      ) {
        console.warn(
          "[vuex] mutation type: " + type + ". Silent option has been removed. " +
          'Use the filter functionality in the vue-devtools'
        );
      }
    };
  
    // 实例上的dispatch 派发action事件
    Store.prototype.dispatch = function dispatch (_type, _payload) {
        var this$1 = this;
  
      // check object-style dispatch
      var ref = unifyObjectStyle(_type, _payload);
        var type = ref.type;
        var payload = ref.payload;
  
      var action = { type: type, payload: payload };
      var entry = this._actions[type];
      if (!entry) {
        {
          console.error(("[vuex] unknown action type: " + type));
        }
        return
      }
  
      // 执行钩子before action之前触发
      try {
        this._actionSubscribers
          .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
          .filter(function (sub) { return sub.before; })
          .forEach(function (sub) { return sub.before(action, this$1.state); });
      } catch (e) {
        {
          console.warn("[vuex] error in before action subscribers: ");
          console.error(e);
        }
      }
  
      // 执行主体目标函数
      var result = entry.length > 1
        ? Promise.all(entry.map(function (handler) { return handler(payload); }))
        : entry[0](payload);
  
      // 处理异步结果
      // 2个钩子 成功和失败之后的 after和error
      return new Promise(function (resolve, reject) {
        result.then(function (res) {
          try {
            this$1._actionSubscribers
              .filter(function (sub) { return sub.after; })
              .forEach(function (sub) { return sub.after(action, this$1.state); });
          } catch (e) {
            {
              console.warn("[vuex] error in after action subscribers: ");
              console.error(e);
            }
          }
          resolve(res);
        }, function (error) {
          try {
            this$1._actionSubscribers
              .filter(function (sub) { return sub.error; })
              .forEach(function (sub) { return sub.error(action, this$1.state, error); });
          } catch (e) {
            {
              console.warn("[vuex] error in error action subscribers: ");
              console.error(e);
            }
          }
          reject(error);
        });
      })
    };
  
    // 添加 commit之后需要触发的订阅者
    Store.prototype.subscribe = function subscribe (fn, options) {
      return genericSubscribe(fn, this._subscribers, options)
    };
  
    // 添加一个异步action的before类型的订阅者
    Store.prototype.subscribeAction = function subscribeAction (fn, options) {
      var subs = typeof fn === 'function' ? { before: fn } : fn;
      return genericSubscribe(subs, this._actionSubscribers, options)
    };
  
    // 调用vue的watch且 getter参数固定为 state 和 getters
    Store.prototype.watch = function watch$1 (getter, cb, options) {
        var this$1 = this;
  
      {
        assert(typeof getter === 'function', "store.watch only accepts a function.");
      }
      return vue.watch(function () { return getter(this$1.state, this$1.getters); }, cb, Object.assign({}, options))
    };
  
    // 提供了替换state的api
    Store.prototype.replaceState = function replaceState (state) {
        var this$1 = this;
  
      this._withCommit(function () {
        this$1._state.data = state;
      });
    };
  
    // 动态注册新的module进来
    Store.prototype.registerModule = function registerModule (path, rawModule, options) {
        if ( options === void 0 ) options = {};
  
      if (typeof path === 'string') { path = [path]; }
  
      {
        assert(Array.isArray(path), "module path must be a string or an Array.");
        assert(path.length > 0, 'cannot register the root module by using registerModule.');
      }
  
      // 添加进modules 建立父子关系
      this._modules.register(path, rawModule);
      // 挂载新module的所有属性字段
      installModule(this, this.state, path, this._modules.get(path), options.preserveState);
      // reset store to update getters...
      resetStoreState(this, this.state);
    };
  
    // 移除path对应的module
    Store.prototype.unregisterModule = function unregisterModule (path) {
        var this$1 = this;
  
      if (typeof path === 'string') { path = [path]; }
  
      {
        assert(Array.isArray(path), "module path must be a string or an Array.");
      }
  
      // 从module树中移除
      this._modules.unregister(path);
      // 修改state
      this._withCommit(function () {
        var parentState = getNestedState(this$1.state, path.slice(0, -1));
        delete parentState[path[path.length - 1]];
      });
      resetStore(this);
    };
  
    // 检测对应path是否存在module
    Store.prototype.hasModule = function hasModule (path) {
      if (typeof path === 'string') { path = [path]; }
  
      {
        assert(Array.isArray(path), "module path must be a string or an Array.");
      }
  
      return this._modules.isRegistered(path)
    };
  
    // 更新
    Store.prototype.hotUpdate = function hotUpdate (newOptions) {
      this._modules.update(newOptions);
      resetStore(this, true);
    };
  
    // 提交包裹函数 很有意思的实现
    Store.prototype._withCommit = function _withCommit (fn) {
      var committing = this._committing;
      this._committing = true;
      fn();
      this._committing = committing;
    };
  
    // 设置state属性
    Object.defineProperties( Store.prototype, prototypeAccessors$1 );
  
    // 添加监听者
    function genericSubscribe (fn, subs, options) {
      if (subs.indexOf(fn) < 0) {
        options && options.prepend
          ? subs.unshift(fn)
          : subs.push(fn);
      }
      return function () {
        var i = subs.indexOf(fn);
        if (i > -1) {
          subs.splice(i, 1);
        }
      }
    }
  
    // 重置整个store
    function resetStore (store, hot) {
      store._actions = Object.create(null);
      store._mutations = Object.create(null);
      store._wrappedGetters = Object.create(null);
      store._modulesNamespaceMap = Object.create(null);
      var state = store.state;
      // 重新安装挂载以及重置state
      // init all modules
      installModule(store, state, [], store._modules.root, true);
      // reset state
      resetStoreState(store, state, hot);
    }
  
    // 重置store的state属性
    function resetStoreState (store, state, hot) {
      var oldState = store._state;
      
      // 清空各个属性
      // bind store public getters
      store.getters = {};
      // reset local getters cache
      store._makeLocalGettersCache = Object.create(null);
      // wrappedGetters 其实只是从 store.getters中延迟延迟取值而已
      var wrappedGetters = store._wrappedGetters;
      var computedObj = {};
      forEachValue(wrappedGetters, function (fn, key) {
        // use computed to leverage its lazy-caching mechanism
        // direct inline function use will lead to closure preserving oldState.
        // using partial to return function with only arguments preserved in closure environment.
        computedObj[key] = partial(fn, store);
        Object.defineProperty(store.getters, key, {
          // TODO: use `computed` when it's possible. at the moment we can't due to
          // https://github.com/vuejs/vuex/pull/1883
          get: function () { return computedObj[key](); },
          enumerable: true // for local getters
        });
      });
  
      // 设置响应式 深度遍历 所有的属性及子属性都是响应式的
      store._state = vue.reactive({
        data: state
      });
  
      // enable strict mode for new state
      if (store.strict) {
        enableStrictMode(store);
      }
  
      if (oldState) {
        if (hot) {
          // dispatch changes in all subscribed watchers
          // to force getter re-evaluation for hot reloading.
          store._withCommit(function () {
            oldState.data = null;
          });
        }
      }
    }
  
    // 安装挂载 我们写的配置项各个属性到store的各个私有属性上去
    function installModule (store, rootState, path, module, hot) {
      // 是否是根节点module
      var isRoot = !path.length;
      // 获取当前module的完整命名空间 存在递归调用 这个命名空间在逐渐加长
      var namespace = store._modules.getNamespace(path);
  
      // register in namespace map
      if (module.namespaced) {
        if (store._modulesNamespaceMap[namespace] && true) {
          console.error(("[vuex] duplicate namespace " + namespace + " for the namespaced module " + (path.join('/'))));
        }
        // 设置module缓存map 不可重名覆盖
        store._modulesNamespaceMap[namespace] = module;
      }
  
      // set state
      // install后 子module的state会安装命名空间的格式 追加到根state对象下面
      // 方便后面的state直接取值
      if (!isRoot && !hot) {
        // 获取父state
        var parentState = getNestedState(rootState, path.slice(0, -1));
        var moduleName = path[path.length - 1];
        store._withCommit(function () {
          {
            if (moduleName in parentState) {
              console.warn(
                ("[vuex] state field \"" + moduleName + "\" was overridden by a module with the same name at \"" + (path.join('.')) + "\"")
              );
            }
          }
          // 子state的内容会被挂载到父state上 所以要求子module的命名空间不得与原父的key重名
          parentState[moduleName] = module.state;
        });
      }
  
      // 针对每个子module 构建当前module的 本地上下文
      // 本地上下文中含有属于当前module的各类属性方法
      var local = module.context = makeLocalContext(store, namespace, path);
  
      // 注册挂载几类属性
      // 注意传入的参数 local
      module.forEachMutation(function (mutation, key) {
        var namespacedType = namespace + key;
        registerMutation(store, namespacedType, mutation, local);
      });
  
      module.forEachAction(function (action, key) {
        var type = action.root ? key : namespace + key;
        var handler = action.handler || action;
        registerAction(store, type, handler, local);
      });
  
      module.forEachGetter(function (getter, key) {
        var namespacedType = namespace + key;
        registerGetter(store, namespacedType, getter, local);
      });
  
      // 递归注册子module
      module.forEachChild(function (child, key) {
        installModule(store, rootState, path.concat(key), child, hot);
      });
    }
  
    /**
     * make localized dispatch, commit, getters and state
     * if there is no namespace, just use root ones
     */
    // 每个子module的本地上下文
    function makeLocalContext (store, namespace, path) {
      var noNamespace = namespace === '';
  
      // 新的上下文对象 没有命名空间的情况只是调用顶级store上的属性方法即可 有的话每个子module的每个属性都是新的一个包裹函数
      var local = {
        dispatch: noNamespace ? store.dispatch : function (_type, _payload, _options) {
          // 格式化参数
          var args = unifyObjectStyle(_type, _payload, _options);
          var payload = args.payload;
          var options = args.options;
          var type = args.type;
          
          // 对应文档中描述的root配置属性
          if (!options || !options.root) {
            // 修改type
            type = namespace + type;
            if ( !store._actions[type]) {
              console.error(("[vuex] unknown local action type: " + (args.type) + ", global type: " + type));
              return
            }
          }
  
          return store.dispatch(type, payload)
        },
        
        // 分析同上
        commit: noNamespace ? store.commit : function (_type, _payload, _options) {
          var args = unifyObjectStyle(_type, _payload, _options);
          var payload = args.payload;
          var options = args.options;
          var type = args.type;
  
          if (!options || !options.root) {
            type = namespace + type;
            if ( !store._mutations[type]) {
              console.error(("[vuex] unknown local mutation type: " + (args.type) + ", global type: " + type));
              return
            }
          }
  
          store.commit(type, payload, options);
        }
      };
  
      // getters and state object must be gotten lazily
      // because they will be changed by state update
      Object.defineProperties(local, {
        getters: {
          get: noNamespace
            ? function () { return store.getters; }
            // 子module的getter需要通过 makeLocalGetters 创建
            : function () { return makeLocalGetters(store, namespace); }
        },
        // 每个上下文 根据path找到属于自己的state
        state: {
          get: function () { return getNestedState(store.state, path); }
        }
      });
  
      // 返回的上下文 包含文档所描述的属性
      return local
    }
  
    // 每个命名空间一个本地 getters
    function makeLocalGetters (store, namespace) {
      // 设置缓存
      if (!store._makeLocalGettersCache[namespace]) {
        var gettersProxy = {};
        var splitPos = namespace.length;
        Object.keys(store.getters).forEach(function (type) {
          // skip if the target getter is not match this namespace
          if (type.slice(0, splitPos) !== namespace) { return }
  
          // 提取最末端名字
          // extract local getter type
          var localType = type.slice(splitPos);
  
          // Add a port to the getters proxy.
          // Define as getter property because
          // we do not want to evaluate the getters in this time.
          // 不需要马上出发取值操作 写成这样
          Object.defineProperty(gettersProxy, localType, {
            get: function () { return store.getters[type]; },
            enumerable: true
          });
        });
        store._makeLocalGettersCache[namespace] = gettersProxy;
      }
  
      return store._makeLocalGettersCache[namespace]
    }
  
    // 下面几个属性其实都在放在store下面的 只有type 命名空间才能区分
    // 注册挂载 Mutation
    function registerMutation (store, type, handler, local) {
      var entry = store._mutations[type] || (store._mutations[type] = []);
      entry.push(function wrappedMutationHandler (payload) {
        handler.call(store, local.state, payload);
      });
    }
  
    // 注册挂载action 注意可以返回promise 以及handler参数中包含的属性 和 文档想对应
    function registerAction (store, type, handler, local) {
      var entry = store._actions[type] || (store._actions[type] = []);
      entry.push(function wrappedActionHandler (payload) {
        var res = handler.call(store, {
          dispatch: local.dispatch,
          commit: local.commit,
          getters: local.getters,
          state: local.state,
          rootGetters: store.getters,
          rootState: store.state
        }, payload);
        // promise包裹
        if (!isPromise(res)) {
          res = Promise.resolve(res);
        }
        if (store._devtoolHook) {
          return res.catch(function (err) {
            store._devtoolHook.emit('vuex:error', err);
            throw err
          })
        } else {
          return res
        }
      });
    }
  
    // 注册挂载getters 注意最后执行的4个参数
    function registerGetter (store, type, rawGetter, local) {
      if (store._wrappedGetters[type]) {
        {
          console.error(("[vuex] duplicate getter key: " + type));
        }
        return
      }
      // 存放在 _wrappedGetters 中
      store._wrappedGetters[type] = function wrappedGetter (store) {
        return rawGetter(
          local.state, // local state
          local.getters, // local getters
          store.state, // root state
          store.getters // root getters
        )
      };
    }
  
    // 添加一个监听者 被修改后 警告 store._state.data 是响应式的
    function enableStrictMode (store) {
      vue.watch(function () { return store._state.data; }, function () {
        {
          assert(store._committing, "do not mutate vuex store state outside mutation handlers.");
        }
      }, { deep: true, flush: 'sync' }); // 同步执行警告
    }
  
    // 根据path获取子state
    function getNestedState (state, path) {
      return path.reduce(function (state, key) { return state[key]; }, state)
    }
  
    // 统一参数格式 如返回体所示
    function unifyObjectStyle (type, payload, options) {
      if (isObject(type) && type.type) {
        options = payload;
        payload = type;
        type = type.type;
      }
  
      {
        assert(typeof type === 'string', ("expects string as the type, but found " + (typeof type) + "."));
      }
  
      return { type: type, payload: payload, options: options }
    }
  
    /**
     * Reduce the code which written in Vue.js for getting the state.
     * @param {String} [namespace] - Module's namespace
     * @param {Object|Array} states # Object's item can be a function which accept state and getters for param, you can do something for state and getters in it.
     * @param {Object}
     */
    // 看下mapState的实现:
    var mapState = normalizeNamespace(function (namespace, states) {
      var res = {};
      if ( !isValidMap(states)) {
        console.error('[vuex] mapState: mapper parameter must be either an Array or an Object');
      }
      normalizeMap(states).forEach(function (ref) {
        var key = ref.key;
        var val = ref.val;
        
        // vue中 computed中的getter们都被bind this到vue的组件实例上的proxy了 也就是下面用到的this 所以可以用this.$store了
        res[key] = function mappedState () {
          var state = this.$store.state;
          var getters = this.$store.getters;
          if (namespace) {
            // 取到对应路径下的module就很方便了
            var module = getModuleByNamespace(this.$store, 'mapState', namespace);
            if (!module) {
              return
            }
            // 借助子module的本地上下文中的state和getters可以方面获取到我们想要的子module的状态值
            state = module.context.state;
            getters = module.context.getters;
          }
          // 对应val 我们可以写的的2种参数形式 1.函数 或者 字符串
          return typeof val === 'function'
            ? val.call(this, state, getters)
            : state[val]
        };
        // mark vuex getter for devtools
        res[key].vuex = true;
      });
      // 最后返回一个对象给computed对象去合并
      return res
    });
  
    /**
     * Reduce the code which written in Vue.js for committing the mutation
     * @param {String} [namespace] - Module's namespace
     * @param {Object|Array} mutations # Object's item can be a function which accept `commit` function as the first param, it can accept another params. You can commit mutation and do any other things in this function. specially, You need to pass anthor params from the mapped function.
     * @return {Object}
     */
    // 整体分析同上
    var mapMutations = normalizeNamespace(function (namespace, mutations) {
      var res = {};
      if ( !isValidMap(mutations)) {
        console.error('[vuex] mapMutations: mapper parameter must be either an Array or an Object');
      }
      normalizeMap(mutations).forEach(function (ref) {
        var key = ref.key;
        var val = ref.val;
  
        res[key] = function mappedMutation () {
          // 由于是函数调用 合并用户传入的参数
          var args = [], len = arguments.length;
          while ( len-- ) args[ len ] = arguments[ len ];
  
          // Get the commit method from store
          var commit = this.$store.commit;
          if (namespace) {
            var module = getModuleByNamespace(this.$store, 'mapMutations', namespace);
            if (!module) {
              return
            }
            commit = module.context.commit;
          }
          // 调用的其实是对应的commit 不过注意在头部插入指定参数
          return typeof val === 'function'
            ? val.apply(this, [commit].concat(args))
            : commit.apply(this.$store, [val].concat(args))
        };
      });
      return res
    });
  
    /**
     * Reduce the code which written in Vue.js for getting the getters
     * @param {String} [namespace] - Module's namespace
     * @param {Object|Array} getters
     * @return {Object}
     */
    // 整体分析同上
    var mapGetters = normalizeNamespace(function (namespace, getters) {
      var res = {};
      if ( !isValidMap(getters)) {
        console.error('[vuex] mapGetters: mapper parameter must be either an Array or an Object');
      }
      normalizeMap(getters).forEach(function (ref) {
        var key = ref.key;
        var val = ref.val;
  
        // The namespace has been mutated by normalizeNamespace
        // 取得完整的path
        val = namespace + val;
        res[key] = function mappedGetter () {
          if (namespace && !getModuleByNamespace(this.$store, 'mapGetters', namespace)) {
            return
          }
          // 都在this.$store.getters中放着 只是path不同而已
          if ( !(val in this.$store.getters)) {
            console.error(("[vuex] unknown getter: " + val));
            return
          }
          // 回顾之前的代码 去对应的module的去取对应的getter
          return this.$store.getters[val]
        };
        // mark vuex getter for devtools
        res[key].vuex = true;
      });
      return res
    });
  
    /**
     * Reduce the code which written in Vue.js for dispatch the action
     * @param {String} [namespace] - Module's namespace
     * @param {Object|Array} actions # Object's item can be a function which accept `dispatch` function as the first param, it can accept anthor params. You can dispatch action and do any other things in this function. specially, You need to pass anthor params from the mapped function.
     * @return {Object}
     */
    // 整体分析同上
    var mapActions = normalizeNamespace(function (namespace, actions) {
      var res = {};
      if ( !isValidMap(actions)) {
        console.error('[vuex] mapActions: mapper parameter must be either an Array or an Object');
      }
      normalizeMap(actions).forEach(function (ref) {
        var key = ref.key;
        var val = ref.val;
  
        res[key] = function mappedAction () {
          // 拷贝用户的参数
          var args = [], len = arguments.length;
          while ( len-- ) args[ len ] = arguments[ len ];
  
          // get dispatch function from store
          var dispatch = this.$store.dispatch;
          if (namespace) {
            var module = getModuleByNamespace(this.$store, 'mapActions', namespace);
            if (!module) {
              return
            }
            dispatch = module.context.dispatch;
          }
          // 2种调用方式
          return typeof val === 'function'
            ? val.apply(this, [dispatch].concat(args))
            : dispatch.apply(this.$store, [val].concat(args))
        };
      });
      return res
    });
  
    /**
     * Rebinding namespace param for mapXXX function in special scoped, and return them by simple object
     * @param {String} namespace
     * @return {Object}
     */
    // 辅助方法 createNamespacedHelpers 其实也是 调用已存在的 各个mapxxx方法 不过利用bind内置了参数 namespace
    var createNamespacedHelpers = function (namespace) { return ({
      mapState: mapState.bind(null, namespace),
      mapGetters: mapGetters.bind(null, namespace),
      mapMutations: mapMutations.bind(null, namespace),
      mapActions: mapActions.bind(null, namespace)
    }); };
  
    /**
     * Normalize the map
     * normalizeMap([1, 2, 3]) => [ { key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 } ]
     * normalizeMap({a: 1, b: 2, c: 3}) => [ { key: 'a', val: 1 }, { key: 'b', val: 2 }, { key: 'c', val: 3 } ]
     * @param {Array|Object} map
     * @return {Object}
     */
    // 统一数组和对象的参数形式
    function normalizeMap (map) {
      if (!isValidMap(map)) {
        return []
      }
      return Array.isArray(map)
        ? map.map(function (key) { return ({ key: key, val: key }); })
        : Object.keys(map).map(function (key) { return ({ key: key, val: map[key] }); })
    }
  
    /**
     * Validate whether given map is valid or not
     * @param {*} map
     * @return {Boolean}
     */
    // 对mapXxxx方法系列的参数要求: 待放入computed中的必须是 数组或者对象
    function isValidMap (map) {
      return Array.isArray(map) || isObject(map)
    }
  
    /**
     * Return a function expect two param contains namespace and map. it will normalize the namespace and then the param's function will handle the new namespace and the map.
     * @param {Function} fn
     * @return {Function}
     */
    // 调用 mapXxxx 系列方法 存在2种调用格式 第一个可以是namespace + 待处理列表 第二种直接是待处理列表 把种情况的参数合一 让 fn 去执行
    function normalizeNamespace (fn) {
      return function (namespace, map) {
        if (typeof namespace !== 'string') {
          map = namespace;
          namespace = '';
        } else if (namespace.charAt(namespace.length - 1) !== '/') {
          namespace += '/';
        }
        return fn(namespace, map)
      }
    }
  
    /**
     * Search a special module from store by namespace. if module not exist, print error message.
     * @param {Object} store
     * @param {String} helper
     * @param {String} namespace
     * @return {Object}
     */
    // 根据命名空间获取module 直接从缓存中取 安装install后 缓存中已存在
    function getModuleByNamespace (store, helper, namespace) {
      var module = store._modulesNamespaceMap[namespace];
      if ( !module) {
        console.error(("[vuex] module namespace not found in " + helper + "(): " + namespace));
      }
      return module
    }
  
    // Credits: borrowed code from fcomb/redux-logger
  
    function createLogger (ref) {
      if ( ref === void 0 ) ref = {};
      var collapsed = ref.collapsed; if ( collapsed === void 0 ) collapsed = true;
      var filter = ref.filter; if ( filter === void 0 ) filter = function (mutation, stateBefore, stateAfter) { return true; };
      var transformer = ref.transformer; if ( transformer === void 0 ) transformer = function (state) { return state; };
      var mutationTransformer = ref.mutationTransformer; if ( mutationTransformer === void 0 ) mutationTransformer = function (mut) { return mut; };
      var actionFilter = ref.actionFilter; if ( actionFilter === void 0 ) actionFilter = function (action, state) { return true; };
      var actionTransformer = ref.actionTransformer; if ( actionTransformer === void 0 ) actionTransformer = function (act) { return act; };
      var logMutations = ref.logMutations; if ( logMutations === void 0 ) logMutations = true;
      var logActions = ref.logActions; if ( logActions === void 0 ) logActions = true;
      var logger = ref.logger; if ( logger === void 0 ) logger = console;
  
      return function (store) {
        var prevState = deepCopy(store.state);
  
        if (typeof logger === 'undefined') {
          return
        }
  
        if (logMutations) {
          store.subscribe(function (mutation, state) {
            var nextState = deepCopy(state);
  
            if (filter(mutation, prevState, nextState)) {
              var formattedTime = getFormattedTime();
              var formattedMutation = mutationTransformer(mutation);
              var message = "mutation " + (mutation.type) + formattedTime;
  
              startMessage(logger, message, collapsed);
              logger.log('%c prev state', 'color: #9E9E9E; font-weight: bold', transformer(prevState));
              logger.log('%c mutation', 'color: #03A9F4; font-weight: bold', formattedMutation);
              logger.log('%c next state', 'color: #4CAF50; font-weight: bold', transformer(nextState));
              endMessage(logger);
            }
  
            prevState = nextState;
          });
        }
  
        if (logActions) {
          store.subscribeAction(function (action, state) {
            if (actionFilter(action, state)) {
              var formattedTime = getFormattedTime();
              var formattedAction = actionTransformer(action);
              var message = "action " + (action.type) + formattedTime;
  
              startMessage(logger, message, collapsed);
              logger.log('%c action', 'color: #03A9F4; font-weight: bold', formattedAction);
              endMessage(logger);
            }
          });
        }
      }
    }
  
    function startMessage (logger, message, collapsed) {
      var startMessage = collapsed
        ? logger.groupCollapsed
        : logger.group;
  
      // render
      try {
        startMessage.call(logger, message);
      } catch (e) {
        logger.log(message);
      }
    }
  
    function endMessage (logger) {
      try {
        logger.groupEnd();
      } catch (e) {
        logger.log('—— log end ——');
      }
    }
  
    function getFormattedTime () {
      var time = new Date();
      return (" @ " + (pad(time.getHours(), 2)) + ":" + (pad(time.getMinutes(), 2)) + ":" + (pad(time.getSeconds(), 2)) + "." + (pad(time.getMilliseconds(), 3)))
    }
  
    function repeat (str, times) {
      return (new Array(times + 1)).join(str)
    }
  
    function pad (num, maxLength) {
      return repeat('0', maxLength - num.toString().length) + num
    }
  
    // 对外暴露的对象集合
    var index_cjs = {
      version: '4.0.0',
      Store: Store,
      storeKey: storeKey,
      createStore: createStore,
      useStore: useStore,
      mapState: mapState,
      mapMutations: mapMutations,
      mapGetters: mapGetters,
      mapActions: mapActions,
      createNamespacedHelpers: createNamespacedHelpers,
      createLogger: createLogger
    };
  
    return index_cjs;
  
  }(Vue));

  /**
   * 最后总结一下 vuex 的整体实现:
   * 1. 根据用户传入的options构建一个module树,每个节点都是我们写的module的一层包裹module对象;
   * 2. installModule方法中,把每个子module对应的state,getters等属性都按照命名空间一一挂载到store实例的内置属性,同时为每个子module建立了本地上下文,有单独自己的getters等属性;module树中所有子module的state都集中到根module的state中,按照命名空间划分好,我们可以自己根据命名空间直接获取或者通过mapState去获取;
   * 3. 把module树根节点的state整体设置为深度响应式对象,然后依靠vue的computed方法,在用户修改state的字段后,触发computed对象的自更新,再触发vue中组件对该computed对象的依赖更新,从而达到整体更新效果;
   * 4. 实现了mapXxxx系列方法, 返回一些列包裹函数,内置了处理逻辑去获取store中对应的属性
   * 
   */

vue-router(v4.0.6)之 RouterLink 和 RouterView 内置组件的实现简析

先看简单一点的 RouterLink:

const RouterLink = RouterLinkImpl;

// TODO: we could allow currentRoute as a prop to expose `isActive` and
// `isExactActive` behavior should go through an RFC
// 重点看下这个方法需要
function useLink(props) {
    // 从当前组件实例中取出router实例
    const router = vue.inject(routerKey);
    // 同上 取出 当前route reactiveRoute 其实是个location类型对象
    const currentRoute = vue.inject(routeLocationKey);
    // 调用实例的resolve解析我们写的to地址或者对象 得到目标route
    const route = vue.computed(() => router.resolve(vue.unref(props.to)));
    // 计算 这个router-link的to的path是否处于当前匹配的path中 并且响应式包裹
    const activeRecordIndex = vue.computed(() => {
        let { matched } = route.value;
        let { length } = matched;
        const routeMatched = matched[length - 1];
        let currentMatched = currentRoute.matched;
        if (!routeMatched || !currentMatched.length)
            return -1;
        // 从当前匹配的 currentMatched 的 record中寻找 目标routeMatched
        let index = currentMatched.findIndex(isSameRouteRecord.bind(null, routeMatched));
        if (index > -1)
            return index;
        // possible parent record
        // 同一父路径下的2个不同子路径
        let parentRecordPath = getOriginalPath(matched[length - 2]);
        return (
        // we are dealing with nested routes
        length > 1 &&
            // if the parent and matched route have the same path, this link is
            // referring to the empty child. Or we currently are on a different
            // child of the same parent
            getOriginalPath(routeMatched) === parentRecordPath &&
            // avoid comparing the child with its parent
            currentMatched[currentMatched.length - 1].path !== parentRecordPath
            ? currentMatched.findIndex(isSameRouteRecord.bind(null, matched[length - 2]))
            : index);
    });
    // 粗略的匹配要求不高 只要找到对应的record以及参数包含就可以了
    const isActive = vue.computed(() => activeRecordIndex.value > -1 &&
        includesParams(currentRoute.params, route.value.params));
    // 精准匹配则要求路径绝对匹配 参数完全相同
    const isExactActive = vue.computed(() => activeRecordIndex.value > -1 &&
        activeRecordIndex.value === currentRoute.matched.length - 1 &&
        isSameRouteLocationParams(currentRoute.params, route.value.params));
    // 我们点击元素的事件的实现
    function navigate(e = {}) {
        if (guardEvent(e))
            // 调用router实例的方法即可
            return router[vue.unref(props.replace) ? 'replace' : 'push'](vue.unref(props.to));
        return Promise.resolve();
    }
    // 暴露给外部的属性 对应文档中的描述v-slot中的参数
    return {
        route,
        href: vue.computed(() => route.value.href),
        isActive,
        isExactActive,
        navigate,
    };
}
const RouterLinkImpl = /*#__PURE__*/ vue.defineComponent({
    name: 'RouterLink',
    props: {
        // 点击router-link组件跳转的地址 可以为 string 或者 一个对象
        // 必选
        to: {
            type: [String, Object],
            required: true,
        },
        // 布尔值 是否是重定向类型跳转
        replace: Boolean,
        // 当前to路由 激活 的时候 追加的激活样式 todo
        activeClass: String,
        // inactiveClass: String,
        // 当前to路由 精准激活 的时候 追加的激活样式 todo
        exactActiveClass: String,
        // 控制是否渲染自定义的元素 而不是把自定义的元素放到默认a标签的里面
        custom: Boolean,
        // 放到prop中的一个属性 具体作用暂时不清楚
        ariaCurrentValue: {
            type: String,
            default: 'page',
        },
    },
    // 存在setup选项 它如果返回一个函数的话 就视为组件的render函数
    setup(props, { slots }) {
        // 响应式包裹我们需要用到的 link 对象的每个key
        const link = vue.reactive(useLink(props));
        // 取出router实例中options
        const { options } = vue.inject(routerKey);
        // class对象 作为prop属性 也需要做响应式包裹
        // 确定class的名字 
        const elClass = vue.computed(() => ({
            // 部分匹配
            [getLinkClass(props.activeClass, options.linkActiveClass, 'router-link-active')]: link.isActive,
            // [getLinkClass(
            //   props.inactiveClass,
            //   options.linkInactiveClass,
            //   'router-link-inactive'
            // )]: !link.isExactActive,
            // 精准匹配
            [getLinkClass(props.exactActiveClass, options.linkExactActiveClass, 'router-link-exact-active')]: link.isExactActive,
        }));
        {
            // 当route发送改变 切换了的时候 我们需要更新一些属性 当然 都是post类型 render渲染之后 也就是下面返回的函数之后的下个tick执行
            const instance = vue.getCurrentInstance();
            vue.watchEffect(() => {
                if (!instance)
                    return;
                instance.__vrl_route = link.route;
            }, { flush: 'post' });
            vue.watchEffect(() => {
                if (!instance)
                    return;
                instance.__vrl_active = link.isActive;
                instance.__vrl_exactActive = link.isExactActive;
            }, { flush: 'post' });
        }
        // 实际的render函数
        return () => {
            // 对于 这种逻辑组件而言 它内部的用户代码都是按照slot来处理的
            const children = slots.default && slots.default(link);
            // 对应文档中的custom属性的解释:用户定义的元素替代默认的a标签 否则就放入a标签的子节点内
            return props.custom
                ? children
                // link的每个属性都是响应式的 触发依赖收集
                : vue.h('a', {
                    'aria-current': link.isExactActive
                        ? props.ariaCurrentValue
                        : null,
                    href: link.href,
                    // this would override user added attrs but Vue will still add
                    // the listener so we end up triggering both
                    onClick: link.navigate,
                    class: elClass.value,
                }, children);
        };
    },
});
// export the public type for h/tsx inference
// also to avoid inline import() in generated d.ts files
/**
    * Component to render a link that triggers a navigation on click.
    */
const RouterLink = RouterLinkImpl;
// 对router-link的点击事件有一定的限制 每行都有原文注释
function guardEvent(e) {
    // don't redirect with control keys
    if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
        return;
    // don't redirect when preventDefault called
    if (e.defaultPrevented)
        return;
    // don't redirect on right click
    if (e.button !== undefined && e.button !== 0)
        return;
    // don't redirect if `target="_blank"`
    // @ts-ignore getAttribute does exist
    if (e.currentTarget && e.currentTarget.getAttribute) {
        // @ts-ignore getAttribute exists
        const target = e.currentTarget.getAttribute('target');
        if (/\b_blank\b/i.test(target))
            return;
    }
    // this may be a Weex event which doesn't have this method
    if (e.preventDefault)
        e.preventDefault();
    return true;
}
// outer 参数 包含 inner 即可
function includesParams(outer, inner) {
    for (let key in inner) {
        let innerValue = inner[key];
        let outerValue = outer[key];
        if (typeof innerValue === 'string') {
            if (innerValue !== outerValue)
                return false;
        }
        else {
            if (!Array.isArray(outerValue) ||
                outerValue.length !== innerValue.length ||
                innerValue.some((value, i) => value !== outerValue[i]))
                return false;
        }
    }
    return true;
}
/**
    * Get the original path value of a record by following its aliasOf
    * @param record
    */
// 获取path
function getOriginalPath(record) {
    return record ? (record.aliasOf ? record.aliasOf.path : record.path) : '';
}
/**
    * Utility class to get the active class based on defaults.
    * @param propClass
    * @param globalClass
    * @param defaultClass
    */
const getLinkClass = (propClass, globalClass, defaultClass) => propClass != null
    ? propClass
    : globalClass != null
        ? globalClass
        : defaultClass;

可以看到,link组件是一种逻辑组件,它内部实现了自己的render函数,默认渲染a标签,同时依赖了几个响应式属性,当我们通过实例的push等方法改变route的时候,
render函数重新执行更新即可,又可以精确确定是否是activeLink了,默认绑定的点击事件也是调用实例的对应方法。总体实现算比较简单。

再看下 RouterView:

const RouterView = RouterViewImpl;

// 格式化vnode节点格式
function normalizeSlot(slot, data) {
    if (!slot)
        return null;
    const slotContent = slot(data);
    return slotContent.length === 1 ? slotContent[0] : slotContent;
}

// router-view的使用方式发生了改变 请注意 详情见文档即可
// warn against deprecated usage with <transition> & <keep-alive>
// due to functional component being no longer eager in Vue 3
function warnDeprecatedUsage() {
    const instance = vue.getCurrentInstance();
    const parentName = instance.parent && instance.parent.type.name;
    if (parentName &&
        (parentName === 'KeepAlive' || parentName.includes('Transition'))) {
        const comp = parentName === 'KeepAlive' ? 'keep-alive' : 'transition';
        warn(`<router-view> can no longer be used directly inside <transition> or <keep-alive>.\n` +
            `Use slot props instead:\n\n` +
            `<router-view v-slot="{ Component }">\n` +
            `  <${comp}>\n` +
            `    <component :is="Component" />\n` +
            `  </${comp}>\n` +
            `</router-view>`);
    }
}

const RouterViewImpl = /*#__PURE__*/ vue.defineComponent({
    name: 'RouterView',
    // #674 we manually inherit them
    inheritAttrs: false,
    props: {
        name: {
            type: String,
            default: 'default',
        },
        route: Object,
    },
    // 返回一个函数 作为组件的render函数
    setup(props, { attrs, slots }) {
        // 声明使用方式改变的警告
        warnDeprecatedUsage();
        // 取出router-view依赖的当前route
        const injectedRoute = vue.inject(routerViewLocationKey);
        // 响应式 待渲染route
        const routeToDisplay = vue.computed(() => props.route || injectedRoute.value);
        // 取出当前的route-view所处的深度
        const depth = vue.inject(viewDepthKey, 0);
        // 取出对应深度的匹配record对象
        const matchedRouteRef = vue.computed(() => routeToDisplay.value.matched[depth]);
        // 重新注入 深度改变
        vue.provide(viewDepthKey, depth + 1);
        vue.provide(matchedRouteKey, matchedRouteRef);
        vue.provide(routerViewLocationKey, routeToDisplay);
        // 给当前组件添加一个ref prop
        const viewRef = vue.ref();
        // watch at the same time the component instance, the route record we are
        // rendering, and the name
        // 添加一个post类型的watcher 等下面的render更新后 执行回调
        // 待监听改变的有3个对象 第一个是 ref指向组件实例对象 第二个 是 recored对象 第三个是 router-view的name
        vue.watch(() => [viewRef.value, matchedRouteRef.value, props.name], ([instance, to, name], [oldInstance, from, oldName]) => {
            // copy reused instances
            if (to) {
                // this will update the instance for new instances as well as reused
                // instances when navigating to a new route
                to.instances[name] = instance;
                // the component instance is reused for a different route or name so
                // we copy any saved update or leave guards. With async setup, the
                // mounting component will mount before the matchedRoute changes,
                // making instance === oldInstance, so we check if guards have been
                // added before. This works because we remove guards when
                // unmounting/deactivating components
                // 重用组件的情况
                if (from && from !== to && instance && instance === oldInstance) {
                    // 继承对应的钩子守卫
                    if (!to.leaveGuards.size) {
                        to.leaveGuards = from.leaveGuards;
                    }
                    if (!to.updateGuards.size) {
                        to.updateGuards = from.updateGuards;
                    }
                }
            }
            // trigger beforeRouteEnter next callbacks
            // 在这里 dom更新后 更新用户设置的next回调
            if (instance &&
                to &&
                // if there is no instance but to and from are the same this might be
                // the first visit
                (!from || !isSameRouteRecord(to, from) || !oldInstance)) {
                (to.enterCallbacks[name] || []).forEach(callback => callback(instance));
            }
        }, { flush: 'post' });
        // render函数的实现
        return () => {
            // 取值 会触发依赖 下面的生成vnode其实也会 不过没关系
            const route = routeToDisplay.value;
            const matchedRoute = matchedRouteRef.value;
            const ViewComponent = matchedRoute && matchedRoute.components[props.name];
            // we need the value at the time we render because when we unmount, we
            // navigated to a different location so the value is different
            const currentName = props.name;
            // 销毁的时候会走这里
            if (!ViewComponent) {
                return normalizeSlot(slots.default, { Component: ViewComponent, route });
            }
            // props from route configuration
            // 对应于文档中 props的格式设置要求
            const routePropsOption = matchedRoute.props[props.name];
            const routeProps = routePropsOption
                ? routePropsOption === true
                    ? route.params
                    : typeof routePropsOption === 'function'
                        ? routePropsOption(route)
                        : routePropsOption
                : null;
            // 注册销毁事件
            const onVnodeUnmounted = vnode => {
                // remove the instance reference to prevent leak
                if (vnode.component.isUnmounted) {
                    matchedRoute.instances[currentName] = null;
                }
            };
            // 得到vnode
            const component = vue.h(ViewComponent, assign({}, routeProps, attrs, {
                onVnodeUnmounted,
                ref: viewRef,
            }));
            // 格式化输出
            return (
            // pass the vnode to the slot as a prop.
            // h and <component :is="..."> both accept vnodes
            normalizeSlot(slots.default, { Component: component, route }) ||
                component);
        };
    },
});

可以看到,router-view组件,主要依赖 当前匹配的route,render方法中对它产生了依赖收集,触发render重新执行,重新计算得到的新vnode。考虑到嵌套路由,
深度值可以确定当前view渲染哪个匹配到的组件内容。

好了,vue-router的主要代码实现分析暂时告一段落啦,以上使我自己阅读过程中的一些笔记,希望能对大家的源码阅读带来一些帮助 :)

Vue(v3.0.11)源码简析之模板编译函数的注册和调用入口的相关实现

vue的模板编译过程分3个大类:1.解析生成ast树 2.transform转化ast节点得到符合预期的codegenNode 3.generate按照codegenNode生成render的代码,最终得到render函数的字符串代码;
整体的调用入口是在这里:

// 转化node上的style attr
const DOMNodeTransforms = [
    transformStyle,
    ...([warnTransitionChildren] )
];
// 内置的几种在运行时需要改变dom的指令
const DOMDirectiveTransforms = {
    cloak: noopDirectiveTransform,
    html: transformVHtml,
    text: transformVText,
    model: transformModel$1,
    on: transformOn$1,
    show: transformShow
};
// 合并众多 options 参数 然后负责调用 baseCompile 返回值中包含 render 的code字符码
function compile$1(template, options = {}) {
    return baseCompile(template, extend({}, parserOptions, options, {
        nodeTransforms: [
            // ignore <script> and <tag>
            // this is not put inside DOMNodeTransforms because that list is used
            // by compiler-ssr to generate vnode fallback branches
            ignoreSideEffectTags,
            ...DOMNodeTransforms,
            ...(options.nodeTransforms || [])
        ],
        directiveTransforms: extend({}, DOMDirectiveTransforms, options.directiveTransforms || {}),
        transformHoist: null 
    }));
}

const compileCache = Object.create(null);
// 编译最顶级入口
/**
 *  template 为我们平常写的html模板的字符串
 *  返回Vue给组件定义的render函数 形式同我们自己写render函数一致
 */
function compileToFunction(template, options) {
    // 兼容情况
    if (!isString(template)) {
        if (template.nodeType) {
            template = template.innerHTML;
        }
        else {
            warn(`invalid template option: `, template);
            return NOOP;
        }
    }
    const key = template;
    // 缓存
    const cached = compileCache[key];
    if (cached) {
        return cached;
    }
    // 兼容情况
    if (template[0] === '#') {
        const el = document.querySelector(template);
        if (!el) {
            warn(`Template element not found or is empty: ${template}`);
        }
        // __UNSAFE__
        // Reason: potential execution of JS expressions in in-DOM template.
        // The user must make sure the in-DOM template is trusted. If it's rendered
        // by the server, the template should not contain any user data.
        template = el ? el.innerHTML : ``;
    }
    // generate得到的render函数代码
    const { code } = compile$1(template, extend({
        hoistStatic: true,
        onError(err) {
            {
                const message = `Template compilation error: ${err.message}`;
                const codeFrame = err.loc &&
                    generateCodeFrame(template, err.loc.start.offset, err.loc.end.offset);
                warn(codeFrame ? `${message}\n${codeFrame}` : message);
            }
        }
    }, options));
    // The wildcard import results in a huge object with every export
    // with keys that cannot be mangled, and can be quite heavy size-wise.
    // In the global build we know `Vue` is available globally so we can avoid
    // the wildcard object.
    // 产生函数
    const render = (new Function(code)()
        );
    render._rc = true;
    // 设置缓存
    return (compileCache[key] = render);
}
// 运行时 动态运行编译函数得到render函数
registerRuntimeCompiler(compileToFunction);

然后就是baseCompile:

// 基础转化函数 后面还会增加几个
function getBaseTransformPreset(prefixIdentifiers) {
    return [
        [
            transformOnce,
            transformIf,
            transformFor,
            ...([transformExpression]
                    ),
            transformSlotOutlet,
            transformElement,
            trackSlotScopes,
            transformText
        ],
        {
            on: transformOn,
            bind: transformBind,
            model: transformModel
        }
    ];
}
// we name it `baseCompile` so that higher order compilers like
// @vue/compiler-dom can export `compile` while re-exporting everything else.
// 编译函数入口 完成3个步骤 1.生成 ast 2.对ast进行transform转换 3. generate生成render函数code字符串
function baseCompile(template, options = {}) {
    const onError = options.onError || defaultOnError;
    const isModuleMode = options.mode === 'module';
    /* istanbul ignore if */
    {
        if (options.prefixIdentifiers === true) {
            onError(createCompilerError(45 /* X_PREFIX_ID_NOT_SUPPORTED */));
        }
        else if (isModuleMode) {
            onError(createCompilerError(46 /* X_MODULE_MODE_NOT_SUPPORTED */));
        }
    }
    const prefixIdentifiers = !true ;
    if (options.cacheHandlers) {
        onError(createCompilerError(47 /* X_CACHE_HANDLER_NOT_SUPPORTED */));
    }
    if (options.scopeId && !isModuleMode) {
        onError(createCompilerError(48 /* X_SCOPE_ID_NOT_SUPPORTED */));
    }
    // 以上几个选项都不支持
    // ast
    const ast = isString(template) ? baseParse(template, options) : template;
    const [nodeTransforms, directiveTransforms] = getBaseTransformPreset();
    // transform
    transform(ast, extend({}, options, {
        prefixIdentifiers,
        nodeTransforms: [
            ...nodeTransforms,
            ...(options.nodeTransforms || []) // user transforms
        ],
        directiveTransforms: extend({}, directiveTransforms, options.directiveTransforms || {} // user transforms
        )
    }));
    // generate
    return generate(ast, extend({}, options, {
        prefixIdentifiers
    }));
}

而我们在finishComponentSetup函数中为了得到render函数,会调用我们注册的编译函数:

// 解析template 构造render函数了
Component.render = compile(Component.template, {
    isCustomElement: instance.appContext.config.isCustomElement,
    delimiters: Component.delimiters
});

vue-router(v4.0.6)之 createRouter 方法的实现简析(1)

由于方法的实现很长,我们分成几篇来写,以免看的太累:)
下面的带有 /* createRouter--start / 和 / createRouter--end */ 中间的内容的代表着源码中的实现:

/* createRouter--start */
const matcher = createRouterMatcher(options.routes, options);
/* createRouter--end */

// 这个就是上文分析过了 得到了matcher对象集合 暴露了5个方法给我们使用
// return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher };

/* createRouter--start */
let parseQuery$1 = options.parseQuery || parseQuery;
let stringifyQuery$1 = options.stringifyQuery || stringifyQuery;
/* createRouter--end */

看下默认的这2个方法实现:

/**
    * Decode text using `decodeURIComponent`. Returns the original text if it
    * fails.
    *
    * @param text - string to decode
    * @returns decoded string
    */
function decode(text) {
    try {
        return decodeURIComponent('' + text);
    }
    catch (err) {
        warn(`Error decoding "${text}". Using original value`);
    }
    return '' + text;
}

/**
    * Transforms a queryString into a {@link LocationQuery} object. Accept both, a
    * version with the leading `?` and without Should work as URLSearchParams

    * @internal
    *
    * @param search - search string to parse
    * @returns a query object
    */
// path中的查询参数部分转化为query对象 ?a=a&b=b...
function parseQuery(search) {
    const query = {};
    // avoid creating an object with an empty key and empty value
    // because of split('&')
    if (search === '' || search === '?')
        return query;
    const hasLeadingIM = search[0] === '?';
    const searchParams = (hasLeadingIM ? search.slice(1) : search).split('&');
    for (let i = 0; i < searchParams.length; ++i) {
        // pre decode the + into space
        // const PLUS_RE = /\+/g; // %2B 这个字符需要转译
        const searchParam = searchParams[i].replace(PLUS_RE, ' ');
        // allow the = character
        // 键值对分隔
        let eqPos = searchParam.indexOf('=');
        let key = decode(eqPos < 0 ? searchParam : searchParam.slice(0, eqPos));
        let value = eqPos < 0 ? null : decode(searchParam.slice(eqPos + 1));
        // 多个值的场景
        if (key in query) {
            // an extra variable for ts types
            let currentValue = query[key];
            if (!Array.isArray(currentValue)) {
                currentValue = query[key] = [currentValue];
            }
            currentValue.push(value);
        }
        else {
            query[key] = value;
        }
    }
    return query;
}
/**
    * Stringifies a {@link LocationQueryRaw} object. Like `URLSearchParams`, it
    * doesn't prepend a `?`
    *
    * @internal
    *
    * @param query - query object to stringify
    * @returns string version of the query without the leading `?`
    */
// 相反的操作 query对象 转化为查询path部分
// 比较简单 拼接起来就好了
function stringifyQuery(query) {
    let search = '';
    for (let key in query) {
        if (search.length)
            search += '&';
        const value = query[key];
        key = encodeQueryKey(key);
        if (value == null) {
            // only null adds the value
            if (value !== undefined)
                search += key;
            continue;
        }
        // keep null values
        let values = Array.isArray(value)
            ? value.map(v => v && encodeQueryValue(v))
            : [value && encodeQueryValue(value)];
        for (let i = 0; i < values.length; i++) {
            // only append & with i > 0
            search += (i ? '&' : '') + key;
            if (values[i] != null)
                search += ('=' + values[i]);
        }
    }
    return search;
}

主要注意的是 url路径的字符需要转码和编码,具体实现参见源码内实现即可,比较简单

接下来看其他主要代码:

/* createRouter--start */
let routerHistory = options.history;
if (!routerHistory)
    throw new Error('Provide the "history" option when calling "createRouter()":' +
        ' https://next.router.vuejs.org/api/#history.');
/* createRouter--end */

这个history就是我们之前分析得到的history了,包含了封装了类似原生window.history对象的操作方法,以及监听事件等;

/* createRouter--start */
const beforeGuards = useCallbacks();
const beforeResolveGuards = useCallbacks();
const afterGuards = useCallbacks();
/* createRouter--end */

这几个都是回调函数的集合包装对象,看下 useCallbacks 的实现:

/**
    * Create a list of callbacks that can be reset. Used to create before and after navigation guards list
    */
function useCallbacks() {
    let handlers = [];
    function add(handler) {
        handlers.push(handler);
        return () => {
            const i = handlers.indexOf(handler);
            if (i > -1)
                handlers.splice(i, 1);
        };
    }
    function reset() {
        handlers = [];
    }
    return {
        add,
        list: () => handlers,
        reset,
    };
}

典型的闭包使用场景+1

/* createRouter--start */
const currentRoute = vue.shallowRef(START_LOCATION_NORMALIZED);
let pendingLocation = START_LOCATION_NORMALIZED;
/* createRouter--end */

vue.shallowRef是对目标对象做一次浅层的value包装,只有对value全部替换的操作 如 ref.value = newValue 这样的 才会触发trigger,而对value的某个key就不会
看下 START_LOCATION_NORMALIZED:

const START_LOCATION_NORMALIZED = {
    path: '/',
    name: undefined,
    params: {},
    query: {},
    hash: '',
    fullPath: '/',
    matched: [],
    meta: {},
    redirectedFrom: undefined,
};

可以看到 一个初始化location的全部字段信息有哪些, currentRoute 代表着当前匹配的路由location 而 pendingLocation 就是跳转的目标路由location

/* createRouter--start */
// leave the scrollRestoration if no scrollBehavior is provided
if (isBrowser && options.scrollBehavior && 'scrollRestoration' in history) {
    history.scrollRestoration = 'manual';
}
/* createRouter--end */

检查是否用户设置了 需要记录历史滚动信息

/* createRouter--start */
const normalizeParams = applyToParams.bind(null, paramValue => '' + paramValue);
const encodeParams = applyToParams.bind(null, encodeParam);
const decodeParams = applyToParams.bind(null, decode);
/* createRouter--end */

const assign = Object.assign;
function applyToParams(fn, params) {
    const newParams = {};
    for (const key in params) {
        const value = params[key];
        newParams[key] = Array.isArray(value) ? value.map(fn) : fn(value);
    }
    return newParams;
}
let noop = () => { };

可以看到,多个参数的调用形式被改成了一个参数即可:柯里化

接下来看很多个方法的实现,一个个慢慢看,里面很多就是我们平常用到的router实例的方法了:

/* createRouter--start */
function addRoute(parentOrRoute, route) {
    let parent;
    let record;
    // 带有name的场景
    if (isRouteName(parentOrRoute)) {
        parent = matcher.getRecordMatcher(parentOrRoute);
        // 第二个参数为待插入record
        record = route;
    }
    // 否则第一个参数就是待插入的record
    else {
        record = parentOrRoute;
    }
    // 最终调用的是matcher的 addRoute 生成新的path对应的新matcher并插入到matchers数组中
    return matcher.addRoute(record, parent);
}
/* createRouter--end */

// 带有name属性
function isRouteName(name) {
    return typeof name === 'string' || typeof name === 'symbol';
}
/* createRouter--start */
function removeRoute(name) {
    // 这个方法只支持根据name删除
    let recordMatcher = matcher.getRecordMatcher(name);
    if (recordMatcher) {
        matcher.removeRoute(recordMatcher);
    }
    else {
        warn(`Cannot remove non-existent route "${String(name)}"`);
    }
}
/* createRouter--end */
/* createRouter--start */
function getRoutes() {
    // 实例的getRouters其实就是machter的对应内部方法,不过额外处理了 只返回record给用户就好了
    return matcher.getRoutes().map(routeMatcher => routeMatcher.record);
}
/* createRouter--end */
/* createRouter--start */
function hasRoute(name) {
    // 也只支持name查询
    return !!matcher.getRecordMatcher(name);
}
/* createRouter--end */
/* createRouter--start */
// 实例上的resolve方法
function resolve(rawLocation, currentLocation) {
    // const objectLocation = routerLocationAsObject(rawLocation)
    // we create a copy to modify it later
    // 克隆出一个新的当前location对象 便于修改覆盖
    currentLocation = assign({}, currentLocation || currentRoute.value);
    // 直接给的是个path路径的话
    if (typeof rawLocation === 'string') {
        // 解析path得到了一些字段信息 fullPath, path, query, hash,
        let locationNormalized = parseURL(parseQuery$1, rawLocation, currentLocation.path);
        // 调用matcher的resolve方法 返回的是匹配对象包裹体
        // name,path, params,matched,meta
        let matchedRoute = matcher.resolve({ path: locationNormalized.path }, currentLocation);
        // 调用 history的方法得到 不同场景下的href 哈希和history是不同的
        let href = routerHistory.createHref(locationNormalized.fullPath);
        {
            if (href.startsWith('//'))
                warn(`Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`);
            else if (!matchedRoute.matched.length) {
                warn(`No match found for location with path "${rawLocation}"`);
            }
        }
        // locationNormalized is always a new object
        // 返回实例的resolve解析得到的匹配对象包裹体
        return assign(locationNormalized, matchedRoute, {
            params: decodeParams(matchedRoute.params),
            hash: decode(locationNormalized.hash),
            redirectedFrom: undefined,
            href,
        });
    }
    let matcherLocation;
    // path could be relative in object as well
    if ('path' in rawLocation) {
        // 使用场景限制
        if ('params' in rawLocation &&
            !('name' in rawLocation) &&
            Object.keys(rawLocation.params).length) {
            warn(`Path "${rawLocation.path}" was passed with params but they will be ignored. Use a named route alongside params instead.`);
        }
        // 构建location信息结构体
        matcherLocation = assign({}, rawLocation, {
            path: parseURL(parseQuery$1, rawLocation.path, currentLocation.path).path,
        });
    }
    else {
        // pass encoded values to the matcher so it can produce encoded path and fullPath
        matcherLocation = assign({}, rawLocation, {
            params: encodeParams(rawLocation.params),
        });
        // current location params are decoded, we need to encode them in case the
        // matcher merges the params
        currentLocation.params = encodeParams(currentLocation.params);
    }
    // 调用matcher的解析
    let matchedRoute = matcher.resolve(matcherLocation, currentLocation);
    const hash = rawLocation.hash || '';
    if (hash && !hash.startsWith('#')) {
        warn(`A \`hash\` should always start with the character "#". Replace "${hash}" with "#${hash}".`);
    }
    // decoding them) the matcher might have merged current location params so
    // we need to run the decoding again
    // 字符串化
    matchedRoute.params = normalizeParams(decodeParams(matchedRoute.params));
    // 得到完整的path
    const fullPath = stringifyURL(stringifyQuery$1, assign({}, rawLocation, {
        hash: encodeHash(hash),
        path: matchedRoute.path,
    }));
    // 调用history的方法
    let href = routerHistory.createHref(fullPath);
    {
        if (href.startsWith('//')) {
            warn(`Location "${rawLocation}" resolved to "${href}". A resolved location cannot start with multiple slashes.`);
        }
        else if (!matchedRoute.matched.length) {
            warn(`No match found for location with path "${'path' in rawLocation ? rawLocation.path : rawLocation}"`);
        }
    }
    // 匹配对象结构体
    return assign({
        fullPath,
        // keep the hash encoded so fullPath is effectively path + encodedQuery +
        // hash
        hash,
        query: 
        // if the user is using a custom query lib like qs, we might have
        // nested objects, so we keep the query as is, meaning it can contain
        // numbers at `$route.query`, but at the point, the user will have to
        // use their own type anyway.
        // https://github.com/vuejs/vue-router-next/issues/328#issuecomment-649481567
        stringifyQuery$1 === stringifyQuery
            ? normalizeQuery(rawLocation.query)
            : rawLocation.query,
    }, matchedRoute, {
        redirectedFrom: undefined,
        href,
    });
}
/* createRouter--end */
/**
    * Transforms an URI into a normalized history location
    *
    * @param parseQuery
    * @param location - URI to normalize
    * @param currentLocation - current absolute location. Allows resolving relative
    * paths. Must start with `/`. Defaults to `/`
    * @returns a normalized history location
    */
// 解析url 得到相应的4个信息 实现比较简单
function parseURL(parseQuery, location, currentLocation = '/') {
    let path, query = {}, searchString = '', hash = '';
    // Could use URL and URLSearchParams but IE 11 doesn't support it
    const searchPos = location.indexOf('?');
    const hashPos = location.indexOf('#', searchPos > -1 ? searchPos : 0);
    if (searchPos > -1) {
        path = location.slice(0, searchPos);
        searchString = location.slice(searchPos + 1, hashPos > -1 ? hashPos : location.length);
        query = parseQuery(searchString);
    }
    if (hashPos > -1) {
        path = path || location.slice(0, hashPos);
        // keep the # character
        hash = location.slice(hashPos, location.length);
    }
    // no search and no query
    path = resolveRelativePath(path != null ? path : location, currentLocation);
    // empty path means a relative query or hash `?foo=f`, `#thing`
    return {
        fullPath: path + (searchString && '?') + searchString + hash,
        path,
        query,
        hash,
    };
}

可以看到,实例上的resolve方法也是返回一个新的代表着匹配当前待解析path的匹配结果体信息的,内部调用了matcher的resolve方法

/* createRouter--start */
// 路径对象化
function locationAsObject(to) {
    return typeof to === 'string'
        ? parseURL(parseQuery$1, to, currentRoute.value.path)
        : assign({}, to);
}

好了,这部分的分析就到此为止,余下的代码下篇文章再分析。剩下的都是跟切换路由地址有关的了,我们常用的push等实例方法也会揭晓它们的实现。

Vue(v3.0.11)源码简析之渲染<slot>的相关实现

我们平常写组件内的插槽时,如 ... 这样,其实也算一种内置组件,不过它不同于其他一般的内置组件和自定义组件,它有着自己的renderSlot方法来返回vnode给父组件,接下来看看vue中的实现:

let isRenderingCompiledSlot = 0;
const setCompiledSlotRendering = (n) => (isRenderingCompiledSlot += n);
/**
 * Compiler runtime helper for rendering `<slot/>`
 * @private
 */
/**
 * 当我们在组件内部写  
 *  <slot name="default" :prop1="a" :prop2="b">
        default slot text  
    </slot> 
 *  不同于普通的组件或者元素节点,slot组件是通过renderSlot来生成的,调用的时候的参数一一对应如下:
    1. slots 从父组件中解析出来的插槽内容,按照name区分的对象,每个值都是一个箭头函数,返回对应的vnode节点内容
    2. name 则是当前正在解析的子组件的slot的名字
    3. props是我们写到这个子组件的slot上要暴露给父组件插槽模板要使用的变量对象集合
    4. fallback是子组件slot自身的默认插槽 也是一个箭头函数 返回vnode内容
    5. noSlotted 作用暂时不明 todo
 */
function renderSlot(slots, name, props = {}, 
// this is not a user-facing function, so the fallback is always generated by
// the compiler and guaranteed to be a function returning an array
fallback, noSlotted) {
    // 取出slot内容生成函数
    let slot = slots[name];
    // 只接受一个参数 对应文档
    if (slot && slot.length > 1) {
        warn(`SSR-optimized slot function detected in a non-SSR-optimized render ` +
            `function. You need to mark this component with $dynamic-slots in the ` +
            `parent template.`);
        slot = () => [];
    }
    // a compiled slot disables block tracking by default to avoid manual
    // invocation interfering with template-based block tracking, but in
    // `renderSlot` we can be sure that it's template-based so we can force
    // enable it.
    // 渲染函数中renderSlot正在运行的深度+1 因为父组件写的插槽内容也可以包含 使用了组件插槽的情况 那时候会形成这个renderSlot的调用栈
    isRenderingCompiledSlot++;
    // 开启一个新的Block 用于收集动态子节点 关于Block的分析我们后面再仔细分析
    openBlock();
    // 得到vnode内容或者null
    const validSlotContent = slot && ensureValidVNode(slot(props));
    // 最终的结果始终用一个 Fragment片段 Block块 来描述 注意创建Block时候的几个参数:
    /**
     * 1.Fragment类型标识 具体分析见后文 用于辅助渲染vnode片段的一种类型 并不实际渲染 
     * 2. 添加key或者 _name
     * 3. 父组件模板插槽内容或者slot默认内容作为子节点内容
     * 4. patchFlag:父组件写的插槽内容如果生成的顺序不稳定,是动态的,我们则为vnode打上 BAIL 标记,稳定的话就是 STABLE_FRAGMENT;它将会影响在vnode更新对比时候的策略
     * 关于更新对比 我们后面再分析
     */
    const rendered = createBlock(Fragment, { key: props.key || `_${name}` }, validSlotContent || (fallback ? fallback() : []), validSlotContent && slots._ === 1 /* STABLE */
        ? 64 /* STABLE_FRAGMENT */
        : -2 /* BAIL */);
    // 此处作用未知 todo
    if (!noSlotted && rendered.scopeId) {
        rendered.slotScopeIds = [rendered.scopeId + '-s'];
    }
    // 深度-1
    isRenderingCompiledSlot--;
    // 返回vnode内容给调用者
    return rendered;
}
// 确保在父组件写的插槽内容slot包裹函数运行得到的是确实可以用来 有效渲染的vnode
function ensureValidVNode(vnodes) {
    return vnodes.some(child => {
        // 非vnode场景 由于可以在使用组件的时候里面可以写任意内容其实 不过一般场景都会被处理成vnode的情况
        if (!isVNode(child))
            return true;
        // 注释不渲染
        if (child.type === Comment)
            return false;
        // 片段的话 取决于子节点们
        if (child.type === Fragment &&
            !ensureValidVNode(child.children))
            return false;
        return true;
    })
        ? vnodes
        : null;
}

/**
 * mark the current rendering instance for asset resolution (e.g.
 * resolveComponent, resolveDirective) during render
 */
let currentRenderingInstance = null;
let currentScopeId = null;
/**
 * Note: rendering calls maybe nested. The function returns the parent rendering
 * instance if present, which should be restored after the render is done:
 *
 * ```js
 * const prev = setCurrentRenderingInstance(i)
 * // ...render
 * setCurrentRenderingInstance(prev)
 * ```
 */
// render过程存在嵌套场景 会形成实例栈
function setCurrentRenderingInstance(instance) {
    const prev = currentRenderingInstance;
    currentRenderingInstance = instance;
    // 同时带上来自组件配置的 __scopeId
    currentScopeId = (instance && instance.type.__scopeId) || null;
    return prev;
}
/**
 * Set scope id when creating hoisted vnodes.
 * @private compiler helper
 */
// 修改currentScopeId
function pushScopeId(id) {
    currentScopeId = id;
}
/**
 * Technically we no longer need this after 3.0.8 but we need to keep the same
 * API for backwards compat w/ code generated by compilers.
 * @private
 */
function popScopeId() {
    currentScopeId = null;
}
/**
 * Only for backwards compat
 * @private
 */
const withScopeId = (_id) => withCtx;
/**
 * Wrap a slot function to memoize current rendering instance
 * @private compiler helper
 */
// 还记得在父组件页面写插槽的时候 可以使用来自于子组件的变量吗? 因为slot函数的执行时机是在渲染子组件的时候触发的,slot本身就可以获取子组件实例赋予的变量,但是父组件实例必须以某种形式
// 记录下来 这样我们才能正常使用父组件实例上的变量
function withCtx(fn, ctx = currentRenderingInstance) {
    if (!ctx)
        return fn;
    const renderFnWithContext = (...args) => {
        // If a user calls a compiled slot inside a template expression (#1745), it
        // can mess up block tracking, so by default we need to push a null block to
        // avoid that. This isn't necessary if rendering a compiled `<slot>`.
        // 见注释中的issue描述的使用场景 非renderSlot执行下 关闭追踪动态节点功能
        if (!isRenderingCompiledSlot) {
            openBlock(true /* null block that disables tracking */);
        }
        // 利用来自闭包函数的自由变量 ctx 我们确保了在fn执行的是 当前实例变成了之前的父组件实例
        const prevInstance = setCurrentRenderingInstance(ctx);
        // 在当前实例是父组件实例的前提下 执行原始的slot方法 得到正确的vnode内容
        const res = fn(...args);
        // 恢复
        setCurrentRenderingInstance(prevInstance);
        // 恢复
        if (!isRenderingCompiledSlot) {
            closeBlock();
        }
        return res;
    };
    // mark this as a compiled slot function.
    // this is used in vnode.ts -> normalizeChildren() to set the slot
    // rendering flag.
    // 代表已经被编译过了 作用域已绑定
    renderFnWithContext._c = true;
    return renderFnWithContext;
}

简单聊一聊generator和async函数的模拟实现吧(2)

// 这次我们在上文的基础上加上 return 接口的实现

// 先看一个简单的例子:

// function* numbers() {
//     yield 1;
//     yield 2;
//     yield 3;
//     yield 4;
//     yield 5;
//     yield 6;
// }
// var g = numbers();
// console.log(g.next()) // { value: 1, done: false }
// console.log(g.next()) // { value: 2, done: false }
// console.log(g.return(7)) // { value: 7, done: true }
// console.log(g.next()) // { value: undefined, done: true }
// console.log(g.next()) // { value: undefined, done: undefined }

// 然后看上面的代码被 regenerator 变成啥了:

// function numbers() {
//     return regeneratorRuntime.wrap(function numbers$(_context) {
//       while (1) {
//         switch (_context.prev = _context.next) {
//           case 0:
//             _context.next = 2;
//             return 1;

//           case 2:
//             _context.next = 4;
//             return 2;

//           case 4:
//             _context.next = 6;
//             return 3;

//           case 6:
//             _context.next = 8;
//             return 4;

//           case 8:
//             _context.next = 10;
//             return 5;

//           case 10:
//             _context.next = 12;
//             return 6;

//           case 12:
//           case "end":
//             return _context.stop();
//         }
//       }
//     }, _marked);
//   }

//   var g = numbers();
//   console.log(g.next()); // { value: 1, done: false }

//   console.log(g.next()); // { value: 2, done: false }

//   console.log(g.return(7)); // { value: 7, done: true }

//   console.log(g.next()); // { value: undefined, done: true }

//   console.log(g.next()); // { value: undefined, done: undefined }

// emmm...真实一段平平无奇的代码呀 主要是g.return(7) 终结了迭代器的状态,其实 return操作也是调用的invoke来实现的,上一个then实现中已经支持了return操作了
// 所以就不需要再额外实现什么代码了,复用then版本的实现就可以了大家可以自行测试

// 我们再来看一段不那么 平平无奇(古天乐) 的代码:

/*
    function* numbers() {
        yield 1;
        try {
            yield 2;
            yield 3;
        } finally {
            yield 4;
            yield 5;
        }
        yield 6;
    }
    var g = numbers();
    console.log(g.next()) // { value: 1, done: false }
    console.log(g.next()) // { value: 2, done: false }
    console.log(g.return(7)) // { value: 4, done: false }
    console.log(g.next()) // { value: 5, done: false }
    console.log(g.next()) // { value: 7, done: true }
    console.log(g.next()) // { value: undefined, done: true }
*/

// 先看文章最后的转码实现:

var regeneratorRuntime = (function (exports) {
    // 一些辅助函数
    var Op = Object.prototype;
    var hasOwn = Op.hasOwnProperty;
    var undefined; // More compressible than void 0.
    var $Symbol = typeof Symbol === "function" ? Symbol : {};
    var iteratorSymbol = $Symbol.iterator || "@@iterator";
    var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator";
    var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag";

    function define(obj, key, value) {
        Object.defineProperty(obj, key, {
            value: value,
            enumerable: true,
            configurable: true,
            writable: true
        });
        return obj[key];
    }

    // 生成器 当做函数看待 只从它身上取 prototype
    function Generator() { }
    // 生成器函数 纯函数看待
    function GeneratorFunction() { }
    // 生成器函数原型对象 我们对它同时看做 函数 和 对象 并赋予各自的属性
    function GeneratorFunctionPrototype() { }

    var IteratorPrototype = {};
    IteratorPrototype[iteratorSymbol] = function () {
        return this;
    };

    var getProto = Object.getPrototypeOf;
    // 取出数组上存在的原生迭代器对象实例 就是下面的Array Iterator {}  然后取出它继承的原型对象的原型对象 也就是 仅仅次于Object用于继承的原型对象那个拥有 Symbol(Symbol.iterator): ƒ [Symbol.iterator]() 属性的原型对象
    /*
        Array Iterator {}
        __proto__: Array Iterator
            next: ƒ next()
            Symbol(Symbol.toStringTag): "Array Iterator"
            __proto__:
                Symbol(Symbol.iterator): ƒ [Symbol.iterator]()
                __proto__: Object
    */
    // 原生迭代器原型对象
    var NativeIteratorPrototype = getProto && getProto(getProto(values([])));
    if (NativeIteratorPrototype &&
        NativeIteratorPrototype !== Op &&
        hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {
        // This environment has a native %IteratorPrototype%; use it instead
        // of the polyfill.
        IteratorPrototype = NativeIteratorPrototype;
    }

    // GeneratorFunctionPrototype 当做函数看待
    // Gp: 生成器函数原型对象 Object.create(IteratorPrototype) 以原生迭代器原型继承构造一个新对象
    var Gp = GeneratorFunctionPrototype.prototype = Object.create(IteratorPrototype);
    // 补充原型对象的构造函数属性
    Gp.constructor = GeneratorFunctionPrototype;

    // GeneratorFunctionPrototype 当做对象再看待
    GeneratorFunction.prototype = GeneratorFunctionPrototype;
    GeneratorFunctionPrototype.constructor = GeneratorFunction;

    // 可能大家不理解上面这几行代码设置原型有啥用 我们写完 mark 和 wrap函数之后再回来分析这几行代码 的目标效果

    Generator.prototype = GeneratorFunctionPrototype.prototype;

    // 再给它加个名字吧
    GeneratorFunction.displayName = define(
        GeneratorFunctionPrototype,
        toStringTagSymbol,
        "GeneratorFunction"
    );

    // 先来实现mark 看它标记了啥:
    exports.mark = function (genFun) {
        if (Object.setPrototypeOf) {
            // genFun.__proto__ = GeneratorFunctionPrototype;
            Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);
        } else {
            genFun.__proto__ = GeneratorFunctionPrototype;
            define(genFun, toStringTagSymbol, "GeneratorFunction");
        }
        // 设置 genFun.prototype = 一个新对象 (new obj, obj.__proto__ = Gp)
        genFun.prototype = Object.create(Gp);


        return genFun;
    };

    // 再看 wrap 包装了啥:
    // innerFn 执行业务代码 outerFn 就是被mark过的函数
    function wrap(innerFn, outerFn, self, tryLocsList) {
        // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.
        // outerFn.prototype 也就是 genFun.prototype 是 Object.create(Gp)得到 也就是 Gp的实例 而 Generator.prototype 就是 Gp 所以满足条件
        var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator;
        // 好了 hw 就是来自于 Object.create(protoGenerator.prototype) 构造的实例 hw.__proto__ === protoGenerator.prototype; hw.__proto__.__proto__ === Gp
        var generator = Object.create(protoGenerator.prototype);
        // 原来 上下文 对象来自于这里构造的实例
        var context = new Context(tryLocsList || []);

        // The ._invoke method unifies the implementations of the .next,
        // .throw, and .return methods.
        // 实例上还有一个方法 
        generator._invoke = makeInvokeMethod(innerFn, self, context);

        // 这就是 hw 迭代器实例对象
        return generator;
    }
    exports.wrap = wrap;

    /*
        现在我们回头看下那几行原型设置的效果:
        规范约定如下: hw.__proto__ === helloWorldGenerator.prototype; hw.__proto__.__proto__ === helloWorldGenerator.__proto__.prototype
        这意味着 我们通过 hw = genFun() 得到的迭代器对象 需要 满足类似 new genFun() 方式得到的原型继承关系,我们来看下hw是什么:
        hw = Object.create(helloWorldGenerator.prototype);
        所以 hw.__proto__ === helloWorldGenerator.prototype 第一个条件成立;
        而 hw.__proto__.__proto__ 在上面的 wrap 分析了 其实 就是 Gp对象,再看 helloWorldGenerator.__proto__ 其实是 GeneratorFunctionPrototype 而 
        GeneratorFunctionPrototype.prototype 就是 Gp,好吧 第二个条件成立 所以满足规范要求了

        facebook人写的代码真是 666...
    */

    // return版本新增:
    function pushTryEntry(locs) {
        // 依次保存4个关键的位置信息 [try, catch, finally, finally after]
        var entry = { tryLoc: locs[0] };

        if (1 in locs) {
            entry.catchLoc = locs[1];
        }

        if (2 in locs) {
            entry.finallyLoc = locs[2];
            entry.afterLoc = locs[3];
        }

        this.tryEntries.push(entry);
    }

    // return版本新增:
    function resetTryEntry(entry) {
        var record = entry.completion || {};
        record.type = "normal";
        delete record.arg;
        entry.completion = record;
    }

    // 好了 我们先构建一个简单的 Context:

    function Context(tryLocsList) {
        // return版本新增: 补充对 tryLocsList 的处理 这个变量的名字透露出它的功能了。。try代码块的位置列表。。。
        this.tryEntries = [{ tryLoc: "root" }];
        tryLocsList.forEach(pushTryEntry, this);

        this.reset(true);
    }

    // 简单的放一些方法到原型对象上去
    // 从转码后的业务代码中可以看到 我们需要的实现有下面几个方法:
    Context.prototype = {
        constructor: Context,

        reset: function (skipTempReset) {
            this.prev = 0;
            this.next = 0;

            this.sent = this._sent = undefined;
            this.done = false;

            this.method = "next";
            this.arg = undefined;

            // return版本新增:
            this.tryEntries.forEach(resetTryEntry);
        },

        // 这个最后执行 _context.stop();
        stop: function () {
            this.done = true;

            // return版本新增:
            var rootEntry = this.tryEntries[0];
            var rootRecord = rootEntry.completion;
            if (rootRecord.type === "throw") {
                throw rootRecord.arg;
            }

            return this.rval;
        },

        dispatchException: function (exception) {

        },

        // 这个 在最后一个yield语句之后执行 _context.abrupt("return", 'ending');
        // 任意 .return() 语句也是走的下面这个方法
        abrupt: function (type, arg) {
            // return版本重写:
            // 我们看待 return(7) 导致发生了什么: 它调用了 abrupt('return ', 7);
            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
                var entry = this.tryEntries[i];
                // 仔细看下面几个判断 分别确定了这个return语句触发的距离它最近的那个try语句块 且 保证了这个try语句块执行了 且 拥有finally块 这时候我们就需要修改一下原本的执行顺序了
                if (entry.tryLoc <= this.prev &&
                    hasOwn.call(entry, "finallyLoc") &&
                    this.prev < entry.finallyLoc) {
                    var finallyEntry = entry;
                    break;
                }
            }

            // break和continue的场景 现在还不涉及 先忽略吧
            if (finallyEntry &&
                (type === "break" ||
                    type === "continue") &&
                finallyEntry.tryLoc <= arg &&
                arg <= finallyEntry.finallyLoc) {
                // Ignore the finally entry if control is not jumping to a
                // location outside the try/catch block.
                finallyEntry = null;
            }

            var record = finallyEntry ? finallyEntry.completion : {};
            record.type = type;
            record.arg = arg;

            // 注意这个哈 再次自动调用了next...且设置了下一步执行哪一句代码 即我们看到的:先执行finally语句中的内容
            if (finallyEntry) {
                this.method = "next";
                this.next = finallyEntry.finallyLoc;
                // 迭代器再迭代一次
                return ContinueSentinel;
            }

            return this.complete(record);
        },

        // 好吧 这个也要实现。。。
        complete: function (record, afterLoc) {
            if (record.type === "throw") {
                throw record.arg;
            }

            if (record.type === "break" ||
                record.type === "continue") {
                this.next = record.arg;
                // 现在我们只关注 return 和 normal 
            } else if (record.type === "return") {
                this.rval = this.arg = record.arg;
                this.method = "return";
                // 业务代码执行最后的stop操作
                this.next = "end";
            } else if (record.type === "normal" && afterLoc) {
                this.next = afterLoc;
            }

            // 完成态自动切换到结束态 需要返回一个特殊的continue类型的返回值
            return ContinueSentinel;
        },
        // return版本新增:
        finish: function (finallyLoc) {
            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
                // 依旧同上 找到最近的一个try语句块
                var entry = this.tryEntries[i];
                // 2者匹配 意味着 finally中的语句执行完 可以执行return原本的功能了 即 终止迭代器 返回 return的值
                if (entry.finallyLoc === finallyLoc) {
                    // 终止
                    this.complete(entry.completion, entry.afterLoc);
                    // 清除这条entry的状态 作用已完成
                    resetTryEntry(entry);
                    // 继续迭代 朝着stop状态出发
                    return ContinueSentinel;
                }
            }
        },

        "catch": function (tryLoc) {

        },

        delegateYield: function (iterable, resultName, nextLoc) {

        }
    };

    // 继续实现 makeInvokeMethod:

    // 迭代器 状态机 的几种状态
    var GenStateSuspendedStart = "suspendedStart";
    var GenStateSuspendedYield = "suspendedYield";
    var GenStateExecuting = "executing";
    var GenStateCompleted = "completed";

    // 状态机继续迭代的特殊返回值
    var ContinueSentinel = {};

    // 返回一个 invoke 内部闭包函数 持有 innerFn context 等自由变量
    function makeInvokeMethod(innerFn, self, context) {
        // 状态机初始化起始状态
        var state = GenStateSuspendedStart;

        return function invoke(method, arg) {
            // todo
        };
    }

    // 然后我们调用的时候从 hw上调用next(),我们在直接原型上加:

    function defineIteratorMethods(prototype) {
        ["next", "throw", "return"].forEach(function (method) {
            define(prototype, method, function (arg) {
                // 其实都是执行实例的 _invoke y也就是上面的 内部函数invoke
                return this._invoke(method, arg);
            });
        });
    }

    defineIteratorMethods(Gp);

    // 所以重点就是 invoke 的实现:

    function makeInvokeMethod(innerFn, self, context) {
        // 状态机初始化起始状态
        var state = GenStateSuspendedStart;

        return function invoke(method, arg) {

            // 初次调用处于 suspendedStart 执行到某个yield语句暂停 处于 suspendedYield 结束后处于 completed
            // 而 executing 指的是 业务代码执行的过程中处于的状态 之后便会过度到 suspendedYield || completed 
            // 不应该在再次invoke的时候遇到的
            if (state === GenStateExecuting) {
                throw new Error("Generator is already running");
            }

            // 结束态后 再次调用迭代器
            if (state === GenStateCompleted) {
                if (method === "throw") {
                    throw arg;
                }

                // Be forgiving, per 25.3.3.3.3 of the spec:
                // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume
                return doneResult();
            }

            // 设置上下文的数据 用户调用的方法 以及 参数
            context.method = method;
            context.arg = arg;

            // 状态机迭代开始啦
            while (true) {

                // 先只实现next吧
                if (context.method === "next") {
                    // Setting context._sent for legacy support of Babel's
                    // function.sent implementation.
                    // next方法是可以接受用户传入的参数噢
                    context.sent = context._sent = context.arg;

                } else if (context.method === "throw") {
                    // ...

                } else if (context.method === "return") {
                    // 存在try语句块的情况下 调用return语句会导致代码块的执行顺序发生改变 具体变化分析见 abrupt 代码的变化
                    context.abrupt("return", context.arg);
                }

                // 进入业务代码执行的状态啦
                state = GenStateExecuting;

                // 一次执行结果的记录 context 就是最开始我们看到的上下文了 就是在这里调用我们的业务代码函数了
                // 在完成态自动过滤到结束态的时候会自动化迭代2次 注意最后次的业务代码中调用了 _context.stop();
                var record = tryCatch(innerFn, self, context);
                // 正常返回值
                if (record.type === "normal") {
                    // If an exception is thrown from innerFn, we leave state ===
                    // GenStateExecuting and loop back for another invocation.
                    // 更新状态 完成态 || 暂停态
                    state = context.done
                        ? GenStateCompleted
                        : GenStateSuspendedYield;

                    // 完成态切换到结束态 需要继续迭代状态机
                    if (record.arg === ContinueSentinel) {
                        continue;
                    }

                    // 这就是我们预期的返回值拉
                    return {
                        value: record.arg,
                        done: context.done
                    };

                    // 错误返回值 状态机继续执行 通过上面的 throw分支代码来实现
                } else if (record.type === "throw") {
                    state = GenStateCompleted;
                    // Dispatch the exception by looping back around to the
                    // context.dispatchException(context.arg) call above.
                    context.method = "throw";
                    context.arg = record.arg;
                }
            }
        };
    }

    // 执行业务代码 对返回结果做了包装处理
    function tryCatch(fn, obj, arg) {
        try {
            return { type: "normal", arg: fn.call(obj, arg) };
        } catch (err) {
            return { type: "throw", arg: err };
        }
    }

    // 返回一个迭代器对象
    function values(iterable) {
        if (iterable) {
            var iteratorMethod = iterable[iteratorSymbol];
            // 原生部署了迭代器生成函数 调用得到一个迭代器即可
            if (iteratorMethod) {
                return iteratorMethod.call(iterable);
            }

            // next是个函数的对象 也算吧
            if (typeof iterable.next === "function") {
                return iterable;
            }

            // 具有长度属性的类数组对象 也算吧
            if (!isNaN(iterable.length)) {
                var i = -1, next = function next() {
                    while (++i < iterable.length) {
                        if (hasOwn.call(iterable, i)) {
                            next.value = iterable[i];
                            next.done = false;
                            return next;
                        }
                    }

                    next.value = undefined;
                    next.done = true;

                    return next;
                };

                // 注意返回的这个next 函数 next.next 才是执行的函数 且持有自由变量 i 用来迭代状态
                return next.next = next;
            }
        }

        // Return an iterator with no values.
        return { next: doneResult };
    }

    exports.values = values;

    function doneResult() {
        return { value: undefined, done: true };
    }

    // 返回一个对象
    return exports;
})({});

var _marked = /*#__PURE__*/regeneratorRuntime.mark(numbers);

function numbers() {
    return regeneratorRuntime.wrap(function numbers$(_context) {
        while (1) {
            switch (_context.prev = _context.next) {
                case 0:
                    _context.next = 2;
                    return 1;

                case 2:
                    _context.prev = 2;
                    _context.next = 5;
                    return 2;

                case 5:
                    _context.next = 7;
                    return 3;

                case 7:
                    _context.prev = 7;
                    _context.next = 10;
                    return 4;

                case 10:
                    _context.next = 12;
                    return 5;

                case 12:
                    return _context.finish(7);

                case 13:
                    _context.next = 15;
                    return 6;

                case 15:
                case "end":
                    return _context.stop();
            }
        }
    }, _marked, null, [[2, , 7, 13]]);
}

var g = numbers();
console.log(g.next()); // { value: 1, done: false }

console.log(g.next()); // { value: 2, done: false }

console.log(g.return(7)); // { value: 4, done: false }

console.log(g.next()); // { value: 5, done: false }

console.log(g.next()); // { value: 7, done: true }

console.log(g.next()); // { value: undefined, done: true }

// 可以看到,带有try finally 的语句执行顺序很有意思,为了能够实现上面的输出顺序 我们需要多实现一个 _context.finish(7),同时 wrap 参数第三个参数终于有值了
// 我们观察下这第三个参数,首先它是一个二维数组,每个项都有4个值 分别是: try代码的起始行数, catch 代码块的起始行数,这里为空, finally代码的起始行数,以及finally结束之后的下一个执行语句的起始行数
// 然后我们还在 2 和 7 这里新增了 保存prev的操作 ,同时新增了一个finally之后的状态来执行 _context.finish(7);
// 以上就是新增的部分代码体现了,我们现在去重新补充之前版本的代码 :)

// 至此 我们基本实现了迭代器的return接口,真是 一段很有意思的实现呢...

Vue(v3.0.11)源码简析之this.$emit的相关实现

组件实例的proxy,也就是我们平常在组件钩子中用到的this,调用this.$emit触发的方法实现如下:

// 我们平常在子组件内部调用 this.$emit('event-name', ...args) ,实际上执行到就是下面的函数 第一个参数是vue固定绑定的this(proxy)所处的组件实例(绑定时机详见后文的分析),后面的参数才是我们用户传入的
function emit(instance, event, ...rawArgs) {
    // 取出组件的props对象结构体 我们在使用组件的时候传入的prop参数以及存在默认值的情况的prop都存放在这个对象里面
    const props = instance.vnode.props || EMPTY_OBJ;
    {
        // 这2个属性对应:我们实现组件的时候传入的配置项 emits和props,此时它们对应的是可以接受的prop和emits的key 名
        const { emitsOptions, propsOptions: [propsOptions] } = instance;
        if (emitsOptions) {
            // 检测是否处于用户配置的可以接受的keys内
            // someEvent 或者 onSomeEvent
            if (!(event in emitsOptions)) {
                if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {
                    warn(`Component emitted event "${event}" but it is neither declared in ` +
                        `the emits option nor as an "${toHandlerKey(event)}" prop.`);
                }
            }
            else {
                // 存在的场景 对应文档中验证函数 用户自定义校验方法
                const validator = emitsOptions[event];
                if (isFunction(validator)) {
                    // 注意返回值要为true
                    const isValid = validator(...rawArgs);
                    if (!isValid) {
                        warn(`Invalid event arguments: event validation failed for event "${event}".`);
                    }
                }
            }
        }
    }
    let args = rawArgs;
    // 普通自定义事件的情况比较简单 需要特别处理的是v-model引入的这类事件: 如下使用 v-model.trim="xxx" 而引入的 update:modelValue 或者我们自定义的 v-model:title.trim="xxx" 而引入的 update:title
    // 这样的事件名形式
    const isModelListener = event.startsWith('update:');
    // for v-model update:xxx events, apply modifiers on args
    // 取出modelValue或者title字段
    const modelArg = isModelListener && event.slice(7);
    if (modelArg && modelArg in props) {
        // 我们使用v-model的时候可以加自定义或者默认的修饰符 而他们都会被解析到props中去 存储在特定 modifiersKey 所对应的对象里面(详情见后文)
        const modifiersKey = `${modelArg === 'modelValue' ? 'model' : modelArg}Modifiers`;
        // 对应文档中的 modifiersKey: {trim: true,...} 这样的形式
        const { number, trim } = props[modifiersKey] || EMPTY_OBJ;
        // 修改参数
        if (trim) {
            args = rawArgs.map(a => a.trim());
        }
        else if (number) {
            args = rawArgs.map(toNumber);
        }
    }
    {
        // devtools先不看
        devtoolsComponentEmit(instance, event, args);
    }
    {
        // 请使用 some-event 这样的方式触发 而不要使用驼峰噢
        const lowerCaseEvent = event.toLowerCase();
        if (lowerCaseEvent !== event && props[toHandlerKey(lowerCaseEvent)]) {
            warn(`Event "${lowerCaseEvent}" is emitted in component ` +
                `${formatComponentName(instance, instance.type)} but the handler is registered for "${event}". ` +
                `Note that HTML attributes are case-insensitive and you cannot use ` +
                `v-on to listen to camelCase events when using in-DOM templates. ` +
                `You should probably use "${hyphenate(event)}" instead of "${event}".`);
        }
    }
    let handlerName;
    // 取得props对应事件key的值 就是一个 箭头函数 而这个箭头函数是父组件render的时候生成的 这个箭头函数的this是指向父组件实例的this(proxy)的 所以执行这个箭头函数的时候可以保证 () => { 父组件.属性 = 子组件的传入的参数值 } 方法正确执行
    let handler = props[(handlerName = toHandlerKey(event))] ||
        // also try camelCase event handler (#2249)
        props[(handlerName = toHandlerKey(camelize(event)))];
    // for v-model update:xxx events, also trigger kebab-case equivalent
    // for props passed via kebab-case
    if (!handler && isModelListener) {
        handler = props[(handlerName = toHandlerKey(hyphenate(event)))];
    }
    if (handler) {
        // 执行方法 第2个和第四个参数是实际的参数
        callWithAsyncErrorHandling(handler, instance, 6 /* COMPONENT_EVENT_HANDLER */, args);
    }
    // 对应这种场景 我们在组件上绑定 @click.once="handler" 那么实际得到的 prop中的对应的key 是 onClickOnce:hanlder 这个once的修饰符处理方案和其他普通的并不一样
    // 其他的作为修饰符对象单独存储在props中 这个once很秀啊
    // 对于加在 v-model 上的once算自定义修饰符 需要用户自己添加处理逻辑去识别
    const onceHandler = props[handlerName + `Once`];
    if (onceHandler) {
        if (!instance.emitted) {
            // 老规矩 缓存记忆一次
            (instance.emitted = {})[handlerName] = true;
        }
        else if (instance.emitted[handlerName]) {
            return;
        }
        // 执行它
        callWithAsyncErrorHandling(onceHandler, instance, 6 /* COMPONENT_EVENT_HANDLER */, args);
    }
}
// 格式化解析 emits 配置项的信息 参数 1.我们写的原始组件配置对象 2. 所有组件共享的app实例对象 3. 这个原始组件是否是作为一个mixin
function normalizeEmitsOptions(comp, appContext, asMixin = false) {
    // app上下文中的 deopt 为true 代表着 用户调用app.mixin方法添加了mixin 且其中存在 prop或者emit选项 告诉我们需要合并全局的emit选项
    // 没有全局的 又解析过了 那就直接返回咯
    if (!appContext.deopt && comp.__emits !== undefined) {
        return comp.__emits;
    }
    // 原始信息
    const raw = comp.emits;
    // 存放最终数据的对象
    let normalized = {};
    // apply mixin/extends props
    let hasExtends = false;
    if (!isFunction(comp)) {
        // 深度遍历 先合并可能存在的子mixin之类的 emits
        const extendEmits = (raw) => {
            // 注意这个递归调用的 第三个参数 说明了自己是作为mixin被合并的 从而约束了 下面的第一个if不会重复触发了
            const normalizedFromExtend = normalizeEmitsOptions(raw, appContext, true);
            if (normalizedFromExtend) {
                hasExtends = true;
                // 合并对象
                extend(normalized, normalizedFromExtend);
            }
        };
        // 首次才需要从全局配置中合并
        if (!asMixin && appContext.mixins.length) {
            appContext.mixins.forEach(extendEmits);
        }
        // 合并extends
        if (comp.extends) {
            extendEmits(comp.extends);
        }
        // 合并子组件的mixin
        if (comp.mixins) {
            comp.mixins.forEach(extendEmits);
        }
    }
    // 没有配置的话
    if (!raw && !hasExtends) {
        return (comp.__emits = null);
    }
    // ['event1', 'event2'] 这样的写法
    if (isArray(raw)) {
        raw.forEach(key => (normalized[key] = null));
    }
    // 写成对象也是可以的 直接合并上去 保留原始信息即可
    else {
        extend(normalized, raw);
    }
    // 最终合并的结果信息都在 __emits 中了
    return (comp.__emits = normalized);
}
// Check if an incoming prop key is a declared emit event listener.
// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are
// both considered matched listeners.
// 检测key是否属于我们在组件配置项中emits包含范围内
// 这个方法主要用于区分prop的分类 是放到常规prop还是emits还是除2者之外的attrs中去
function isEmitListener(options, key) {
    // 简单的排除一下
    if (!options || !isOn(key)) {
        return false;
    }
    // 取出事件 on之后的 Once这个之前分析过 移除它现在
    key = key.slice(2).replace(/Once$/, '');
    // 驼峰 连字符 首字符大写格式 都符合要求
    return (hasOwn(options, key[0].toLowerCase() + key.slice(1)) ||
        hasOwn(options, hyphenate(key)) ||
        hasOwn(options, key));
}

简单聊一聊JS中的 call apply 和 bind 的实现



// call的模拟实现

Function.prototype.call2 = function (context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}

// apply的模拟实现

Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;

    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }

    delete context.fn
    return result;
}

// bind 的模拟实现

Function.prototype.bind2 = function (context) {

    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;
    var args = Array.prototype.slice.call(arguments, 1);

    var fNOP = function () {};

    var fBound = function () {
        var bindArgs = Array.prototype.slice.call(arguments);
        return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));
    }

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

// 文章参考 https://github.com/mqyqingfeng/Blog/issues/12
// 文章参考 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

vue-router(v4.0.6)之 createWebHashHistory, createWebHistory和 createMemoryHistory 实现简析

我们先看下大概的实现 createWebHashHistory:

/**
    * Creates a hash history. Useful for web applications with no host (e.g.
    * `file://`) or when configuring a server to handle any URL is not possible.
    *
    * @param base - optional base to provide. Defaults to `location.pathname +
    * location.search` If there is a `<base>` tag in the `head`, its value will be
    * ignored in favor of this parameter **but note it affects all the
    * history.pushState() calls**, meaning that if you use a `<base>` tag, it's
    * `href` value **has to match this parameter** (ignoring anything after the
    * `#`).
    *
    * @example
    * ```js
    * // at https://example.com/folder
    * createWebHashHistory() // gives a url of `https://example.com/folder#`
    * createWebHashHistory('/folder/') // gives a url of `https://example.com/folder/#`
    * // if the `#` is provided in the base, it won't be added by `createWebHashHistory`
    * createWebHashHistory('/folder/#/app/') // gives a url of `https://example.com/folder/#/app/`
    * // you should avoid doing this because it changes the original url and breaks copying urls
    * createWebHashHistory('/other-folder/') // gives a url of `https://example.com/other-folder/#`
    *
    * // at file:///usr/etc/folder/index.html
    * // for locations with no `host`, the base is ignored
    * createWebHashHistory('/iAmIgnored') // gives a url of `file:///usr/etc/folder/index.html#`
    * ```
    */
function createWebHashHistory(base) {
    // Make sure this implementation is fine in terms of encoding, specially for IE11
    // for `file://`, directly use the pathname and ignore the base
    // location.pathname contains an initial `/` even at the root: `https://example.com`
    base = location.host ? base || location.pathname + location.search : '';
    // allow the user to provide a `#` in the middle: `/base/#/app`
    if (base.indexOf('#') < 0)
        base += '#';
    if (!base.endsWith('#/') && !base.endsWith('#')) {
        warn(`A hash base must end with a "#":\n"${base}" should be "${base.replace(/#.*$/, '#')}".`);
    }
    return createWebHistory(base);
}

作者给出的注释里面描述了几种例子情况,很清晰的描述了对应情况产生的url,hash history模式下,正常情况尾部会追加一个 # 符号,得到格式化的 base 路径;
而这个经过hash化的 base 会传递给 createWebHistory ,也就是普通的 history 模式,2种模式归一成一种实现,只是参数 base 不同而已;而浏览器url中带有#符号也就是带有
hash路径的时候,我们改变hash url部分,不会引起浏览器去尝试加载对应的实际资源,但是我们可以通过添加事件来捕获这个变化,从而引发router-view组件的重新渲染,
从而正确更新dom视图;而普通history的url是一个不带hash的url,我们手动改变url后,浏览器会尝试去加载这个url对应的资源,这时候需要我们对服务器做一定配置,让服务器对指定
范围的url都返回单页应用的首页html,然后我们再添加捕获history的state改变事件,从而更新router-view的视图,效果和hash模式一样。
接下来看 createWebHistory 的实现:

/**
    * Creates an HTML5 history. Most common history for single page applications.
    *
    * @param base -
    */
function createWebHistory(base) {
    // 格式化base路径
    base = normalizeBase(base);
    // 创建router使用的包含 修改原生window的history属性的方法及属性集合 对象
    const historyNavigation = useHistoryStateNavigation(base);
    // 创建包含监听事件等属性方法的 historyListeners 对象
    const historyListeners = useHistoryListeners(base, historyNavigation.state, historyNavigation.location, historyNavigation.replace);
    // 模拟的go方法
    function go(delta, triggerListeners = true) {
        // 暂停触发state改变的监听者们
        if (!triggerListeners)
            historyListeners.pauseListeners();
        // 调用有原生history的方法
        history.go(delta);
    }
    // 创建history对象
    const routerHistory = assign({
        // it's overridden right after
        // 当前url 浏览器中我们看到的是哪个url,除去下面的base部分后面的路径,剩下的部分就等于这个值 2者保持同步
        // 初始化的时候这个会被 historyNavigation 的location覆盖
        location: '',
        // pathname + [search] + [#]
        base,
        go,
        // 根据base和参数得到完整的href
        createHref: createHref.bind(null, base),
    }, historyNavigation, historyListeners);

    // historyNavigation.location和state都是 {value: xxx} 类型的包裹体
    Object.defineProperty(routerHistory, 'location', {
        get: () => historyNavigation.location.value,
    });
    Object.defineProperty(routerHistory, 'state', {
        get: () => historyNavigation.state.value,
    });

    return routerHistory;
}

主体逻辑主要是合并几个对象成一个,先分别看几个组成部分:

1. normalizeBase
/**
    * Normalizes a base by removing any trailing slash and reading the base tag if
    * present.
    *
    * @param base - base to normalize
    */
function normalizeBase(base) {
    if (!base) {
        if (isBrowser) {
            // respect <base> tag
            // 从html标签中取base值
            const baseEl = document.querySelector('base');
            base = (baseEl && baseEl.getAttribute('href')) || '/';
            // strip full URL origin
            // 去除 协议 和 host 部分
            base = base.replace(/^\w+:\/\/[^\/]+/, '');
        }
        else {
            base = '/';
        }
    }
    // ensure leading slash when it was removed by the regex above avoid leading
    // slash with hash because the file could be read from the disk like file://
    // and the leading slash would cause problems
    // 不是 #/path/a 的情况下 path/a -> /path/a
    if (base[0] !== '/' && base[0] !== '#')
        base = '/' + base;
    // remove the trailing slash so all other method can just do `base + fullPath`
    // to build an href
    return removeTrailingSlash(base);
}

// 移除尾部的 /path/a/ -> /path/a 
const TRAILING_SLASH_RE = /\/$/;
const removeTrailingSlash = (path) => path.replace(TRAILING_SLASH_RE, '');
  1. useHistoryStateNavigation
function useHistoryStateNavigation(base) {
    const { history, location } = window;
    // private variables

    // 当前location的 url 我们通过router实例的push等操作修改url后 对应这个的value url 也会被修改 2者保持同步
    let currentLocation = {
        value: createCurrentLocation(base, location),
    };

    // state对象包裹体
    let historyState = { value: history.state };
    // build current history entry as this is a fresh navigation
    // 新开始一个页面的时候 state初始为null
    if (!historyState.value) {
        // 设置 currentLocation 的state 初始值
        changeLocation(currentLocation.value, {
            // location栈中上一个location url
            back: null,
            // location栈中当前location url
            current: currentLocation.value,
            // state栈中下一个location url
            forward: null,
            // the length is off by one, we need to decrease it
            // 当前location在栈中所处位置
            position: history.length - 1,
            // 是否是重定向
            replaced: true,
            // don't add a scroll as the user may have an anchor and we want
            // scrollBehavior to be triggered without a saved position
            // 滚动位置信息
            scroll: null,
        }, true);
    }
    function changeLocation(to, state, replace) {
        ...
    }
    function replace(to, data) {
        ...
    }
    function push(to, data) {
        ...
    }
    return {
        location: currentLocation,
        state: historyState,
        push,
        replace,
    };
}

可以看到 useHistoryStateNavigation 主要得到了 location对象并完成初始化,state对象并完成初始化, 声明了2个外部方法。返回体包含4个属性。
逻辑比较简单,再看它内部的方法:

createCurrentLocation:

/**
    * Creates a normalized history location from a window.location object
    * @param location -
    */
function createCurrentLocation(base, location) {
    const { pathname, search, hash } = location;
    // allows hash based url
    const hashPos = base.indexOf('#');
    if (hashPos > -1) {
        // prepend the starting slash to hash so the url starts with /#
        // 取出哈希路径 这个是哈希模式下需要的url
        let pathFromHash = hash.slice(1);
        if (pathFromHash[0] !== '/')
            pathFromHash = '/' + pathFromHash;
        return stripBase(pathFromHash, '');
    }
    // history模式下需要的url 不过需要去除 base部分 因为base是公共部分 我们只关注 base之后的路径部分
    const path = stripBase(pathname, base);
    return path + search + hash;
}

/**
    * Strips off the base from the beginning of a location.pathname in a non
    * case-sensitive way.
    *
    * @param pathname - location.pathname
    * @param base - base to strip off
    */
function stripBase(pathname, base) {
    // no base or base is not found at the beginning
    // base存在的话 必须要 目标pathname的0位置开始 也就是 startswith -1 或者 大于0 都无效
    if (!base || pathname.toLowerCase().indexOf(base.toLowerCase()))
        return pathname;
    return pathname.slice(base.length) || '/';
}

内部方法 changeLocation:

function changeLocation(to, state, replace) {
    /**
        * if a base tag is provided and we are on a normal domain, we have to
        * respect the provided `base` attribute because pushState() will use it and
        * potentially erase anything before the `#` like at
        * https://github.com/vuejs/vue-router-next/issues/685 where a base of
        * `/folder/#` but a base of `/` would erase the `/folder/` section. If
        * there is no host, the `<base>` tag makes no sense and if there isn't a
        * base tag we can just use everything after the `#`.
        */
    const hashIndex = base.indexOf('#');
    // hash模式下只取hash部分地址 
    const url = hashIndex > -1
        ? (location.host && document.querySelector('base')
            ? base
            : base.slice(hashIndex)) + to
        : createBaseLocation() + base + to;
    try {
        // BROWSER QUIRK
        // NOTE: Safari throws a SecurityError when calling this function 100 times in 30 seconds
        // 修改history的state 并且让浏览器url定位到我们设置的url上
        history[replace ? 'replaceState' : 'pushState'](state, '', url);
        // 同步state到我们的history
        historyState.value = state;
    }
    catch (err) {
        {
            warn('Error with push/replace State', err);
        }
        // Force the navigation, this also resets the call count
        // 失败情况下的方案 同样可以修改url
        location[replace ? 'replace' : 'assign'](url);
    }
}

// history模式下获取 协议+host
let createBaseLocation = () => location.protocol + '//' + location.host;

可以看到 changeLocation(to, state, replace) 就是把参数中的值同步到window的history中 且修改浏览器的url,是对真实history的操作封装

外部方法:replace

function replace(to, data) {
    // 构建目标state 注意assign的参数顺序
    // {} 一个新对象
    // 当前history的state
    // 新的计算出来的state buildState(historyState.value.back, to, historyState.value.forward, true)
    // 传入的state data
    // 之前的滚动信息{ position: historyState.value.position }
    // 以上参数从后往前覆盖
    const state = assign({}, history.state, buildState(historyState.value.back, 
    // keep back and forward entries but override current position
    to, historyState.value.forward, true), data, { position: historyState.value.position });

    // 调用 changeLocation 重定向覆盖即可
    changeLocation(to, state, true);
    // 更新我们的 currentLocation 的 url值
    currentLocation.value = to;
}

/**
    * Creates a state object
    * 返回一个新的state对象
    */
function buildState(back, current, forward, replaced = false, computeScroll = false) {
    return {
        back,
        current,
        forward,
        replaced,
        position: window.history.length,
        scroll: computeScroll ? computeScrollPosition() : null,
    };
}

再看下 push:

function push(to, data) {
    // 注意 assign 的几个参数顺序
    // {}
    // 我们的 historyState.value
    // 浏览器的 history.state
    // 由于是push操作 这个state是要替换当前state的 所以设置它的forward值 同时还要计算当前的滚动信息 用于回退的时候使用位置信息 { forward: to, scroll: computeScrollPosition(),}

    // Add to current entry the information of where we are going
    // as well as saving the current position
    const currentState = assign({}, 
    // use current history state to gracefully handle a wrong call to
    // history.replaceState
    // https://github.com/vuejs/vue-router-next/issues/366
    historyState.value, history.state, {
        forward: to,
        scroll: computeScrollPosition(),
    });
    // 重定向时候的限制
    if (!history.state) {
        warn(`history.state seems to have been manually replaced without preserving the necessary values. Make sure to preserve existing history state if you are manually calling history.replaceState:\n\n` +
            `history.replaceState(history.state, '', url)\n\n` +
            `You can find more information at https://next.router.vuejs.org/guide/migration/#usage-of-history-state.`);
    }
    // 替换当前的state 因为它的信息更新了 注意第三个参数replace为true
    changeLocation(currentState.current, currentState, true);
    // 计算新的to state
    // 注意buildState的三个参数
    // 更新 position 历史栈+1
    // 加入传入的data
    const state = assign({}, buildState(currentLocation.value, to, null), { position: currentState.position + 1 }, data);
    // 调用 changeLocation
    changeLocation(to, state, false);
    // 更新我们的 currentLocation 的url值
    currentLocation.value = to;
}

总结一下 useHistoryStateNavigation 做的事情:把我们对浏览器地址的改变操作 映射到实际的浏览器history中 并做了一些操作封装
得到了如下对象:

return {
    // 2个对象属性 由于是对象 被别人直接当做成参数使用的话 如果其他调用方有修改它们的value 会导致这里同步被修改
    location: currentLocation,
    state: historyState,
    // 对外暴露2个方法
    push,
    replace,
};

再看 const historyListeners = useHistoryListeners(base, historyNavigation.state, historyNavigation.location, historyNavigation.replace) 的实现:

function useHistoryListeners(base, historyState, currentLocation, replace) {
    // 事件监听者集合
    let listeners = [];
    // 待执行的移除监听者闭包函数集合
    let teardowns = [];
    // TODO: should it be a stack? a Dict. Check if the popstate listener
    // can trigger twice
    // 暂停state监听者触发
    let pauseState = null;
    // 事件handler
    const popStateHandler = ({ state, }) => {
        ...
    };
    // 暂停置位
    function pauseListeners() {
        pauseState = currentLocation.value;
    }
    // 添加监听者
    function listen(callback) {
        // setup the listener and prepare teardown callbacks
        listeners.push(callback);
        // 每个监听者对应一个销毁者 用户可以手动调用 单个 teardown
        const teardown = () => {
            const index = listeners.indexOf(callback);
            if (index > -1)
                listeners.splice(index, 1);
        };
        teardowns.push(teardown);
        return teardown;
    }
    // 事件handler
    function beforeUnloadListener() {
        const { history } = window;
        if (!history.state)
            return;
        history.replaceState(assign({}, history.state, { scroll: computeScrollPosition() }), '');
    }
    // 销毁所有资源
    function destroy() {
        for (const teardown of teardowns)
            teardown();
        teardowns = [];
        window.removeEventListener('popstate', popStateHandler);
        window.removeEventListener('beforeunload', beforeUnloadListener);
    }
    // setup the listeners and prepare teardown callbacks
    // 监听2个事件
    window.addEventListener('popstate', popStateHandler);
    window.addEventListener('beforeunload', beforeUnloadListener);
    // 对外暴露3个方法
    return {
        pauseListeners,
        listen,
        destroy,
    };
}

来看下重点的 popStateHandler:
这是 MDN 对这个事件的定义:

当活动历史记录条目更改时,将触发popstate事件。如果被激活的历史记录条目是通过对history.pushState()的调用创建的,或者受到对history.replaceState()的调用的影响,popstate事件的state属性包含历史条目的状态对象的副本。

需要注意的是调用history.pushState()或history.replaceState()不会触发popstate事件。只有在做出浏览器动作时,才会触发该事件,如用户点击浏览器的回退按钮(或者在Javascript代码中调用history.back()或者history.forward()方法)

const popStateHandler = ({ state, }) => {
    // 计算浏览器新地址 url
    const to = createCurrentLocation(base, location);
    // 我们的location url还是旧值
    const from = currentLocation.value;
    // 我们的旧state
    const fromState = historyState.value;
    let delta = 0;
    // 没有超出栈的底部的话 这个state就有值 到最底层后就是 null 了 需要replace一下
    if (state) {
        // 同步下值到我们的状态中
        currentLocation.value = to;
        historyState.value = state;
        // ignore the popstate and reset the pauseState
        // 暂停标志在这里起作用 对某个url设置了 暂停trigger话 后面就不执行了
        if (pauseState && pauseState === from) {
            pauseState = null;
            return;
        }
        // 计算间隔
        delta = fromState ? state.position - fromState.position : 0;
    }
    else {
        replace(to);
    }
    // console.log({ deltaFromCurrent })
    // Here we could also revert the navigation by calling history.go(-delta)
    // this listener will have to be adapted to not trigger again and to wait for the url
    // to be updated before triggering the listeners. Some kind of validation function would also
    // need to be passed to the listeners so the navigation can be accepted
    // call all listeners
    // trigger一下监听者们执行
    listeners.forEach(listener => {
        listener(currentLocation.value, from, {
            delta,
            // 下面的参数都是字符 表示跳转方向
            type: NavigationType.pop,
            direction: delta
                ? delta > 0
                    ? NavigationDirection.forward
                    : NavigationDirection.back
                : NavigationDirection.unknown,
        });
    });
};

var NavigationType;
(function (NavigationType) {
    NavigationType["pop"] = "pop";
    NavigationType["push"] = "push";
})(NavigationType || (NavigationType = {}));
var NavigationDirection;
(function (NavigationDirection) {
    NavigationDirection["back"] = "back";
    NavigationDirection["forward"] = "forward";
    NavigationDirection["unknown"] = "";
})(NavigationDirection || (NavigationDirection = {}));

总结一下:useHistoryListeners 做的事情:主要监听popstate事件 来同步浏览器地址到我们的状态中
// 监听2个事件
window.addEventListener('popstate', popStateHandler);
window.addEventListener('beforeunload', beforeUnloadListener);
// 对外暴露3个方法
return {
pauseListeners,
listen,
destroy,
};

然后再看下 go:

function go(delta, triggerListeners = true) {
    // 对应上文的暂停操作
    if (!triggerListeners)
        historyListeners.pauseListeners();
    // 其实就是原生的go 不过我们在上面监听了popstate事件了已经
    history.go(delta);
}

// 可以看到 createHref这个方法就是为了得到 去除#之前的字符的base + 目标地址location
// remove any character before the hash
const BEFORE_HASH_RE = /^[^#]+#/;
function createHref(base, location) {
    return base.replace(BEFORE_HASH_RE, '#') + location;
}

合并对象:

const routerHistory = assign({
    // it's overridden right after
    location: '',
    base,
    go,
    createHref: createHref.bind(null, base),
}, historyNavigation, historyListeners);

看下现在总共得到了哪些属性:
routerHistory =
{
location: '', // 初始值 会被下面的覆盖
base, // base路径值
go, // go方法
createHref,
// 2个对象属性 由于是对象 被别人直接当做成参数使用的话 如果其他调用方有修改它们的value 会导致这里同步被修改
// 覆盖上面的
location: currentLocation,
state: historyState,
// 对外暴露的几个个方法
push,
replace,
pauseListeners,
listen,
destroy,
}

然后就是2个getter的取值:
Object.defineProperty(routerHistory, 'location', {
get: () => historyNavigation.location.value,
});
Object.defineProperty(routerHistory, 'state', {
get: () => historyNavigation.state.value,
});

到此,createWebHashHistory 和 createWebHistory 的分析基本完成,它们主要是对base的格式化不同,内部实现中对浏览器url的取值截取部分也不同,其他整体逻辑基本相同。
都是内部自己维持 2个对象 location和history来维持对浏览器地址和history的抽象,然后把方法对自己对象的操作映射到实际的浏览器history中;同时都监听了popstate事件,通过截取不同的url和计算
state来更新自己的变量,维持数据同步。

另外,源码还有一部分关于 createMemoryHistory 的实现, 我们也顺便看一下:

const START = '';

/**
    * Creates a in-memory based history. The main purpose of this history is to handle SSR. It starts in a special location that is nowhere.
    * It's up to the user to replace that location with the starter location by either calling `router.push` or `router.replace`.
    *
    * @param base - Base applied to all urls, defaults to '/'
    * @returns a history object that can be passed to the router constructor
    */

function createMemoryHistory(base = '') {
    let listeners = [];
    // 模拟的历史记录队列
    let queue = [START];
    // 模拟的url位置 从 0 开始 默认就有一个初始值
    let position = 0;
    function setLocation(location) {
        // 直接位置+1
        position++;
        // 长度和位置相等 意味着 新push了一个进来 不然长度大1
        if (position === queue.length) {
            // we are at the end, we can simply append a new entry
            queue.push(location);
        }
        // 在中间位置上的push操作 会把当前位置上的后面的队列中的历史信息都清除 这符合history的对push操作的定义
        else {
            // we are in the middle, we remove everything from here in the queue
            queue.splice(position);
            queue.push(location);
        }
    }
    // trigger更新观察者 分析同上面的2个模式
    function triggerListeners(to, from, { direction, delta }) {
        const info = {
            direction,
            delta,
            type: NavigationType.pop,
        };
        for (let callback of listeners) {
            callback(to, from, info);
        }
    }
    // 构建类似上面的history的对象 方法都比较简单 分析同上
    const routerHistory = {
        // rewritten by Object.defineProperty
        location: START,
        state: {},
        base,
        createHref: createHref.bind(null, base),
        replace(to) {
            // remove current entry and decrement position
            queue.splice(position--, 1);
            setLocation(to);
        },
        push(to, data) {
            setLocation(to);
        },
        listen(callback) {
            listeners.push(callback);
            return () => {
                const index = listeners.indexOf(callback);
                if (index > -1)
                    listeners.splice(index, 1);
            };
        },
        destroy() {
            listeners = [];
        },
        go(delta, shouldTrigger = true) {
            const from = this.location;
            const direction = 
            // we are considering delta === 0 going forward, but in abstract mode
            // using 0 for the delta doesn't make sense like it does in html5 where
            // it reloads the page
            delta < 0 ? NavigationDirection.back : NavigationDirection.forward;
            position = Math.max(0, Math.min(position + delta, queue.length - 1));
            if (shouldTrigger) {
                triggerListeners(this.location, from, {
                    direction,
                    delta,
                });
            }
        },
    };
    Object.defineProperty(routerHistory, 'location', {
        get: () => queue[position],
    });
    return routerHistory;
}

总结一下,createMemoryHistory就是一个简易的history模拟实现。

至此,vue-router中的3种history模式的实现分析完成,在分析其他方法的文章会有对他们如何使用的解释。

Vue(v3.0.11)源码简析之组件实例上slots对象的初始化和更新赋值的相关实现

从编译模板得到包含着我们写在组件中间的插槽内容对应的vnode内容的slots对象信息后,我们需要把它们同步到我们的组件实例上来,这个叫初始化,同时组件更新后,slots重新生成,是可能发生改变的,这个需要更新,看下代码实现:

// 2个内部key _是用来标记父组件中写的插槽的类型 $stable给用户手动设定的slot内容稳定字段
const isInternalKey = (key) => key[0] === '_' || key === '$stable';
// 标准化slot函数返回的内容 数组形式的vnode 内部调用 vnode所属的格式化函数即可
const normalizeSlotValue = (value) => isArray(value)
    ? value.map(normalizeVNode)
    : [normalizeVNode(value)];
// slot函数的作用域是需要特殊处理的 可以回顾下 withCtx 是如何利用闭包保留 ctx 作为上下文的哈
const normalizeSlot = (key, rawSlot, ctx) => withCtx((props) => {
    if (currentInstance) {
        warn(`Slot "${key}" invoked outside of the render function: ` +
            `this will not track dependencies used in the slot. ` +
            `Invoke the slot function inside the render function instead.`);
    }
    // 执行rawSlot函数 格式化结果即可
    return normalizeSlotValue(rawSlot(props));
}, ctx);
// 格式化原始对象slots得到符合我们要求的slots对象
const normalizeObjectSlots = (rawSlots, slots) => {
    // 作用域
    const ctx = rawSlots._ctx;
    for (const key in rawSlots) {
        if (isInternalKey(key))
            continue;
        const value = rawSlots[key];
        // 期望是个slot函数
        if (isFunction(value)) {
            // 我们对slot这个函数本身是有限制的
            slots[key] = normalizeSlot(key, value, ctx);
        }
        else if (value != null) {
            {
                warn(`Non-function value encountered for slot "${key}". ` +
                    `Prefer function slots for better performance.`);
            }
            // 赋值即可
            const normalized = normalizeSlotValue(value);
            slots[key] = () => normalized;
        }
    }
};
// 把子节点vnode直接处理成默认slot方法即可
const normalizeVNodeSlots = (instance, children) => {
    if (!isKeepAlive(instance.vnode)) {
        warn(`Non-function value encountered for default slot. ` +
            `Prefer function slots for better performance.`);
    }
    // 只有一个默认default的slot
    const normalized = normalizeSlotValue(children);
    instance.slots.default = () => normalized;
};
// 初始化组件实例的slots属性
const initSlots = (instance, children) => {
    // 使用大部分普通组件的时候在HTML中写了插槽内容 因此存在 SLOTS_CHILDREN
    if (instance.vnode.shapeFlag & 32 /* SLOTS_CHILDREN */) {
        const type = children._;
        if (type) {
            // 赋值
            instance.slots = children;
            // make compiler marker non-enumerable
            // 不需要支持可枚举这个key 内部使用的属性
            def(children, '_', type);
        }
        // 通过编译得到的虽然已经是合格的slots内容了 但是用户还是可以手动写render的这时候传入的slots对象只是一个普通对象 还需要做额外的处理
        else {
            normalizeObjectSlots(children, (instance.slots = {}));
        }
    }
    // 如果没有插槽内容的话 理论上children是不存在的 但是从normalizeVNodeSlots的逻辑看 KeepAlive 组件似乎很特殊 它不满足上面的条件从而需要下面的处理
    else {
        instance.slots = {};
        if (children) {
            normalizeVNodeSlots(instance, children);
        }
    }
    // 打上内部属性标记
    def(instance.slots, InternalObjectKey, 1);
};
// 相对应init的 组件更新后 更新组件实例的slots
const updateSlots = (instance, children, optimized) => {
    // 取出之前的值
    const { vnode, slots } = instance;
    let needDeletionCheck = true;
    // 对比对象 不在其中的key 需要移除
    let deletionComparisonTarget = EMPTY_OBJ;
    // 同上面的init分析
    if (vnode.shapeFlag & 32 /* SLOTS_CHILDREN */) {
        const type = children._;
        if (type) {
            // compiled slots.
            if (isHmrUpdating) {
                // Parent was HMR updated so slot content may have changed.
                // force update slots and mark instance for hmr as well
                // 覆盖对象key的值
                extend(slots, children);
            }
            // 不需要更新 删除
            else if (optimized && type === 1 /* STABLE */) {
                // compiled AND stable.
                // no need to update, and skip stale slots removal.
                needDeletionCheck = false;
            }
            else {
                // compiled but dynamic (v-if/v-for on slots) - update slots, but skip
                // normalization.
                // 动态slots因为if或者for产生了变化
                extend(slots, children);
                // #2893
                // when rendering the optimized slots by manually written render function,
                // we need to delete the `slots._` flag if necessary to make subsequent updates reliable,
                // i.e. let the `renderSlot` create the bailed Fragment
                // 这个的具体场景待分析 TODO
                if (!optimized && type === 1 /* STABLE */) {
                    delete slots._;
                }
            }
        }
        else {
            // 不稳定才需要检查删除项咯
            needDeletionCheck = !children.$stable;
            // 更新slots内容
            normalizeObjectSlots(children, slots);
        }
        // 跟新slots对比就可以了
        deletionComparisonTarget = children;
    }
    else if (children) {
        // non slot object children (direct value) passed to a component
        normalizeVNodeSlots(instance, children);
        deletionComparisonTarget = { default: 1 };
    }
    // delete stale slots
    if (needDeletionCheck) {
        for (const key in slots) {
            // 移除已经不存在的key就好了
            if (!isInternalKey(key) && !(key in deletionComparisonTarget)) {
                delete slots[key];
            }
        }
    }
};

Vue(v3.0.11)源码简析之响应式(reactive,ref,computed)相关实现

vue中的响应式主要是通过把目标对象变成代理对象,同时设置对应属性的handlers来控制的,我们看下实际vue中用到的handlers都是怎么实现的,以及ref对象和computed对象的实现:

// 不予track追踪的对象key
const isNonTrackableKeys = /*#__PURE__*/ makeMap(`__proto__,__v_isRef,__isVue`);
// Object.getOwnPropertyNames(Symbol): ["length", "name", "prototype", "for", "keyFor", "asyncIterator", "hasInstance", "isConcatSpreadable", "iterator", "match", "matchAll", "replace", "search", "species", "split", "toPrimitive", "toStringTag", "unscopables"]
// .map(key => Symbol[key]): 得到对应key的值
// .filter(isSymbol) 保留 Symbol 类型的值
// builtInSymbols 中的key 也同 isNonTrackableKeys 中的key一样 不予track追踪
const builtInSymbols = new Set(Object.getOwnPropertyNames(Symbol)
    .map(key => Symbol[key])
    .filter(isSymbol));
// proxy代理的拦截器handler的get实现
// 深度遍历追踪 每层都追踪当前属性
const get = /*#__PURE__*/ createGetter();
// 只代理一层 每层都追踪当前属性(只有一层)
const shallowGet = /*#__PURE__*/ createGetter(false, true);
// 深度遍历追踪 每层都不追踪当前属性
const readonlyGet = /*#__PURE__*/ createGetter(true);
// 只代理一层 每层都不追踪当前属性(其实只有一层)
const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true);
// 我们对数组对象的 'includes', 'indexOf', 'lastIndexOf' 'push', 'pop', 'shift', 'unshift', 'splice'
// 上述几个方法做了拦截处理 用户在调用数组实例的原生方法的时候 其实是调用了我们的方法 在原生方法的基础上加了额外的控制逻辑
const arrayInstrumentations = {};
['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
    // 原生方法函数的引用
    const method = Array.prototype[key];
    // args就是 用户在形式上调用原生数组方法时候 其实调用的是这个匿名函数 传入的参数 
    arrayInstrumentations[key] = function (...args) {
        // 在下面的get实现中 Reflect中指定了this为 数组实例对象
        const arr = toRaw(this);
        // 上述3个操作 都需要遍历每个元素做对比 所以每个都需要触发一次get
        for (let i = 0, l = this.length; i < l; i++) {
            track(arr, "get" /* GET */, i + '');
        }
        // we run the method using the original args first (which may be reactive)
        // 获取原始结果 args有可能是其他data中声明的对象变成响应式代理了 所以还需要用原始值对象尝试一次 以免有误
        const res = method.apply(arr, args);
        if (res === -1 || res === false) {
            // if that didn't work, run it again using raw values.
            return method.apply(arr, args.map(toRaw));
        }
        else {
            return res;
        }
    };
});
// 首先 我们要知道一点 下面5个方法调用的时候会先取数组实例的length,然后再对数组的索引做对应的set操作
// 对于push 就是 arr[length] = newVal 触发trigger中的add事件
// 对于pop 就是 先取 arr[length -1] = val;因为是删除key 所以会触发调用deleteProperty 从而触发trigger中的delete事件
// 其他的方法分析类似 不过由于改变的位置在前面和任意位置,导致有更多元素要触发对应的 get 和 set 操作
// 不过最终的目的都是达到 通过下面5个方法更新对象后 它的观察者们可以重新更新就可以了
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(key => {
    const method = Array.prototype[key];
    arrayInstrumentations[key] = function (...args) {
        // 不需要追踪原生方法执行过程中对length或者元素get的操作
        pauseTracking();
        // this同上面的分析 指向数组实例 其实是个proxy代理 对它的修改 会触发trigger
        const res = method.apply(this, args);
        // 恢复
        resetTracking();
        return res;
    };
});
// 普通对象的代理拦截器handler属性中 get 方法的实现
function createGetter(isReadonly = false, shallow = false) {
    // 返回的get 闭包函数 持有调用 createGetter 时候的 2个自由变量 isReadonly shallow
    return function get(target, key, receiver) {
        // isReadonly 代表着不追踪变化 非响应式
        if (key === "__v_isReactive" /* IS_REACTIVE */) {
            return !isReadonly;
        }
        // 返回isReadonly即可
        else if (key === "__v_isReadonly" /* IS_READONLY */) {
            return isReadonly;
        }
        // __v_raw 获取 当前代理对象的原始关联对象
        // 当 receiver 上不存在属性a,而我们获取a的时候,恰好它的原型对象也是一个代理对象 这时候get方法会触发 而此时的target指向的是原型对象 而不是我们最初的target对象了
        // 所以这时候 (某个map).get(target) 是得不到我们之前建立好的关联关系的 目标 receiver 的
        // https://es6.ruanyifeng.com/#docs/proxy#Proxy-%E5%AE%9E%E4%BE%8B%E7%9A%84%E6%96%B9%E6%B3%95
        else if (key === "__v_raw" /* RAW */ &&
            receiver ===
            (isReadonly
                ? shallow
                    ? shallowReadonlyMap
                    : readonlyMap
                : shallow
                    ? shallowReactiveMap
                    : reactiveMap).get(target)) {
            return target;
        }
        // 数组实例的几个方法 调用对应的 arrayInstrumentations 中的实现即可
        const targetIsArray = isArray(target);
        if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
            // 第三个参数指定了 key对应的值如果是函数的话 函数的this就是 receiver 在这里 就是我们的数组实例对象
            return Reflect.get(arrayInstrumentations, key, receiver);
        }
        // 获取原始对象的值
        const res = Reflect.get(target, key, receiver);
        // 不予追踪的2类key
        if (isSymbol(key)
            ? builtInSymbols.has(key)
            : isNonTrackableKeys(key)) {
            return res;
        }
        // 触发track追踪get事件
        if (!isReadonly) {
            track(target, "get" /* GET */, key);
        }
        // 不再深度遍历继续代理下一层了
        if (shallow) {
            return res;
        }
        // 对于 ref 变量来说 存在一层value包裹 
        // 非数组内的ref 就展开ref的value得到原始值
        if (isRef(res)) {
            // ref unwrapping - does not apply for Array + integer key.
            const shouldUnwrap = !targetIsArray || !isIntegerKey(key);
            return shouldUnwrap ? res.value : res;
        }
        if (isObject(res)) {
            // Convert returned value into a proxy as well. we do the isObject check
            // here to avoid invalid value warning. Also need to lazy access readonly
            // and reactive here to avoid circular dependency.
            // 延迟追踪 只有get触发之后再视情况代理返回的值
            return isReadonly ? readonly(res) : reactive(res);
        }
        return res;
    };
}
// 包含ref类型变量赋值处理的set
const set = /*#__PURE__*/ createSetter();
// 不包含ref类型变量赋值处理的set
const shallowSet = /*#__PURE__*/ createSetter(true);
// 普通对象的代理拦截器handler属性中 set 方法的实现
function createSetter(shallow = false) {
    return function set(target, key, value, receiver) {
        // 旧值
        let oldValue = target[key];
        if (!shallow) {
            value = toRaw(value);
            oldValue = toRaw(oldValue);
            // 非数组情况下 旧值是ref类型 新值却不是 
            if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
                // 处理方案如下
                oldValue.value = value;
                return true;
            }
        }
        // 确定是否当前key存在
        const hadKey = isArray(target) && isIntegerKey(key)
            ? Number(key) < target.length
            : hasOwn(target, key);
        // 设置原始对象新值
        const result = Reflect.set(target, key, value, receiver);
        // don't trigger if target is something up in the prototype chain of original
        // 同上面get的分析 只有在对原始target触发的set操作的时候2者才想到
        if (target === toRaw(receiver)) {
            // 分情况触发2类事件
            if (!hadKey) {
                trigger(target, "add" /* ADD */, key, value);
            }
            else if (hasChanged(value, oldValue)) {
                trigger(target, "set" /* SET */, key, value, oldValue);
            }
        }
        return result;
    };
}
// 当我们删除对象的属性时候 下面会触发
// 普通对象的代理拦截器handler属性中 deleteProperty 方法的实现
function deleteProperty(target, key) {
    // 调用原生方法检测
    const hadKey = hasOwn(target, key);
    const oldValue = target[key];
    // 执行删除删除
    const result = Reflect.deleteProperty(target, key);
    if (result && hadKey) {
        // 触发delte事件
        trigger(target, "delete" /* DELETE */, key, undefined, oldValue);
    }
    return result;
}
// 当我们用 in 操作符  检测对象属性时候 下面会触发
// 值得注意的是,has()方法拦截的是HasProperty操作,而不是HasOwnProperty操作,即has()方法不判断一个属性是对象自身的属性,还是继承的属性。
// 另外,虽然for...in循环也用到了in运算符,但是has()拦截对for...in循环不生效。
function has(target, key) {
    const result = Reflect.has(target, key);
    if (!isSymbol(key) || !builtInSymbols.has(key)) {
        // 触发has事件的观察者
        track(target, "has" /* HAS */, key);
    }
    return result;
}
/**
 *  ownKeys()方法用来拦截对象自身属性的读取操作。具体来说,拦截以下操作。
    Object.getOwnPropertyNames()
    Object.getOwnPropertySymbols()
    Object.keys()
    for...in循环 
 */
function ownKeys(target) {
    // 追踪遍历操作类型事件
    track(target, "iterate" /* ITERATE */, isArray(target) ? 'length' : ITERATE_KEY);
    return Reflect.ownKeys(target);
}
// 由此我们构成了普通对象代理的handler拦截的几个属性实现
const mutableHandlers = {
    get,
    set,
    deleteProperty,
    has,
    ownKeys
};
// 只读handler
const readonlyHandlers = {
    get: readonlyGet,
    // 重写set 不可覆盖值
    set(target, key) {
        {
            console.warn(`Set operation on key "${String(key)}" failed: target is readonly.`, target);
        }
        return true;
    },
    // 同上
    deleteProperty(target, key) {
        {
            console.warn(`Delete operation on key "${String(key)}" failed: target is readonly.`, target);
        }
        return true;
    }
};
// 只对get和set做shallow处理
const shallowReactiveHandlers = extend({}, mutableHandlers, {
    get: shallowGet,
    set: shallowSet
});
// Props handlers are special in the sense that it should not unwrap top-level
// refs (in order to allow refs to be explicitly passed down), but should
// retain the reactivity of the normal readonly object.
// 对get做 shallowReadonly 处理
const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
    get: shallowReadonlyGet
});

// 转化目标对象 分别调用实际的实现方法
const toReactive = (value) => isObject(value) ? reactive(value) : value;
const toReadonly = (value) => isObject(value) ? readonly(value) : value;
const toShallow = (value) => value;
const getProto = (v) => Reflect.getPrototypeOf(v);
// 下面的方法都是 容器类型的对象 如 Map Set的代理handlers方法的实现了
// 拦截map的get操作
function get$1(target, key, isReadonly = false, isShallow = false) {
    // #1772: readonly(reactive(Map)) should return readonly + reactive version
    // of the value
    // 实际上所有的组件实例proxy上面的对象最终会包装一层代理对象 我们取值的最终对象是原始对象
    // key也是一样 因为是map key可能是对象
    target = target["__v_raw" /* RAW */];
    const rawTarget = toRaw(target);
    const rawKey = toRaw(key);
    if (key !== rawKey) {
        // 这里意味着 key 是代理对象 那我们就也添加 代理对象key的追踪
        !isReadonly && track(rawTarget, "get" /* GET */, key);
    }
    // 追踪原始key
    !isReadonly && track(rawTarget, "get" /* GET */, rawKey);
    // 原始map 的has
    const { has } = getProto(rawTarget);
    // 取值的发生的延迟代理 map的get操作 同普通对象的get一样
    const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;
    // 同普通对象的get 以2种key都尝试
    if (has.call(rawTarget, key)) {
        return wrap(target.get(key));
    }
    else if (has.call(rawTarget, rawKey)) {
        return wrap(target.get(rawKey));
    }
}
// 拦截都有的has操作
function has$1(key, isReadonly = false) {
    // this指向 map或者set的实例 分析见下文
    const target = this["__v_raw" /* RAW */];
    const rawTarget = toRaw(target);
    const rawKey = toRaw(key);
    // 分析同上
    if (key !== rawKey) {
        !isReadonly && track(rawTarget, "has" /* HAS */, key);
    }
    !isReadonly && track(rawTarget, "has" /* HAS */, rawKey);
    // 分析同上
    return key === rawKey
        ? target.has(key)
        : target.has(key) || target.has(rawKey);
}
// 拦截size操作
function size(target, isReadonly = false) {
    target = target["__v_raw" /* RAW */];
    // 追踪迭代事件
    !isReadonly && track(toRaw(target), "iterate" /* ITERATE */, ITERATE_KEY);
    return Reflect.get(target, 'size', target);
}
// 拦截 add 操作
function add(value) {
    // 分析同上
    value = toRaw(value);
    // this指向分析同上
    const target = toRaw(this);
    const proto = getProto(target);
    const hadKey = proto.has.call(target, value);
    if (!hadKey) {
        target.add(value);
        // 触发add类型的事件
        trigger(target, "add" /* ADD */, value, value);
    }
    return this;
}
// 拦截set操作
function set$1(key, value) {
    // 分析同上
    value = toRaw(value);
    const target = toRaw(this);
    const { has, get } = getProto(target);
    let hadKey = has.call(target, key);
    // !hadKey 意味着当前不存在这个 有可能是代理对象的key
    if (!hadKey) {
        // 以原始key再做一次has检测
        key = toRaw(key);
        hadKey = has.call(target, key);
    }
    else {
        // 存在可能以代理对象为key的情况下 做一次检测
        checkIdentityKeys(target, has, key);
    }
    const oldValue = get.call(target, key);
    target.set(key, value);
    if (!hadKey) {
        // 触发关注add操作的观察者们
        trigger(target, "add" /* ADD */, key, value);
    }
    else if (hasChanged(value, oldValue)) {
        // 值改变后 触发set类型的事件
        trigger(target, "set" /* SET */, key, value, oldValue);
    }
    return this;
}
// 拦截map的删除键值对操作
function deleteEntry(key) {
    // this的分析见下文
    const target = toRaw(this);
    const { has, get } = getProto(target);
    let hadKey = has.call(target, key);
    // 分析同上文
    if (!hadKey) {
        key = toRaw(key);
        hadKey = has.call(target, key);
    }
    else {
        checkIdentityKeys(target, has, key);
    }
    const oldValue = get ? get.call(target, key) : undefined;
    // forward the operation before queueing reactions
    const result = target.delete(key);
    if (hadKey) {
        // 触发delete类型的事件
        trigger(target, "delete" /* DELETE */, key, undefined, oldValue);
    }
    return result;
}
// 拦截清空操作
function clear() {
    const target = toRaw(this);
    // 之前是否有存储
    const hadItems = target.size !== 0;
    // 保留未删除之前的数据备份
    const oldTarget = isMap(target)
            ? new Map(target)
            : new Set(target)
        ;
    // forward the operation before queueing reactions
    const result = target.clear();
    if (hadItems) {
        // 触发clear类型的事件 注意最后一个参数 oldTarget
        trigger(target, "clear" /* CLEAR */, undefined, undefined, oldTarget);
    }
    return result;
}
// 构造我们定义的forEach函数
function createForEach(isReadonly, isShallow) {
    // 2个用于选择 响应式类型的 自由变量
    // 实际执行的是下面这个函数 注意 用户传入可以有2个参数 第二个参数可以绑定任意对象作为第一个参数函数的this 
    // https://es6.ruanyifeng.com/#docs/set-map#Map
    return function forEach(callback, thisArg) {
        const observed = this;
        // 取得实例的原始对象
        const target = observed["__v_raw" /* RAW */];
        const rawTarget = toRaw(target);
        // 选择合适的响应式转化函数
        const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;
        // 遍历操作需要追踪
        !isReadonly && track(rawTarget, "iterate" /* ITERATE */, ITERATE_KEY);
        // 执行原始方法
        return target.forEach((value, key) => {
            // important: make sure the callback is
            // 1. invoked with the reactive map as `this` and 3rd arg
            // 2. the value received should be a corresponding reactive/readonly.
            // 以用户的this为准 
            // 同时由于触发了key和value的get 我们先对他们做响应式代理 以建立依赖收集
            // 最后一个参数 也是实例对象本身 同见上面的链接解释
            return callback.call(thisArg, wrap(value), wrap(key), observed);
        });
    };
}
// 用到了迭代器实现的几个含有遍历操作的方法 entries keys values
function createIterableMethod(method, isReadonly, isShallow) {
    return function (...args) {
        const target = this["__v_raw" /* RAW */];
        const rawTarget = toRaw(target);
        // 是否是map实例
        const targetIsMap = isMap(rawTarget);
        const isPair = method === 'entries' || (method === Symbol.iterator && targetIsMap);
        // map的keys只获取key
        const isKeyOnly = method === 'keys' && targetIsMap;
        // 这个方法实际上都是返回一个迭代器对象
        const innerIterator = target[method](...args);
        // 选择合适的响应式函数转化参数
        const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive;
        // 追踪对应的遍历操作
        !isReadonly &&
            track(rawTarget, "iterate" /* ITERATE */, isKeyOnly ? MAP_KEY_ITERATE_KEY : ITERATE_KEY);
        // return a wrapped iterator which returns observed versions of the
        // values emitted from the real iterator
        // 按照迭代器协议实现
        return {
            // iterator protocol
            next() {
                // 调用原生的迭代器对象的方法
                const { value, done } = innerIterator.next();
                return done
                    ? { value, done }
                    : {
                        // 触发了参数的get 建立依赖收集
                        value: isPair ? [wrap(value[0]), wrap(value[1])] : wrap(value),
                        done
                    };
            },
            // iterable protocol
            [Symbol.iterator]() {
                return this;
            }
        };
    };
}
// 对只读对象 用修改操作方法的是时候 添加警告信息
function createReadonlyMethod(type) {
    return function (...args) {
        {
            const key = args[0] ? `on key "${args[0]}" ` : ``;
            console.warn(`${capitalize(type)} operation ${key}failed: target is readonly.`, toRaw(this));
        }
        return type === "delete" /* DELETE */ ? false : this;
    };
}
// 基础容器的handlers 下面其他几个都是对应 只读 浅层的版本
const mutableInstrumentations = {
    get(key) {
        return get$1(this, key);
    },
    get size() {
        return size(this);
    },
    has: has$1,
    add,
    set: set$1,
    delete: deleteEntry,
    clear,
    forEach: createForEach(false, false)
};
const shallowInstrumentations = {
    get(key) {
        return get$1(this, key, false, true);
    },
    get size() {
        return size(this);
    },
    has: has$1,
    add,
    set: set$1,
    delete: deleteEntry,
    clear,
    forEach: createForEach(false, true)
};
const readonlyInstrumentations = {
    get(key) {
        return get$1(this, key, true);
    },
    get size() {
        return size(this, true);
    },
    // 因为多一个参数比上面的
    has(key) {
        return has$1.call(this, key, true);
    },
    // 只读不允许下面的操作
    add: createReadonlyMethod("add" /* ADD */),
    set: createReadonlyMethod("set" /* SET */),
    delete: createReadonlyMethod("delete" /* DELETE */),
    clear: createReadonlyMethod("clear" /* CLEAR */),
    forEach: createForEach(true, false)
};
const shallowReadonlyInstrumentations = {
    get(key) {
        return get$1(this, key, true, true);
    },
    get size() {
        return size(this, true);
    },
    has(key) {
        return has$1.call(this, key, true);
    },
    add: createReadonlyMethod("add" /* ADD */),
    set: createReadonlyMethod("set" /* SET */),
    delete: createReadonlyMethod("delete" /* DELETE */),
    clear: createReadonlyMethod("clear" /* CLEAR */),
    forEach: createForEach(true, true)
};
// 创建几个返回迭代器对象的方法
const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator];
iteratorMethods.forEach(method => {
    mutableInstrumentations[method] = createIterableMethod(method, false, false);
    readonlyInstrumentations[method] = createIterableMethod(method, true, false);
    shallowInstrumentations[method] = createIterableMethod(method, false, true);
    shallowReadonlyInstrumentations[method] = createIterableMethod(method, true, true);
});
// 容器对象任意方法key get操作的统一拦截handler
function createInstrumentationGetter(isReadonly, shallow) {
    // 选择合适的基础容器对象 包含所有的handlers实现
    const instrumentations = shallow
        ? isReadonly
            ? shallowReadonlyInstrumentations
            : shallowInstrumentations
        : isReadonly
            ? readonlyInstrumentations
            : mutableInstrumentations;
    return (target, key, receiver) => {
        // 同普通对象的分析
        if (key === "__v_isReactive" /* IS_REACTIVE */) {
            return !isReadonly;
        }
        else if (key === "__v_isReadonly" /* IS_READONLY */) {
            return isReadonly;
        }
        else if (key === "__v_raw" /* RAW */) {
            return target;
        }
        // 我们自己实现的拦截handler的key 才调用我们的实现 其余的用原生的
        return Reflect.get(hasOwn(instrumentations, key) && key in target
            ? instrumentations
            : target, key, receiver);
    };
}
// 构造对应的几类handlers对象
const mutableCollectionHandlers = {
    get: createInstrumentationGetter(false, false)
};
const shallowCollectionHandlers = {
    get: createInstrumentationGetter(false, true)
};
const readonlyCollectionHandlers = {
    get: createInstrumentationGetter(true, false)
};
const shallowReadonlyCollectionHandlers = {
    get: createInstrumentationGetter(true, true)
};
// 检测map的key的有效性
function checkIdentityKeys(target, has, key) {
    const rawKey = toRaw(key);
    // 存储响应式map仓库中发现 同时存在以 某个对象key及它的代理对象 为key的情况
    if (rawKey !== key && has.call(target, rawKey)) {
        const type = toRawType(target);
        console.warn(`Reactive ${type} contains both the raw and reactive ` +
            `versions of the same object${type === `Map` ? ` as keys` : ``}, ` +
            `which can lead to inconsistencies. ` +
            `Avoid differentiating between the raw and reactive versions ` +
            `of an object and only use the reactive version if possible.`);
    }
}

// 之前有个 targetMap 是被观察者和观察者之间的联系
// 下面几个则是 被代理原始对象和代理对象的之间的联系存储对象
const reactiveMap = new WeakMap();
const shallowReactiveMap = new WeakMap();
const readonlyMap = new WeakMap();
const shallowReadonlyMap = new WeakMap();
// 待代理对象的原始类型 普通对象和容器对象 其他不合法 不于代理
function targetTypeMap(rawType) {
    switch (rawType) {
        case 'Object':
        case 'Array':
            return 1 /* COMMON */;
        case 'Map':
        case 'Set':
        case 'WeakMap':
        case 'WeakSet':
            return 2 /* COLLECTION */;
        default:
            return 0 /* INVALID */;
    }
}
// 调用上面的方法
function getTargetType(value) {
    // 不可拓展的对象 和 vue忽略对象
    return value["__v_skip" /* SKIP */] || !Object.isExtensible(value)
        ? 0 /* INVALID */
        : targetTypeMap(toRawType(value));
}
// 下面4个对应不同的参数而已
// 响应式函数高级调用api
function reactive(target) {
    // if trying to observe a readonly proxy, return the readonly version.
    if (target && target["__v_isReadonly" /* IS_READONLY */]) {
        return target;
    }
    // 调用内部实现
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
/**
 * Return a shallowly-reactive copy of the original object, where only the root
 * level properties are reactive. It also does not auto-unwrap refs (even at the
 * root level).
 */
function shallowReactive(target) {
    return createReactiveObject(target, false, shallowReactiveHandlers, shallowCollectionHandlers, shallowReactiveMap);
}
/**
 * Creates a readonly copy of the original object. Note the returned copy is not
 * made reactive, but `readonly` can be called on an already reactive object.
 */
function readonly(target) {
    return createReactiveObject(target, true, readonlyHandlers, readonlyCollectionHandlers, readonlyMap);
}
/**
 * Returns a reactive-copy of the original object, where only the root level
 * properties are readonly, and does NOT unwrap refs nor recursively convert
 * returned properties.
 * This is used for creating the props proxy object for stateful components.
 */
function shallowReadonly(target) {
    return createReactiveObject(target, true, shallowReadonlyHandlers, shallowReadonlyCollectionHandlers, shallowReadonlyMap);
}
// 实际的实现在这里
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {
    // 只代理object
    if (!isObject(target)) {
        {
            console.warn(`value cannot be made reactive: ${String(target)}`);
        }
        return target;
    }
    // target is already a Proxy, return it.
    // exception: calling readonly() on a reactive object
    if (target["__v_raw" /* RAW */] &&
        !(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
        return target;
    }
    // target already has corresponding Proxy
    // 无需反复代理同一个对象
    const existingProxy = proxyMap.get(target);
    if (existingProxy) {
        return existingProxy;
    }
    // only a whitelist of value types can be observed.
    const targetType = getTargetType(target);
    if (targetType === 0 /* INVALID */) {
        return target;
    }
    // 建立代理
    const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
    // 建立map对应
    proxyMap.set(target, proxy);
    // 返回代理对象
    return proxy;
}
function isReactive(value) {
    if (isReadonly(value)) {
        return isReactive(value["__v_raw" /* RAW */]);
    }
    return !!(value && value["__v_isReactive" /* IS_REACTIVE */]);
}
function isReadonly(value) {
    return !!(value && value["__v_isReadonly" /* IS_READONLY */]);
}
function isProxy(value) {
    return isReactive(value) || isReadonly(value);
}
// 由于存在多层代理 所以需要递归调用toRaw一直找到最底层的原始对象
function toRaw(observed) {
    return ((observed && toRaw(observed["__v_raw" /* RAW */])) || observed);
}
// 标记这始终不需要代理它 一直保持原始对象
function markRaw(value) {
    def(value, "__v_skip" /* SKIP */, true);
    return value;
}
// 转化为响应式代理对象
const convert = (val) => isObject(val) ? reactive(val) : val;
// ref型对象标识
function isRef(r) {
    return Boolean(r && r.__v_isRef === true);
}
// 外部调用api
function ref(value) {
    return createRef(value);
}
// 浅层ref对象 意味着不做递归响应式代理 只代理value这一层
function shallowRef(value) {
    return createRef(value, true);
}
// ref具体实现 总体来说就是一层value对象的包裹 然后拦截get和set value的操作
class RefImpl {
    constructor(_rawValue, _shallow = false) {
        // 原始值
        this._rawValue = _rawValue;
        // 浅层标志
        this._shallow = _shallow;
        // 自身标记
        this.__v_isRef = true;
        // 不继续代理了
        this._value = _shallow ? _rawValue : convert(_rawValue);
    }
    get value() {
        // 对 someRef.value 这样的取值操作 触发我们想要的get追踪 等于对原始任意值多了一层对象包裹
        track(toRaw(this), "get" /* GET */, 'value');
        return this._value;
    }
    set value(newVal) {
        if (hasChanged(toRaw(newVal), this._rawValue)) {
            this._rawValue = newVal;
            // 不继续代理新值
            this._value = this._shallow ? newVal : convert(newVal);
            // 触发set类型事件
            trigger(toRaw(this), "set" /* SET */, 'value', newVal);
        }
    }
}
// 对外暴露的高级api调用
function createRef(rawValue, shallow = false) {
    if (isRef(rawValue)) {
        return rawValue;
    }
    return new RefImpl(rawValue, shallow);
}
// 手动触发 与ref关联的副作用函数
function triggerRef(ref) {
    trigger(toRaw(ref), "set" /* SET */, 'value', ref.value );
}
// 接触ref包裹 返回原始对象
function unref(ref) {
    return isRef(ref) ? ref.value : ref;
}
const shallowUnwrapHandlers = {
    get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
    set: (target, key, value, receiver) => {
        const oldValue = target[key];
        if (isRef(oldValue) && !isRef(value)) {
            oldValue.value = value;
            return true;
        }
        else {
            return Reflect.set(target, key, value, receiver);
        }
    }
};
// 对ref类型对象做一层代理 主要是对ref对象的get操作 返回去ref的值 set操作 根据旧值和新值的对比来处理
function proxyRefs(objectWithRefs) {
    return isReactive(objectWithRefs)
        ? objectWithRefs
        : new Proxy(objectWithRefs, shallowUnwrapHandlers);
}
// 自定义ref实现
class CustomRefImpl {
    constructor(factory) {
        this.__v_isRef = true;
        // 对应文档中描述的2个内置函数 需要用户在get和set中调用
        const { get, set } = factory(() => track(this, "get" /* GET */, 'value'), () => trigger(this, "set" /* SET */, 'value'));
        // 绑定
        this._get = get;
        this._set = set;
    }
    get value() {
        return this._get();
    }
    set value(newVal) {
        this._set(newVal);
    }
}
// 外部调用api
function customRef(factory) {
    return new CustomRefImpl(factory);
}
// 把一个响应式对象 转化为 每个对应的key的值都是ref对象
function toRefs(object) {
    if (!isProxy(object)) {
        console.warn(`toRefs() expects a reactive object but received a plain one.`);
    }
    const ret = isArray(object) ? new Array(object.length) : {};
    // 遍历生成
    for (const key in object) {
        ret[key] = toRef(object, key);
    }
    return ret;
}
// 根据原始对象仓库和对应的key 构造ref对象的实现
class ObjectRefImpl {
    constructor(_object, _key) {
        this._object = _object;
        this._key = _key;
        this.__v_isRef = true;
    }
    // get 和 set 操作都是对原对象仓库的操作
    // 由于 _object 是响应式的 所以还是会触发对应的track和trigger
    get value() {
        return this._object[this._key];
    }
    set value(newVal) {
        this._object[this._key] = newVal;
    }
}
// 单个key生成对应的ref对象
function toRef(object, key) {
    return isRef(object[key])
        ? object[key]
        : new ObjectRefImpl(object, key);
}

// computed对象实现
class ComputedRefImpl {
    constructor(getter, _setter, isReadonly) {
        // 绑定setter
        this._setter = _setter;
        // 懒更新 只有这个为true get操作才会取最新的值 否则一直返回之前的缓存值
        this._dirty = true;
        // 自我类型标识
        this.__v_isRef = true;
        // 创建响应式effect 注意入参getter函数 作为 effect每次在触发更新后执行的目标函数
        this.effect = effect(getter, {
            // 并不马上执行 除非触发了get操作
            lazy: true,
            // 当computed对象所依赖的对象发生改变后 scheduler 会因为被依赖变量的trigger的执行而执行
            scheduler: () => {
                if (!this._dirty) {
                    // 通知 可以重新取新值了
                    this._dirty = true;
                    // 触发关注computed对象的观察者们 可以更新了 这时候render重新执行 重新触发下面的get 就可以得到computed对象的新值了
                    trigger(toRaw(this), "set" /* SET */, 'value');
                }
            }
        });
        this["__v_isReadonly" /* IS_READONLY */] = isReadonly;
    }
    get value() {
        // the computed ref may get wrapped by other proxies e.g. readonly() #3376
        // 取得computed实例对象
        const self = toRaw(this);
        // 只有上面因为getter的依赖发送改变而引起了 effect的重新执行而导致scheduler执行了 或者 初始化第一次取值 才会进入下面逻辑 去计算一次getter得到新值
        if (self._dirty) {
            self._value = this.effect();
            // 重新进入懒更新状态
            self._dirty = false;
        }
        // 追踪谁依赖当前computed实例对象
        track(self, "get" /* GET */, 'value');
        return self._value;
    }
    set value(newValue) {
        this._setter(newValue);
    }
}
// 外部调用api 我们可以传入一个getter或者包含 getter setter的对象
function computed(getterOrOptions) {
    let getter;
    let setter;
    // 只传一个getter
    if (isFunction(getterOrOptions)) {
        getter = getterOrOptions;
        setter = () => {
                console.warn('Write operation failed: computed value is readonly');
            }
            ;
    }
    // 第二种情况
    else {
        getter = getterOrOptions.get;
        setter = getterOrOptions.set;
    }
    return new ComputedRefImpl(getter, setter, isFunction(getterOrOptions) || !getterOrOptions.set);
}

Vue(v3.0.11)源码简析之一些内置指令的相关实现

vue中的运行时指令其实都是有一些钩子函数组成的对象,在挂载组件生成组件实例的时候会被加入到dirs数组中,随着vnode对应的元素的生命周期而执行各个阶段的钩子,所以我们看下这些指令对象的内置实现:

// 一些指令的相关实现 我们知道 一些特殊指令会影响vnode的生成格式 这些指令往往在render创建的时候就发挥了作用了 而剩下的呢 则是以钩子函数组成的对象存在 这些指令会成为vnode的dirs数组的一部分 随着vnode和el的生命周期阶段而执行不同的钩子 从而实现指令本身的功能
// v-model有关的指令实现
// 获取一个或者多个v-model对应产生的事件
const getModelAssigner = (vnode) => {
    const fn = vnode.props['onUpdate:modelValue'];
    return isArray(fn) ? value => invokeArrayFns(fn, value) : fn;
};
// 输入汉字这种的组合事件start
function onCompositionStart(e) {
    e.target.composing = true;
}
// 输入汉字这种的组合事件end
function onCompositionEnd(e) {
    const target = e.target;
    if (target.composing) {
        target.composing = false;
        // 最终触发一次就可以了 输入过程中不触发
        trigger$1(target, 'input');
    }
}
// 触发某类事件
function trigger$1(el, type) {
    const e = document.createEvent('HTMLEvents');
    e.initEvent(type, true, true);
    el.dispatchEvent(e);
}
// We are exporting the v-model runtime directly as vnode hooks so that it can
// be tree-shaken in case v-model is never used.
// 使用v-model的时候 看下它是如何影响实际的元素的
const vModelText = {
    // el创建后
    created(el, { modifiers: { lazy, trim, number } }, vnode) {
        // 获取v-model的自定义事件函数引用
        el._assign = getModelAssigner(vnode);
        const castToNumber = number || el.type === 'number';
        // 添加事件
        addEventListener(el, lazy ? 'change' : 'input', e => {
            // 输入汉字过程的组合事件
            if (e.target.composing)
                return;
            let domValue = el.value;
            if (trim) {
                domValue = domValue.trim();
            }
            else if (castToNumber) {
                domValue = toNumber(domValue);
            }
            // 触发用户的回调
            el._assign(domValue);
        });
        // 最终的change一次
        if (trim) {
            addEventListener(el, 'change', () => {
                el.value = el.value.trim();
            });
        }
        // 输入汉语之类的事件 最终输入完成后触发一次input上的回调
        if (!lazy) {
            addEventListener(el, 'compositionstart', onCompositionStart);
            addEventListener(el, 'compositionend', onCompositionEnd);
            // Safari < 10.2 & UIWebView doesn't fire compositionend when
            // switching focus before confirming composition choice
            // this also fixes the issue where some browsers e.g. iOS Chrome
            // fires "change" instead of "input" on autocomplete.
            addEventListener(el, 'change', onCompositionEnd);
        }
    },
    // set value on mounted so it's after min/max for type="range"
    mounted(el, { value }) {
        el.value = value == null ? '' : value;
    },
    // vnode更新 prop属性value发生改变了
    beforeUpdate(el, { value, modifiers: { trim, number } }, vnode) {
        el._assign = getModelAssigner(vnode);
        // avoid clearing unresolved text. #2302
        if (el.composing)
            return;
        if (document.activeElement === el) {
            if (trim && el.value.trim() === value) {
                return;
            }
            if ((number || el.type === 'number') && toNumber(el.value) === value) {
                return;
            }
        }
        const newValue = value == null ? '' : value;
        // 更新值
        if (el.value !== newValue) {
            el.value = newValue;
        }
    }
};
// checkbox绑定的其实可以是一个数组或者单个任意对象 数组中的内容是否有选中 主要依靠 looseIndexOf 来判断的
const vModelCheckbox = {
    created(el, _, vnode) {
        // 取出用户事件函数包裹体
        el._assign = getModelAssigner(vnode);
        // 添加事件
        addEventListener(el, 'change', () => {
            // 之前的值
            const modelValue = el._modelValue;
            // 现在el的值
            const elementValue = getValue(el);
            const checked = el.checked;
            const assign = el._assign;
            if (isArray(modelValue)) {
                // 判断当前value在不在之前的值数组中
                const index = looseIndexOf(modelValue, elementValue);
                const found = index !== -1;
                if (checked && !found) {
                    // 更新新的值进入
                    assign(modelValue.concat(elementValue));
                }
                else if (!checked && found) {
                    const filtered = [...modelValue];
                    filtered.splice(index, 1);
                    // 移除后 更新新值 主要是新数组对象
                    assign(filtered);
                }
            }
            // set结构也支持
            else if (isSet(modelValue)) {
                const cloned = new Set(modelValue);
                if (checked) {
                    cloned.add(elementValue);
                }
                else {
                    cloned.delete(elementValue);
                }
                assign(cloned);
            }
            // 单个checkbox
            else {
                assign(getCheckboxValue(el, checked));
            }
        });
    },
    // set initial checked on mount to wait for true-value/false-value
    mounted: setChecked,
    beforeUpdate(el, binding, vnode) {
        el._assign = getModelAssigner(vnode);
        setChecked(el, binding, vnode);
    }
};
// 设置当前值 且设置元素是否选中
function setChecked(el, { value, oldValue }, vnode) {
    el._modelValue = value;
    if (isArray(value)) {
        el.checked = looseIndexOf(value, vnode.props.value) > -1;
    }
    else if (isSet(value)) {
        el.checked = value.has(vnode.props.value);
    }
    else if (value !== oldValue) {
        el.checked = looseEqual(value, getCheckboxValue(el, true));
    }
}
// 单选框比较简单 绑定一个值
const vModelRadio = {
    created(el, { value }, vnode) {
        el.checked = looseEqual(value, vnode.props.value);
        el._assign = getModelAssigner(vnode);
        addEventListener(el, 'change', () => {
            el._assign(getValue(el));
        });
    },
    beforeUpdate(el, { value, oldValue }, vnode) {
        el._assign = getModelAssigner(vnode);
        if (value !== oldValue) {
            el.checked = looseEqual(value, vnode.props.value);
        }
    }
};
// select标签 绑定的就是一个数组
const vModelSelect = {
    created(el, { value, modifiers: { number } }, vnode) {
        const isSetModel = isSet(value);
        // 添加事件
        addEventListener(el, 'change', () => {
            const selectedVal = Array.prototype.filter
                .call(el.options, (o) => o.selected)
                .map((o) => number ? toNumber(getValue(o)) : getValue(o));
            el._assign(el.multiple
                ? isSetModel
                    ? new Set(selectedVal)
                    : selectedVal
                : selectedVal[0]);
        });
        el._assign = getModelAssigner(vnode);
    },
    // set value in mounted & updated because <select> relies on its children
    // <option>s.
    mounted(el, { value }) {
        setSelected(el, value);
    },
    beforeUpdate(el, _binding, vnode) {
        el._assign = getModelAssigner(vnode);
    },
    updated(el, { value }) {
        setSelected(el, value);
    }
};
// 判断value是否选中
function setSelected(el, value) {
    const isMultiple = el.multiple;
    if (isMultiple && !isArray(value) && !isSet(value)) {
        warn(`<select multiple v-model> expects an Array or Set value for its binding, ` +
                `but got ${Object.prototype.toString.call(value).slice(8, -1)}.`);
        return;
    }
    for (let i = 0, l = el.options.length; i < l; i++) {
        const option = el.options[i];
        const optionValue = getValue(option);
        if (isMultiple) {
            if (isArray(value)) {
                option.selected = looseIndexOf(value, optionValue) > -1;
            }
            else {
                option.selected = value.has(optionValue);
            }
        }
        else {
            if (looseEqual(getValue(option), value)) {
                el.selectedIndex = i;
                return;
            }
        }
    }
    if (!isMultiple) {
        el.selectedIndex = -1;
    }
}
// retrieve raw value set via :value bindings
// 取设置prop时候设置的原始值或者普通值 有些属性值会被string话 _value保留着原始对象值
function getValue(el) {
    return '_value' in el ? el._value : el.value;
}
// retrieve raw value for true-value and false-value set via :true-value or :false-value bindings
// checkbox获取用户设置的true false指定取什么值
function getCheckboxValue(el, checked) {
    const key = checked ? '_trueValue' : '_falseValue';
    return key in el ? el[key] : checked;
}
// 动态type v-model指令的实际实现
const vModelDynamic = {
    created(el, binding, vnode) {
        callModelHook(el, binding, vnode, null, 'created');
    },
    mounted(el, binding, vnode) {
        callModelHook(el, binding, vnode, null, 'mounted');
    },
    beforeUpdate(el, binding, vnode, prevVNode) {
        callModelHook(el, binding, vnode, prevVNode, 'beforeUpdate');
    },
    updated(el, binding, vnode, prevVNode) {
        callModelHook(el, binding, vnode, prevVNode, 'updated');
    }
};
// 动态type的v-model绑定 延迟执行 调用实际某种type
function callModelHook(el, binding, vnode, prevVNode, hook) {
    let modelToUse;
    switch (el.tagName) {
        case 'SELECT':
            modelToUse = vModelSelect;
            break;
        case 'TEXTAREA':
            modelToUse = vModelText;
            break;
        default:
            switch (vnode.props && vnode.props.type) {
                case 'checkbox':
                    modelToUse = vModelCheckbox;
                    break;
                case 'radio':
                    modelToUse = vModelRadio;
                    break;
                default:
                    modelToUse = vModelText;
            }
    }
    const fn = modelToUse[hook];
    fn && fn(el, binding, vnode, prevVNode);
}

// 一些事件modify的实际需要添加的执行代码
const systemModifiers = ['ctrl', 'shift', 'alt', 'meta'];
const modifierGuards = {
    stop: e => e.stopPropagation(),
    prevent: e => e.preventDefault(),
    self: e => e.target !== e.currentTarget,
    ctrl: e => !e.ctrlKey,
    shift: e => !e.shiftKey,
    alt: e => !e.altKey,
    meta: e => !e.metaKey,
    left: e => 'button' in e && e.button !== 0,
    middle: e => 'button' in e && e.button !== 1,
    right: e => 'button' in e && e.button !== 2,
    exact: (e, modifiers) => systemModifiers.some(m => e[`${m}Key`] && !modifiers.includes(m))
};
/**
 * @private
 */
// 带有modify的事件执行包裹函数
const withModifiers = (fn, modifiers) => {
    return (event, ...args) => {
        for (let i = 0; i < modifiers.length; i++) {
            const guard = modifierGuards[modifiers[i]];
            // 注意返回结果 对应上文的返回false这里才不会直接return 导致实际的 用户方法不执行了 所以上面的结果是 满足条件时 返回false 不满足 返回 true
            if (guard && guard(event, modifiers))
                return;
        }
        // 最后再执行用户设置的fn
        return fn(event, ...args);
    };
};
// Kept for 2.x compat.
// Note: IE11 compat for `spacebar` and `del` is removed for now.
const keyNames = {
    esc: 'escape',
    space: ' ',
    up: 'arrow-up',
    left: 'arrow-left',
    right: 'arrow-right',
    down: 'arrow-down',
    delete: 'backspace'
};
/**
 * @private
 */
// 带有键盘事件的包裹函数
const withKeys = (fn, modifiers) => {
    return (event) => {
        if (!('key' in event))
            return;
        const eventKey = hyphenate(event.key);
        // 只有带有指定内容的键盘某个键才符合条件
        if (
        // None of the provided key modifiers match the current event key
        !modifiers.some(k => k === eventKey || keyNames[k] === eventKey)) {
            return;
        }
        return fn(event);
    };
};

// v-show的实际内置指令配置
// 几个钩子函数 在vnode对应的阶段去触发执行
const vShow = {
    beforeMount(el, { value }, { transition }) {
        // 保留初始的原始值
        el._vod = el.style.display === 'none' ? '' : el.style.display;
        if (transition && value) {
            transition.beforeEnter(el);
        }
        else {
            setDisplay(el, value);
        }
    },
    mounted(el, { value }, { transition }) {
        if (transition && value) {
            transition.enter(el);
        }
    },
    updated(el, { value, oldValue }, { transition }) {
        if (!value === !oldValue)
            return;
        if (transition) {
            if (value) {
                transition.beforeEnter(el);
                setDisplay(el, true);
                transition.enter(el);
            }
            else {
                transition.leave(el, () => {
                    setDisplay(el, false);
                });
            }
        }
        else {
            setDisplay(el, value);
        }
    },
    beforeUnmount(el, { value }) {
        setDisplay(el, value);
    }
};
// 设置style的display的值
function setDisplay(el, value) {
    // 所谓的true就是恢复原始存在的值 否则就是display:none
    el.style.display = value ? el._vod : 'none';
}

Vue(v3.0.11)源码简析之一些公共工具方法

我们先从vue中用到的一些公共方法解读开始,它们往往是一些辅助性质的通用函数,被其他高级业务函数方法所调用:

/**
     * Make a map and return a function for checking if a key
     * is in that map.
     * IMPORTANT: all calls of this function must be prefixed with
     * \/\*#\_\_PURE\_\_\*\/
     * So that rollup can tree-shake them if necessary.
     */
    // 返回一个函数闭包 持有自由变量 map 缓存着之前遇到过的str被分隔后的字符集
    // 用于确定是否目标字符val是否出现在str中
    function makeMap(str, expectsLowerCase) {
        const map = Object.create(null);
        const list = str.split(',');
        for (let i = 0; i < list.length; i++) {
            map[list[i]] = true;
        }
        return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val];
    }
  
    /**
     * dev only flag -> name mapping
     */
    // 生成render函数的时候 给每个vnode节点后面根据当前节点渲染时候存在的动态属性 加上对应的辅助标志信息
    const PatchFlagNames = {
        // 当前node节点中出现了动态text 也就是插槽的情况
        [1 /* TEXT */]: `TEXT`,
        // 当前node节点中出现了动态class
        [2 /* CLASS */]: `CLASS`,
        // 当前node节点中出现了动态style
        [4 /* STYLE */]: `STYLE`,
        // 当前node节点中出现了v-bind:id 或者 :id 这样动态绑定的属性
        [8 /* PROPS */]: `PROPS`,
        // 当前node节点中出现了v-bind:[xxx] 或者 :[xxx] 这样动态key绑定的属性
        [16 /* FULL_PROPS */]: `FULL_PROPS`,
        // 当前node节点中出现了 v-model 和 其他普通事件(非click) 同时出现的情况
        [32 /* HYDRATE_EVENTS */]: `HYDRATE_EVENTS`,
        // 当前node节点中出现了 template + v-for产生的 顺序确定的多个子节点
        [64 /* STABLE_FRAGMENT */]: `STABLE_FRAGMENT`,
        // 当前node节点中出现了 v-for产生的 带有key多个子节点
        [128 /* KEYED_FRAGMENT */]: `KEYED_FRAGMENT`,
        // 当前node节点中出现了 v-for产生的 未带有key多个子节点
        [256 /* UNKEYED_FRAGMENT */]: `UNKEYED_FRAGMENT`,
        // 当前node节点中出现了 只有 ref属性 || 运行时指令如v-show等 而无其他动态prop的时候
        [512 /* NEED_PATCH */]: `NEED_PATCH`,
        // 当前node节点中出现了使用 待渲染插槽内容上出现了以下3种情况:1.if 2.for 3.动态插槽key名
        [1024 /* DYNAMIC_SLOTS */]: `DYNAMIC_SLOTS`,
        // 本来是个片段 但是只有一个有效子节点 其他都是注释节点的情况
        [2048 /* DEV_ROOT_FRAGMENT */]: `DEV_ROOT_FRAGMENT`,
        // 被静态提升的node节点
        [-1 /* HOISTED */]: `HOISTED`,
        // 拥有这个标记的node节点在patch渲染的会退出优化模式 但是它出现的时机还有进一步分析确定 todo ?
        [-2 /* BAIL */]: `BAIL`
    };
 /**
     * Dev only
     */
    // 在使用组件的时候 在某个组件内部写的 <com-a> ... </com-a> 中间的内容的所属几种类型
    const slotFlagsText = {
        // 优先级第三 1. 内容节点整体固定
        [1 /* STABLE */]: 'STABLE',
        // 优先级第一 2. 同上面的 DYNAMIC_SLOTS 场景
        [2 /* DYNAMIC */]: 'DYNAMIC',
        // 优先级第二 3. 非第2种情况下 如果内容中含有 转发的slot <com-a> ...<slot></slot> </com-a>
        [3 /* FORWARDED */]: 'FORWARDED'
    };

    // 我们可以在template中用到的全局变量白名单 而其他全局比变量在render的时候会触发代理的变量检查是否在组件实例上存在 从而抛出错误
    const GLOBALS_WHITE_LISTED = 'Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,' +
        'decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,' +
        'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt';
    // 构造检查函数
    const isGloballyWhitelisted = /*#__PURE__*/ makeMap(GLOBALS_WHITE_LISTED);

    // 按照一定格式输出错误信息 
    // let str = "aaddisdifdj\ndfsifjsiofsi\nfnisajfisjfsiffnmk\nfhnisfjsif" start = 10 end = 20
    // generateCodeFrame(str, start, end)
    // 输出如下:
    /**
     *  1  |  aaddisdifdj
           |            ^
        2  |  dfsifjsiofsi
           |  ^^^^^^^^
        3  |  fnisajfisjfsiffnmk 
     */
    // 在错误信息区间所在行分布的上下 range 这么多行内输出对应文案
    const range = 2;
    function generateCodeFrame(source, start = 0, end = source.length) {
        const lines = source.split(/\r?\n/);
        let count = 0;
        const res = [];
        for (let i = 0; i < lines.length; i++) {
            count += lines[i].length + 1;
            // 进入错误区间
            if (count >= start) {
                for (let j = i - range; j <= i + range || end > count; j++) {
                    // 非法场景 忽略
                    if (j < 0 || j >= lines.length)
                        continue;
                    const line = j + 1;
                    // 输出指定range内的原始文案
                    res.push(`${line}${' '.repeat(Math.max(3 - String(line).length, 0))}|  ${lines[j]}`);
                    const lineLength = lines[j].length;
                    // 处理错误的第一行
                    if (j === i) {
                        // push underline
                        // 第一行下面输出 ^^^^
                        const pad = start - (count - lineLength) + 1;
                        const length = Math.max(1, end > count ? lineLength - pad : end - start);
                        res.push(`   |  ` + ' '.repeat(pad) + '^'.repeat(length));
                    }
                    // 继续输出其他行
                    else if (j > i) {
                        // 仍在错误范围内
                        if (end > count) {
                            const length = Math.max(Math.min(end - count, lineLength), 1);
                            // 继续输出
                            res.push(`   |  ` + '^'.repeat(length));
                        }
                        count += lineLength + 1;
                    }
                }
                // 正负range范围内输出完毕 结束即可
                break;
            }
        }
        return res.join('\n');
    }

    // 这几个attr很有意思 我们平常写的绑定在目标元素上的attr或者prop 在dom元素的property都有对应的小写形式的key 但是下面这几个就不完整对等
    // 导致后面从虚拟vnode上映射prop或者attr到实际dom元素上时,会把下面几个从prop中区分出来 哪怕我们写的是 :readonly 这样的动态绑定写法 依旧会调用patchAttr而不是patchProp去处理
    /**
     * On the client we only need to offer special cases for boolean attributes that
     * have different names from their corresponding dom properties:
     * - itemscope -> N/A
     * - allowfullscreen -> allowFullscreen
     * - formnovalidate -> formNoValidate
     * - ismap -> isMap
     * - nomodule -> noModule
     * - novalidate -> noValidate
     * - readonly -> readOnly
     */
    const specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly`;
    const isSpecialBooleanAttr = /*#__PURE__*/ makeMap(specialBooleanAttrs);

    // 格式化返回style对象给调用者
    function normalizeStyle(value) {
        // 入参是数组
        if (isArray(value)) {
            const res = {};
            for (let i = 0; i < value.length; i++) {
                const item = value[i];
                // 数组中的可能是一个字符串行内样式的情况  或者 是对象或者数组 就递归解析
                const normalized = normalizeStyle(isString(item) ? parseStringStyle(item) : item);
                // 合并解析出来的子对象
                if (normalized) {
                    for (const key in normalized) {
                        res[key] = normalized[key];
                    }
                }
            }
            // 返回上一级调用
            return res;
        }
        // 最终返回的都是对象结构
        else if (isObject(value)) {
            return value;
        }
    }
    const listDelimiterRE = /;(?![^(]*\))/g;
    const propertyDelimiterRE = /:(.+)/;
    // 解析单行行内样式的字符串
    function parseStringStyle(cssText) {
        const ret = {};
        // 分割 ; 
        cssText.split(listDelimiterRE).forEach(item => {
            if (item) {
                const tmp = item.split(propertyDelimiterRE);
                // 分割 :
                // 建立键值对
                tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());
            }
        });
        return ret;
    }
    // 解析class 可选格式 字符、数组、对象
    function normalizeClass(value) {
        let res = '';
        if (isString(value)) {
            res = value;
        }
        else if (isArray(value)) {
            for (let i = 0; i < value.length; i++) {
                // 递归解析
                const normalized = normalizeClass(value[i]);
                if (normalized) {
                    // 拼接结果
                    res += normalized + ' ';
                }
            }
        }
        else if (isObject(value)) {
            for (const name in value) {
                if (value[name]) {
                    // 拼接结果
                    res += name + ' ';
                }
            }
        }
        // 返回给上级调用者
        return res.trim();
    }

    // 一些元素标签
    // These tag configs are shared between compiler-dom and runtime-dom, so they
    // https://developer.mozilla.org/en-US/docs/Web/HTML/Element
    const HTML_TAGS = '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,' +
        'summary,template,blockquote,iframe,tfoot';
    // https://developer.mozilla.org/en-US/docs/Web/SVG/Element
    const SVG_TAGS = 'svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,' +
        'defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,' +
        'feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,' +
        'feDistanceLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,' +
        'feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,' +
        'fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,' +
        'foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,' +
        'mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,' +
        'polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,' +
        'text,textPath,title,tspan,unknown,use,view';
    // 自闭和元素
    const VOID_TAGS = 'area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr';
    // 构建对应的检测函数
    const isHTMLTag = /*#__PURE__*/ makeMap(HTML_TAGS);
    const isSVGTag = /*#__PURE__*/ makeMap(SVG_TAGS);
    const isVoidTag = /*#__PURE__*/ makeMap(VOID_TAGS);

    // 数组之间的 非严格相等 检测
    function looseCompareArrays(a, b) {
        if (a.length !== b.length)
            return false;
        let equal = true;
        for (let i = 0; equal && i < a.length; i++) {
            equal = looseEqual(a[i], b[i]);
        }
        return equal;
    }
    // 非严格相等 主要是用于 我们在例如select这样的表单元素上绑定了v-model 而值指向对象形式 这时候为了校验2个对象的值是否相等来判断是否处于选中状态 所以需要这样实现的比较函数
    function looseEqual(a, b) {
        if (a === b)
            return true;
        // date对象
        let aValidType = isDate(a);
        let bValidType = isDate(b);
        if (aValidType || bValidType) {
            return aValidType && bValidType ? a.getTime() === b.getTime() : false;
        }
        // 数组
        aValidType = isArray(a);
        bValidType = isArray(b);
        if (aValidType || bValidType) {
            return aValidType && bValidType ? looseCompareArrays(a, b) : false;
        }
        // 对象object
        aValidType = isObject(a);
        bValidType = isObject(b);
        if (aValidType || bValidType) {
            /* istanbul ignore if: this if will probably never be called */
            if (!aValidType || !bValidType) {
                return false;
            }
            const aKeysCount = Object.keys(a).length;
            const bKeysCount = Object.keys(b).length;
            if (aKeysCount !== bKeysCount) {
                return false;
            }
            // 逐个key对比 不校验原型链上的
            for (const key in a) {
                const aHasKey = a.hasOwnProperty(key);
                const bHasKey = b.hasOwnProperty(key);
                if ((aHasKey && !bHasKey) ||
                    (!aHasKey && bHasKey) ||
                    !looseEqual(a[key], b[key])) {
                    return false;
                }
            }
        }
        // string
        return String(a) === String(b);
    }
    // 不严格相等 查找
    function looseIndexOf(arr, val) {
        return arr.findIndex(item => looseEqual(item, val));
    }

    /**
     * For converting {{ interpolation }} values to displayed strings.
     * @private
     */
    // 以字符形式 输出插槽的值 
    const toDisplayString = (val) => {
        return val == null
            ? ''
            : isObject(val)
                // 注意 replacer 函数的实现
                ? JSON.stringify(val, replacer, 2)
                : String(val);
    };
    const replacer = (_key, val) => {
        // Map对象的字符展示形式 转化为 [Map(1)]: { a => : xxx ,...} 这样的
        if (isMap(val)) {
            return {
                [`Map(${val.size})`]: [...val.entries()].reduce((entries, [key, val]) => {
                    entries[`${key} =>`] = val;
                    return entries;
                }, {})
            };
        }
        // Set对象 Set(1): [xxx]
        else if (isSet(val)) {
            return {
                [`Set(${val.size})`]: [...val.values()]
            };
        }
        // 其他非object array类型的对象类型 如 function 之类的 直接 String 即可
        else if (isObject(val) && !isArray(val) && !isPlainObject(val)) {
            return String(val);
        }
        // 默认情况 直接返回
        return val;
    };

    // 空对象 空数组 可做为初始值使用 方便判断
    const EMPTY_OBJ = Object.freeze({})
        ;
    const EMPTY_ARR = Object.freeze([]);
    // 空函数 一般作为默认值占位使用
    const NOOP = () => { };
    /**
     * Always return false.
     */
    // 返回false的默认值函数
    const NO = () => false;
    // 判断事件prop的正则 onClick 之类的
    const onRE = /^on[^a-z]/;
    const isOn = (key) => onRE.test(key);
    // v-model产生的2个prop 一个是 :value="xxx" 值绑定 一个是类似 onUpdate:modelValue="eventHandler..." 这样的vue内置自定义事件
    const isModelListener = (key) => key.startsWith('onUpdate:');
    const extend = Object.assign;
    // 数组arr移除el
    const remove = (arr, el) => {
        const i = arr.indexOf(el);
        if (i > -1) {
            arr.splice(i, 1);
        }
    };
    // 自有属性 非原型链上的
    const hasOwnProperty = Object.prototype.hasOwnProperty;
    const hasOwn = (val, key) => hasOwnProperty.call(val, key);
    const isArray = Array.isArray;
    // 精确类型判断
    const isMap = (val) => toTypeString(val) === '[object Map]';
    const isSet = (val) => toTypeString(val) === '[object Set]';
    const isDate = (val) => val instanceof Date;
    const isFunction = (val) => typeof val === 'function';
    const isString = (val) => typeof val === 'string';
    const isSymbol = (val) => typeof val === 'symbol';
    // 非null的object
    const isObject = (val) => val !== null && typeof val === 'object';
    const isPromise = (val) => {
        return isObject(val) && isFunction(val.then) && isFunction(val.catch);
    };
    const objectToString = Object.prototype.toString;
    const toTypeString = (value) => objectToString.call(value);
    // 只取 最后的 类型字符串
    const toRawType = (value) => {
        // extract "RawType" from strings like "[object RawType]"
        return toTypeString(value).slice(8, -1);
    };
    // {...} 这样的 null不算 它是 [object Null]
    const isPlainObject = (val) => toTypeString(val) === '[object Object]';
    // 整数key
    const isIntegerKey = (key) => isString(key) &&
        key !== 'NaN' &&
        key[0] !== '-' &&
        '' + parseInt(key, 10) === key;
    // vue保留 不给我们用的几个prop名字 '' key ref 和 6个vnode钩子事件
    const isReservedProp = /*#__PURE__*/ makeMap(
        // the leading comma is intentional so empty string "" is also included
        ',key,ref,' +
        'onVnodeBeforeMount,onVnodeMounted,' +
        'onVnodeBeforeUpdate,onVnodeUpdated,' +
        'onVnodeBeforeUnmount,onVnodeUnmounted');
    // 带有缓存功能的一些转换字符串格式的函数
    // 闭包使用+1 返回的匿名函数 持有自由变量 cache 和 fn cache缓存处理过的字符结果 用户传入的fn实现转换
    const cacheStringFunction = (fn) => {
        const cache = Object.create(null);
        return ((str) => {
            const hit = cache[str];
            return hit || (cache[str] = fn(str));
        });
    };
    const camelizeRE = /-(\w)/g;
    /**
     * @private
     */
    // aa-bb-ccc 变 aaBbCc 连字符变驼峰
    const camelize = cacheStringFunction((str) => {
        return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
    });
    const hyphenateRE = /\B([A-Z])/g;
    /**
     * @private
     */
    // aaBbCc 变 aa-bb-cc 驼峰变 连字符
    const hyphenate = cacheStringFunction((str) => str.replace(hyphenateRE, '-$1').toLowerCase());
    /**
     * @private
     */
    // 首字符大写 abc -> Abc
    const capitalize = cacheStringFunction((str) => str.charAt(0).toUpperCase() + str.slice(1));
    /**
     * @private
     */
    // 事件名字转换 change -> onChange
    const toHandlerKey = cacheStringFunction((str) => (str ? `on${capitalize(str)}` : ``));
    // compare whether a value has changed, accounting for NaN.
    // 一般情况的值 改变后 value !== oldValue 都是成立的 只有NaN在未曾改变的情况 NaN !== NaN 也成立 所以需要后面的 (value === value || oldValue === oldValue) 
    // 因为 NaN === NaN 不成立 借助2个条件 我们可以正确识别 值为NaN时候是否真的发生改变了以及普通场景的值改变
    const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue);
    // 批量执行函数数组
    const invokeArrayFns = (fns, arg) => {
        for (let i = 0; i < fns.length; i++) {
            fns[i](arg);
        }
    };
    // 添加一个属性给obj
    const def = (obj, key, value) => {
        Object.defineProperty(obj, key, {
            configurable: true,
            // 不可枚举
            enumerable: false,
            value
        });
    };
    // 数字化
    const toNumber = (val) => {
        const n = parseFloat(val);
        return isNaN(n) ? val : n;
    };
    // 确定顶层对象 浏览器运行环境下 是 window
    let _globalThis;
    const getGlobalThis = () => {
        return (_globalThis ||
            (_globalThis =
                typeof globalThis !== 'undefined'
                    ? globalThis
                    : typeof self !== 'undefined'
                        ? self
                        : typeof window !== 'undefined'
                            ? window
                            : typeof global !== 'undefined'
                                ? global
                                : {}));
    };

Vue(v3.0.11)源码简析之提取组件和指令对象的相关实现

render函数中会根据name来提取对应的自定义组件和指令对象:

// 2类需要根据name解析出对应的配置对象
const COMPONENTS = 'components';
const DIRECTIVES = 'directives';
/**
 * @private
 */
// 根据name解析出组件原始对象
function resolveComponent(name, maybeSelfReference) {
    return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name;
}
const NULL_DYNAMIC_COMPONENT = Symbol();
/**
 * @private
 */
// 动态组件的解析
function resolveDynamicComponent(component) {
    if (isString(component)) {
        return resolveAsset(COMPONENTS, component, false) || component;
    }
    else {
        // invalid types will fallthrough to createVNode and raise warning
        return (component || NULL_DYNAMIC_COMPONENT);
    }
}
/**
 * @private
 */
// 根据name解析出指令配置对象
function resolveDirective(name) {
    return resolveAsset(DIRECTIVES, name);
}
// implementation
// 具体实现
function resolveAsset(type, name, warnMissing = true, maybeSelfReference = false) {
    // 取得当前渲染实例 或者 组件实例
    const instance = currentRenderingInstance || currentInstance;
    if (instance) {
        const Component = instance.type;
        // explicit self name has highest priority
        // name如果和当前实例名字匹配 就用它 最高优先级
        if (type === COMPONENTS) {
            const selfName = getComponentName(Component);
            if (selfName &&
                (selfName === name ||
                    selfName === camelize(name) ||
                    selfName === capitalize(camelize(name)))) {
                return Component;
            }
        }
        // 然后从 局部组件 和 全局组件 配置中取
        const res = 
        // local registration
        // check instance[type] first for components with mixin or extends.
        resolve(instance[type] || Component[type], name) ||
            // global registration
            resolve(instance.appContext[type], name);
        // 失败且可以引用自身的话 也返回组件实例的组件配置
        if (!res && maybeSelfReference) {
            // fallback to implicit self-reference
            return Component;
        }
        if (warnMissing && !res) {
            warn(`Failed to resolve ${type.slice(0, -1)}: ${name}`);
        }
        return res;
    }
    else {
        warn(`resolve${capitalize(type.slice(0, -1))} ` +
            `can only be used in render() or setup().`);
    }
}
// 根据name从注册对象中取出对象
function resolve(registry, name) {
    return (registry &&
        (registry[name] ||
            registry[camelize(name)] ||
            registry[capitalize(camelize(name))]));
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.