首页>>前端>>JavaScript->彻底掌握Function(一)

彻底掌握Function(一)

时间:2023-11-30 本站 点击:0

highlight: obsidian theme: cyanosis

前言

Array 跟 Object 两兄弟的故事告一段落了,接着是 Object 在外面养 (?) 的另外一个兄弟 - function

浪漫一点来说,function 其实就像是另一个平行宇宙,从 function 的呼叫处一跃而下,穿越虫洞到了另一个与世隔绝的小空间,而在这个空间发生的事、定义的变数,(基本上) 不会影响到原来的时空。

所以我称进入 function 是一场「时空旅行」,好啦可能时间还是照常流动 (同步),不过起码空间是换了一个 context。

今天我们先来聊聊,要进入时空旅行之前,总是要带一些干粮或行李,我们把它称作:

✅ 参数优化

蛤?function 的参数太基本了吧!呼叫的时候一个一个丢进去,使用的时候一个一个取出来,同学你不是来骗赞的吧!?

嘿啊,我也知道 function 带参数是基本中的基本了,所以这个问题应该改成:

参数怎么带比较「好」

✔ 参数一个一个带会遇到的问题

我们举个例会比较清楚:

// 查询网银交易的纪录 /*\* userId : 使用者 id\* startDate : 查询区间开始\* endDate : 查询区间结束\* searchKeyword: 搜寻备注关键字\* type : 'deposit'(存款) 或 'withdraw'(提款)\*/ const queryTransaction = (userId, startDate, endDate, searchKeyword, type) => { // 实作到 DB query 资料的部分 }; queryTransaction('612b06609ea3b35614c0edbd', '2021-09-16', '2021-10-15', '同事代垫', 'deposit');

上面的例子可以看出,当一个 function 的带入参数很多时,很容易会遇到这些问题:

呼叫时,看不出每个参数各自代表什么意义,需要跑到 function 定义的位置才知道。

某个参数是选填时,如上例,如果使用者不想特别「搜寻备注关键字」,就还得刻意填个 null 之类的。

函式定义如果要修改参数的顺序,就要把所有用到这个函式的地方改过一次。 ✔ 参数过多就组成 Object 传进去吧

稍微改进一下会变这样:

// 查询网银交易的纪录/* * userId       : 使用者 id * startDate    : 查询区间开始 * endDate      : 查询区间结束 * searchKeyword: 搜寻备注关键字 * type         : 'deposit'(存款) 或 'withdraw'(提款)*/const queryTransaction = ({ userId, startDate, endDate, searchKeyword, type }) => {    // 实作到 DB query 资料的部分};queryTransaction({    userId: '612b06609ea3b35614c0edbd',     startDate: '2021-09-16',     endDate: '2021-10-15',     searchKeyword: '同事代垫',     type: 'deposit'});

如果是上述的版本,因为是带入一个 object 当作参数

我可以帮每个参数「命名」,就可以知道每个参数代表什么。

object 内的 key 是没有顺序性的,所以即便我把 userId 搬到 type 的后面,也不用去改函式呼叫的地方。

如果我今天不想要「搜寻备注关键字」,那就直接不要带这组 key/value 即可,完全让它变成一个 optional 的选项,不用特地 null。

以上三点就把刚才上面提到的三个问题都解决了,变成更有弹性,且即便后人接手 refactor,也比较不会因为参数的增减、顺序而造成 bug。

✔ 再优化!必要参数与选填参数区隔开

可以想像我现在是带着一个后背包出门,里面装了我所有的旅行用品,但外表看起来就是一个后背包,所以里面少带了什么其实不一定会发现

所以我们把后背包里面,比较重要的东西拿出来握在手上,告诉自己一定要手上有东西才可以出门,有点像是出门先喊「手机、钥匙、钱包!」一样

因此这边可以再做一个小优化,因为上述提到的三个问题,都比较是因为参数过多,而且有一些其实是选填参数所造成的。

因此我可以只把选填参数包成 object,而像 userId 这种肯定是必填的栏位,要避免其它同事不小心漏掉,就可以直接用原本的方式放在前面:

const queryTransaction = (userId, option) => {    const { startDate, endDate, searchKeyword, type } = option;    // 实作到 DB query 数据的部分};const option = {    startDate: '2021-09-16',     endDate: '2021-10-15',     searchKeyword: '同事代垫',     type: 'deposit'};queryTransaction(    '612b06609ea3b35614c0edbd',     option);

参考其它套件的类似做法

用fetch发送 request 的时候,必要的栏位是 URL,其它都放在 option

const fetchOption = {    method: 'POST',    body: JSON.stringify(data),     headers: {      'user-agent': 'Mozilla/4.0 MDN Example',      'content-type': 'application/json'    }};fetch('这里是 URL', fetchOption)     .then(response => response.json())

用mongoose连线 DB 时,必要的栏位是 URL,其它都放在 option

const mongooseOption = {  ssl: true,  autoIndex: true,  serverSelectionTimeoutMS: 5000,};mongoose.createConnection('这里是 URL', mongooseOption);

✔ 不要修改带入的参数

没错,时空旅行就是单纯去旅行就好,背包里的行李不要变质。。。

翻成白话就是,「不会有任何一个参数,会因为执行了这个 function,而产生任何变化」。

这是之后会提到的 Functional Programming 的其中一个重点,也就是避免像下面这种 side effects:

const arr = ['Jack', 'Allen', 'Alice', 'Susan'];const sortArr = () => {    arr.sort();};console.log(arr);sortArr();console.log(arr);执行结果['Jack', 'Allen', 'Alice', 'Susan']["Alice", "Allen", "Jack", "Susan"]

这种状况通常会在 function 最开头,先把参数拷贝一份,然后用拷贝的资料来修改,保持正本 read-only:

const arr = ['Jack', 'Allen', 'Alice', 'Susan'];const sortArr = () => {    const copiedArr = [ ...arr ];    copiedArr.sort();};console.log(arr);sortArr();console.log(arr);执行结果['Jack', 'Allen', 'Alice', 'Susan']['Jack', 'Allen', 'Alice', 'Susan']

这个问题只会出现在参数是 non-primitive 的时候。

因为如果是把 primitive 变数当作参数,会是call by value的方式,丢一个拷贝后的副本进去 function,不管怎么改都不会影响到正本。

而 non-primitive 则是call by reference,会直接把正本丢到 function 里面,如果改了就会连带影响外面。

主要是为了避免不可预期的 bug,因为如果每次执行这个 function 都会造成外面的变数变化,那代表如果这个 function 出现问题写错了,就会造成其它地方也着火,到时会很难厘清问题点到底在哪,对于 unit testing 也是相当不利的。

不要修改带入的参数的例外

前面提到,不要修改参数的原因是怕「不可预期」的状况,反之,如果是「可预期」的,那么直接修改参数其实是效能更好的哦!

最常见的例子就是前两天介绍的彻底掌握 Array(一) reduce,当初要做「Array 转换成 Object」的例子时,就直接塞新的 property 给 reduce 内的那个 prev (第 7 行):

const arr = [    { id: 'item1', name: 'TV', price: 13500 },    { id: 'item2', name: 'washing machine', price: 8200 },    { id: 'item3', name: 'laptop', price: 25000 },];const resultObject = arr.reduce((prev, curr) => {    prev[curr.id] = curr;    return prev;}, {});console.log(resultObject);

可以直接进行参数修改的原因是,reduce里面的这个 function,本身是「可预期的」,因为我们已经把初始值定为{},所以不管这个 reduce 执行多少遍,里面的 function 都是从 {} 开始跑,永无例外。

在这种非常确定可以直接修改的情况下,「直接修改」比起「先拷贝再修改」的效能还快得多,因为拷贝本身真的很吃效能 (尤其 deep copy),有兴趣可以参考这篇,有实测数据可以参考。

结语

function 的参数,简单可以很简单,难起来居然也像这样可以独立一篇出来讨论了!

但无论如何,今天讨论的都不是如何把程式写「对」,最上面第一块程式码区块,就已经是可以正常运作的了。

差别只在于,如何在运作正确之余,让程式规模更容易扩充、除错,这些都将是未来设计更大、更复杂程式会遇到的难题。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/JavaScript/3967.html