V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
autoxbc
V2EX  ›  JavaScript

发现一种反模式写法,问问大家的意见

  •  1
     
  •   autoxbc · 2018-12-03 01:00:58 +08:00 · 6544 次点击
    这是一个创建于 2188 天前的主题,其中的信息可能已经有所发展或是发生改变。

    有如下一小段代码,能否发现这里有什么问题?

    const math = {
        sin(val){
            return Math.sin(val);    
        },
        cos(val){
            return Math.cos(val);    
        },
        tan(val){
            const sin = this.sin ;
            const cos = this.cos ;
            return sin(val)/cos(val);    
        },
    };
    

    一个粗心的程序员可能会这么续写

    const { sin , cos , tan } = math ;
    console.log( tan(.5) );
    

    这里就会报错了,因为 tan 方法内部需要的 this 对象在解构赋值的时候丢失了。要想正确使用,必须把代码写成这样的丑陋形式

    let { sin , cos , tan } = math ;
    [ sin , cos , tan ] = [ sin , cos , tan ].map( e => e.bind(math) );
    console.log( tan(.5) );
    

    不过,其实只要在开始的时候,把这两行

    const sin = this.sin ;
    const cos = this.cos ;
    

    写成

    const sin = math.sin ;
    const cos = math.cos ;
    

    这样,就消除了隐患。显然,对于 tan 方法,动态绑定 this 对象是没有意义的,因为这个值既不会也不允许改变

    总结一下就是,this 是针对动态绑定需求设计的,如果没有这个需求,就不要强行使用,哪怕表面上看上去很优雅

    第 1 条附言  ·  2018-12-03 15:26:54 +08:00

    看到好几个同学说字面量定义本身就是种反模式,并用闭包演示如何解决持有变量

    可是,闭包无法实现字面量写法提供的自注册效果,像这样

    const math = {
        ... ,
        cot(val){    // <-- 自注册
            const tan = math.tan ;
            return 1/tan(val);
        },
    };
    
    const math = ( () => {
        ... ;
        const cot = val => 1/tan(val);
    
        return { ... , tan , cot }; // <-- 手动注册
    } )();
    
    第 2 条附言  ·  2018-12-04 17:11:20 +08:00
    还有些同学偏楼到 OO , prototype , class , new 上的,这里和这些都没有关系

    这里说的是,当你提供给别人一个静态方法集时,如果这些方法互相引用,那么不要用动态绑定 this 的方法去查找父对象。这样你的方法就可以直接解构赋值,消除使用者的隐患

    这里隐患的本质是不当使用 this 对象,这种不当是种反模式,远离了 this 的初衷
    35 条回复    2018-12-04 19:04:44 +08:00
    yaozeyuan93
        1
    yaozeyuan93  
       2018-12-03 02:32:49 +08:00   ❤️ 1
    如果一定依赖 this 的话, 为什么不在初始化时直接使用呢

    ```javascript
    const math = {
    sin(val){
    return Math.sin(val);
    },
    cos(val){
    return Math.cos(val);
    },
    tan(val){
    const sin = math.sin ;
    const cos = math.cos ;
    return sin(val)/cos(val);
    },
    };
    ```
    sker101
        2
    sker101  
       2018-12-03 04:53:50 +08:00
    个人认为在非对象里面用 this 没啥意义, 纯粹自找 bug
    真要那么用可以

    ```

    const math = {
    sin(val){
    return Math.sin(val);
    },
    cos(val){
    return Math.cos(val);
    },
    tan(val){
    const sin = this.sin ;
    const cos = this.cos ;
    return sin(val)/cos(val);
    },
    };

    const { sin, cos, tan } = math;


    console.log(tan.call(math, 1));


    ```
    zn
        3
    zn  
       2018-12-03 07:50:40 +08:00 via iPhone
    js 里的 this 是我见过的最恶心的东西之一。
    0xff0x77
        4
    0xff0x77  
       2018-12-03 08:00:19 +08:00
    个人觉得 js 少了很多东西,OO 很不成熟,所以我看到的大部分 js 库都是函数编程的。
    wangxiaoaer
        5
    wangxiaoaer  
       2018-12-03 08:08:54 +08:00
    const { sin , cos , tan } = math ;

    说实在的,非常恶心这种写法,可能是因为 java 写多了吧。

    因为这样子在阅读代码的时候根本就不知道 sin cos tan 是本文件定义的还是引入的,甚至是哪个库、哪个包都不清楚,需要去 import 里面看。

    而 math.sin math.cos 就明显多了。
    Jex
        6
    Jex  
       2018-12-03 08:22:36 +08:00   ❤️ 1
    @wangxiaoaer Java 里面 import static 可是后来才加的特性,ES 也照抄了。真的,对 JS 程序员来讲多学一点 Java 是有好处的,然后记得换一个支持 Go to definition 的 IDE。
    ChefIsAwesome
        7
    ChefIsAwesome  
       2018-12-03 08:53:48 +08:00   ❤️ 1
    生搬硬套,半桶水,以为写了个对象就叫面向对象了。
    azh7138m
        8
    azh7138m  
       2018-12-03 09:14:52 +08:00 via Android
    @wangxiaoaer 就一个解构,C++都有的写法,为啥会恶心。
    解构出来的场景大部分是个局部变量,方便优化。
    zhyl
        9
    zhyl  
       2018-12-03 09:20:23 +08:00 via Android   ❤️ 1
    既然要持有相关量,为何不用闭包?
    shintendo
        10
    shintendo  
       2018-12-03 09:26:57 +08:00
    我选择
    const math = (function() {
    const sin = function(val) {
    return Math.sin(val)
    }
    cosst cos = function(val) {
    return Math.cos(val)
    }
    const tan = function(val) {
    return sin(val) / cos(val)
    }
    return {sin, cos, tan}
    })()
    petelin
        11
    petelin  
       2018-12-03 09:28:54 +08:00 via iPhone
    其实我根本不知道搞这些东西在干什么,尤其是写了 Go 之后。
    1010543618
        12
    1010543618  
       2018-12-03 09:36:50 +08:00
    Jex
        13
    Jex  
       2018-12-03 09:41:27 +08:00   ❤️ 1
    @wangxiaoaer 差点忘了黑一次 NPM,看看 lodash-modularized,就 NPM 这种风气,import module; module.method 这种写法?不存在的!


    https://www.npmjs.com/search?q=keywords:lodash-modularized
    wly19960911
        14
    wly19960911  
       2018-12-03 09:47:54 +08:00
    楼主的代码就有问题了,没有哪个语言直接定义对象来定义 this 的关系。这根本不是面向对象好不好

    js 就因为对象定义就直接用对象? 你这个写法完全不符合闭包的策略 /
    FakeLeung
        15
    FakeLeung  
       2018-12-03 09:54:53 +08:00
    用 class 来写就好懂很多。
    wangxiaoaer
        16
    wangxiaoaer  
       2018-12-03 10:13:59 +08:00
    @Jex #6 static 特性用的不多,而且跟 node 这种类似,不直观,基本不用。
    wangxiaoaer
        17
    wangxiaoaer  
       2018-12-03 10:15:45 +08:00
    @azh7138m #8 局部变量我是认可的,比如在一个局部函数里面,但是现在看看 npm 上的库,多少事直接在库文件根结构直接就用这中写法,美其名曰只引入需要的库,减少依赖,也是醉了。
    Justin13
        18
    Justin13  
       2018-12-03 10:50:45 +08:00 via Android
    非对象,非 new 的用例为啥要用 this?
    知道有坑就绕,而不是学如何从坑里爬出来。
    DOLLOR
        19
    DOLLOR  
       2018-12-03 11:16:18 +08:00 via Android
    非 OO 开发时,JS 的 this 毫无使用的必要。
    wxsm
        20
    wxsm  
       2018-12-03 11:53:57 +08:00
    这就是所谓的引战帖吧。明明是 LZ 自己写的一坨翔,本来是静态方法的东西偏要加 this,还说自己写的“看上去很优雅”,啧啧。
    tommyZZM
        21
    tommyZZM  
       2018-12-03 12:09:13 +08:00
    正确的做法是这样。

    ```
    function sin(val) {
    return Math.sin(val);
    }

    function cos(val) {
    return Math.cos(val);
    }

    function tan(val) {
    return sin(val)/cos(val);
    },

    const math = {
    sin, cos, tan
    };
    ```

    什么`this`啊, `Function.prototype.bind`啊其实都是糟粕,能不用就不用
    tommyZZM
        22
    tommyZZM  
       2018-12-03 12:14:15 +08:00
    有一个很核心的思路是,尽可能降低函数对外部环境的隐式依赖,同时函数执行时不应该隐式地影响外部环境。

    函数之间的关联应该是通过参数的传入联系起来的。

    使用了 this 实际上就依赖了调用环境上下文,是一种不好的做法。
    no1xsyzy
        23
    no1xsyzy  
       2018-12-03 13:31:17 +08:00
    @ChefIsAwesome 对啊,这个明显是函数式写法啊,为什么要加 this
    no1xsyzy
        24
    no1xsyzy  
       2018-12-03 13:34:45 +08:00
    “论如何同时激怒两拨人”
    写一个函数式的代码,并且用上 this 并开始谈论对象。

    ----

    const sin = this.sin ;
    const cos = this.cos ;

    这两行直接删去即可。
    66beta
        25
    66beta  
       2018-12-03 13:52:21 +08:00 via Android
    js 严格来讲没有“对象”
    class 也只是模拟,将来指不定就成了糟粕
    marcong95
        26
    marcong95  
       2018-12-03 15:29:23 +08:00
    @66beta js 是没有类吧,js 无处不在的对象,你总不能说那是个哈希表吧
    autoxbc
        27
    autoxbc  
    OP
       2018-12-03 15:37:00 +08:00
    @no1xsyzy #24 删去能运行刚好是碰运气,后面写成 const { tan } = math 不引入 sin cos 就无效了
    66beta
        28
    66beta  
       2018-12-03 18:37:50 +08:00 via Android
    @marcong95 你说的对,我话不严谨,是 OOP 里的 oo
    mskf
        29
    mskf  
       2018-12-03 19:40:38 +08:00
    你是说静态方法只能在静态方法中被引用吗
    rabbbit
        30
    rabbbit  
       2018-12-03 20:17:35 +08:00
    royzxq
        31
    royzxq  
       2018-12-03 23:08:35 +08:00
    意义不明。
    zealot0630
        32
    zealot0630  
       2018-12-04 06:06:21 +08:00
    楼主对 OO 的理解有严重问题,OO 设计中,成员函数是放在 prototype 或 meta 里面,而不是对象里面。
    cyssxt
        33
    cyssxt  
       2018-12-04 08:16:59 +08:00 via iPhone
    math 本来就封装好的 为什么再来一次
    nullcc
        34
    nullcc  
       2018-12-04 08:43:42 +08:00   ❤️ 1
    说 JS this 恶心的应该是那些完全不理解 JS 对象模型的人
    zealot0630
        35
    zealot0630  
       2018-12-04 19:04:44 +08:00
    @nullcc

    JS 的 this 是恶心,这点没得洗,都是历史遗留的包袱。

    this 现在大部分情况作为函数的一个额外参数使用,而不是用于调用成员函数,访问成员变量。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5539 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 36ms · UTC 08:12 · PVG 16:12 · LAX 00:12 · JFK 03:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.