kotlin html builder 到底是如何工作的?

How does kotlin's htmlx builder works under the hood exactly?

这是一个片段,解释了 htmlx 构建器的某些部分(来自文档):

protected fun <T : Element> initTag(tag: T, init: T.() -> Unit): T {
    tag.init()
    children.add(tag)
    return tag
}

重点是children.add(tag),所以我们可以声明:

html {
    head {}
    body {}
}

因为head和body是html的成员函数。 但是 DIV 标签呢?我可以在任何地方声明 div,而且我可以这样写:

someEnclosingTag { (1..3).forEach { div {+"MyCustomDivFromEverywhere"}  }}

封闭的 lambda 如何知道可以在任何地方声明的 "child" lambda(以及分别将 'child' 标记添加到整个 html)?

如果我有什么地方不对,请指正。

更新

根据答案,我以以下脏虚拟代码结束,它显示了函数作用域(闭包的某些方面)和隐式接收器遗漏(希望它能以某种方式帮助某人):

fun main(args: Array<String>) {
    Child().childFun {
        /*childFun lambda receiver implements parent1Fun lambda receiver, so the receiver can be omitted*/
        parent1Fun {
            /*call from Child.() -> Unit receiver*/
            someIntrestingFun()
        }
        /*same as with parent1Fun*/
        parent2Fun {
            /*call from Child.() -> Unit receiver*/
            someIntrestingFun()
        }
    }
}

fun Child.childFun(lambda: Child.() -> Unit): Child = genericFun(Child(), lambda)

fun ParentInt1.parent1Fun(lambda: ParentInt1.() -> Unit): ParentInt1 = genericFun(Child(), lambda)

fun ParentInt2.parent2Fun(lambda: ParentInt2.() -> Unit): ParentInt2 = genericFun(Child(), lambda)

fun <T> genericFun(instance:T, lambda:T.() -> Unit): T {
    instance.lambda()
    return instance
}

interface ParentInt1
interface ParentInt2

class Child : ParentInt1, ParentInt2 {
    fun someIntrestingFun() { println(this) }
}

您可以在语言参考中找到有关构建此类 DSL 的技术的更多信息,请参阅:Type-Safe Builders,该页面提供了一个示例 HTML 构建器(尽管 kotlinx.html 更多复杂)。

How does enclosing lambda knows about "child" lambdas that can be declared everywhere?

这就是函数解析的工作原理:当你有嵌套的 lambdas 时,无论是否有接收器,你都可以在内部的 lambda 中调用 member/extension 外部接收器的函数(* ),这是一个非常综合的例子:

with(arrayListOf<String>()) {
    with(hashMapOf<Int, String>()) {
        // You can call both functions of `ArrayList` and `HashMap`:
        add("foo")
        put(1, "bar")

        // Even in the nested lambdas with no receiver:
        baz.forEach { put(it, "it = $it") }
    }
}

(*):在高级 DSL 中,可以使用 @DslMarker 来限制作用域,以避免意外地从外部作用域调用接收器上的函数。