这些都代表算数操作new运算符的作用用是什么

{"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":{"":{"title":"今天最期待的谎言,就是你们不曾离去","author":"sgai","content":"就在我写文章前,人民日报发了一篇推送: ,整篇文章怀恋了在4月1日离我们而去的:张国荣、王伟,很是感人。确实,每年的4月1日,很多人都会以不同的方式来怀恋他们。而我今天也想借这个特殊的日子,写一篇关于张国荣的简短的文章,算是以程序员的方式怀恋张国荣吧,谢谢哥哥留下的那些先锋前卫的舞台记忆。根据音乐网站虾米音乐网的搜索结果,张国荣生前共计唱过 325 首歌曲,其中原唱有180首左右,在正式场合被翻唱并且有记录的有 121 首,其中有 14 首歌在正式场合被翻唱超过 10 次以及以上,前前后有 282 位歌手曾经在正式场合翻唱过张国荣的歌曲。那些被翻唱最多的歌曲包括以下 14 首: :10次 :14次:17次:13次:10次:10次:10次:12次:14次:15次:11次:12次:14次不知道是什么原因,张国荣自己最喜欢的歌:& 我&并没有被很多人翻唱,其中&风再起时&是张国荣最后一次演唱的歌。电影方面:根据百科搜索结果,张国荣曾经参演或者兼职或编剧共有 72 部电影,其中包括:华语史上成就最高的《霸王别姬》既具挑战性的《东邪西毒》获得金像影帝的《阿飞正传》人尽皆知的《倩女幽魂》最经典的警匪片之一的《英雄本色》最后一部《异度空间》张国荣唱歌很特别,他一定要先确定想要什么,再去找歌。虽然他的大部分歌都不是自己做的词但是歌词还是能在一定程度上反应张国荣的情感,所以我们可以通过歌词了解了解他。对张国荣的所有歌曲的歌词进行分词等处理之后,我们看看张国荣的歌词中出现频率最高的一些词语是什么?张国荣有一些英文歌,所以以下词云中会出现一些英文单词。我们发现:张国荣通常会在歌词中通过我、你等人称代词来表达情感,就算是在英文歌曲中you和me也占据了歌词的重要地位,他希望每一个听歌的人能在各种找到他、找到自己的身影,他是在唱自己,也是在唱每个人。而如果我们进一步对他的歌词进行情感分析:出乎我们的意料,我们会发现他的歌中大部分是正面情绪,正面情绪占到了68.41%,张国荣在歌词中传达了很多对美好的向往,对自己那份感情的痴情,希望爱情里两人都能真诚坦白,渴望能和爱人一起甜蜜恩爱的生活。他是一位理想主义者、是一位完美主义者。我就是我 是颜色不一样的烟火 天空开阔 要做最坚强的泡沫 我喜欢我 让蔷薇开出一种结果 孤独的沙漠里 一样盛放的赤裸裸。烟火绚烂只是一瞬,可那一瞬也比泯然众人精彩万倍;泡沫脆弱一触即溃,可也有坚强的那一刹那;蔷薇喜水喜阴,可在贫瘠干旱的沙漠中只要能绽放一刻,便是那一整个世界的光彩。","updated":"T08:38:55.000Z","canComment":false,"commentPermission":"anyone","commentCount":37,"likeCount":412,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T16:38:55+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-05ee7c36fe296e68255a_r.jpg","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":37,"likesCount":412},"":{"title":"共享单车的混战","author":"sgai","content":"从2007年由国内政府主导分城市管理引入了在国外兴起的公共单车模式,到2014年年末,公共单车多以有桩单车形式存在,此时的单车有固定的停放点;随着移动互联网的快速发展,进入2014年,以北大学子戴伟等创立的OFO为首的互联网共共享单车应运而生,更加便捷的无桩单车快速大面积的取代了有桩单车。截至2016年,至少有27个共享单车平台涌入共享单车的混战。这些共享单车平台包括:小鸣单车、小蓝单车、智享单车、北京公共自行车、骑点、奇奇出行、CCbike、7号电单车、黑鸟单车、hellobike、酷骑单车、1步单车、由你单车、踏踏、Funbike单车、悠悠单车、骑呗、熊猫单车、云单车、摩拜单车、优拜单车、电电Go单车、永安行、小鹿单车、小白单车、快兔出行、OFO。根据iiMedia Research(艾媒咨询)的《Q1中国共享单车市场研究报告》显示,2016年中国共享单车市场规模打到12.3亿元,用户规模达到0.28亿人,预计到2017年,中国共享单车市场规模达到102.8亿,增长率为735.8%资本市场看好迅速推动共享单车市场发展的同时,也进一步推动市场的激烈竞争,截至2017年03月部分优秀单车平台获得融资,其中ofo和摩拜已获得D轮或以上融资:共享单车出来短短两年时间,发展尚未十分成熟,各品牌共享单车的问题不断出现,为了发现共享单车的共同问题、以及各品牌的共享单车的各自问题,我对三个共享单车品牌:OFO、摩拜以及小蓝的App内评论数据进行了分析。从三款共享单车的总体评分等级我们会发现,整体情况来说:三款共享单车的人群满意度都较高,五星评价都达到了60%以上,其中摩拜单车的5星评价更是达到了86.12%,摩拜单车的低星级评价较少,而小蓝和ofo的评论数据中1星级的评价达到了20%左右。上图:从左到右分别是ofo、摩拜、小蓝单车20%左右的1星评价急需去解决,通过这些评价数据是否能发现到底这三款单车的问题出在哪里?他们的共同问题又是什么?应该如何去解决?为此,我筛选出来近三个月以来应用市场上各款App的3星以及以下的评论具体内容。1、ofo由上图得出ofo共享单车的主要问题是:车少、客服电话打不通、无法定位、网络错误以及上私锁问题。其中无法定位、网络错误问题主要来自软件自身问题;而上私锁的问题可以效仿摩拜单车的处理方法;对于客服电话一直打不通问题,是不是也要考虑一下多招点客服人员。2、摩拜单车摩拜单车的主要问题是:与押金有关的问题包括押金过高以及无法退还押金、车太重比较难骑、骑行价格较贵、扫码经常会死机。其中扫码问题主要来源于软件自身问题,骑行价格也可以参考一下ofo的定价策略。3、小蓝单车小蓝单车的主要问题:小蓝单车由于投放量比较少,目前主要的问题是车子太少。4、共同问题三款共享单车比较集中的问题是:车太少。根据比达咨询发布的《2016中国共享单车市场研究报告》ofo的投放量和市场占有率都是最高的,尽管如此,很多用户仍然感觉ofo还是要增大其在核心地段的投放量。我们可以通过共享单车的受众群体来看投放量与投放地段。使用共享单车大部分人群集中在20~49岁,其中30-39岁年龄段最为集中,而在性别方面,男性使用共享单车的比例更高,女性偏低,可以结合以上两点对受众群体较为集中的地段进行投放。--当资本未选出最出色的那两三位的时候:我们就尽情的享受百花齐放给我们带来的各种各样的优惠吧;参考资料:","updated":"T14:08:40.000Z","canComment":false,"commentPermission":"anyone","commentCount":36,"likeCount":154,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T22:08:40+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-882ad161fff9c5e968323addb58ac93a_r.jpg","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":36,"likesCount":154},"":{"title":"Python魔法方法指南","author":"sgai","content":"有很多人说学习Python基础之后不知道干什么,不管你是从w3c还是从廖雪峰的教程学习的,这些教程都有一个特点:只能引你快速入门,但是有关于Python的很多基础内容这些教程中都没介绍,而这些你没学习的内容会让你在后期做项目的时候非常困惑。就比如下面这篇我要给大家推荐的文章所涉及的内容,不妨你用一天时间耐心看完,把代码都敲上一遍。--11:33更新--很多人想要我的一份学习笔记,所以在魔法指南之前,我放上我自己学习过程中提炼的一些知识点和自己的感悟,如果不想看我的笔记可以直接跳过。从入门到进阶,一个很重要的点就是Python中的魔法方法,魔法方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现了这些方法中的某一个,那么这个方法就会在特殊的情况下被 Python 所调用,你可以定义自己想要的行为,而这一切都是自动发生的。它们经常是两个下划线包围来命名的(比如 __init__/__new__等等),Python的魔法方法是非常强大的。如果你学习过Java,那你会发现Python中的魔法方法像是Java中的重载,Python中的魔法方法可以理解为:对类中的内置方法的重载,注意这里不是重写。举个例子,Python中有个比较操作符==用来比较两个变量的大小,而这个操作符是通过内置函数__eq__来实现的,所以我们只需要通过改变这个内置函数代码,就可以改变重新定义这个操作符的行为。我们定义一个类Word,继承自str类,现需要重新定义该类的操作符==,使这个操作符用来判断两个字符串长度是否相等,而不是通过字母顺序判断两个字符串是否相等。注意该变化只适用于Word类,而不适用于其它类。再举个例子:Python中的__new__方法是对象实例化时调用的第一个方法,该方法仅读取一个cls参数后再把其他参数都传给用于指明对象初始化行为的__init__方法,也就是说我们可以在一个对象初始化之前进行其他操作,比如检查是否合法等;而另一个方法__del__可以用来销毁对象,定义了对象被垃圾回收的行为,我们可以利用该方法进行资源回收等操作。我们可以通过重写__new__方法实现一个单例模式,在每次实例化之前检查该对象是否有已有实例。通过这两个例子相信你已经对Python的魔法方法比较理解了,但是Python中的魔法方法远不止两三个,而在官方文档中,也没有一个比较详细的归纳,所以这里参考译文(在此文的最后部分)做一个简单的汇总,希望大家可以根据这个汇总的表单对其中的魔法方法进行尝试,下次再看到这样的用法也就不足为奇了。由于版本等问题,以下的表格对译文的内容进行了修改、删减。以上是关于操作符运算符的方法,我们很少会改变这些魔法方法除非你真的需要改变他们,当然还有一些我们可能会用到的:1、__str__/__repr____str__定义对类的实例调用str()时的行为。而__repr__定义对类的实例调用repr()的行为,这两者的区别就是repr面向机器,str面向人。定义类的输出的时候经常会使用这两个其中的魔法。 2、__getattr__/__setattr__/__del__attr使用这三个方法你可以随时修改、删除、添加类属性或值,是不是觉得大吃一惊?Python中魔法函数常用的大体如上,也还有一些没有提到,你可以继续往下阅读。以下是译文的原文正文原作者:Rafe Kettler ; 翻译:hit9原文地址:1、简介本指南归纳于我的几个月的博客,主题是 魔法方法 。什么是魔法方法呢?它们在面向对象的Python的处处皆是。它们是一些可以让你对类添加“魔法”的特殊方法。 它们经常是两个下划线包围来命名的(比如 __init__ , __lt__ )。但是现在没有很好的文档来解释它们。 所有的魔法方法都会在Python的官方文档中找到,但是它们组织松散。而且很少会有示例(有的是无聊的语法描述, 语言参考)。所以,为了修复我感知的Python文档的缺陷,我开始提供更为通俗的,有示例支持的Python魔法方法指南。我一开始 写了一些博文,现在我把这些博文总起来成为一篇指南。希望你喜欢这篇指南,一篇友好,通俗易懂的Python魔法方法指南!2、构造方法我们最为熟知的基本的魔法方法就是 __init__ ,我们可以用它来指明一个对象初始化的行为。然而,当我们调用 x = SomeClass() 的时候, __init__ 并不是第一个被调用的方法。事实上,第一个被调用的是 __new__ ,这个 方法才真正地创建了实例。当这个对象的生命周期结束的时候, __del__ 会被调用。让我们近一步理解这三个方法:__new__(cls,[...)__new__ 是对象实例化时第一个调用的方法,它只取下 cls 参数,并把其他参数传给 __init__ 。 __new__ 很少使用,但是也有它适合的场景,尤其是当类继承自一个像元组或者字符串这样不经常改变的类型的时候。我不打算深入讨论 __new__ ,因为它并不是很有用,
中 有详细的说明。__init__(self,[...])类的初始化方法。它获取任何传给构造器的参数(比如我们调用 x = SomeClass(10, ‘foo’) , __init__ 就会接到参数 10 和 ‘foo’ 。 __init__ 在Python的类定义中用的最多。__del__(self)__new__ 和 __init__ 是对象的构造器, __del__ 是对象的销毁器。它并非实现了语句 del x (因此该语句不等同于 x.__del__())。而是定义了当对象被垃圾回收时的行为。 当对象需要在销毁时做一些处理的时候这个方法很有用,比如 socket 对象、文件对象。但是需要注意的是,当Python解释器退出但对象仍然存活的时候, __del__ 并不会 执行。 所以养成一个手工清理的好习惯是很重要的,比如及时关闭连接。这里有个 __init__ 和 __del__ 的例子:from os.path import join\n\nclass FileObject:\n
'''文件对象的装饰类,用来保证文件被删除时能够正确关闭。'''\n\n
def __init__(self, filepath='~', filename='sample.txt'):\n
# 使用读写模式打开filepath中的filename文件\n
self.file = open(join(filepath, filename), 'r+')\n\n
def __del__(self):\n
self.file.close()\n
del self.file\n3、操作符使用Python魔法方法的一个巨大优势就是可以构建一个拥有Python内置类型行为的对象。这意味着你可以避免使用非标准的、丑陋的方式来表达简单的操作。 在一些语言中,这样做很常见:if instance.equals(other_instance):\n
# do something\n你当然可以在Python也这么做,但是这样做让代码变得冗长而混乱。不同的类库可能对同一种比较操作采用不同的方法名称,这让使用者需要做很多没有必要的工作。运用魔法方法的魔力,我们可以定义方法 __eq__if instance == other_instance:\n
# do something\n这是魔法力量的一部分,这样我们就可以创建一个像内建类型那样的对象了!3.1、比较操作符Python包含了一系列的魔法方法,用于实现对象之间直接比较,而不需要采用方法调用。同样也可以重载Python默认的比较方法,改变它们的行为。下面是这些方法的列表:__cmp__(self, other)__cmp__ 是所有比较魔法方法中最基础的一个,它实际上定义了所有比较操作符的行为(&,==,!=,等等),但是它可能不能按照你需要的方式工作(例如,判断一个实例和另一个实例是否相等采用一套标准,而与判断一个实例是否大于另一实例采用另一套)。 __cmp__ 应该在 self & other 时返回一个负整数,在 self == other 时返回0,在 self & other 时返回正整数。最好只定义你所需要的比较形式,而不是一次定义全部。 如果你需要实现所有的比较形式,而且它们的判断标准类似,那么 __cmp__ 是一个很好的方法,可以减少代码重复,让代码更简洁。__eq__`(self, other)定义等于操作符(==)的行为。__ne__(self, other)定义不等于操作符(!=)的行为。__lt__(self, other)定义小于操作符(&)的行为。__gt__(self, other)定义大于操作符(&)的行为。__le__(self, other)定义小于等于操作符(&)的行为。__ge__(self, other)定义大于等于操作符(&)的行为。举个例子,假如我们想用一个类来存储单词。我们可能想按照字典序(字母顺序)来比较单词,字符串的默认比较行为就是这样。我们可能也想按照其他规则来比较字符串,像是长度,或者音节的数量。在这个例子中,我们使用长度作为比较标准,下面是一种实现:class Word(str):\n
'''单词类,按照单词长度来定义比较行为'''\n\n
def __new__(cls, word):\n
# 注意,我们只能使用 __new__ ,因为str是不可变类型\n
# 所以我们必须提前初始化它(在实例创建时)\n
if ' ' in word:\n
print \"Value contains spaces. Truncating to first space.\"\n
word = word[:word.index(' ')]\n
# Word现在包含第一个空格前的所有字母\n
return str.__new__(cls, word)\n\n
def __gt__(self, other):\n
return len(self) & len(other)\n
def __lt__(self, other):\n
return len(self) & len(other)\n
def __ge__(self, other):\n
return len(self) &= len(other)\n
def __le__(self, other):\n
return len(self) &= len(other)\n现在我们可以创建两个 Word 对象( Word(‘foo’) 和 Word(‘bar’))然后根据长度来比较它们。注意我们没有定义 __eq__ 和 __ne__ ,这是因为有时候它们会导致奇怪的结果(很明显, Word(‘foo’) == Word(‘bar’) 得到的结果会是true)。根据长度测试是否相等毫无意义,所以我们使用 str 的实现来比较相等。从上面可以看到,不需要实现所有的比较魔法方法,就可以使用丰富的比较操作。标准库还在 functools 模块中提供了一个类装饰器,只要我们定义 __eq__ 和另外一个操作符( __gt__, __lt__ 等),它就可以帮我们实现比较方法。这个特性只在 Python 2.7 中可用。当它可用时,它能帮助我们节省大量的时间和精力。要使用它,只需要它 @total_ordering 放在类的定义之上就可以了3.2、数值操作符就像你可以使用比较操作符来比较类的实例,你也可以定义数值操作符的行为。固定好你的安全带,这样的操作符真的有很多。看在组织的份上,我把它们分成了五类:一元操作符,常见算数操作符,反射算数操作符(后面会涉及更多),增强赋值操作符,和类型转换操作符。一元操作符只有一个操作符。__pos__(self)实现取正操作,例如 +some_object。__neg__(self)实现取负操作,例如 -some_object。__abs__(self)实现内建绝对值函数 abs() 操作。__invert__(self)实现取反操作符 ~。__round__(self, n)实现内建函数 round() ,n 是近似小数点的位数。__floor__(self)实现 math.floor() 函数,即向下取整。__ceil__(self)实现 math.ceil() 函数,即向上取整。__trunc__(self)实现 math.trunc() 函数,即距离零最近的整数。3.2.2. 常见算数操作符现在,我们来看看常见的二元操作符(和一些函数),像+,-,*之类的,它们很容易从字面意思理解。__add__(self, other)实现加法操作。__sub__(self, other)实现减法操作。__mul__(self, other)实现乘法操作。__floordiv__(self, other)实现使用 // 操作符的整数除法。__div__(self, other)实现使用 / 操作符的除法。__truediv__(self, other)实现 _true_ 除法,这个函数只有使用 from __future__ import division 时才有作用。__mod__(self, other)实现 % 取余操作。__divmod__(self, other)实现 divmod 内建函数。__pow__实现 ** 操作符。__lshift__(self, other)实现左移位运算符 && 。__rshift__(self, other)实现右移位运算符 && 。__and__(self, other)实现按位与运算符 & 。__or__(self, other)实现按位或运算符 | 。__xor__(self, other)实现按位异或运算符 ^ 。3.2.3. 反射算数运算符还记得刚才我说会谈到反射运算符吗?可能你会觉得它是什么高端霸气上档次的概念,其实这东西挺简单的,下面举个例子:some_object + other\n这是“常见”的加法,反射是一样的意思,只不过是运算符交换了一下位置:other + some_object\n所有反射运算符魔法方法和它们的常见版本做的工作相同,只不过是处理交换连个操作数之后的情况。绝大多数情况下,反射运算和正常顺序产生的结果是相同的,所以很可能你定义 __radd__ 时只是调用一下 __add__。注意一点,操作符左侧的对象(也就是上面的 other )一定不要定义(或者产生 NotImplemented 异常) 操作符的非反射版本。例如,在上面的例子中,只有当 other 没有定义 __add__ 时 some_object.__radd__ 才会被调用。__radd__(self, other)实现反射加法操作。__rsub__(self, other)实现反射减法操作。__rmul__(self, other)实现反射乘法操作。__rfloordiv__(self, other)实现使用 // 操作符的整数反射除法。__rdiv__(self, other)实现使用 / 操作符的反射除法。__rtruediv__(self, other)实现 _true_ 反射除法,这个函数只有使用 from __future__ import division 时才有作用。__rmod__(self, other)实现 % 反射取余操作符。__rdivmod__(self, other)实现调用 divmod(other, self) 时 divmod 内建函数的操作。__rpow__实现 ** 反射操作符。__rlshift__(self, other)实现反射左移位运算符 && 的作用。__rshift__(self, other)实现反射右移位运算符 && 的作用。__rand__(self, other)实现反射按位与运算符 & 。__ror__(self, other)实现反射按位或运算符 | 。__rxor__(self, other)实现反射按位异或运算符 ^ 。3.2.4. 增强赋值运算符Python同样提供了大量的魔法方法,可以用来自定义增强赋值操作的行为。或许你已经了解增强赋值,它融合了“常见”的操作符和赋值操作,如果你还是没听明白,看下面的例子:x = 5\nx += 1 # 也就是 x = x + 1\n这些方法都应该返回左侧操作数应该被赋予的值(例如, a += b __iadd__ 也许会返回 a + b ,这个结果会被赋给 a ),下面是方法列表:__iadd__(self, other)实现加法赋值操作。__isub__(self, other)实现减法赋值操作。__imul__(self, other)实现乘法赋值操作。__ifloordiv__(self, other)实现使用 //= 操作符的整数除法赋值操作。__idiv__(self, other)实现使用 /= 操作符的除法赋值操作。__itruediv__(self, other)实现 _true_ 除法赋值操作,这个函数只有使用 from __future__ import division 时才有作用。__imod__(self, other)实现 %= 取余赋值操作。__ipow__实现 **= 操作。__ilshift__(self, other)实现左移位赋值运算符 &&= 。__irshift__(self, other)实现右移位赋值运算符 &&= 。__iand__(self, other)实现按位与运算符 &= 。__ior__(self, other)实现按位或赋值运算符 | 。__ixor__(self, other)实现按位异或赋值运算符 ^= 。3.2.5. 类型转换操作符Python也有一系列的魔法方法用于实现类似 float() 的内建类型转换函数的操作。它们是这些:__int__(self)实现到int的类型转换。__long__(self)实现到long的类型转换。__float__(self)实现到float的类型转换。__complex__(self)实现到complex的类型转换。__oct__(self)实现到八进制数的类型转换。__hex__(self)实现到十六进制数的类型转换。__index__(self)实现当对象用于切片表达式时到一个整数的类型转换。如果你定义了一个可能会用于切片操作的数值类型,你应该定义 __index__。__trunc__(self)当调用 math.trunc(self) 时调用该方法, __trunc__ 应该返回 self 截取到一个整数类型(通常是long类型)的值。__coerce__(self)该方法用于实现混合模式算数运算,如果不能进行类型转换, __coerce__ 应该返回 None 。反之,它应该返回一个二元组 self 和 other ,这两者均已被转换成相同的类型。4、类的表示使用字符串来表示类是一个相当有用的特性。在Python中有一些内建方法可以返回类的表示,相对应的,也有一系列魔法方法可以用来自定义在使用这些内建函数时类的行为。__str__(self)定义对类的实例调用 str() 时的行为。__repr__(self)定义对类的实例调用 repr() 时的行为。 str() 和 repr() 最主要的差别在于“目标用户”。 repr() 的作用是产生机器可读的输出(大部分情况下,其输出可以作为有效的Python代码),而 str() 则产生人类可读的输出。__unicode__(self)定义对类的实例调用 unicode() 时的行为。 unicode() 和 str() 很像,只是它返回unicode字符串。注意,如果调用者试图调用 str() 而你的类只实现了 __unicode__() ,那么类将不能正常工作。所有你应该总是定义 __str__() ,以防有些人没有闲情雅致来使用unicode。__format__(self)定义当类的实例用于新式字符串格式化时的行为,例如, “Hello, 0:abc!”.format(a) 会导致调用 a.__format__(“abc”) 。当定义你自己的数值类型或字符串类型时,你可能想提供某些特殊的格式化选项,这种情况下这个魔法方法会非常有用。__hash__(self)定义对类的实例调用 hash() 时的行为。它必须返回一个整数,其结果会被用于字典中键的快速比较。同时注意一点,实现这个魔法方法通常也需要实现 __eq__ ,并且遵守如下的规则: a == b 意味着 hash(a) == hash(b)。__nonzero__(self)定义对类的实例调用 bool() 时的行为,根据你自己对类的设计,针对不同的实例,这个魔法方法应该相应地返回True或False。__dir__(self)定义对类的实例调用 dir() 时的行为,这个方法应该向调用者返回一个属性列表。一般来说,没必要自己实现 __dir__ 。但是如果你重定义了 __getattr__ 或者 __getattribute__ (下个部分会介绍),乃至使用动态生成的属性,以实现类的交互式使用,那么这个魔法方法是必不可少的。到这里,我们基本上已经结束了魔法方法指南中无聊并且例子匮乏的部分。既然我们已经介绍了较为基础的魔法方法,是时候涉及更高级的内容了。5、访问控制很多从其他语言转向Python的人都抱怨Python的类缺少真正意义上的封装(即没办法定义私有属性然后使用公有的getter和setter)。然而事实并非如此。实际上Python不是通过显式定义的字段和方法修改器,而是通过魔法方法实现了一系列的封装。__getattr__(self, name)当用户试图访问一个根本不存在(或者暂时不存在)的属性时,你可以通过这个魔法方法来定义类的行为。这个可以用于捕捉错误的拼写并且给出指引,使用废弃属性时给出警告(如果你愿意,仍然可以计算并且返回该属性),以及灵活地处理AttributeError。只有当试图访问不存在的属性时它才会被调用,所以这不能算是一个真正的封装的办法。__setattr__(self, name, value)和 __getattr__ 不同, __setattr__ 可以用于真正意义上的封装。它允许你自定义某个属性的赋值行为,不管这个属性存在与否,也就是说你可以对任意属性的任何变化都定义自己的规则。然后,一定要小心使用 __setattr__ ,这个列表最后的例子中会有所展示。__delattr__(self, name)这个魔法方法和 __setattr__ 几乎相同,只不过它是用于处理删除属性时的行为。和 _setattr__ 一样,使用它时也需要多加小心,防止产生无限递归(在 __delattr__ 的实现中调用 del self.name 会导致无限递归)。__getattribute__(self, name)` __getattribute__` 看起来和上面那些方法很合得来,但是最好不要使用它。 __getattribute__ 只能用于新式类。在最新版的Python中所有的类都是新式类,在老版Python中你可以通过继承 object 来创建新式类。 __getattribute__ 允许你自定义属性被访问时的行为,它也同样可能遇到无限递归问题(通过调用基类的 __getattribute__ 来避免)。 __getattribute__ 基本上可以替代 __getattr__ 。只有当它被实现,并且显式地被调用,或者产生 AttributeError 时它才被使用。 这个魔法方法可以被使用(毕竟,选择权在你自己),我不推荐你使用它,因为它的使用范围相对有限(通常我们想要在赋值时进行特殊操作,而不是取值时),而且实现这个方法很容易出现Bug。自定义这些控制属性访问的魔法方法很容易导致问题,考虑下面这个例子:def __setattr__(self, name. value):\n
self.name = value\n
# 因为每次属性幅值都要调用 __setattr__(),所以这里的实现会导致递归\n
# 这里的调用实际上是 self.__setattr('name', value)。因为这个方法一直\n
# 在调用自己,因此递归将持续进行,直到程序崩溃\n\ndef __setattr__(self, name, value):\n
self.__dict__[name] = value # 使用 __dict__ 进行赋值\n
# 定义自定义行为\n再次重申,Python的魔法方法十分强大,能力越强责任越大,了解如何正确的使用魔法方法更加重要。到这里,我们对Python中自定义属性存取控制有了什么样的印象?它并不适合轻度的使用。实际上,它有些过分强大,而且违反直觉。然而它之所以存在,是因为一个更大的原则:Python不指望让杜绝坏事发生,而是想办法让做坏事变得困难。自由是至高无上的权利,你真的可以随心所欲。下面的例子展示了实际应用中某些特殊的属性访问方法(注意我们之所以使用 super 是因为不是所有的类都有 __dict__ 属性):class AccessCounter(object):\n
''' 一个包含了一个值并且实现了访问计数器的类\n
每次值的变化都会导致计数器自增'''\n\n
def __init__(self, val):\n
super(AccessCounter, self).__setattr__('counter', 0)\n
super(AccessCounter, self).__setattr__('value', val)\n\n
def __setattr__(self, name, value):\n
if name == 'value':\n
super(AccessCounter, self).__setattr_('counter', self.counter + 1)\n
# 使计数器自增变成不可避免\n
# 如果你想阻止其他属性的赋值行为\n
# 产生 AttributeError(name) 就可以了\n
super(AccessCounter, self).__setattr__(name, value)\n\n
def __delattr__(self, name):\n
if name == 'value':\n
super(AccessCounter, self).__setattr('counter', self.counter + 1)\n
super(AccessCounter, self).__delattr(name)\n6、自定义序列有许多办法可以让你的Python类表现得像是内建序列类型(字典,元组,列表,字符串等)。这些魔法方式是目前为止我最喜欢的。它们给了你难以置信的控制能力,可以让你的类与一系列的全局函数完美结合。在了解激动人心的内容之前,首先你需要掌握一些预备知识。6.1、预备知识既然讲到创建自己的序列类型,就不得不说一说协议了。协议类似某些语言中的接口,里面包含的是一些必须实现的方法。在Python中,协议完全是非正式的,也不需要显式的声明,事实上,它们更像是一种参考标准。为什么我们要讲协议?因为在Python中实现自定义容器类型需要用到一些协议。首先,不可变容器类型有如下协议:想实现一个不可变容器,你需要定义 __len__ 和 __getitem__ (后面会具体说明)。可变容器的协议除了上面提到的两个方法之外,还需要定义 __setitem__ 和 __delitem__ 。最后,如果你想让你的对象可以迭代,你需要定义 __iter__ ,这个方法返回一个迭代器。迭代器必须遵守迭代器协议,需要定义 __iter__ (返回它自己)和 next 方法。6.2、容器背后的魔法方法__len__(self)返回容器的长度,可变和不可变类型都需要实现。__getitem__(self, key)定义对容器中某一项使用 self[key] 的方式进行读取操作时的行为。这也是可变和不可变容器类型都需要实现的一个方法。它应该在键的类型错误式产生 TypeError 异常,同时在没有与键值相匹配的内容时产生 KeyError 异常。__setitem__(self, key)定义对容器中某一项使用 self[key] 的方式进行赋值操作时的行为。它是可变容器类型必须实现的一个方法,同样应该在合适的时候产生 KeyError 和 TypeError 异常。__iter__(self, key)它应该返回当前容器的一个迭代器。迭代器以一连串内容的形式返回,最常见的是使用 iter() 函数调用,以及在类似 for x in container: 的循环中被调用。迭代器是他们自己的对象,需要定义 __iter__ 方法并在其中返回自己。__reversed__(self)定义了对容器使用 reversed() 内建函数时的行为。它应该返回一个反转之后的序列。当你的序列类是有序时,类似列表和元组,再实现这个方法,__contains__(self, item)__contains__ 定义了使用 in 和 not in 进行成员测试时类的行为。你可能好奇为什么这个方法不是序列协议的一部分,原因是,如果 __contains__ 没有定义,Python就会迭代整个序列,如果找到了需要的一项就返回 True 。__missing__(self ,key)__missing__ 在字典的子类中使用,它定义了当试图访问一个字典中不存在的键时的行为(目前为止是指字典的实例,例如我有一个字典 d , “george” 不是字典中的一个键,当试图访问 d[“george’] 时就会调用 d.__missing__(“george”) )。6.3、一个例子让我们来看一个实现了一些函数式结构的列表,可能在其他语言中这种结构更常见(例如Haskell):class FunctionalList:\n
'''一个列表的封装类,实现了一些额外的函数式\n
方法,例如head, tail, init, last, drop和take。'''\n\n
def __init__(self, values=None):\n
if values is None:\n
self.values = []\n
self.values = values\n\n
def __len__(self):\n
return len(self.values)\n\n
def __getitem__(self, key):\n
# 如果键的类型或值不合法,列表会返回异常\n
return self.values[key]\n\n
def __setitem__(self, key, value):\n
self.values[key] = value\n\n
def __delitem__(self, key):\n
del self.values[key]\n\n
def __iter__(self):\n
return iter(self.values)\n\n
def __reversed__(self):\n
return reversed(self.values)\n\n
def append(self, value):\n
self.values.append(value)\n\n
def head(self):\n
# 取得第一个元素\n
return self.values[0]\n\n
def tail(self):\n
# 取得除第一个元素外的所有元素\n
return self.valuse[1:]\n\n
def init(self):\n
# 取得除最后一个元素外的所有元素\n
return self.values[:-1]\n\n
def last(self):\n
# 取得最后一个元素\n
return self.values[-1]\n\n
def drop(self, n):\n
# 取得除前n个元素外的所有元素\n
return self.values[n:]\n\n
def take(self, n):\n
# 取得前n个元素\n
return self.values[:n]\n就是这些,一个(微不足道的)有用的例子,向你展示了如何实现自己的序列。当然啦,自定义序列有更大的用处,而且绝大部分都在标准库中实现了(Python是自带电池的,记得吗?),像 Counter , OrderedDict 和 NamedTuple 。7、反射你可以通过定义魔法方法来控制用于反射的内建函数 isinstance 和 issubclass 的行为。下面是对应的魔法方法:__instancecheck__(self, instance)检查一个实例是否是你定义的类的一个实例(例如 isinstance(instance, class) )。__subclasscheck__(self, subclass)检查一个类是否是你定义的类的子类(例如 issubclass(subclass, class) )。这几个魔法方法的适用范围看起来有些窄,事实也正是如此。我不会在反射魔法方法上花费太多时间,因为相比其他魔法方法它们显得不是很重要。但是它们展示了在Python中进行面向对象编程(或者总体上使用Python进行编程)时很重要的一点:不管做什么事情,都会有一个简单方法,不管它常用不常用。这些魔法方法可能看起来没那么有用,但是当你真正需要用到它们的时候,你会感到很幸运,因为它们还在那儿(也因为你阅读了这本指南!)8、抽象基类请参考 .9、可调用的对象你可能已经知道了,在Python中,函数是一等的对象。这意味着它们可以像其他任何对象一样被传递到函数和方法中,这是一个十分强大的特性。Python中一个特殊的魔法方法允许你自己类的对象表现得像是函数,然后你就可以“调用”它们,把它们传递到使用函数做参数的函数中,等等等等。这是另一个强大而且方便的特性,让使用Python编程变得更加幸福。__call__(self, [args...])允许类的一个实例像函数那样被调用。本质上这代表了 x() 和 x.__call__() 是相同的。注意 __call__ 可以有多个参数,这代表你可以像定义其他任何函数一样,定义 __call__ ,喜欢用多少参数就用多少。__call__ 在某些需要经常改变状态的类的实例中显得特别有用。“调用”这个实例来改变它的状态,是一种更加符合直觉,也更加优雅的方法。一个表示平面上实体的类是一个不错的例子:class Entity:\n
'''表示一个实体的类,调用它的实例\n
可以更新实体的位置'''\n\n
def __init__(self, size, x, y):\n
self.x, self.y = x, y\n
self.size = size\n\n
def __call__(self, x, y):\n
'''改变实体的位置'''\n
self.x, self.y = x, y\n10、上下文管理器在Python 2.5中引入了一个全新的关键词,随之而来的是一种新的代码复用方法—— with 声明。上下文管理的概念在Python中并不是全新引入的(之前它作为标准库的一部分实现),直到PEP 343被接受,它才成为一种一级的语言结构。可能你已经见过这种写法了:with open('foo.txt') as bar:\n
# 使用bar进行某些操作\n当对象使用 with 声明创建时,上下文管理器允许类做一些设置和清理工作。上下文管理器的行为由下面两个魔法方法所定义:__enter__(self)定义使用 with 声明创建的语句块最开始上下文管理器应该做些什么。注意 __enter__ 的返回值会赋给 with 声明的目标,也就是 as 之后的东西。__exit__(self, exception_type, exception_value, traceback)定义当 with 声明语句块执行完毕(或终止)时上下文管理器的行为。它可以用来处理异常,进行清理,或者做其他应该在语句块结束之后立刻执行的工作。如果语句块顺利执行, exception_type , exception_value 和 traceback 会是 None 。否则,你可以选择处理这个异常或者让用户来处理。如果你想处理异常,确保 __exit__ 在完成工作之后返回 True 。如果你不想处理异常,那就让它发生吧。对一些具有良好定义的且通用的设置和清理行为的类,__enter__ 和 __exit__ 会显得特别有用。你也可以使用这几个方法来创建通用的上下文管理器,用来包装其他对象。下面是一个例子:class Closer:\n
'''一个上下文管理器,可以在with语句中\n
使用close()自动关闭对象'''\n\n
def __init__(self, obj):\n
self.obj = obj\n\n
def __enter__(self, obj):\n
return self.obj # 绑定到目标\n\n
def __exit__(self, exception_type, exception_value, traceback):\n
self.obj.close()\n
except AttributeError: # obj不是可关闭的\n
print 'Not closable.'\n
return True # 成功地处理了异常\n这是一个 Closer 在实际使用中的例子,使用一个FTP连接来演示(一个可关闭的socket):&&& from magicmethods import Closer\n&&& from ftplib import FTP\n&&& with Closer(FTP('')) as conn:\n...
conn.dir()\n...\n# 为了简单,省略了某些输出\n&&& conn.dir()\n# 很长的 AttributeError 信息,不能使用一个已关闭的连接\n&&& with Closer(int(5)) as i:\n...
i += 1\n...\nNot closable.\n&&& i\n6\n看到我们的包装器是如何同时优雅地处理正确和不正确的调用了吗?这就是上下文管理器和魔法方法的力量。Python标准库包含一个 contextlib 模块,里面有一个上下文管理器 contextlib.closing() 基本上和我们的包装器完成的是同样的事情(但是没有包含任何当对象没有close()方法时的处理)。1、创建描述符对象描述符是一个类,当使用取值,赋值和删除 时它可以改变其他对象。描述符不是用来单独使用的,它们需要被一个拥有者类所包含。描述符可以用来创建面向对象数据库,以及创建某些属性之间互相依赖的类。描述符在表现具有不同单位的属性,或者需要计算的属性时显得特别有用(例如表现一个坐标系中的点的类,其中的距离原点的距离这种属性)。要想成为一个描述符,一个类必须具有实现 __get__ , __set__ 和 __delete__ 三个方法中至少一个。让我们一起来看一看这些魔法方法:__get__(self, instance, owner)定义当试图取出描述符的值时的行为。 instance 是拥有者类的实例, owner 是拥有者类本身。__set__(self, instance, owner)定义当描述符的值改变时的行为。 instance 是拥有者类的实例, value 是要赋给描述符的值。__delete__(self, instance, owner)定义当描述符的值被删除时的行为。 instance 是拥有者类的实例现在,来看一个描述符的有效应用:单位转换:class Meter(object):\n
'''米的描述符。'''\n\n
def __init__(self, value=0.0):\n
self.value = float(value)\n
def __get__(self, instance, owner):\n
return self.value\n
def __set__(self, instance, owner):\n
self.value = float(value)\n\nclass Foot(object):\n
'''英尺的描述符。'''\n\n
def __get(self, instance, owner):\n
return instance.meter * 3.2808\n
def __set(self, instance, value):\n
instance.meter = float(value) / 3.2808\n\nclass Distance(object):\n
'''用于描述距离的类,包含英尺和米两个描述符。'''\n
meter = Meter()\n
foot = Foot()\n2、拷贝有些时候,特别是处理可变对象时,你可能想拷贝一个对象,改变这个对象而不影响原有的对象。这时就需要用到Python的 copy 模块了。然而(幸运的是),Python模块并不具有感知能力, 因此我们不用担心某天基于Linux的机器人崛起。但是我们的确需要告诉Python如何有效率地拷贝对象。__copy__(self)定义对类的实例使用 copy.copy() 时的行为。 copy.copy() 返回一个对象的浅拷贝,这意味着拷贝出的实例是全新的,然而里面的数据全都是引用的。也就是说,对象本身是拷贝的,但是它的数据还是引用的(所以浅拷贝中的数据更改会影响原对象)。__deepcopy__(self, memodict=)定义对类的实例使用 copy.deepcopy() 时的行为。 copy.deepcopy() 返回一个对象的深拷贝,这个对象和它的数据全都被拷贝了一份。 memodict 是一个先前拷贝对象的缓存,它优化了拷贝过程,而且可以防止拷贝递归数据结构时产生无限递归。当你想深拷贝一个单独的属性时,在那个属性上调用 copy.deepcopy() ,使用 memodict 作为第一个参数。这些魔法方法有什么用武之地呢?像往常一样,当你需要比默认行为更加精确的控制时。例如,如果你想拷贝一个对象,其中存储了一个字典作为缓存(可能会很大),拷贝缓存可能是没有意义的。如果这个缓存可以在内存中被不同实例共享,那么它就应该被共享。3、Pickling如果你和其他的Python爱好者共事过,很可能你已经听说过Pickling了。Pickling是Python数据结构的序列化过程,当你想存储一个对象稍后再取出读取时,Pickling会显得十分有用。然而它同样也是担忧和混淆的主要来源。Pickling是如此的重要,以至于它不仅仅有自己的模块( pickle ),还有自己的协议和魔法方法。首先,我们先来简要的介绍一下如何pickle已存在的对象类型(如果你已经知道了,大可跳过这部分内容)。3.1、Pickling小试牛刀我们一起来pickle吧。假设你有一个字典,你想存储它,稍后再取出来。你可以把它的内容写入一个文件,小心翼翼地确保使用了正确地格式,要把它读取出来,你可以使用 exec() 或处理文件输入。但是这种方法并不可靠:如果你使用纯文本来存储重要数据,数据很容易以多种方式被破坏或者修改,导致你的程序崩溃,更糟糕的情况下,还可能在你的计算机上运行恶意代码。因此,我们要pickle它:import pickle\n\ndata = {'foo': [1,2,3],\n
'bar': ('Hello', 'world!'),\n
'baz': True}\njar = open('data.pkl', 'wb')\npickle.dump(data, jar) # 将pickle后的数据写入jar文件\njar.close()\n过了几个小时,我们想把它取出来,我们只需要反pickle它:import pickle\n\npkl_file = open('data.pkl', 'rb') # 与pickle后的数据连接\ndata = pickle.load(pkl_file) # 把它加载进一个变量\nprint data\npkl_file.close()\n将会发生什么?正如你期待的,它就是我们之前的 data 。现在,还需要谨慎地说一句: pickle并不完美。Pickle文件很容易因为事故或被故意的破坏掉。Pickling或许比纯文本文件安全一些,但是依然有可能被用来运行恶意代码。而且它还不支持跨Python版本,所以不要指望分发pickle对象之后所有人都能正确地读取。然而不管怎么样,它依然是一个强有力的工具,可以用于缓存和其他类型的持久化工作。13.2、Pickle你的对象Pickle不仅仅可以用于内建类型,任何遵守pickle协议的类都可以被pickle。Pickle协议有四个可选方法,可以让类自定义它们的行为(这和C语言扩展略有不同,那不在我们的讨论范围之内)。__getinitargs__(self)如果你想让你的类在反pickle时调用 __init__ ,你可以定义 __getinitargs__(self) ,它会返回一个参数元组,这个元组会传递给 __init__ 。注意,这个方法只能用于旧式类。__getnewargs__(self)对新式类来说,你可以通过这个方法改变类在反pickle时传递给 __new__ 的参数。这个方法应该返回一个参数元组。__getstate__(self)你可以自定义对象被pickle时被存储的状态,而不使用对象的 __dict__ 属性。 这个状态在对象被反pickle时会被 __setstate__ 使用。__setstate__(self)当一个对象被反pickle时,如果定义了 __setstate__ ,对象的状态会传递给这个魔法方法,而不是直接应用到对象的 __dict__ 属性。这个魔法方法和 __getstate__ 相互依存:当这两个方法都被定义时,你可以在Pickle时使用任何方法保存对象的任何状态。__reduce__(self)当定义扩展类型时(也就是使用Python的C语言API实现的类型),如果你想pickle它们,你必须告诉Python如何pickle它们。 __reduce__ 被定义之后,当对象被Pickle时就会被调用。它要么返回一个代表全局名称的字符串,Pyhton会查找它并pickle,要么返回一个元组。这个元组包含2到5个元素,其中包括:一个可调用的对象,用于重建对象时调用;一个参数元素,供那个可调用对象使用;被传递给 __setstate__ 的状态(可选);一个产生被pickle的列表元素的迭代器(可选);一个产生被pickle的字典元素的迭代器(可选);__reduce_ex__(self)__reduce_ex__ 的存在是为了兼容性。如果它被定义,在pickle时 __reduce_ex__ 会代替 __reduce__ 被调用。 __reduce__ 也可以被定义,用于不支持 __reduce_ex__ 的旧版pickle的API调用。13.3、一个例子我们的例子是 Slate ,它会记住它的值曾经是什么,以及那些值是什么时候赋给它的。然而 每次被pickle时它都会变成空白,因为当前的值不会被存储:import time\n\nclass Slate:\n
'''存储一个字符串和一个变更日志的类\n
每次被pickle都会忘记它当前的值'''\n\n
def __init__(self, value):\n
self.value = value\n
self.last_change = time.asctime()\n
self.history = {}\n\n
def change(self, new_value):\n
# 改变当前值,将上一个值记录到历史\n
self.history[self.last_change] = self.value\n
self.value = new_value)\n
self.last_change = time.asctime()\n\n
def print_change(self):\n
print 'Changelog for Slate object:'\n
for k,v in self.history.items():\n
print '%s\\t %s' % (k,v)\n\n
def __getstate__(self):\n
# 故意不返回self.value或self.last_change\n
# 我们想在反pickle时得到一个空白的slate\n
return self.history\n\n
def __setstate__(self):\n
# 使self.history = slate,last_change\n
# 和value为未定义\n
self.history = state\n
self.value, self.last_change = None, None\n14、总结这本指南的目标是使所有阅读它的人都能有所收获,无论他们有没有使用Python或者进行面向对象编程的经验。如果你刚刚开始学习Python,你会得到宝贵的基础知识,了解如何写出具有丰富特性的,优雅而且易用的类。如果你是中级的Python程序员,你或许能掌握一些新的概念和技巧,以及一些可以减少代码行数的好办法。如果你是专家级别的Python爱好者,你又重新复习了一遍某些可能已经忘掉的知识,也可能顺便了解了一些新技巧。无论你的水平怎样,我希望这趟遨游Python特殊方法的旅行,真的对你产生了魔法般的效果(实在忍不住不说最后这个双关)。15、附录1:如何调用魔法方法一些魔法方法直接和内建函数对应,这种情况下,如何调用它们是显而易见的。然而,另外的情况下,调用魔法方法的途径并不是那么明显。这个附录旨在展示那些不那么明显的调用魔法方法的语法。16、附录2:Python 3中的变化在这里,我们记录了几个在对象模型方面 Python 3 和 Python 2.x 之间的主要区别。Python 3中string和unicode的区别不复存在,因此 __unicode__ 被取消了, __bytes__ 加入进来(与Python 2.7 中的 __str__ 和 __unicode__ 行为类似),用于新的创建字节数组的内建方法。Python 3中默认除法变成了 true 除法,因此 __div__ 被取消了。__coerce__ 被取消了,因为和其他魔法方法有功能上的重复,以及本身行为令人迷惑。__cmp__ 被取消了,因为和其他魔法方法有功能上的重复。__nonzero__ 被重命名成 __bool__ 。译文作者:读书笔记作者:","updated":"T00:02:32.000Z","canComment":false,"commentPermission":"anyone","commentCount":37,"likeCount":713,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T08:02:32+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-dbbadc0a3f6f3c282bc4de_r.jpg","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":37,"likesCount":713},"":{"title":"全国高校技术大赛报名倒计时!(附校招真题&面经汇总)","author":"dorothy-33-6","content":"2017爱奇艺全国高校技术大赛,是由爱奇艺创办的面向全国高校算法以及开发人才的技术类竞赛,比赛分为“最强开发者”(应用开发类)和 “算法英雄”(后台算法类)两大方向。比赛1:最强开发者积累项目经验,提前赢取校招offer!报名爱奇艺“最强开发者”,报名即可参加初赛,完成线上笔试即可有机会参与抽奖。10万现金大奖,海量奖品等你来拿,还有专业大牛给予指导。报名截止:5月12日适合人群:所有技术类大学生比赛详情:马上报名:比赛2:算法英雄10万现金大奖,校招Special Offer,等你瓜分!报名参加爱奇艺“算法英雄大赛”,挑战自我,和圈内高手切磋,还有机会获得大牛专属指导,更有10万现金大奖得你瓜分!报名截止:5月12日适合人群:所有技术类大学生比赛详情:马上报名:想要拿个校招好offer,现在正是好机会!参加“爱奇艺·全国高校技术大赛”,积累项目经验,丰富你的简历,为求职增彩!电脑端报名: 手机端报名:---------------------------------------------------------------------------------------------------------------------附:校招真题&面经汇总校招真题汇总:一、二、2017互联网名企·春招真题汇总1、2、3、4、校招面经汇总:一、二、","updated":"T06:46:28.000Z","canComment":false,"commentPermission":"anyone","commentCount":9,"likeCount":168,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T14:46:28+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-001b31e9e35ffd98627cdbf_r.png","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":9,"likesCount":168},"":{"title":"WebSocket 教程","author":"stormzhang","content":"作者:阮一峰原文链接: 是一种网络通信协议,很多高级功能都需要它。本文介绍 WebSocket 协议的使用方法。一、为什么需要 WebSocket?初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用:每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。二、简介WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持了。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于的一种。其他特点包括:(1)建立在 TCP 协议之上,服务器端的实现比较容易。(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。(3)数据格式比较轻量,性能开销小,通信高效。(4)可以发送文本,也可以发送二进制数据。(5)没有同源限制,客户端可以与任意服务器通信。(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。ws://:80/some/path\n三、客户端的简单示例WebSocket 的用法相当简单。下面是一个网页脚本的例子(点击看运行结果),基本上一眼就能明白。var ws = new WebSocket(\"wss://echo.websocket.org\");\n\nws.onopen = function(evt) { \n
console.log(\"Connection open ...\"); \n
ws.send(\"Hello WebSockets!\");\n};\n\nws.onmessage = function(evt) {\n
console.log( \"Received Message: \" + evt.data);\n
ws.close();\n};\n\nws.onclose = function(evt) {\n
console.log(\"Connection closed.\");\n};
\n四、客户端的 APIWebSocket 客户端的 API 如下。4.1 WebSocket 构造函数WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。var ws = new WebSocket('ws://localhost:8080');\n执行上面语句之后,客户端就会与服务器进行连接。实例对象的所有属性和方法清单,参见。4.2 webSocket.readyStatereadyState属性返回实例对象的当前状态,共有四种。CONNECTING:值为0,表示正在连接。OPEN:值为1,表示连接成功,可以通信了。CLOSING:值为2,表示连接正在关闭。CLOSED:值为3,表示连接已经关闭,或者打开连接失败。下面是一个示例。switch (ws.readyState) {\n
case WebSocket.CONNECTING:\n
// do something\\n
case WebSocket.OPEN:\n
// do something\\n
case WebSocket.CLOSING:\n
// do something\\n
case WebSocket.CLOSED:\n
// do something\\n
default:\n
// this never happens\\n}\n4.3 webSocket.onopen实例对象的onopen属性,用于指定连接成功后的回调函数。ws.onopen = function () {\n
ws.send('Hello Server!');\n}\n如果要指定多个回调函数,可以使用addEventListener方法。ws.addEventListener('open', function (event) {\n
ws.send('Hello Server!');\n});\n4.4 webSocket.onclose实例对象的onclose属性,用于指定连接关闭后的回调函数。ws.onclose = function(event) {\n
var code = event.\n
var reason = event.\n
var wasClean = event.wasC\n
// handle close event\n};\n\nws.addEventListener(\"close\", function(event) {\n
var code = event.\n
var reason = event.\n
var wasClean = event.wasC\n
// handle close event\n});\n4.5 webSocket.onmessage实例对象的onmessage属性,用于指定收到服务器数据后的回调函数。ws.onmessage = function(event) {\n
var data = event.\n
// 处理数据\n};\n\nws.addEventListener(\"message\", function(event) {\n
var data = event.\n
// 处理数据\n});\n注意,服务器数据可能是文本,也可能是二进制数据(blob对象或Arraybuffer对象)。ws.onmessage = function(event){\n
if(typeof event.data === String) {\n
console.log(\"Received data string\");\n
if(event.data instanceof ArrayBuffer){\n
var buffer = event.\n
console.log(\"Received arraybuffer\");\n
}\n}\n除了动态判断收到的数据类型,也可以使用binaryType属性,显式指定收到的二进制数据类型。// 收到的是 blob 数据\nws.binaryType = \"blob\";\nws.onmessage = function(e) {\n
console.log(e.data.size);\n};\n\n// 收到的是 ArrayBuffer 数据\nws.binaryType = \"arraybuffer\";\nws.onmessage = function(e) {\n
console.log(e.data.byteLength);\n};\n4.6 webSocket.send()实例对象的send()方法用于向服务器发送数据。发送文本的例子。ws.send('your message');\n发送 Blob 对象的例子。var file = document\n
.querySelector('input[type=\"file\"]')\n
.files[0];\nws.send(file);\n发送 ArrayBuffer 对象的例子。// Sending canvas ImageData as ArrayBuffer\nvar img = canvas_context.getImageData(0, 0, 400, 320);\nvar binary = new Uint8Array(img.data.length);\nfor (var i = 0; i & img.data. i++) {\n
binary[i] = img.data[i];\n}\nws.send(binary.buffer);\n4.7 webSocket.bufferedAmount实例对象的bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。var data = new ArrayBuffer();\nsocket.send(data);\n\nif (socket.bufferedAmount === 0) {\n
// 发送完毕\n} else {\n
// 发送还没结束\n}\n4.8 webSocket.onerror实例对象的onerror属性,用于指定报错时的回调函数。socket.onerror = function(event) {\n
// handle error event\n};\n\nsocket.addEventListener(\"error\", function(event) {\n
// handle error event\n});\n五、服务端的实现WebSocket 服务器的实现,可以查看维基百科的。常用的 Node 实现有以下三种。具体的用法请查看它们的文档,这里不详细介绍了。六、WebSocketd下面,我要推荐一款非常特别的 WebSocket 服务器:。它的最大特点,就是后台脚本不限语言,标准输入(stdin)就是 WebSocket 的输入,标准输出(stdout)就是 WebSocket 的输出。举例来说,下面是一个 Bash 脚本counter.sh。#!/bin/bash\n\necho 1\nsleep 1\n\necho 2\nsleep 1\n\necho 3\n命令行下运行这个脚本,会输出1、2、3,每个值之间间隔1秒。$ bash ./counter.sh\n1\n2\n3\n现在,启动websocketd,指定这个脚本作为服务。$ websocketd --port=8080 bash ./counter.sh\n上面的命令会启动一个 WebSocket 服务器,端口是8080。每当客户端连接这个服务器,就会执行counter.sh脚本,并将它的输出推送给客户端。var ws = new WebSocket('ws://localhost:8080/');\n\nws.onmessage = function(event) {\n
console.log(event.data);\n};\n上面是客户端的 JavaScript 代码,运行之后会在控制台依次输出1、2、3。有了它,就可以很方便地将命令行的输出,发给浏览器。$ websocketd --port=8080 ls\n上面的命令会执行ls命令,从而将当前目录的内容,发给浏览器。使用这种方式实时监控服务器,简直是轻而易举()。更多的用法可以参考。Bash 脚本的例子五行代码实现一个最简单的websocketd 的实质,就是命令行的 WebSocket 代理。只要命令行可以执行的程序,都可以通过它与浏览器进行 WebSocket 通信。下面是一个 Node 实现的回声服务。process.stdin.setEncoding('utf8');\n\nprocess.stdin.on('readable', function() {\n
var chunk = process.stdin.read();\n
if (chunk !== null) {\n
process.stdout.write('data: ' + chunk);\n
}\n});\n启动这个脚本的命令如下。$ websocketd --port=8080 node ./greeter.js\n官方仓库还有其他的例子。七、参考链接","updated":"T06:14:47.000Z","canComment":false,"commentPermission":"anyone","commentCount":14,"likeCount":305,"state":"published","isLiked":false,"slug":"","isTitleImageFullScreen":false,"rating":"none","sourceUrl":"","publishedTime":"T14:14:47+08:00","links":{"comments":"/api/posts//comments"},"url":"/p/","titleImage":"/v2-6b303d9afea_r.png","summary":"","href":"/api/posts/","meta":{"previous":null,"next":null},"snapshotUrl":"","commentsCount":14,"likesCount":305},"":{"title":"响应式编程(Reactive Programming)介绍","author":"sgai","content":"英文原文:中文链接:翻译:极客学院wiki,已获得转载权限这篇文章在GitHubGist上面获得了12531个star,文章很长,但是希望你耐心读完,感谢极客学院的翻译!学习响应式编程是很困难的一个过程,特别是在缺乏优秀资料的前提下。刚开始学习时,我试过去找一些教程,并找到了为数不多的实用教程,但是它们都流于表面,从没有围绕响应式编程构建起一个完整的知识体系。库的文档往往也无法帮助你去了解它的函数,不信的话可以看一下这个:通过合并元素的指针,将每一个可观察的元素序列放射到一个新的可观察的序列中,然后将多个可观察的序列中的一个转换成一个只从最近的可观察序列中产生值得可观察的序列。天啊。我看过两本书,一本只是讲述了一些概念,而另一本则纠结于如何使用响应式编程库。我最终放弃了这种痛苦的学习方式,决定在开发中一边使用响应式编程,一边理解它。在
工作期间,我尝试在真实项目中使用响应式编程,并且当我遇到困难时,得到了同事们的帮助。在学习过程中最困难的一部分是 以响应式编程的方式思考 。这意味着要放弃命令式且带状态的编程习惯,并且要强迫你的大脑以一种不同的方式去工作。在互联网上我找不到任何关于这方面的教程,而我觉得这世界需要一份关于怎么以响应式编程的方式思考的实用教程,这样你就有足够的资料去起步。库的文档无法为你的学习提供指引,而我希望这篇文章可以。什么是响应式编程?在互联网上有着一大堆糟糕的解释与定义。 一如既往的空泛与理论化。 的权威答案明显不适合初学者。 看起来是你展示给你公司的项目经理或者老板们看的东西。微软的 \"Rx = Observables + LINQ + Schedulers\" 过于重量级且微软味十足,只会让大部分人困惑。相对于你所使用的 MV* 框架以及钟爱的编程语言,\"Reactive\" 和 \"Propagation of change\" 这些术语并没有传达任何有意义的概念。框架的 Views 层当然要对 Models 层作出反应,改变当然会传播。如果没有这些,就没有东西会被渲染了。所以不要再扯这些废话了。响应式编程是使用异步数据流进行编程一方面,这并不是什么新东西。Event buses 或者 Click events 本质上就是异步事件流,你可以监听并处理这些事件。响应式编程的思路大概如下:你可以用包括 Click 和 Hover 事件在内的任何东西创建 Data stream。Stream 廉价且常见,任何东西都可以是一个 Stream:变量、用户输入、属性、Cache、数据结构等等。举个例子,想像一下你的 Twitter feed 就像是 Click events 那样的 Data stream,你可以监听它并相应的作出响应。在这个基础上,你还有令人惊艳的函数去组合、创建、过滤这些 Streams,这就是函数式魔法的用武之地。Stream 能接受一个,甚至多个 Stream 为输入,你可以融合两个 Stream,也可以从一个 Stream 中过滤出你感兴趣的 Events 以生成一个新的 Stream,还可以把一个 Stream 中的数据值 映射到一个新的 Stream 中。既然 Stream 在响应式编程中如此重要,那么我们就应该好好的了解它们,就从我们熟悉的\"Clicks on a button\" Event stream 开始。Stream 就是一个按时间排序的 Events 序列,它可以放射三种不同的 Events:(某种类型的)Value、Error 或者一个\" Completed\" Signal。考虑一下\"Completed\"发生的时机,例如,当包含这个按钮的窗口或者视图被关闭时。通过分别为 Value、Error、\"Completed\"定义事件处理函数,我们将会异步地捕获这些 Events。有时可以忽略 Error 与\"Completed\",你只需要定义 Value 的事件处理函数就行。监听一个 Stream 也被称作是订阅 ,而我们所定义的函数就是观察者,Stream则是被观察者,其实就是 。上面的示意图也可以使用ASCII重画为下图,在下面的部分教程中我们会使用这幅图:
--a---b-c---d---X---|-&\n\n
a, b, c, d are emitted values\n
X is an error\n
| is the 'completed' signal\n
---& is the timeline 既然已经开始对响应式编程感到熟悉,为了不让你觉得无聊,我们可以尝试做一些新东西:我们将会把一个 Click event stream 转为新的 Click event stream。首先,让我们做一个能记录一个按钮点击了多少次的计数器 Stream。在常见的响应式编程库中,每个Stream都会有多个方法,如 map, filter, scan, 等等。当你调用其中一个方法时,例如 clickStream.map(f),它就会基于原来的 Click stream 返回一个新的 Stream 。它不会对原来的 Click steam 作任何修改。这个特性称为不可变性,它对于响应式编程 Stream,就如果汁对于薄煎饼。我们也可以对方法进行链式调用,如 clickStream.map(f).scan(g):
clickStream: ---c----c--c----c------c--&\n
vvvvv map(c becomes 1) vvvv\n
---1----1--1----1------1--&\n
vvvvvvvvv scan(+) vvvvvvvvv\n
counterStream: ---1----2--3----4------5--& map(f) 会根据你提供的 f 函数把原 Stream 中的 Value 分别映射到新的 Stream 中。在我们的例子中,我们把每一次 Click 都映射为数字 1。scan(g) 会根据你提供的 g 函数把 Stream 中的所有 Value 聚合成一个 Value x = g(accumulated, current) ,这个示例中 g 只是一个简单的添加函数。然后,每 Click 一次, counterStream 就会把点击的总次数发给它的观察者。为了展示响应式编程真正的实力,让我们假设你想得到一个包含“双击”事件的 Stream。为了让它更加有趣,假设我们想要的这个 Stream 要同时考虑三击(Triple clicks),或者更加宽泛,连击(两次或更多)。深呼吸一下,然后想像一下在传统的命令式且带状态的方式中你会怎么实现。我敢打赌代码会像一堆乱麻,并且会使用一些变量保存状态,同时也有一些计算时间间隔的代码。而在响应式编程中,这个功能的实现就非常简单。事实上,这逻辑只有 。但现在我们先不管那些代码。用图表的方式思考是理解怎样构建Stream的最好方法,无论你是初学者还是专家。灰色的方框是用来转换 Stream 函数的。首先,简而言之,我们把连续 250 ms 内的 Click 都积累到一个列表中(就是 buffer(stream.throttle(250ms) 做的事。不要在意这些细节,我们只是展示一下响应式编程而已)。结果是一个列表的 Stream ,然后我们使用 map() 把每个列表映射为一个整数,即它的长度。最终,我们使用 filter(x &= 2) 把整数 1 给过滤掉。就这样,3 个操作就生成了我们想要的 Stream。然后我们就可以订阅(“监听”)这个 Stream,并以我们所希望的方式作出反应。我希望你能感受到这个示例的优美之处。这个示例只是冰山一角:你可以把同样的操作应用到不同种类的 Stream 上,例如,一个 API 响应的 Stream;另一方面,还有很多其它可用的函数。为什么我要使用响应式编程(RP)?响应式编程提高了代码的抽象层级,所以你可以只关注定义了业务逻辑的那些相互依赖的事件,而非纠缠于大量的实现细节。RP 的代码往往会更加简明。特别是在开发现在这些有着大量与数据事件相关的 UI events 的高互动性 Webapps、手机 apps 的时候,RP 的优势就更加明显。10年前,网页的交互就只是提交一个很长的表单到后端,而在前端只产生简单的渲染。Apps 就表现得更加的实时了:修改一个表单域就能自动地把修改后的值保存到后端,为一些内容\"点赞\"时,会实时的反应到其它在线用户那里等等。现在的 Apps 有着大量各种各样的实时 Events,以给用户提供一个交互性较高的体验。我们需要工具去应对这个变化,而响应式编程就是一个答案。以 RP 方式思考的例子让我们做一些实践。一个真实的例子一步一步的指导我们以 RP 的方式思考。不是虚构的例子,也没有只解释了一半的概念。学完教程之后,我们将写出真实可用的代码,并做到知其然,知其所以然。在这个教程中,我将会使用 JavaScript 和
作为工具 ,因为JavaScript是现在最多人会的语言,而
有多种语言版本,并支持多种平台(, , Scala, Clojure, , , , , , Groovy等等)。所以,无论你用的是什么工具,你都能从下面这个教程中受益。实现Who to follow推荐界面在 Twitter 上,这个表明其他账户的 UI 元素看起来是这样的:我们将会重点模拟它的核心功能,如下:启动时从 API 那里加载帐户数据,并显示 3 个推荐点击\"Refresh\"时,加载另外 3 个推荐用户到这三行中点击帐户所在行的'x'按钮时,只清除那一个推荐然后显示一个新的推荐每行都会显示帐户的头像,以及他们主页的链接我们可以忽略其它的特性和按钮,因为它们是次要的。同时,因为 Twitter 最近关闭了对非授权用户的 API,我们将会为 Github 实现这个推荐界面,而非 Twitter。这是。如果你想先看一下最终效果,这里有完成后的代码 。请求和响应在 Rx 中你该怎么处理这个问题呢? 好吧,首先,(几乎) 所有的东西都可以转为一个Stream 。这就是Rx的咒语。让我们先从最简单的特性开始:\"在启动时,从API加载3个帐户的数据\"。这并没有什么特别,就只是简单的(1)发出一个请求,(2)收到一个响应,(3)渲染这个响应。所以,让我们继续,并用Stream代表我们的请求。一开始可能会觉得杀鸡用牛刀,但我们应当从最基本的开始,对吧?在启动的时候,我们只需要发出一个请求,所以如果我们把它转为一个Data stream的话,那就是一个只有一个Value的Stream。稍后,我们知道将会有多个请求发生,但现在,就只有一个请求。
--a------|-&\n\n
Where a is the string '/users' 这是一个我们想向其发出请求的 URL 的 Stream。每当一个请求事件发生时,它会告诉我们两件事:\"什么时候\"与\"什么东西\"。\"什么时候\"这个请求会被执行,就是什么时候这个 Event 会被映射。\"什么东西\"会被请求,就是这个映射出来的值:一个包含 URL 的 String。在 RX 中,创建只有一个值的 Stream 是非常简单的。官方把一个 Stream 称作“Observable”,因为它可以被观察,但是我发现那是个很愚蠢的名子,所以我把它叫做 Stream*。
var requestStream = Rx.Observable.just('/users'); 但是现在,那只是一个包含了String的Stream,并没有其他操作,所以我们需要以某种方式使那个值被映射。就是通过
这个 Stream。
requestStream.subscribe(function(requestUrl) {\n
// execute the request\n
jQuery.getJSON(requestUrl, function(responseData) {\n
}留意一下我们使用了 jQuery 的 Ajax 函数(我们假设你已经知道 )去处理异步请求操作。但先等等,Rx 可以用来处理异步 Data stream。那这个请求的响应就不能当作一个包含了将会到达的数据的 Stream 吗?当然,从理论上来讲,应该是可以的,所以我们尝试一下。
requestStream.subscribe(function(requestUrl) {\n
// execute the request\n
var responseStream = Rx.Observable.create(function (observer) {\n
jQuery.getJSON(requestUrl)\n
.done(function(response) { observer.onNext(response); })\n
.fail(function(jqXHR, status, error) { observer.onError(error); })\n
.always(function() { observer.onCompleted(); });\n
responseStream.subscribe(function(response) {\n
// do something with the response\n
} 所做的事就是通过显式的通知每一个 Observer (或者说是“Subscriber”) Data events( onNext() )或者 Errors ( onError() )来创建你自己的 Stream。而我们所做的就只是把 jQuery Ajax Promise 包装起来而已。打扰一下,这意味者Promise本质上就是一个Observable?是的。Observable 就是 Promise++。在 Rx 中,你可以用 var stream = Rx.Observable.fromPromise(promise) 轻易的把一个 Promise 转为 Observable,所以我们就这样子做吧。唯一的不同就是 Observable 并不遵循 ,但概念上没有冲突。Promise 就是只有一个映射值的 Observable。Rx Stream 比 Promise 更进一步的是允许返回多个值。这样非常不错,并展现了 Observables 至少有 Promise 那么强大。所以如果你相信 Promise 宣传的那些东西,那么也请留意一下 Rx Observables 能胜任些什么。现在回到我们的例子,如果你已经注意到了我们在 subscribe() 内又调用了另外一个 subscribe() ,这类似于 Callback hell。同样,你应该也注意到 responseStream 是建立在 requestStream 之上的。就像你之前了解到的那样,在 Rx 内有简单的机制可以从其它 Stream 中转换并创建出新的 Stream,所以我们也应该这样子做。你现在需要知道的一个基本的函数是 [map(f)]() ,它分别把 f() 应用到 Stream A 中的每一个值中,并把返回的值放进 Stream B 里。如果我们也对请求 Stream 与响应 Stream 进行同样的处理,我们可以把 Request URL 映射为响应 Promise(而 Promise 可以转为 Streams)。
var responseMetastream = requestStream\n
.map(function(requestUrl) {\n
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));\n
});然后,我们将会创造一个叫做\" Metastream \"的怪物:包含 Stream 的 Stream。暂时不需要害怕。Metastream 就是一个 Stream,其中映射的值还是另外一个 Stream。你可以把它想像为 :每个映射的值都是一个指向其它 Stream 的指针。在我们的例子里,每个请求 URL 都会被映射一个指向包含响应 Promise stream 的指针。Response 的 Metastream 看起来会让人困惑,并且看起来也没有帮到我们什么。我们只想要一个简单的响应 stream,其中每个映射的值应该是 JSON 对象,而不是一个 JSON 对象的'Promise'。是时候介绍 (Mr. Flatmap)() 了:它是 map() 的一个版本,通过把应用到\"trunk\" Stream 上的所有操作都应用到\"branch\" Stream 上,可以\"flatten\" Metastream。Flatmap 并不是用来\"修复\" Metastream 的,因为 Metastream 也不是一个漏洞,这只是一些用来处理 Rx 中的异步响应的工具。
var responseStream = requestStream\n
.flatMap(function(requestUrl) {\n
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));\n
});很好。因为响应stream是根据请求 stream定义的,所以 如果 我们后面在请求 stream上发起更多的请求的话,在响应 stream上我们将会得到相应的响应事件,就像预期的那样:
requestStream:
--a-----b--c------------|-&\n
responseStream: -----A--------B-----C---|-&\n\n
(lowercase is a request, uppercase is its response) 现在,我们终于有了一个响应 stream,所以可以把收到的数据渲染出来了:
responseStream.subscribe(function(response) {\n
// render `response` to the DOM however you wish\n
}); 把目前为止所有的代码放到一起就是这样:
var requestStream = Rx.Observable.just('/users');\n\n
var responseStream = requestStream\n
.flatMap(function(requestUrl) {\n
return Rx.Observable.fromPromise(jQuery.getJSON(requestUrl));\n
responseStream.subscribe(function(response) {\n
// render `response` to the DOM however you wish\n
}); 刷新按钮我之前并没有提到返回的 JSON 是一个有着 100 个用户数据的列表。因为这个 API 只允许我们设置偏移量,而无法设置返回的用户数,所以我们现在是只用了 3 个用户的数据而浪费了另外 97 个的数据。这个问题暂时可以忽略,稍后我们会学习怎么缓存这些数据。每点击一次刷新按钮,请求 stream 就会映射一个新的 URL,同时我们也能得到一个新的响应。我们需要两样东西:一个是刷新按钮上 Click events 组成的 Stream(咒语:一切都能是 Stream),同时我们需要根据刷新 click stream 而改变请求 stream。幸运的是,RxJS 提供了从 Event listener 生成 Observable 的函数。
var refreshButton = document.querySelector('.refresh');\n
var refreshClickStream = Rx.Observable.fromEvent(refreshButton, 'click'); 既然刷新 click event 本身并没有提供任何要请求的 API URL,我们需要把每一次的 Click 都映射为一个实际的 URL。现在,我们把刷新 click stream 改为新的请求 stream,其中每一个 Click 都分别映射为带有随机偏移量的 API 端点。
var requestStream = refreshClickStream\n
.map(function() {\n
var randomOffset = Math.floor(Math.random()*500);\n
return '/users?since=' + randomO\n
}); 因为我比较笨并且也没有使用自动化测试,所以我刚把之前做好的一个特性毁掉了。现在在启动时不会再发出任何的请求,而只有在点击刷新按钮时才会。额...这两个行为我都需要:无论是点击刷新按钮时还是刚打开页面时都该发出一个请求。我们知道怎么分别为这两种情况生成 Stream:
var requestOnRefreshStream = refreshClickStream\n
.map(function() {\n
var randomOffset = Math.floor(Math.random()*500);\n
return '/users?since=' + randomO\n
var startupRequestStream = Rx.Observable.just('/users'); 但我们怎样才能把这两个\"融合\"为一个呢?好吧,有
函数。这就是它做的事的图解:
stream A: ---a--------e-----o-----&\n
stream B: -----B---C-----D--------&\n
vvvvvvvvv merge vvvvvvvvv\n
---a-B---C--e--D--o-----& 这样就简单了:
var requestOnRefreshStream = refreshClickStream\n
.map(function() {\n
var randomOffset = Math.floor(Math.random()*500);\n
return '/users?since=' + randomO\n
var startupRequestStream = Rx.Observable.just('/users');\n\n
var requestStream = Rx.Observable.merge(\n
requestOnRefreshStream, startupRequestStream\n
); 还有一个更加简洁的可选方案,不需要使用中间变量。
var requestStream = refreshClickStream\n
.map(function() {\n
var randomOffset = Math.floor(Math.random()*500);\n
return '/users?since=' + randomO\n
.merge(Rx.Observable.just('/users')); 甚至可以更简短,更具有可读性:
var requestStream = refreshClickStream\n
.map(function() {\n
var randomOffset = Math.floor(Math.random()*500);\n
return '/users?since=' + randomO\n
.startWith('/users'); 函数做的事和你预期的完全一样。无论你输入的 Stream 是怎样,startWith(x) 输出的 Stream 一开始都是 x 。但是还不够 ,我重复了 API 终端 string。一种修复的方法是去掉 refreshClickStream 最后的 startWith() ,并在一开始的时候\"模拟\"一次刷新 Click。var requestStream = refreshClickStream.startWith('startup click')\n
.map(function() {\n
var randomOffset = Math.floor(Math.random()*500);\n
return '/users?since=' + randomO\n
});很好。如果你把之前我\"毁掉了的版本\"的代码和现在的相比,就会发现唯一的不同是加了 startWith() 函数。用 Stream 构建三个推荐到现在为止,我们只是谈及了这个推荐 UI 元素在 responeStream 的 subscribe() 内执行的渲染步骤。对于刷新按钮,我们还有一个问题:当你点击‘刷新’ 时,当前存在的三个推荐并不会被清除。新的推荐会在响应到达后出现,为了让 UI 看起来舒服一些,当点击刷新时,我们需要清理掉当前的推荐。
refreshClickStream.subscribe(function() {\n
// clear the 3 suggestion DOM elements \n
});不,别那么快,朋友。这样不好,我们现在有两个订阅者会影响到推荐的 DOM 元素(另外一个是 responseStream.subscribe() ),而且这样完全不符合 。还记得响应式编程的咒语么?所以让我们把显示的推荐设计成一个 stream,其中每一个映射的值都是包含了推荐内容的 JSON 对象。我们以此把三个推荐内容分开来。现在第一个推荐看起来是这样子的:
var suggestion1Stream = responseStream\n
.map(function(listUsers) {\n
// get one random user from the list\n
return listUsers[Math.floor(Math.random()*listUsers.length)];\n
}); 其他的, suggestion2Stream 和 suggestion3Stream 可以简单的拷贝 suggestion1Stream 的代码来使用。这不是 DRY,它会让我们的例子变得更加简单一些,加之我觉得这是一个可以帮助考虑如何减少重复的良好实践。我们不在 responseStream 的 subscribe() 中处理渲染了,我们这么处理:
suggestion1Stream.subscribe(function(suggestion) {\n
// render the 1st suggestion to the DOM\n
}); 回到\"当刷新时,清理掉当前的推荐\",我们可以很简单的把刷新点击映射为 null,并且在 suggestion1Stream 中包含进来,如下:
var suggestion1Stream = responseStream\n
.map(function(listUsers) {\n
// get one random user from the list\n
return listUsers[Math.floor(Math.random()*listUsers.length)];\n
refreshClickStream.map(function(){ })\n
);当渲染时,null 解释为\"没有数据\",所以把 UI 元素隐藏起来。
suggestion1Stream.subscribe(function(suggestion) {\n
if (suggestion === null) {\n
// hide the first suggestion DOM element\n
// show the first suggestion DOM element\n
// and render the data\n
}); 现在的示意图:
refreshClickStream: ----------o--------o----&\n
requestStream: -r--------r--------r----&\n
responseStream: ----R---------R------R--&
suggestion1Stream: ----s-----N---s----N-s--&\n
suggestion2Stream: ----q-----N---q----N-q--&\n
suggestion3Stream: ----t-----N---t----N-t--& 其中,N 代表了 null作为一种补充,我们也可以在一开始的时候就渲染“空的”推荐内容。这通过把 startWith(null) 添加到 Suggestion stream 就完成了:
var suggestion1Stream = responseStream\n
.map(function(listUsers) {\n
// get one random user from the list\n
return listUsers[Math.floor(Math.random()*listUsers.length)];\n
refreshClickStream.map(function(){ })\n
.startWith(null); 现在结果是:
refreshClickStream: ----------o---------o----&\n
requestStream: -r--------r---------r----&\n
responseStream: ----R----------R------R--&
suggestion1Stream: -N--s-----N----s----N-s--&\n
suggestion2Stream: -N--q-----N----q----N-q--&\n
suggestion3Stream: -N--t-----N----t----N-t--& 关闭推荐并使用缓存的响应还有一个功能需要实现。每一个推荐,都该有自己的\"X\"按钮以关闭它,然后在该位置加载另一个推荐。最初的想法,点击任何关闭按钮时都需要发起一个新的请求:
var close1Button = document.querySelector('.close1');\n
var close1ClickStream = Rx.Observable.fromEvent(close1Button, 'click');\

我要回帖

更多关于 new运算符的作用 的文章

 

随机推荐