V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yagamil
V2EX  ›  程序员

为啥 js 语言里面 那么喜欢嵌套,匿名

  •  
  •   yagamil · 2023-05-01 16:34:42 +08:00 · 5288 次点击
    这是一个创建于 573 天前的主题,其中的信息可能已经有所发展或是发生改变。

    作为一个后端,算法的开发者,平时会写点简单的页面。

    不过看着 js,vue 的一些语法,感觉可读性非常差,函数喜欢用匿名的,在一个函数传参里面,把函数作为参数传入,导致的是,这个函数的参数 入参 非常冗长,不用 IDE ,根本不清楚这个匿名函数在哪里结束,因为里面可能还嵌套里其他参数。

    好好定义一个函数名,设置一个好的名称,好好写,不行么?

    还有链式调用有点泛滥。

    这样做的原因是做什么?

    50 条回复    2023-05-21 23:25:48 +08:00
    GoldenSheep
        1
    GoldenSheep  
       2023-05-01 16:42:31 +08:00 via Android
    不懂,看 js 给人一种精神升华的感觉
    jisuowei
        2
    jisuowei  
       2023-05-01 16:49:48 +08:00   ❤️ 1
    众所周知,编程的时候有一半的时间都耗费在起名字上(狗头
    codehz
        3
    codehz  
       2023-05-01 16:55:25 +08:00 via iPhone
    还不是因为主流的事件处理方式(浏览器提供的,不是你想改就能改的),要求提供一个函数做回调,这种情况下,写一个函数名意味着函数声明的位置和使用的位置就有一定距离,原地写 function 也有太长的问题
    还有就是,箭头函数不会有 this 的问题,不需要外面设置一个 var self = this 这样的写法(虽然现在大家都不用 class 了,不存在 this 的问题)
    虽然我觉得还是视角的问题,把回调看作延续就会比较自然了
    hello2090
        4
    hello2090  
       2023-05-01 16:59:18 +08:00
    你完全可以按照你喜欢的不匿名,不链式来写啊
    churchill
        5
    churchill  
       2023-05-01 17:02:33 +08:00
    第一点个人认为不是问题,其他语言像 c++ 一样也喜欢用 lamda ,参数再配上 auto 关键字,没有一个好的代码提示工具基本没法读
    第二点不能同意更多,但有的人认为方法链可读性更高
    wdssmq
        6
    wdssmq  
       2023-05-01 17:02:51 +08:00
    PHP: 可变函数 - Manual
    https://www.php.net/manual/zh/functions.variable-functions.php

    就,,感觉差不多,匿名还能省个名字。。
    Leviathann
        7
    Leviathann  
       2023-05-01 17:03:13 +08:00
    你用 java7 的?
    shyangs
        8
    shyangs  
       2023-05-01 17:05:17 +08:00
    你先說你入門的語言和用的最多的語言是什麼.

    母語會影響一個人的思維的。
    veike
        9
    veike  
       2023-05-01 17:28:31 +08:00
    js 里就是有大量的匿名,不过我看很多文档都推荐给每个函数起一个名字。
    还有链式调用滥用,到什么程度算滥用呢?
    大部分编程语言都能实现链式操作,js 里大量的链式调用我认为可读性并没有很糟糕。
    并且可读性和每个人的书写习惯有关。jetbrains 的 IDE 里有每行最多多少个字符的辅助线,不知道大家会参照那个辅助线来写代码不
    surfwave
        10
    surfwave  
       2023-05-01 17:47:14 +08:00
    后端的习惯了 OO 的,要上手前端 js 这块,还是需要学学函数式编程,习惯了就好了
    DOLLOR
        11
    DOLLOR  
       2023-05-01 17:54:54 +08:00   ❤️ 17
    别人也可以反过来问,
    为什么明明一个 function 就能解决的事情,某些编程语言非要写成一个 class 把方法封装起来,然后需要调用的时候,还要 new 一遍,再调用里面的方法。
    Trello
        12
    Trello  
       2023-05-01 18:08:27 +08:00
    哦豁,说到嵌套,那绝对比不过 flutter 鹅心,吐了。
    caqiko
        13
    caqiko  
       2023-05-01 18:14:49 +08:00 via Android
    匿名的剪头回调函数应该是 js 推荐的写法
    避免暴露太多不必要的对象给全局
    Aloento
        14
    Aloento  
       2023-05-01 18:18:00 +08:00
    毕竟是一个没有命名空间隔离的语言
    rocmax
        15
    rocmax  
       2023-05-01 18:18:54 +08:00 via Android   ❤️ 1
    seers
        16
    seers  
       2023-05-01 18:19:59 +08:00
    虽然可读性差,但是写起来感觉爽飞,尤其是过几个月回头看自己写的,酸爽
    ahhtree
        17
    ahhtree  
       2023-05-01 18:22:12 +08:00 via Android
    链式调用指的一般是函数式编程,函数式编程可读性可是很好的(屎坑除外)
    makelove
        18
    makelove  
       2023-05-01 18:23:53 +08:00   ❤️ 3
    写了多年 python 和 js,我最讨厌 python 的一点是没有多行匿名函数
    humbass
        19
    humbass  
       2023-05-01 18:24:03 +08:00
    我是先学 javascript ,后面学的 C

    当你发现 C 的复杂参数只可以传指针时,就瞬间明白 Javascript 的好处。 只可惜各互联网大佬不给力,无法做出一种直接可以编译 javacript 的方法.
    Al0rid4l
        20
    Al0rid4l  
       2023-05-01 18:30:33 +08:00   ❤️ 1
    黑点动态类型啥的就算了, lambda 表达式也来黑? 这真黑不动吧

    你怎么不去问丘奇为什么要发明 lambda 演算呢

    不得不讲上来就跟着谭浩强学 C 是容易把人学傻, 以为自己懂了所谓计算机的本质
    nomagick
        21
    nomagick  
       2023-05-01 18:32:45 +08:00
    1. 你说得太对了
    2. 不光是 vue, react 的可读性一样差,这是前端行业目前的结构性问题
    3. 这个问题不是 javascript 的问题,甚至语言层面上有 typescript 解决了基本上所有问题
    4. 根本上来讲是人的问题,而且是领路人的问题。
    lower
        22
    lower  
       2023-05-01 18:34:00 +08:00
    前端如果不是写库或框架,只处理页面事件和后台交互的话,逻辑可能一般都比较短、也很分散,还可能经常业务会变化……
    链式调用 不是因为回调地狱问题而搞得语法糖么?
    ppooqq
        23
    ppooqq  
       2023-05-01 18:48:12 +08:00
    js 就是一个缝缝补补过来的语音
    liberty1900
        24
    liberty1900  
       2023-05-01 18:57:00 +08:00
    这标题就有引导性。语言只是规范,规范里可从没强制要使用匿名函数和嵌套
    TWorldIsNButThis
        25
    TWorldIsNButThis  
       2023-05-01 19:03:56 +08:00 via iPhone
    @Trello flutter 的嵌套是什么概念
    没有 jsx 的 react 那种吗
    anguiao
        26
    anguiao  
       2023-05-01 19:06:41 +08:00
    如果一个函数没有长到难以理解的话,为什么一定要拆分出来呢?
    只用一次的函数,更没有必要专门起个名字。放在上下文里面,自然就知道它是用来干什么的。
    banricho
        27
    banricho  
       2023-05-01 19:10:25 +08:00

    不是……难道不是应该先问有没有再问为什么吗?
    我咋就没遇到这个情况,你看的啥代码?
    nbhaohao
        28
    nbhaohao  
       2023-05-01 19:20:40 +08:00
    因为在 JS 里, 函数是第一公民?
    liuzhifei
        29
    liuzhifei  
       2023-05-01 19:32:01 +08:00
    用 react
    janus77
        30
    janus77  
       2023-05-01 21:01:31 +08:00
    他不是面向对象的
    GeruzoniAnsasu
        31
    GeruzoniAnsasu  
       2023-05-01 21:20:05 +08:00   ❤️ 2
    感觉楼上都没说到点子上。

    「喜欢用匿名函数」的原因是,你正在使用的 function 要求传入一个 transformer , 由于这个 transformer 往往是定域的局部逻辑,因此基本上不会复用。不会复用,也就不用非得为它赋予一个公共名字,不需要命名自然就不命名了。

    然后,为什么 js 写的代码更容易见到「传入 transformer 」这样的约定,本质上是因为前端的代码究其根本就是一系列状态转换函数。我们可以把 fetch 后端数据到渲染成 html 这个流程抽象为有一个 函数 F ,对后端传来的 json O 应用 F() 得到我们想要的 html:document = F(O) = "<html>...</html>"

    这时候应该就很容易理解这与函数式编程的理念和思维方法不谋而合,我们逐渐把 F 细分,F(O) = Apply(O, Render1, Render2, ...) = Apply(O, (o)=>{renderComponent1(o.part1),renderComponent2(o.part2)},Render2, ...) ,然后把其中的某个部分,比如 renderComponent1 挖空逻辑变为接口,它自然就要求实现者实现具体的逻辑了:

    type O = {part1:any}

    function renderComponent1(o:O,impl:(o:O)=>HTMLElement) { // 要求传入一个把 O 转换为 DOM 的函数
    document.appendChild(impl(o))
    }

    //你实际要写的
    export function impl(o:O):HTMLElement{
    const e = new HTMLDivElement()
    e.innerText = o.part1
    return e
    // return ( <div>{o.part1}</div> )
    }

    这时候应该可以理解为什么传递函数作为参数是必要且常见的写法了。

    不过这时候还有另一个问题,为什么非得是匿名函数不能是 function
    —— 一句话来说,只有匿名函数(箭头函数)才可以完美 capture 上下文,这与 js 的语言债有关,作为半吊子就不展开了,可以自行翻阅关于 this 的前端八股文。
    codehz
        32
    codehz  
       2023-05-01 21:29:35 +08:00
    @GeruzoniAnsasu 最后那点其实不太对(
    既然都 fp 了,还用啥 this 啊🤔️,没有 this 那用啥都差不多
    还不是主要为了少打几个字(
    chihiro2014
        33
    chihiro2014  
       2023-05-01 21:32:40 +08:00
    js 和 ts 的对象生命周期很迷
    autoxbc
        34
    autoxbc  
       2023-05-01 22:32:12 +08:00
    感觉回答的都不对,具名和匿名没有本质区别,只是排版上具名是扁平结构,匿名是嵌套结构。而很多炫技偏好的人,或者其他语言母语者,总是迷信把代码写成一行最酷,造成横向上嵌套过多

    个人遵循一个编码习惯,对于括号和花括号嵌套,横向最多两层,第三层必须展开

    // 这种写法不可接受,callback 部分有三层嵌套
    initFn( callee , () => target.observeNode( () => callee.callback() ) ) ;

    // 展开后会变成这样,读起来就没有负担
    initFn( callee , () => {
    target.observeNode( () => callee.callback() );
    } ) ;
    n18255447846
        35
    n18255447846  
       2023-05-01 22:42:45 +08:00
    @humbass 我也是正学 c ,,函数不能嵌套有点难受,还不能动态分配内存。不过 c 可以传个函数指针进去
    yagamil
        36
    yagamil  
    OP
       2023-05-02 00:02:00 +08:00
    @nomagick 我也觉得是,python 等语言也有回调,但人家会把回调抽出来,写成函数,即使不复用。

    难到因为不复用,只用一次,就不能好好给一个规范的名字? 你们试过改过一个别人写的函数,超过几百行的吗?

    当然一次性的项目,怎么写都没问题,只要你后面保证不会再看的话。。。
    fox0001
        37
    fox0001  
       2023-05-02 06:37:55 +08:00 via Android
    @Trello #12 Flutter ,深有体会,特别需要依赖 IDE 才能更好地阅读…不过,主要代码作者可以优化一下。
    dvsilch
        38
    dvsilch  
       2023-05-02 09:35:12 +08:00
    用 lambda 通常是因为需要用到的变量生命周期只在某个函数作用域内...尤其是各种事件回调限定了入参,就只能自己考虑怎么 capture 上下文了
    uni
        39
    uni  
       2023-05-02 13:10:18 +08:00
    问题的关键不在于匿名函数吧,而在于没有用 ts 好好定义类型吧
    虽然有点偏激,但是类型是否清晰是我判断这个代码是不是带脑子的标准
    uni
        40
    uni  
       2023-05-02 13:11:20 +08:00
    至于看函数是否结束,对我来说仅仅是代码格式化和括号高亮颜色的问题……
    FrankHB
        41
    FrankHB  
       2023-05-02 15:51:23 +08:00
    @Al0rid4l 其实和为什么没多大关系,虽然允许遵循链式命名解析的变量(名)算是 lambda 演算的主要贡献,lambda 演算的命名就只有约束变量(函数参数)这个地方能用。而现代语言更常见的命名依赖的环境这种抽象是在 Lisp 用动态作用域实现 lambda 演算变体时提出的副产品(相比纯 lambda 演算依赖 capture 和 capture avoidance substitution ),并且环境的实现方式( alist )因为保存了状态恰好还同时更加兼容词法作用域和非纯函数式的用法( impure λv calculus ),能够直接平替没作用域的符号表而已。
    所以也不是学 C 学傻,基本上不清楚一些历史的,默认只能当作都是傻的。
    FrankHB
        42
    FrankHB  
       2023-05-02 16:21:42 +08:00
    @yagamil 明明命名困难是两大计算机科学难题之一,你不在乎就无所谓了?
    你这首先用复用不复用来判断命名的方式是病,得治。

    使用符号命名是有明确的目的性的,就是塞入语义上无关紧要(操作语义上就是某种 alpha renaming ;虽然遇到反射会失效)但有确切含义的元数据来辅助人类阅读者改善推理,同时语言实现不影响语义等价翻译(比如编译优化)的可行性。后者可以在语义上约定自己的假设而同时能被机器改善推理(比如 devirtualization )。
    其实这就是所谓的封装性(encapsulating)的确切内涵。因为教学质量的普遍垃圾,很少有人理解,不管是 access control 这样在 name resolution 上加 hook 还是 inclusion polymorphism 这样在 call 上加 hook ,都只是 encapsulation 的实现细节而已。

    你若只是因为要让看得清楚,而不管这里的原始目的,这是胡搅蛮缠。因为只是为了看清楚,而不是区分“这个不是那个”这种依赖元数据内容的场合,完全可以用编辑器辅助的纯语法方式(例如高亮边界)解决。

    一般地,在一个合理的设计中要求实体具名,充分必要条件是这个命名属于用户关心的 API 的一部分——只有这种场合人理解命名的冗余含义才能确保可能帮助准确地正确使用(当然前提是命名对了)而不是干扰人类读者的冗余噪声。否则,都是耍流氓。

    有一种常见的误解是关于人为在源代码(而非实现内部变换)中引入“中间变量”的必要性。
    实际上如果不考虑一等状态(比如 C++的那种 RAII ),任意的显式声明引入的中间变量在逻辑上是冗余的,只不过因为考虑读者的脑容量有限,避免发生 register spill 之类的问题,所以有的作者才试图这样做。
    但是,这种中间变量完全可以 canonicalize 成 let form 。(学 ALGOL-like 语言学傻的不容易理解这一点,因为不知道 block 实际上是 lambda 的 derivation——用 JS 的话来说就是 IIFE 。)
    这种情况下,到底什么变量才是真正有资格当中间变量的问题才会显著——只有能被复用时,才配被提升到 let 的变量名的位置上。
    那么不被复用的复杂表达式可读性差怎么办?很简单,拆成具名函数的调用。引入的函数名就不是这种中间变量了,它可以是内部 API ,即便只被用到了一次。
    之所以函数名和普通的变量名区别对待,是因为具名函数的实现默认是封装的(函数体不可见),因此它有资格单独占据名称。
    当然,如果考虑一等状态,比如 lifetime 这种依赖对象自身存在的情况,那就有复杂多了的例外——因为存在隐含的资源释放,实际上有类似函数调用的作用。不过,遵循约定的代码反而会更清晰可读:如果严格遵循“不复用的对象不在 let form 中出现为变量名”,人很容易推理出为什么需要有孤零零(甚至一次都没用到)的对象;反之就麻烦多了,读者要自行验证这里到底复用了几次才放心。结果,在这个上下文中尽量排除没有复用的具名对象,反倒更合理了。

    题外话,显式类型(explicit typing)和命名的必要性同等,也就是在 API 上才配用。所以 bb 什么 auto 啊 var 啊看不清楚类型的,那也是耍流氓。
    FrankHB
        43
    FrankHB  
       2023-05-02 16:50:15 +08:00
    @FrankHB 严格来说,现在大多语言支持的 lambda 都是 applicative 的,也就是实际参数必须在调用前被严格求值,那么所有对应的形参和函数体默认都是封装的(编译器能够自由重写其内容而不管源代码里到底用了什么命名),除非有 MIT Scheme 那种 procedural-environment 这样的反射功能,或者另外严格约定基于源代码等价性的等于操作之类的奇葩设计。但如果函数不仅仅是 applicative 而可能是 operative 的(包括一般的过程宏或者 vau 这样的 FEXPR ctor 引入的匿名函数),实际参数可以非严格求值而直接把 AST 这样暴露源代码信息的东西扔进去计算(换种说法,默认就对源代码总是有完全反射),那么函数体封装和不封装之间区别就大了去了:不封装的函数会要求实现单独证明语义等价之后才允许变换,这一般是不可能的(除非预知实际参数是什么内容,做 partial evaluation )。

    具名函数和匿名函数在这里的习惯性差别是,前者是否使用单独的语法形式(而非把匿名函数当初值进行变量定义的方式引入)的选择是未指定的。因此前者能够具有更严格的封装性要求,例如完全可以在语言规则中指定假定函数体就是封装的。ALGOL-like 的语言中函数声明和定义分离其实就依赖这种假设。
    FrankHB
        44
    FrankHB  
       2023-05-02 17:47:50 +08:00
    @FrankHB 还要做个语言限制的修正。
    上文所述的 API ,不仅仅是环境中提供访问的函数名,明确包括形参名称这种 lambda calculus 中唯一提供的命名( bound variable )的方式。
    实际情况是,具名函数的函数名被当作 API 的一部分,因为它指称的对象(函数)的内部结构是默认封装的,所以也就函数名以及类型这样的外部特征允许标识(identify) 一个 API 了。更何况还有基于具名函数的 ad-hoc polymorphism:重载。
    然而函数以外的普通的对象的表示其实也是封装的,甚至某种意义上是更加封装的,内部结构同样不保证总是清楚唯一( ABI 的意义上例外)。那么为什么这些对象名不是 API 的一部分?因为两者的实现难度存在差别:实现的语义等价变换会重写函数体(或者 basic block 之类的变体),但是重写更一般的对象内部结构会依赖更困难和特设(ad-hoc)的方式来证明语义等价而不属于标准的优化技术(比如把整数和浮点数类型相互替换需要证明计算结果没有误差没有 fenv 副作用),所以一般不考虑。因此,把一个非函数的对象的结构暴露到 API 层次,容易混淆接口和实现的边界。所以就算全局对象作为 API 的一部分,一般也不会提供字段这样的内部表示,而是具体函数功能的一部分配置(典型例子是语言提供的标准流对象)。
    但是,参数名在实现内部实际上还遵循一种更特殊普遍的 API:naming convention 。大多数情况下,只有一个函数形参命名成 x ,只要没其它嵌套作用域冲突,就已经够可读了。循环不变量的 i 、j 、k 同理。要强行提供更具体的命名反倒不一定更清晰,因为原始目的就没有这个意思。这种约定在大多数语言中不被视为 API ,因为大多数语言压根就没提供足够的反射机制来对变量名直接进行操作。但是,如果语言允许局部的完全反射并提供充分的操作(如 Lisp-like 的 symbol->string/string->symbol ),对代码对应的 AST 直接进行元编程,应该怎么命名变量名的 pattern 就是 API 的一部分。只不过这类 API 基本只会在自动化代码审查的元程序中用到,而且通常不像函数名和类型一样会被提前静态检查。
    其它语言中这种情况相当少见,不过不是没有:例如 C 的宏允许做字符串拼接生成参数;例如 #pragma GCC poison 。无论如何,这些只能生成而无法反射的用法都相当恶心,所以缺少关注。
    icenine
        45
    icenine  
       2023-05-02 20:53:13 +08:00
    由于历史原因 JS 工程化程度不高,处理的问题也相对单一
    就像小公司小团队一般有什么问题当场就办了,很少专门立项拉会批文
    yagamil
        46
    yagamil  
    OP
       2023-05-03 02:18:13 +08:00 via Android
    @FrankHB 谢谢回复。
    命名是计算机学科的两大难题之一。
    这个很早就听过,只是某些媒体的吸引眼球的论点而已。 这都算难度,要么英语四级没过,要么就是个菜鸡。这都算是难题,还是原来计算机了
    yagamil
        47
    yagamil  
    OP
       2023-05-03 02:18:53 +08:00 via Android
    @yagamil 原来->远离
    FrankHB
        48
    FrankHB  
       2023-05-03 03:06:17 +08:00
    @yagamil 根本不是你理解的这样。就因为这种冗余的元数据的选取跟语义独立,所以才困难。因为这种东西就是给人看的,机器原则上不可能检查(除非是具有比你更强 NLP 能力的真·强 AI 到直接能把你淘汰的那种),你不可能预知别人看了会跟你想得一样,更不可能保证你的命名绝对是最优解,光是确保能满足需求的工作量就触及了“软件工程的本质困难”。一旦要添加能影响语义的特性(比如反射),又是一地鸡毛。
    当然机器原则上不可能检查的东西多了,比如怎么让 GC 代替显式资源释放的位置。但是没有一个其它问题的普遍性能和命名困难相提并论。(如果有,那么最接近的是如何定义相等操作。)
    chnwillliu
        49
    chnwillliu  
       2023-05-03 08:54:42 +08:00 via Android
    @yagamil 哈? native speakers 一样烦恼命名问题啊,这跟英语好不好没关系。
    yagamil
        50
    yagamil  
    OP
       2023-05-21 23:25:48 +08:00 via Android
    反正就是不懂为啥那么多箭头函数,还有作为回调函数的参数传入。
    比如 setTimeout(res=>{
    xxxxxx
    xxxxxx
    },5000 )

    快速阅读代码时,我要知道定时器里面做什么东
    东,还得看那个箭头函数里面的大体直接。

    比如如
    直接给一个函数名叫 collect_user_feedback, 这样是不是会更好? 起码大概意图阅读者已经清楚了。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5620 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 44ms · UTC 07:41 · PVG 15:41 · LAX 23:41 · JFK 02:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.