请教C++Builder里面有没有像javascript split里面的split函数

JSsplit报错问题,使用了Echart{title:'天数',field:'ChiD',width:120,formatter:function(value,row,index){varCD1="
JS split报错问题,使用了Echart
title: '天数', field: 'ChiD', width: 120, formatter: function (value, row, index) {
var CD1 = "";
var CMingX = [];
CMingX = CD1.split(",");
value = "";
for(var c=0;c&CMingX.length-1;c++)
value += CMingX[c] + "n";
value =" 总计:8 天,,,,,,,,,"
目的 = 总计:8 天
现在报错:Uncaught TypeError: Cannot read property 'split' of undefined
说明传入的value为undefined或者null,调用split报错了
CD1 =改成下面这样就不会报错了
CD1 = value||'';
解决方案二:
你把那个value toString看看呢 感觉类型问题吧
解决方案三:
同意楼上的【说明传入的value为undefined或者null,调用split报错了】,检查下你传进来的value值
【云栖快讯】集合阿里巴巴、阿里云技术干货!历届云栖社区在线峰会技术荟萃专题出炉,赶紧收藏吧~&&
弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率
稳定可靠、可弹性伸缩的在线数据库服务,全球最受欢迎的开源数据库之一
6款热门基础云产品6个月免费体验;2款产品1年体验;1款产品2年体验
开发者常用软件,超百款实用软件一站式提供{"debug":false,"apiRoot":"","paySDK":"/api/js","wechatConfigAPI":"/api/wechat/jssdkconfig","name":"production","instance":"column","tokens":{"X-XSRF-TOKEN":null,"X-UDID":null,"Authorization":"oauth c3cef7c66aa9e6a1e3160e20"}}
{"database":{"Post":{"":{"contributes":[{"sourceColumn":{"lastUpdated":,"description":"","permission":"COLUMN_PUBLIC","memberId":1360792,"contributePermission":"COLUMN_PUBLIC","translatedCommentPermission":"all","canManage":true,"intro":"简介 is not defined","urlToken":"starkwang","id":11622,"imagePath":"faead8d536.jpeg","slug":"starkwang","applyReason":"0","name":"一只码农的技术日记","title":"一只码农的技术日记","url":"/starkwang","commentPermission":"COLUMN_ALL_CAN_COMMENT","canPost":true,"created":,"state":"COLUMN_NORMAL","followers":4717,"avatar":{"id":"faead8d536","template":"/{id}_{size}.jpeg"},"activateAuthorRequested":false,"following":false,"imageUrl":"/faead8d536_l.jpeg","articlesCount":23},"state":"accepted","targetPost":{"titleImage":"/b4022bca78f103dfdff3_r.jpg","lastUpdated":,"imagePath":"b4022bca78f103dfdff3.jpg","permission":"ARTICLE_PUBLIC","topics":[5],"summary":"这是完结篇了,前两篇文章在这里:在第二篇文章里,我们介绍了 Maybe、Either、IO 等几种常见的 Functor,或许很多看完第二篇文章的人都会有疑惑:『这些东西有什么卵用?』事实上,如果只是为了学习…","copyPermission":"ARTICLE_COPYABLE","translatedCommentPermission":"all","likes":0,"origAuthorId":0,"publishedTime":"T23:09:21+08:00","sourceUrl":"","urlToken":,"id":1022345,"withContent":false,"slug":,"bigTitleImage":false,"title":"JavaScript函数式编程(三)","url":"/p/","commentPermission":"ARTICLE_ALL_CAN_COMMENT","snapshotUrl":"","created":,"comments":0,"columnId":11622,"content":"","parentId":0,"state":"ARTICLE_PUBLISHED","imageUrl":"/b4022bca78f103dfdff3_r.jpg","author":{"bio":"腾讯@AlloyTeam","isFollowing":false,"hash":"d965f32ae58ad3a48a1585a4","uid":68,"isOrg":false,"slug":"starkwei","isFollowed":false,"description":"/starkwang","name":"Stark伟","profileUrl":"/people/starkwei","avatar":{"id":"92edb0c47afd538c4e25e2b","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"memberId":1360792,"excerptTitle":"","voteType":"ARTICLE_VOTE_CLEAR"},"id":415968}],"title":"JavaScript函数式编程(三)","author":"starkwei","content":"这是完结篇了,前两篇文章在这里:在第二篇文章里,我们介绍了 Maybe、Either、IO 等几种常见的 Functor,或许很多看完第二篇文章的人都会有疑惑:『这些东西有什么卵用?』事实上,如果只是为了学习编写函数式、副作用小的代码的话,看完第一篇文章就足够了。第二篇文章和这里的第三篇着重于的是一些函数式理论的实践,是的,这些很难(但并非不可能)应用到实际的生产中,因为很多轮子都已经造好了并且很好用了。比如现在在前端大规模使用的 Promise 这种异步调用规范,其实就是一种 Monad(等下会讲到);现在日趋成熟的 Redux 作为一种 FLUX 的变种实现,核心理念也是状态机和函数式编程。一、Monad关于 Monad 的介绍和教程在网络上已经层出不穷了,很多文章都写得比我下面的更好,所以我在这里只是用一种更简单易懂的方式介绍 Monad,当然简单易懂带来的坏处就是不严谨,所以见谅/w\\如果你对 Promise 这种规范有了解的话,应该记得 Promise 里一个很惊艳的特性:doSomething()\n
.then(result =& {\n
// 你可以return一个Promise链!\n
return fetch('url').then(result =& parseBody(result));\n
.then(result =& {\n
// 这里的result是上面那个Promise的终值\n
\ndoSomething()\n
.then(result =& {\n
// 也可以直接return一个具体的值!\n
return 123;\n
.then(result =& {\n
// result === 123\n
})\n对于 Promise 的一个回调函数来说,它既可以直接返回一个值,也可以返回一个新的 Promise,但对于他们后续的回调函数来说,这二者都是等价的,这就很巧妙地解决了 nodejs 里被诟病已久的嵌套地狱。事实上,Promise 就是一种 Monad,是的,可能你天天要写一大堆 Promise,可直到现在才知道天天用的这个东西竟然是个听起来很高大上的函数式概念。下面我们来实际实现一个 Monad,如果你不想看的话,只要记住 『Promise 就是一种 Monad』 这句话然后直接跳过这一章就好了。我们来写一个函数 cat,这个函数的作用和 Linux 命令行下的 cat 一样,读取一个文件,然后打出这个文件的内容,这里 IO 的实现请参考上一篇文章:import fs from 'fs';\nimport _ from 'lodash';\n\nvar map = _.curry((f, x) =& x.map(f));\nvar compose = _.flowRight;\n\nvar readFile = function(filename) {\n
return new IO(_ =& fs.readFileSync(filename, 'utf-8'));\n};\n\nvar print = function(x) {\n
return new IO(_ =& {\n
console.log(x);\n
return x;\n
});\n}\n\nvar cat = compose(map(print), readFile);\n\ncat(\"file\")\n//=& IO(IO(\"file的内容\"))\n由于这里涉及到两个 IO:读取文件和打印,所以最后结果就是我们得到了两层 IO,想要运行它,只能调用:cat(\"file\").__value().__value();\n//=& 读取文件并打印到控制台\n很尴尬对吧,如果我们涉及到 100 个 IO 操作,那么难道要连续写 100 个 __value() 吗?当然不能这样不优雅,我们来实现一个 join 方法,它的作用就是剥开一层 Functor,把里面的东西暴露给我们:var join = x =& x.join();\nIO.prototype.join = function() {\n
return this.__value ? IO.of(null) : this.__value();\n}\n\n// 试试看\nvar foo = IO.of(IO.of('123'));\n\nfoo.join();\n//=& IO('123')\n有了 join 方法之后,就稍微优雅那么一点儿了:var cat = compose(join, map(print), readFile);\ncat(\"file\").__value();\n//=& 读取文件并打印到控制台\njoin 方法可以把 Functor 拍平(flatten),我们一般把具有这种能力的 Functor 称之为 Monad。这里只是非常简单地移除了一层 Functor 的包装,但作为优雅的程序员,我们不可能总是在 map 之后手动调用 join 来剥离多余的包装,否则代码会长得像这样:var doSomething = compose(join, map(f), join, map(g), join, map(h));\n所以我们需要一个叫 chain 的方法来实现我们期望的链式调用,它会在调用 map 之后自动调用 join 来去除多余的包装,这也是 Monad 的一大特性:var chain = _.curry((f, functor) =& functor.chain(f));\nIO.prototype.chain = function(f) {\n
return this.map(f).join();\n}\n\n// 现在可以这样调用了\nvar doSomething = compose(chain(f), chain(g), chain(h));\n\n// 当然,也可以这样\nsomeMonad.chain(f).chain(g).chain(h)\n\n// 写成这样是不是很熟悉呢?\nreadFile('file')\n
.chain(x =& new IO(_ =& {\n
console.log(x);\n
return x;\n
.chain(x =& new IO(_ =& {\n
// 对x做一些事情,然后返回\n
}))\n哈哈,你可能看出来了,chain 不就类似 Promise 中的 then 吗?是的,它们行为上确实是一致的(then 会稍微多一些逻辑,它会记录嵌套的层数以及区别 Promise 和普通返回值),Promise 也确实是一种函数式的思想。(我本来想在下面用 Promise 为例写一些例子,但估计能看到这里的人应该都能熟练地写各种 Promise 链了,所以就不写了0w0)总之就是,Monad 让我们避开了嵌套地狱,可以轻松地进行深度嵌套的函数式编程,比如IO和其它异步任务。二、函数式编程的应用好了,关于函数式编程的一些基础理论的介绍就到此为止了,如果想了解更多的话其实建议去学习 Haskell 或者 Lisp 这样比较正统的函数式语言。下面我们来回答一个问题:函数式编程在实际应用中到底有啥用咧?1、ReactReact 现在已经随处可见了,要问它为什么流行,可能有人会说它『性能好』、『酷炫』、『第三方组件丰富』、『新颖』等等,但这些都不是最关键的,最关键是 React 给前端开发带来了全新的理念:函数式和状态机。我们来看看 React 怎么写一个『纯组件』吧:var Text = props =& (\n
&div style={props.style}&{props.text}&/div&\n)\n咦这不就是纯函数吗?对于任意的 text 输入,都会产生唯一的固定输出,只不过这个输出是一个 virtual DOM 的元素罢了。配合状态机,就大大简化了前端开发的复杂度:state =& virtual DOM =& 真实 DOM\n在 Redux 中更是可以把核心逻辑抽象成一个纯函数 reducer:reducer(currentState, action) =& newState\n关于 React+Redux(或者其它FLUX架构)就不在这里介绍太多了,有兴趣的可以参考相关的教程。2、Rxjs 从诞生以来一直都不温不火,但它函数响应式编程(Functional Reactive Programming,FRP)的理念非常先进,虽然或许对于大部分应用环境来说,外部输入事件并不是太频繁,并不需要引入一个如此庞大的 FRP 体系,但我们也可以了解一下它有哪些优秀的特性。在 Rxjs 中,所有的外部输入(用户输入、网络请求等等)都被视作一种 『事件流』:--- 用户点击了按钮 --& 网络请求成功 --& 用户键盘输入 --& 某个定时事件发生 --& ......\n举个最简单的例子,下面这段代码会监听点击事件,每 2 次点击事件产生一次事件响应:var clicks = Rx.Observable\n
.fromEvent(document, 'click')\n
.bufferCount(2)\n
.subscribe(x =& console.log(x)); // 打印出前2次点击事件\n其中 bufferCount 对于事件流的作用是这样的:是不是很神奇呢?Rxjs 非常适合游戏、编辑器这种外部输入极多的应用,比如有的游戏可能有『搓大招』这个功能,即监听用户一系列连续的键盘、鼠标输入,比如上上下下左右左右BABA,不用事件流的思想的话,实现会非常困难且不优雅,但用 Rxjs 的话,就只是维护一个定长队列的问题而已:var inputs = [];\nvar clicks = Rx.Observable\n
.fromEvent(document, 'keydown')\n
.scan((acc, cur) =& {\n
acc.push(cur.keyCode);\n
var start = acc.length - 12 & 0 ? 0 : acc.length - 12;\n
return acc.slice(start);\n
}, inputs)\n
.filter(x =& x.join(',') == [38, 38, 40, 40, 37, 39, 37, 39, 66, 65, 66, 65].join(','))// 上上下下左右左右BABA,这里用了比较奇技淫巧的数组对比方法\n
.subscribe(x =& console.log('!!!!!!ACE!!!!!!'));\n 当然,Rxjs 的作用远不止于此,但可以从这个范例里看出函数响应式编程的一些优良的特性。3、Cycle.js 是一个基于 Rxjs 的框架,它是一个彻彻底底的 FRP 理念的框架,和 React 一样支持 virtual DOM、JSX 语法,但现在似乎还没有看到大型的应用经验。本质的讲,它就是在 Rxjs 的基础上加入了对 virtual DOM、容器和组件的支持,比如下面就是一个简单的『开关』按钮:import xs from 'xstream';\nimport {run} from '@cycle/xstream-run';\nimport {makeDOMDriver} from '@cycle/dom';\nimport {html} from 'snabbdom-jsx';\n\nfunction main(sources) {\n
const sinks = {\n
DOM: sources.DOM.select('input').events('click')\n
.map(ev =& ev.target.checked)\n
.startWith(false)\n
.map(toggled =&\n
&input type=\"checkbox\" /& Toggle me\n
&p&{toggled ? 'ON' : 'off'}&/p&\n
return sinks;\n}\n\nconst drivers = {\n
DOM: makeDOMDriver('#app')\n};\n\nrun(main, drivers);\n当然,Cycle.js 这种『侵入式』的框架适用性不是太广,因为使用它就意味着应用中必须全部或者大部分都要围绕它的理念设计,这对于大规模应用来说反而是负担。三、总结既然是完结篇,那我们来总结一下这三篇文章究竟讲了些啥?里,介绍了纯函数、柯里化、Point Free、声明式代码和命令式代码的区别,你可能忘记得差不多了,但只要记住『函数对于外部状态的依赖是造成系统复杂性大大提高的主要原因』以及『让函数尽可能地纯净』就行了。,或许是最没有也或许是最有干货的一篇,里面介绍了『容器』的概念和 Maybe、Either、IO 这三个强大的 Functor。是的,大多数人或许都没有机会在生产环境中自己去实现这样的玩具级 Functor,但通过了解它们的特性会让你产生对于函数式编程的意识。软件工程上讲『没有银弹』,函数式编程同样也不是万能的,它与烂大街的 OOP 一样,只是一种编程范式而已。很多实际应用中是很难用函数式去表达的,选择 OOP 亦或是其它编程范式或许会更简单。但我们要注意到函数式编程的核心理念,如果说 OOP 降低复杂度是靠良好的封装、继承、多态以及接口定义的话,那么函数式编程就是通过纯函数以及它们的组合、柯里化、Functor 等技术来降低系统复杂度,而 React、Rxjs、Cycle.js 正是这种理念的代言人,这可能是大势所趋,也或许是昙花一现,但不妨碍我们去多掌握一种编程范式嘛0w0","updated":"T15:09:21.000Z","canComment":false,"commentPermission":"anyone","commentCount":16,"collapsedCount":0,"likeCount":110,"state":"published","isLiked":false,"slug":"","lastestTipjarors":[{"isFollowed":false,"name":"快没时间了","headline":"","avatarUrl":"/da8e974dc_s.jpg","isFollowing":false,"type":"people","slug":"fang-ke-yi-41","bio":"..","hash":"86e86cbcb72d5a70e6679e","uid":44,"isOrg":false,"description":"","profileUrl":"/people/fang-ke-yi-41","avatar":{"id":"da8e974dc","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"isFollowed":false,"name":"Kraig嘿嘿嘿","headline":"大多时间在吐槽,有空写写产品,偶尔撸撸前端,业余做做设计养家糊口。","avatarUrl":"/v2-8ab3ccdf5735bbabbe2a2edbcc4e66ae_s.jpg","isFollowing":false,"type":"people","slug":"Xandstorm","bio":"学中药的鹅厂产品","hash":"0cac0cd0fdfd2bb2034c","uid":52,"isOrg":false,"description":"大多时间在吐槽,有空写写产品,偶尔撸撸前端,业余做做设计养家糊口。","profileUrl":"/people/Xandstorm","avatar":{"id":"v2-8ab3ccdf5735bbabbe2a2edbcc4e66ae","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false}],"isTitleImageFullScreen":false,"rating":"none","titleImage":"/b4022bca78f103dfdff3_r.jpg","links":{"comments":"/api/posts//comments"},"reviewers":[],"topics":[{"url":"/topic/","id":"","name":"函数式编程"},{"url":"/topic/","id":"","name":"JavaScript"},{"url":"/topic/","id":"","name":"前端开发"}],"adminClosedComment":false,"titleImageSize":{"width":596,"height":320},"href":"/api/posts/","excerptTitle":"","column":{"slug":"starkwang","name":"一只码农的技术日记"},"tipjarState":"activated","tipjarTagLine":"谢谢OvO","sourceUrl":"","pageCommentsCount":16,"tipjarorCount":2,"annotationAction":[],"hasPublishingDraft":false,"snapshotUrl":"","publishedTime":"T23:09:21+08:00","url":"/p/","lastestLikers":[{"bio":null,"isFollowing":false,"hash":"0dce27be8ee6e0f43848","uid":545200,"isOrg":false,"slug":"mizunotsukasa","isFollowed":false,"description":"","name":"忧郁的夏恋","profileUrl":"/people/mizunotsukasa","avatar":{"id":"v2-927e29e26b61d35dd26075","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"逃离制造业的IT","isFollowing":false,"hash":"bcf7e2adf5","uid":84,"isOrg":false,"slug":"chao-qin","isFollowed":false,"description":"","name":"Chao Qin","profileUrl":"/people/chao-qin","avatar":{"id":"da8e974dc","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"FE engineer & Designer","isFollowing":false,"hash":"e24debeff53ac","uid":52,"isOrg":false,"slug":"NoBey","isFollowed":false,"description":"Don't contact me. I don't want to make friends with anyone.","name":"NoBey","profileUrl":"/people/NoBey","avatar":{"id":"cbb05bebcb985","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":null,"isFollowing":false,"hash":"71e3ef4f7db92fb0daf5a01","uid":96,"isOrg":false,"slug":"huan-lian-69","isFollowed":false,"description":"技术宅","name":"幻潋","profileUrl":"/people/huan-lian-69","avatar":{"id":"87e19696d","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},{"bio":"hello","isFollowing":false,"hash":"3feb38ff10","uid":32,"isOrg":false,"slug":"wang-zeng-di-90","isFollowed":false,"description":"略懂函数式编程","name":"王增迪","profileUrl":"/people/wang-zeng-di-90","avatar":{"id":"a824ac6fe","template":"/{id}_{size}.png"},"isOrgWhiteList":false}],"summary":"这是完结篇了,前两篇文章在这里:在第二篇文章里,我们介绍了 Maybe、Either、IO 等几种常见的 Functor,或许很多看完第二篇文章的人都会有疑惑:『这些东西有什么卵用?』事实上,如果只是为了学习…","reviewingCommentsCount":0,"meta":{"previous":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"/401f374cf5ddd1_r.jpg","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"函数式编程"},{"url":"/topic/","id":"","name":"JavaScript"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"腾讯@AlloyTeam","isFollowing":false,"hash":"d965f32ae58ad3a48a1585a4","uid":68,"isOrg":false,"slug":"starkwei","isFollowed":false,"description":"/starkwang","name":"Stark伟","profileUrl":"/people/starkwei","avatar":{"id":"92edb0c47afd538c4e25e2b","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"starkwang","name":"一只码农的技术日记"},"content":"拖延症了好久,第二篇终于写出来了。上一篇在这里:上一篇文章里我们提到了纯函数的概念,所谓的纯函数就是,对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态(我偷懒复制过来的)。但是实际的编程中,特别是前端的编程范畴里,“不依赖外部环境”这个条件是根本不可能的,我们总是不可避免地接触到 DOM、AJAX 这些状态随时都在变化的东西。所以我们需要用更强大的技术来干这些脏活。一、容器、Functor如果你熟悉 jQuery 的话,应该还记得,$(...) 返回的对象并不是一个原生的 DOM 对象,而是对于原生对象的一种封装:var foo = $('#foo'); \nfoo == document.getElementById('foo'); \n//=& false\n\nfoo[0] == document.getElementById('foo'); \n//=& true\n这在某种意义上就是一个“容器”(但它并不函数式)。接下类我们会看到,容器为函数式编程里普通的变量、对象、函数提供了一层极其强大的外衣,赋予了它们一些很惊艳的特性,就好像 Tony Stark 的钢铁外衣,Dva 的机甲,明日香的2号机一样。下面我们就来写一个最简单的容器吧:var Container = function(x) {\n
this.__value = x;\n}\nContainer.of = x =& new Container(x);\n\n//试试看\nContainer.of(1);\n//=& Container(1)\n\nContainer.of('abcd');\n//=& Container('abcd')\n我们调用 Container.of 把东西装进容器里之后,由于这一层外壳的阻挡,普通的函数就对他们不再起作用了,所以我们需要加一个接口来让外部的函数也能作用到容器里面的值:Container.prototype.map = function(f){\n
return Container.of(f(this.__value))\n}\n我们可以这样使用它:Container.of(3)\n
.map(x =& x + 1)
//=& Container(4)\n
.map(x =& 'Result is ' + x);
//=& Container('Result is 4')\n没错!我们仅花了 7 行代码就实现了很炫的『链式调用』,这也是我们的第一个 Functor。Functor(函子)是实现了 map 并遵守一些特定规则的容器类型。也就是说,如果我们要将普通函数应用到一个被容器包裹的值,那么我们首先需要定义一个叫 Functor 的数据类型,在这个数据类型中需要定义如何使用 map 来应用这个普通函数。把东西装进一个容器,只留出一个接口 map 给容器外的函数,这么做有什么好处呢?本质上,Functor 是一个对于函数调用的抽象,我们赋予容器自己去调用函数的能力。当 map 一个函数时,我们让容器自己来运行这个函数,这样容器就可以自由地选择何时何地如何操作这个函数,以致于拥有惰性求值、错误处理、异步调用等等非常牛掰的特性。举个例子,我们现在为 map 函数添加一个检查空值的特性,这个新的容器我们称之为 Maybe(原型来自于Haskell):var Maybe = function(x) {\n
this.__value = x;\n}\n\nMaybe.of = function(x) {\n
return new Maybe(x);\n}\n\nMaybe.prototype.map = function(f) {\n
return this.isNothing() ? Maybe.of(null) : Maybe.of(f(this.__value));\n}\n\nMaybe.prototype.isNothing = function() {\n
return (this.__value === null || this.__value === undefined);\n}\n\n//试试看\nimport _ from 'lodash';\nvar add = _.curry(_.add);\n\nMaybe.of({name: \"Stark\"})\n
.map(_.prop(\"age\"))\n
.map(add(10));\n//=& Maybe(null)\n\nMaybe.of({name: \"Stark\", age: 21})\n
.map(_.prop(\"age\"))\n
.map(add(10));\n//=& Maybe(31)\n看了这些代码,觉得链式调用总是要输入一堆 .map(...) 很烦对吧?这个问题很好解决,还记得我们上一篇文章里介绍的柯里化吗?有了柯里化这个强大的工具,我们可以这样写:import _ from 'lodash';\nvar compose = _.flowRight;\nvar add = _.curry(_.add);\n\n// 创造一个柯里化的 map\nvar map = _.curry((f, functor) =& functor.map(f));\n\nvar doEverything = map(compose(add(10), _.property(\"age\")));\n\nvar functor = Maybe.of({name: \"Stark\", age: 21});\ndoEverything(functor);\n//=& Maybe(31)\n二、错误处理、Either现在我们的容器能做的事情太少了,它甚至连做简单的错误处理都做不到,现在我们只能类似这样处理错误:try{\n
doSomething();\n}catch(e){\n
// 错误处理\n}\ntry/catch/throw 并不是“纯”的,因为它从外部接管了我们的函数,并且在这个函数出错时抛弃了它的返回值。这不是我们期望的函数式的行为。如果你对 Promise 熟悉的话应该还记得,Promise 是可以调用 catch 来集中处理错误的:doSomething()\n
.then(async1)\n
.then(async2)\n
.catch(e =& console.log(e));\n对于函数式编程我们也可以做同样的操作,如果运行正确,那么就返回正确的结果;如果错误,就返回一个用于描述错误的结果。这个概念在 Haskell 中称之为 Either 类,Left 和 Right 是它的两个子类。我们用 JS 来实现一下:// 这里是一样的=。=\nvar Left = function(x) {\n
this.__value = x;\n}\nvar Right = function(x) {\n
this.__value = x;\n}\n\n// 这里也是一样的=。=\nLeft.of = function(x) {\n
return new Left(x);\n}\nRight.of = function(x) {\n
return new Right(x);\n}\n\n// 这里不同!!!\nLeft.prototype.map = function(f) {\n
return this;\n}\nRight.prototype.map = function(f) {\n
return Right.of(f(this.__value));\n}\n下面来看看 Left 和 Right 的区别吧:Right.of(\"Hello\").map(str =& str + \" World!\");\n// Right(\"Hello World!\")\n\nLeft.of(\"Hello\").map(str =& str + \" World!\");\n// Left(\"Hello\")\nLeft 和 Right 唯一的区别就在于 map 方法的实现,Right.map 的行为和我们之前提到的 map 函数一样。但是 Left.map 就很不同了:它不会对容器做任何事情,只是很简单地把这个容器拿进来又扔出去。这个特性意味着,Left 可以用来传递一个错误消息。var getAge = user =& user.age ? Right.of(user.age) : Left.of(\"ERROR!\");\n\n//试试\ngetAge({name: 'stark', age: '21'}).map(age =& 'Age is ' + age);\n//=& Right('Age is 21')\n\ngetAge({name: 'stark'}).map(age =& 'Age is ' + age);\n//=& Left('ERROR!')\n是的,Left 可以让调用链中任意一环的错误立刻返回到调用链的尾部,这给我们错误处理带来了很大的方便,再也不用一层又一层的 try/catch。Left 和 Right 是 Either 类的两个子类,事实上 Either 并不只是用来做错误处理的,它表示了逻辑或,范畴学里的 coproduct。但这些超出了我们的讨论范围。三、IO下面我们的程序要走出象牙塔,去接触外面“肮脏”的世界了,在这个世界里,很多事情都是有副作用的或者依赖于外部环境的,比如下面这样:function readLocalStorage(){\n
return window.localStorage;\n}\n这个函数显然不是纯函数,因为它强依赖外部的 window.localStorage 这个对象,它的返回值会随着环境的变化而变化。为了让它“纯”起来,我们可以把它包裹在一个函数内部,延迟执行它:function readLocalStorage(){\n
return function(){\n
return window.localStorage;
}\n}\n这样 readLocalStorage 就变成了一个真正的纯函数! OvO为机智的程序员鼓掌!额……好吧……好像确实没什么卵用……我们只是(像大多数拖延症晚期患者那样)把讨厌做的事情暂时搁置了而已。为了能彻底解决这些讨厌的事情,我们需要一个叫 IO 的新的 Functor:import _ from 'lodash';\nvar compose = _.flowRight;\n\nvar IO = function(f) {\n
this.__value = f;\n}\n\nIO.of = x =& new IO(_ =& x);\n\nIO.prototype.map = function(f) {\n
return new IO(compose(f, this.__value))\n};\nIO 跟前面那几个 Functor 不同的地方在于,它的 __value 是一个函数。它把不纯的操作(比如 IO、网络请求、DOM)包裹到一个函数内,从而延迟这个操作的执行。所以我们认为,IO 包含的是被包裹的操作的返回值。var io_document = new IO(_ =& window.document);\n\nio_document.map(function(doc){ return doc.title });\n//=& IO(document.title)\n注意我们这里虽然感觉上返回了一个实际的值 IO(document.title),但事实上只是一个对象:{ __value: [Function] },它并没有执行,而是简单地把我们想要的操作存了起来,只有当我们在真的需要这个值得时候,IO 才会真的开始求值,这个特性我们称之为『惰性求值』。(培提尔其乌斯:“这是怠惰啊!”)是的,我们依然需要某种方法让 IO 开始求值,并且把它返回给我们。它可能因为 map 的调用链积累了很多很多不纯的操作,一旦开始求值,就可能会把本来很干净的程序给“弄脏”。但是去直接执行这些“脏”操作不同,我们把这些不纯的操作带来的复杂性和不可维护性推到了 IO 的调用者身上(嗯就是这么不负责任)。下面我们来做稍微复杂点的事情,编写一个函数,从当前 url 中解析出对应的参数。import _ from 'lodash';\n\n// 先来几个基础函数:\n// 字符串\nvar split = _.curry((char, str) =& str.split(char));\n// 数组\nvar first = arr =& arr[0];\nvar last = arr =& arr[arr.length - 1];\nvar filter = _.curry((f, arr) =& arr.filter(f));\n//注意这里的 x 既可以是数组,也可以是 functor\nvar map = _.curry((f, x) =& x.map(f)); \n// 判断\nvar eq = _.curry((x, y) =& x == y);\n// 结合\nvar compose = _.flowRight;\n\n\nvar toPairs = compose(map(split('=')), split('&'));\n// toPairs('a=1&b=2')\n//=& [['a', '1'], ['b', '2']]\n\nvar params = compose(toPairs, last, split('?'));\n// params('?a=1&b=2')\n//=& [['a', '1'], ['b', '2']]\n\n// 这里会有些难懂=。= 慢慢看\n// 1.首先,getParam是一个接受IO(url),返回一个新的接受 key 的函数;\n// 2.我们先对 url 调用 params 函数,得到类似[['a', '1'], ['b', '2']]\n//
这样的数组;\n// 3.然后调用 filter(compose(eq(key), first)),这是一个过滤器,过滤的\n//
条件是 compose(eq(key), first) 为真,它的意思就是只留下首项为 key\n//
的数组;\n// 4.最后调用 Maybe.of,把它包装起来。\n// 5.这一系列的调用是针对 IO 的,所以我们用 map 把这些调用封装起来。\nvar getParam = url =& key =& map(compose(Maybe.of, filter(compose(eq(key), first)), params))(url);\n\n// 创建充满了洪荒之力的 IO!!!\nvar url = new IO(_ =& window.location.href);\n// 最终的调用函数!!!\nvar findParam = getParam(url);\n\n// 上面的代码都是很干净的纯函数,下面我们来对它求值,求值的过程是非纯的。\n// 假设现在的 url 是 ?a=1&b=2\n// 调用 __value() 来运行它!\nfindParam(\"a\").__value();\n//=& Maybe(['a', '1'])\n四、总结如果你还能坚持看到这里的话,不管看没看懂,已经是勇士了。在这篇文章里,我们先后提到了 Maybe、Either、IO 这三种强大的 Functor,在链式调用、惰性求值、错误捕获、输入输出中都发挥着巨大的作用。事实上 Functor 远不止这三种,但由于篇幅的问题就不再继续介绍了(哼才不告诉你其实是因为我还没看懂其它 Functor 的原理)但依然有问题困扰着我们:1. 如何处理嵌套的 Functor 呢?(比如 Maybe(IO(42)))2. 如何处理一个由非纯的或者异步的操作序列呢?在这个充满了容器和 Functor 的世界里,我们手上的工具还不够多,函数式编程的学习还远远没有结束,在下一篇文章里会讲到 Monad 这个神奇的东西(然而我也不知道啥时候写下一篇,估计等到实习考核后吧OvO)。五、参考1、2、3、《JavaScript函数式编程》【美】迈克尔·佛格斯","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T22:41:35+08:00","url":"/p/","title":"JavaScript函数式编程(二)","summary":"拖延症了好久,第二篇终于写出来了。 上一篇在这里: 上一篇文章里我们提到了纯函数的概念,所谓的纯函数就是,对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态(我偷懒复制过来的)…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":37,"likesCount":287},"next":{"isTitleImageFullScreen":false,"rating":"none","titleImage":"/b4e55f928a88e8a17759_r.png","links":{"comments":"/api/posts//comments"},"topics":[{"url":"/topic/","id":"","name":"React"},{"url":"/topic/","id":"","name":"JavaScript"},{"url":"/topic/","id":"","name":"前端开发"}],"adminClosedComment":false,"href":"/api/posts/","excerptTitle":"","author":{"bio":"腾讯@AlloyTeam","isFollowing":false,"hash":"d965f32ae58ad3a48a1585a4","uid":68,"isOrg":false,"slug":"starkwei","isFollowed":false,"description":"/starkwang","name":"Stark伟","profileUrl":"/people/starkwei","avatar":{"id":"92edb0c47afd538c4e25e2b","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false},"column":{"slug":"starkwang","name":"一只码农的技术日记"},"content":"Redux 的第一次代码提交是在 2015 年 5 月底(也就是一年多前的样子),那个时候 React 的最佳实践还不是明晰,作为一个 View 层,有人会用 backbone 甚至是 angular 和它搭配,也有人觉得这层 View 功能已经足够强大,简单地搭配一些 utils 就直接上。后来便有了 FLUX 的演讲,React 社区开始注意到这种新的类似函数式编程的理念,Redux 也作为 FLUX 的一种变体开始受到关注,再后来顺理成章地得到 React 的『钦点』,作者也加入了 Facebook 从事 React 的开发。生态圈经过了这一年的成熟,现在很多第三方库已经非常完善,所以这里想介绍一下目前 Redux 的一些最佳实践。一、复习一下 Redux 的基本概念首先我们复习一下 Redux 的基本概念,如果你已经很熟悉了,就直接跳过这一章吧。Redux 把界面视为一种状态机,界面里的所有状态、数据都可以由一个状态树来描述。所以对于界面的任何变更都简化成了状态机的变化:(State, Input) =& NewState\n这其中切分成了三个阶段: action reducer store所谓的 action,就是用一个对象描述发生了什么,Redux 中一般使用一个纯函数,即 actionCreator 来生成 action 对象。// actionCreator =& action\n// 这是一个纯函数,只是简单地返回 action\nfunction somethingHappened(data){\n
return {\n
type: 'foo',\n
data: data\n
}\n}\n随后这个 action 对象和当前的状态树 state 会被传入到 reducer 中,产生一个新的 state//reducer(action, state) =& newState\nfunction reducer(action, state){\n
switch(action.type){\n
case 'foo':\n
return { data: data };\n
default:\n
return state;\n
}\n}\nstore 的作用就是储存 state,并且监听其变化。简单地说就是你可以这样产生一个 store :import { createStore } from 'redux'\n\n//这里的 reducer 就是刚才的 Reducer 函数\nlet store = createStore(reducer);\n然后你可以通过 dispatch 一个 action 来让它改变状态:store.getState(); // {}\nstore.dispatch(somethingHappened('aaa'));\nstore.getState(); // { data: 'aaa'}\n好了,这就是 Redux 的全部功能。对的,它就是如此简单,以至于它本体只有 3KB 左右的代码,因为它只是实现了一个简单的状态机而已,任何稍微有点编程能力的人都能很快写出这个东西。至于和 React 的结合,则需要
这个库,这里我们就不讲怎么用了。二、Redux 的一些痛点大体上,Redux 的数据流是这样的:界面 =& action =& reducer =& store =& react =& virtual dom =& 界面\n每一步都很纯净,看起来很美好对吧?对于一些小小的尝试性质的 DEMO 来说确实很美好。但其实当应用变得越来越大的时候,这其中存在诸多问题: 如何优雅地写异步代码?(从简单的数据请求到复杂的异步逻辑) 状态树的结构应该怎么设计? 如何避免重复冗余的 actionCreator? 状态树中的状态越来越多,结构越来越复杂的时候,和 react 的组件映射如何避免混乱? 每次状态的细微变化都会生成全新的 state 对象,其中大部分无变化的数据是不用重新克隆的,这里如何提高性能?你以为我会在下面一一介绍这些问题是怎么解决的?还真不是,这里大部分问题的回答都可以在官方文档中看到:,文档里讲得已经足够详细(有些甚至详细得有些啰嗦了)。所以下面只挑 Redux 生态圈里几个比较成熟且流行的组件来讲讲。三、Redux 异步控制官方文档里介绍了一种很朴素的异步控制中间件 (如果你还不了解中间件的话请看 ,事实上 redux-thunk 的代码很简单,简单到只有几行代码:function createThunkMiddleware(extraArgument) {\n
return ({ dispatch, getState }) =& next =& action =& {\n
if (typeof action === 'function') {\n
return action(dispatch, getState, extraArgument);\n
return next(action);\n
};\n}\n它其实只干了一件事情,判断 actionCreator 返回的是不是一个函数,如果不是的话,就很普通地传给下一个中间件(或者 reducer);如果是的话,那么把 dispatch、getState、extraArgument 作为参数传入这个函数里,实现异步控制。比如我们可以这样写://普通action\nfunction foo(){\n
return {\n
type: 'foo',\n
data: 123\n
}\n}\n\n//异步action\nfunction fooAsync(){\n
return dispatch =& {\n
setTimeout(_ =& dispatch(123), 3000);\n
}\n}\n但这种简单的异步解决方法在应用变得复杂的时候,并不能满足需求,反而会使 action 变得十分混乱。举个比较简单的例子,我们现在要实现『图片上传』功能,用户点击开始上传之后,显示出加载效果,上传完毕之后,隐藏加载效果,并显示出预览图;如果发生错误,那么显示出错误信息,并且在2秒后消失。用普通的 redux-thunk 是这样写的:function upload(data){\n
return dispatch =& {\n
\t// 显示出加载效果\n
\tdispatch({ type: 'SHOW_WAITING_MODAL' });\n
\t// 开始上传\n
\tapi.upload(data)\n
.then(res =& {\n
\t\t// 成功,隐藏加载效果,并显示出预览图\n\t
\tdispatch({ type: 'PRELOAD_IMAGES', data: res.images });\n\t
\tdispatch({ type: 'HIDE_WAITING_MODAL' });\n\t
.catch(err =& {\n\t
\t// 错误,隐藏加载效果,显示出错误信息,2秒后消失\n\t
\tdispatch({ type: 'SHOW_ERROR', data: err });\n\t
\tdispatch({ type: 'HIDE_WAITING_MODAL' });\n\t
\tsetTimeout(_ =& dispatch({ type: 'HIDE_ERROR' }), 2000);\n\t
}\n}\n这里的问题在于,一个异步的 upload action 执行过程中会产生好几个新的 action,更可怕的是这些新的 action 也是包含逻辑的(比如要判断是否错误),这直接导致异步代码中到处都是 dispatch(action),是很不可控的情况。如果还要进一步考虑取消、超时、队列的情况,就更加混乱了。所以我们需要更强大的异步流控制,这就是 。下面我们来看看如果换成 redux-saga 的话会怎么样:import { take, put, call, delay } from 'redux-saga/effects'\n// 上传的异步流\nfunction *uploadFlow(action) {\n\t// 显示出加载效果\n
\tyield put({ type: 'SHOW_WAITING_MODAL' });\n
\t// 简单的 try-catch\n
const response = yield call(api.upload, action.data);\n\t
yield put({ type: 'PRELOAD_IMAGES', data: response.images });\n\t
yield put({ type: 'HIDE_WAITING_MODAL' });\n
\t}catch(err){\n
yield put({ type: 'SHOW_ERROR', data: err });\n\t
yield put({ type: 'HIDE_WAITING_MODAL' });\n\t
yield delay(2000);\n\t
\tyield put({ type: 'HIDE_ERROR' });\n
\t} \t\n}\n\n\nfunction* watchUpload() {\n
yield* takeEvery('BEGIN_REQUEST', uploadFlow)\n}\n是不是规整很多呢?redux-saga 允许我们使用简单的 try-catch 来进行错误处理,更神奇的是竟然可以直接使用 delay 来替代 setTimeout 这种会造成回调和嵌套的不优雅的方法。本质上讲,redux-sage 提供了一系列的『副作用(side-effects)方法』,比如以下几个:put(产生一个 action)call(阻塞地调用一个函数)fork(非阻塞地调用一个函数)take(监听且只监听一次 action)delay(延迟)race(只处理最先完成的任务)并且通过 Generator 实现对于这些副作用的管理,让我们可以用同步的逻辑写一个逻辑复杂的异步流。下面这个例子出自于,实现了一个对于请求的队列,即让程序同一时刻只会进行一个请求,其它请求则排队等待,直到前一个请求结束:import { buffers } from 'redux-saga';\nimport { take, actionChannel, call, ... } from 'redux-saga/effects';\n\nfunction* watchRequests() {\n
// 1- 创建一个针对请求事件的 channel\n
const requestChan = yield actionChannel('REQUEST');\n
while (true) {\n
// 2- 从 channel 中拿出一个事件\n
const {payload} = yield take(requestChan);\n
// 3- 注意这里我们使用的是阻塞的函数调用\n
yield call(handleRequest, payload);\n
}\n}\n\nfunction* handleRequest(payload) { ... }\n 更多关于 redux-saga 的内容,请参考(中文文档:)。四、提高 selector 的性能把 react 与 redux 结合的时候,react-redux 提供了一个极其重要的方法:connect,它的作用就是选取 redux store 中的需要的 state 与 dispatch, 交由 connect 去绑定到 react 组件的 props 中:import { connect } from 'react-redux';\nimport { toggleTodo } from '../actions'\nimport TodoList from '../components/TodoList'\n\n// 我们需要向 TodoList 中注入一个名为 todos 的 prop\n// 它通过以下这个函数从 state 中提取出来:\nconst mapStateToProps = (state) =& {\n
// 下面这个函数就是所谓的selector\n
todos: state.todos.filter(i =& i.completed)\n
// 其它props...\n}\n\nconst mapDispatchToProps = (dispatch) =& {\n\tonTodoClick: (id) =& {\n\t\tdispatch(toggleTodo(id))\n\t}\n}\n\n// 绑定到组件上\nconst VisibleTodoList = connect(\n
mapStateToProps,\n
mapDispatchToProps\n)(TodoList)\n\nexport default VisibleTodoList\n在这里需要指定哪些 state 属性被注入到 component 的 props 中,这是通过一个叫 selector 的函数完成的。上面这个例子存在一个明显的性能问题,每当组件有任何更新时都会调用一次 state.todos.filter 来计算 todos,但我们实际上只需要在 state.todos 变化时重新计算即可,每次更新都重算一遍是非常不合适的做法。下面介绍的这个
就能帮你省去这些没必要的重新计算。你可能会注意到,selector 实际上就是一个『纯函数』:selector(state) =& some props\n而纯函数是具有可缓存性的,即对于同样的输入参数,永远会得到相同的输出值(如果对这个不太熟悉的同学可以参考我之前写的,reselect 的原理就是如此,每次调用 selector 函数之前,它会判断参数与之前缓存的是否有差异,若无差异,则直接返回缓存的结果,反之则重新计算:import { createSelector } from 'reselect';\n\nvar state = {\n
a: 100\n}\n\nvar naiveSelector = state =& state.a;\n\n// mySelector 会缓存输入 a 对应的输出值\nvar mySelector = createSelector(\n\tnaiveSelector, \n\ta =& {\n\t
console.log('做一次乘法!!!');\n\t
return a * a;\n\t}\n)\n\nconsole.log(mySelector(state));\t// 第一次计算,需要做一次乘法\nconsole.log(mySelector(state));\t// 输入值未变化,直接返回缓存的结果\nconsole.log(mySelector(state));\t// 同上\nstate.a = 5;\t\t\t\t\t\t\t// 改变 a 的值\nconsole.log(mySelector(state));\t// 输入值改变,做一次乘法\nconsole.log(mySelector(state));\t// 输入值未变化,直接返回缓存的结果\nconsole.log(mySelector(state));\t// 同上\n上面的输出值是:做一次乘法!!!\n1\n10000\n做一次乘法!!!\n25\n25\n25\n之前那个关于 todos 的范例可以这样改,就可以避免 todos 数组被重复计算的性能问题:import { createSelector } from 'reselect';\nimport { connect } from 'react-redux';\nimport { toggleTodo } from '../actions'\nimport TodoList from '../components/TodoList'\n\nconst todoSelector = createSelector(\n\tstate =& state.todos,\n\ttodos =& todos.filter(i =& i.completed)\n)\n\nconst mapStateToProps = (state) =& {\n
todos: todoSelector\n
// 其它props...\n}\n\nconst mapDispatchToProps = (dispatch) =& {\n\tonTodoClick: (id) =& {\n\t\tdispatch(toggleTodo(id))\n\t}\n}\n\n// 绑定到组件上\nconst VisibleTodoList = connect(\n
mapStateToProps,\n
mapDispatchToProps\n)(TodoList)\n\nexport default VisibleTodoList\n更多可以参考 五、减少冗余代码redux 中的 action 一般都类似这样写:function foo(data){\n\treturn {\n\t\ttype: 'FOO',\n\t\tdata: data\n\t}\n}\n\n//或者es6写法:\nvar foo = data =& ({ type: 'FOO', data})\n当应用越来越大之后,action 的数量也会大大增加,为每个 action 对象显式地写上 type 和 data 或者其它属性会造成大量的代码冗余,这一块是完全可以优化的。比如我们可以写一个最简单的 actionCreator:function actionCreator(type){\n
return function(data){\n\treturn {\n\t
type: type,\n\t
data: data\n\t}\n
}\n}\n\nvar foo = actionCreator('FOO');\nfoo(123); // {type: 'FOO', data: 123} \n 就可以为我们做这样的事情,除了上面这种朴素的做法,它还有其它比较好用的功能,比如它提供的 createActions 方法可以接受不同类型的参数,以产生不同效果的 actionCreator,下面这个范例来自官方文档:import { createActions } from 'redux-actions';\n\nconst { actionOne, actionTwo, actionThree } = createActions({\n
// 函数类型\n
ACTION_ONE: (key, value) =& ({ [key]: value }),\n\n
// 数组类型\n
ACTION_TWO: [\n
(first) =& first,
// payload\n
(first, second) =& ({ second }) // meta\n
// 最简单的字符串类型\n}, 'ACTION_THREE');\n\nactionOne('key', 1));\n//=&\n//{\n//
type: 'ACTION_ONE',\n//
payload: { key: 1 }\n//}\n\nactionTwo('Die! Die! Die!', 'It\\'s highnoon~');\n//=&\n//{\n//
type: 'ACTION_TWO',\n//
payload: ['Die! Die! Die!'],\n//
meta: { second: 'It\\'s highnoon~' }\n//}\n\nactionThree(76);\n//=&\n//{\n//
type: 'ACTION_THREE',\n//
payload: 76,\n//}\n更多可以参考 六、更多其实还有太多 Redux 生态圈中的轮子没拿出来讲,比如:(可以用于范式化状态树)(用于引入 RxJS 的响应式编程思想)(Redux 中间件,用于处理 Promise)我总是觉得,轮子永远是造不完的,也是看不完的,这么多轮子的取舍其实终究还是要看开发者的能力以及实际项目的需求,有时你或许根本不需要这些东西,有时甚至连 Redux 本身也是多余的,毕竟,第三方库其实也是另一种意义上的『复杂度』嘛。","state":"published","sourceUrl":"","pageCommentsCount":0,"canComment":false,"snapshotUrl":"","slug":,"publishedTime":"T23:31:49+08:00","url":"/p/","title":"Redux的全家桶与最佳实践","summary":"Redux 的第一次代码提交是在 2015 年 5 月底(也就是一年多前的样子),那个时候 React 的最佳实践还不是明晰,作为一个 View 层,有人会用 backbone 甚至是 angular 和它搭配,也有人觉得这层 View 功能已经足够强大,简单地搭配一些 utils 就直接上。后来…","reviewingCommentsCount":0,"meta":{"previous":null,"next":null},"commentPermission":"anyone","commentsCount":21,"likesCount":262}},"annotationDetail":null,"commentsCount":16,"likesCount":110,"FULLINFO":true}},"User":{"starkwei":{"isFollowed":false,"name":"Stark伟","headline":"/starkwang","avatarUrl":"/92edb0c47afd538c4e25e2b_s.jpg","isFollowing":false,"type":"people","slug":"starkwei","bio":"腾讯@AlloyTeam","hash":"d965f32ae58ad3a48a1585a4","uid":68,"isOrg":false,"description":"/starkwang","profileUrl":"/people/starkwei","avatar":{"id":"92edb0c47afd538c4e25e2b","template":"/{id}_{size}.jpg"},"isOrgWhiteList":false,"badge":{"identity":null,"bestAnswerer":null}}},"Comment":{},"favlists":{}},"me":{},"global":{},"columns":{"next":{},"starkwang":{"following":false,"canManage":false,"href":"/api/columns/starkwang","name":"一只码农的技术日记","creator":{"slug":"starkwei"},"url":"/starkwang","slug":"starkwang","avatar":{"id":"faead8d536","template":"/{id}_{size}.jpeg"}}},"columnPosts":{},"columnSettings":{"colomnAuthor":[],"uploadAvatarDetails":"","contributeRequests":[],"contributeRequestsTotalCount":0,"inviteAuthor":""},"postComments":{},"postReviewComments":{"comments":[],"newComments":[],"hasMore":true},"favlistsByUser":{},"favlistRelations":{},"promotions":{},"switches":{"couldAddVideo":false},"draft":{"titleImage":"","titleImageSize":{},"isTitleImageFullScreen":false,"canTitleImageFullScreen":false,"title":"","titleImageUploading":false,"error":"","content":"","draftLoading":false,"globalLoading":false,"pendingVideo":{"resource":null,"error":null}},"drafts":{"draftsList":[],"next":{}},"config":{"userNotBindPhoneTipString":{}},"recommendPosts":{"articleRecommendations":[],"columnRecommendations":[]},"env":{"isAppView":false,"appViewConfig":{"content_padding_top":128,"content_padding_bottom":56,"content_padding_left":16,"content_padding_right":16,"title_font_size":22,"body_font_size":16,"is_dark_theme":false,"can_auto_load_image":true,"app_info":"OS=iOS"},"isApp":false},"sys":{}}}

我要回帖

更多关于 javascript的split 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信