{"action":"created","issue":{"url":"https://api.github.com/repos/frontend9/fe9-library/issues/188","repository_url":"https://api.github.com/repos/frontend9/fe9-library","labels_url":"https://api.github.com/repos/frontend9/fe9-library/issues/188/labels{/name}","comments_url":"https://api.github.com/repos/frontend9/fe9-library/issues/188/comments","events_url":"https://api.github.com/repos/frontend9/fe9-library/issues/188/events","html_url":"https://github.com/frontend9/fe9-library/issues/188","id":392002119,"node_id":"MDU6SXNzdWUzOTIwMDIxMTk=","number":188,"title":"fre 揭秘系列：arguments.callee.caller、Function.caller 替代方案","user":{"login":"132yse","id":12951461,"node_id":"MDQ6VXNlcjEyOTUxNDYx","avatar_url":"https://avatars3.githubusercontent.com/u/12951461?v=4","gravatar_id":"","url":"https://api.github.com/users/132yse","html_url":"https://github.com/132yse","followers_url":"https://api.github.com/users/132yse/followers","following_url":"https://api.github.com/users/132yse/following{/other_user}","gists_url":"https://api.github.com/users/132yse/gists{/gist_id}","starred_url":"https://api.github.com/users/132yse/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/132yse/subscriptions","organizations_url":"https://api.github.com/users/132yse/orgs","repos_url":"https://api.github.com/users/132yse/repos","events_url":"https://api.github.com/users/132yse/events{/privacy}","received_events_url":"https://api.github.com/users/132yse/received_events","type":"User","site_admin":false},"labels":[{"id":1006456400,"node_id":"MDU6TGFiZWwxMDA2NDU2NDAw","url":"https://api.github.com/repos/frontend9/fe9-library/labels/javascript","name":"javascript","color":"ffc53d","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2018-12-18T05:27:16Z","updated_at":"2018-12-22T13:37:16Z","closed_at":null,"author_association":"NONE","body":"halo，大家好，我是 132\r\n前两天在写框架，然后这段时间会写一些文章，分享开发中遇到的一些问题\r\n\r\n这次的问题比较坑，先来看看目标 API \r\n```JavaScript\r\nimport { useState, html, render } from 'fre'\r\n\r\nfunction counter() {\r\n  const state = useState({\r\n    count: 0\r\n  })\r\n\r\n  return html`\r\n    <div>\r\n      <p>${state.count}</p>\r\n      <button onclick=${() => {state.count++}}>+</button>\r\n      <button onclick=${() => {state.count--}}>-</button>\r\n    </div> \r\n  `\r\n}\r\n\r\nrender(html`<${counter}/>`, document.body)\r\n```\r\n这是 fre 的最小使用 demo，看过 fre github 的应该都有所了解\r\n这里面有个很骚的 API ，就是 useState 了\r\n\r\n可以看到，和 react 的 hooks 还是不一样的，其实熟悉的人看到类似的 API ，指定是劫持\r\n劫持这个对象，触发 set 后就执行这个函数拿到新的 state 对应的新的 vnode\r\n\r\n这就比较尴尬了，也就是说，useState 这个 API 内部是需要执行 counter 函数的\r\n\r\nfre 最初实现是通过 arguments.callee.caller 拿到 counter，嗯，这也是 useState 这个 API 最重要的一个函数了\r\n可是严格模式下，被禁用了::>_<::连 Function.caller 也被禁用了，我真的是！\r\n\r\n欲哭无泪啊emmm\r\n\r\n\r\n最终没办法，我不得不，换个思路\r\n我有没有办法，拿到这个函数名，然后去遍历 vdom，将这个函数匹配出来？\r\n\r\n肯定有可能的\r\n\r\n所以现在的问题变成了：\r\n1. 想办法拿到函数名\r\n2. 想办法通过函数名（一个字符串），从 vnode 中匹配处函数的引用\r\n\r\n可是 useState 只接受了一个对象作为入参，又没有了 caller，怎么拿到函数名！\r\n\r\n当然，修改 API 是很容易实现的，比如这样\r\n\r\n```JavaScript\r\n  const state = useState({\r\n    count: 0\r\n  },this)\r\n```\r\n直接传进去第二个参数，可是加了一个单词，这个 API 瞬间没了逼格\r\n\r\n所以，用了一个很骚的方法，通过 try catch 来拿函数名\r\n\r\n```JavaScript\r\nfunction caller() {\r\n  try {\r\n    throw new Error()\r\n  }\r\n  catch (e) {\r\n    try {\r\n      return e.stack.split('at ')[3].split(' ')[0]\r\n    } catch (e) {\r\n      return ''\r\n    }\r\n  }\r\n}\r\n\r\nfunction useState(){\r\n  let caller = caller()\r\n  console.log(caller) //counter\r\n}\r\n\r\nfunction counter(){\r\n  useState()\r\n}\r\n\r\ncounter()\r\n```\r\n\r\n很好，成功拿到了 couter 这个字符串，然后就要去遍历 vnode 树了\r\n\r\n然后每次 useState 都去遍历一整颗树的话，感觉不是那么容易接受，这个遍历量太大\r\n\r\n我想了一个办法，就是，首次渲染，进行一次遍历，筛选出 function （组件）\r\n然后后续的遍历只需要遍历这颗组件树，而且这棵树，并不会很复杂\r\n\r\n```javascript\r\nlet vnode = {\r\n  type: counter,\r\n  props: {},\r\n  children: [\r\n    {\r\n      type: count,\r\n      props: {},\r\n      children: []\r\n    }\r\n  ]\r\n}\r\n\r\nfunction filterFn(obj) {\r\n  let fns = {}\r\n  return walk(obj, fns)\r\n}\r\n\r\nfunction walk(obj, fns) {\r\n  Object.keys(obj).forEach(i => {\r\n    if (i === 'type') {\r\n      let f = obj[i]\r\n      if (typeof f === 'function') {\r\n        fns[f.name] = f\r\n      }\r\n    } else if (i === 'children') {\r\n      let a = obj[i]\r\n      a.forEach(child => {\r\n        walk(child, fns)\r\n      })\r\n    } else {\r\n      return\r\n    }\r\n  })\r\n  return fns\r\n}\r\n\r\nlet fns = filterFn(vnode)\r\nconsole.log(fns['counter']) // counter()\r\n```\r\n很好，仿佛完美\r\n我们只要拿到这个函数的引用，那就等于拿到了新的 vdom\r\n\r\n这个 API 这才算是真正实现了，但是这个实现方法确实要增加一次大的遍历和n次小遍历\r\n\r\n暂时预期应该还是可以接受的，后期会做兼容处理\r\n\r\n因为 fre 的立足点并不仅仅是 webpack 场景，更多的是期望应用于浏览器，所以如果用于浏览器，不经过打包，其实大多是 非严格模式 的\r\n\r\n所以正常是可以通过 caller 来拿到组件函数的\r\n\r\n最后，送上 fre 的 github 地址：[https://github.com/132yse/fre](https://github.com/132yse/fre)\r\n\r\n欢迎 试用与 star ！\r\n"},"comment":{"url":"https://api.github.com/repos/frontend9/fe9-library/issues/comments/449570830","html_url":"https://github.com/frontend9/fe9-library/issues/188#issuecomment-449570830","issue_url":"https://api.github.com/repos/frontend9/fe9-library/issues/188","id":449570830,"node_id":"MDEyOklzc3VlQ29tbWVudDQ0OTU3MDgzMA==","user":{"login":"afeiship","id":3038631,"node_id":"MDQ6VXNlcjMwMzg2MzE=","avatar_url":"https://avatars2.githubusercontent.com/u/3038631?v=4","gravatar_id":"","url":"https://api.github.com/users/afeiship","html_url":"https://github.com/afeiship","followers_url":"https://api.github.com/users/afeiship/followers","following_url":"https://api.github.com/users/afeiship/following{/other_user}","gists_url":"https://api.github.com/users/afeiship/gists{/gist_id}","starred_url":"https://api.github.com/users/afeiship/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/afeiship/subscriptions","organizations_url":"https://api.github.com/users/afeiship/orgs","repos_url":"https://api.github.com/users/afeiship/repos","events_url":"https://api.github.com/users/afeiship/events{/privacy}","received_events_url":"https://api.github.com/users/afeiship/received_events","type":"User","site_admin":false},"created_at":"2018-12-22T13:37:16Z","updated_at":"2018-12-22T13:37:16Z","author_association":"NONE","body":"记得之前写框架的时候，用过这个方法，不过，这个方法在不同的浏览器下，获取函数名的位置并不一样，如果测试一下应该会有新的结果。"}}
{"actor":{"display_login":"afeiship"}}