-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 571 KB
/
content.json
1
{"meta":{"title":"Mr.喵的网络日志","subtitle":null,"description":null,"author":"ValenZhou","url":"http://yoursite.com"},"pages":[],"posts":[{"title":"养犬指南","slug":"养犬指南","date":"2022-08-30T07:56:46.000Z","updated":"2022-08-30T07:58:54.079Z","comments":true,"path":"2022/08/30/养犬指南/","link":"","permalink":"http://yoursite.com/2022/08/30/养犬指南/","excerpt":"","text":"养犬笔记喂食 一只吃湿的狗粮的狗狗,每天所需要的平均水量是每公斤体重8毫升(即一只10公斤重的狗狗每天需要80毫升水);一只吃干燥狗粮的狗狗每天所需要的平均水量是每公斤体重45毫升(即10公斤重的的狗狗每天需要450毫升水),如果它每天的活动量大或者周围环境的温度高,那么它就需要更多的水。 狗狗突然增加饮水量,有可能是生病的征兆。 狗狗可以吃牛奶和鸡蛋吗?每个个体的情况是不一样的,许多狗狗不能喝牛奶,或者只能接受一点点,原因是它们对乳糖不耐受。不含乳糖的牛奶大多数狗狗都是可以接受的。生鸡蛋被认为是保持漂亮的皮毛的偏方,但是蛋清含有抗生物素蛋白。如果生吃鸡蛋,会妨碍维生素B族中生物素的吸收,这会对皮肤和皮毛有消极的影响,把鸡蛋煮熟再吃则不会有影响。 狗粮 干型狗粮.这种狗粮是经过高压、脱水、膨化或者挤压、烘干制成的。经过高温后,一些营养成分,例如维生素,已经流失了.干型狗粮在食用之前可以先用水浸湿,这种做法尤其推荐给那些家里有小狗的养狗人。但是狗狗必须多喝水,可接受性比湿型狗粮或者自己做的狗粮要低一些。干型狗粮大多含有大量的碳水化合物。 半干型狗粮: 含水量是15%~20%。这种狗粮为了防止产生霉菌和细菌,大多含有防腐剂和添加剂。通常半干型的狗粮还会含糖。 湿型狗粮:通常被存放在罐头或袋子中,含有70%以上的水分。取出狗粮要进行加热、消毒再放到狗狗的食盆中。这个过程中流失的营养成分(例如维生素)需要进行额外补充。 在注意产品的外包装上这个产品是主食、补充食品还是辅食(例如零食),另外还有配料、营养成分、添加剂、生产日期、保质期、净含量、湿度(如果高于14%)以及制造商的名字和地址.人造的防腐剂有BHA/E320和BHT/E321。某些人工合成的防腐剂会导致假性过敏或加重过敏症,而且有引起其他疾病的嫌疑。如果您家的狗狗容易过敏,那么您在购买狗粮的时候一定要注意这些添加剂. 生肉中含有沙门氏菌。不能喂给狗狗吃。 零食不能确保让狗狗喜欢您,但是这种偶尔的小惊喜可以给狗狗带来快乐,甚至可以在学习方面帮助它们。训练零食.这些零食是作为奖励给狗狗的。每一份零食的量应该都很小,因为在训练的过程中会使用很多份零食。 您的狗狗已经在期待饱餐一顿了。为了不让它妨碍您为它做饭或者上菜,它应该乖乖坐在一边,或者趴在它的小篮子里,直到您给它信号,告诉它可以开饭了。请您注意,在狗狗吃饭的时候,为它提供一个安静的角落,不让别人打扰它。狗狗的主人有责任维持狗狗用餐环境的卫生。 请不要把刚从冰箱里拿出来的食物喂给狗狗吃,这样会引起消化问题和胃病,室内温度也同样重要。在冰箱里冷藏或者冷冻的食物应该提前拿出来解冻。 不能给狗狗吃的东西 巧克力:可可含有对狗狗来说有毒的成分可可碱。 糖果只给狗狗提供一些没有价值的热量,不应该写进狗狗的饮食计划。 通常狗狗是不能消化猫粮的。 生猪肉. 狗狗吃饭喜欢狼吞虎咽。它们吃饭太着急了,以至于吃一顿饭和狂欢节一样,让它们用有槽的食盆可以减慢它们吃饭的速度。 狗狗的餐具也要保持卫生。水盆一天至少清洗一次,食盆在每次吃饭后都要清洗,最好用水和清洁剂,要彻底冲洗掉清洁剂的残留。 嘈杂会对狗狗适应新生活产生负面的影响,尤其是小狗,很容易就感到压力大了。请您多给它一些时间去认识新的家人和新的生活环境。大约一周之后,它渐渐地就可以开始会见拜访者了。如果来到您家里的是一只小狗崽,您最好是坐到地上跟它交流,这样它可以更好地和您取得联系:从一开始您就是它安全的港湾,它可以随时靠岸。在接下来的几天中,也要一步一步地来,告诉它其他可以停留的地方。当狗狗放松下来,您就可以给它喂食了。 日常护理 刷子、梳子还有其他身体护理工具给您提供全心全意为狗狗服务的机会。请您抛开每天的忙碌和压力,抽出时间来做这件事。您和狗狗可以享受这段二人世界的美好时光,关系也能变得更亲密。因为互相护理身体不仅在狗狗之间可以起到增进友谊的作用,也适用于狗狗和人之间的关系。 狗狗沐浴露.只能使用那种可以保湿、含有油脂的狗狗专用沐浴露。人类使用的产品会损害狗狗皮肤上的酸性保护层。 耳部护理乳液.用来清洁和护理耳朵的乳液。 泡澡. 狗狗一年泡2~3次澡就够了。如果这个调皮鬼在粪便里面打滚了,那就要额外再泡一次澡了。为除去皮毛上的尘土和脏东西,用淋浴冲一下就行了,不需要用沐浴露。在泡澡之前,请您给狗狗用刷子刷一下皮毛,在浴盆下面铺一层防滑的垫子。如果您家的狗狗不喜欢泡澡,那么给它泡澡的时候应该有一个人帮您抓着它。先用温水给它冲一遍身体,然后抹上沐浴露。不要让水进入它的耳朵和眼睛里。用水冲掉泡沫,然后再抹一次沐浴露。最后用清水冲洗干净。洗完后快速用毛巾把狗狗包裹起来,否则它会抖动身体想要甩掉身上的水。请您仔细给它擦干净身上的水。如果外面比较温暖,也可以自然风干。如果外面温度较低或狗狗的毛比较长,那就需要用到吹风机了,这样狗狗就不会着凉了。只有当它的身体完全干了以后才可以到外面比较冷的地方去。 跳蚤. 跳蚤会一整年都困扰着狗狗,尤其是当天气温暖湿润的时候,它们就会大量繁殖。强烈的瘙痒大多是狗狗被跳蚤袭击的证据。一般来说,当您用一种齿特别细密的梳子给狗狗梳理毛发的时候就能找到跳蚤了。其他征兆还有狗狗皮肤上红色的小斑点或者一些微小的深褐色的跳蚤粪便。跳蚤的卵、幼虫和蛹生活在地毯和家具之类的环境中。如果不把这些东西消灭干净,烦恼永远不会消失。因此,仅仅是处理狗狗身上的跳蚤是不够的,周围的环境也要大扫除。可以在动物医生那里找到去除跳蚤的药物。 壁虱 壁虱会隐藏在草丛里或者其他低矮、靠近地面的植被中,例如草地等.在动物专用品商店和动物医生那里都有专门去除壁虱的工具。请您按照产品说明进行操作,就可以顺利去除壁虱了。然后在被壁虱咬伤的伤口涂上消毒的药物。不要把您抓到的壁虱捏碎,避免更多的壁虱的唾液从伤口进入身体。此外,您应该在每一次外出散步回来的时候检查一下狗狗的身体,把狗毛里的壁虱找出来. 眼部清洁:用湿润的化妆棉轻敷狗狗眼角的分泌物形成的痂,然后把它们擦掉. 医学训练. 狗狗首先要学的是,让主人给它梳理毛发、刷牙、检查耳朵、固定住整个身体。请您从一开始就用游戏的方式训练狗狗适应这些身体护理的方式。可以使用自然材料制成的刷子练习给狗狗梳理皮毛。您和狗狗依偎在一起的时候,可以顺便触摸它的身体,给它检查耳朵等,然后因为它的配合而表扬它或者给它一些好吃的零食,这样狗狗甚至会爱上这种身体护理的方式。 如果指甲太长了,必须使用特制的指甲剪给它剪指甲。初学者要向动物医生或者狗狗饲养者学习这种技术。不要剪得太短,误伤会非常疼,有可能导致狗狗以后一看到指甲剪就陷入恐慌。 10.预防措施: - 均衡的膳食可以帮助狗狗保持健康, - 每年至少要带狗狗去看一次动物医生,让医生给它做身体检查 - 适量的活动对于每一只狗狗来说都是必需的。散步就足够了. - 那些让狗狗感觉不能承受的压力,会让它们生病,这些压力可能由多种因素引起,例如活动太少或者太多、让狗狗做人才能做的事、忽视它们、错误的教育或者缺少教育、错误的饲养以及反复无常的行为。 - 充分的疫苗接种可以避免狗狗患上那些危险的传染病。除虫和预防寄生虫的措施可以防止狗狗的健康受到损害。 正确的教育狗狗 那些清楚地知道自己可以做什么的狗狗,在这个框架中可以不受约束地自由行动。给狗狗划定界限并不意味着限制它,而是给它指引方向。一只狗狗,如果主人一召唤它回来,它就很听话地回来了,那么,主人就不会给它拴遛狗绳,而是让它自由地去追逐嬉戏。如果它不知道界限在哪里,它必然就会越线,表现出一些您不喜欢甚至导致严重的问题的行为。 具有可预见性的行为,也属于清晰框架的一部分。如果以前不允许狗狗做的事现在又允许它做了,三天之后又不允许它做了,这可能是您和狗狗关系的头号杀手。 您可以有多种途径和狗狗进行沟通。 声音信号.和狗狗的有意识的交流大多数时候是通过声音信号。请您给您的狗狗一个声音信号,例如“坐下”,在您说出指令的时候教给它如何做,然后它听到这个指令就知道您想让它干什么了。视觉信号 - 视觉信号也是一样的。只不过这些是可以看见的信号,例如举起食指代表让它坐下。声音信号和视觉信号的原理非常简单,但交流是件复杂的事,您传达给狗狗的信号远远比您想象的要多。 您希望您的狗狗正确理解您的意思,不出现误解,那么您在和它交流的时候就要简单明确,避免误解。 如果您想爱抚它,可以从下面抚摸它的胸部,不要摸它的头。从上方“友好地”摸它的头,会让很多狗狗觉得受到了威胁。 靠狗狗太近,甚至在它的上方弯腰或者直接看着它的眼睛,会让它感觉到自己受到了威胁,甚至有可能让它觉得您在挑衅它。 如果您不想让狗狗感到不安,那么就尽量避免出现上述情况。例如,当您想要给它系上遛狗绳的时候,您会在它的上方弯腰,这时它会后退,蜷缩起身体,甚至反复地吐舌头,因为它感觉到不舒服。这个时候您最好蹲下。如果有必要,您可以有目的地使用一些威胁性的行为,告诉您的狗狗:“现在不是玩闹的时候了!”有些狗狗,只需严厉地看着它,就可以给它留下一个深刻的印象了;另外一些狗狗,则需要做更多表示。注意,这些都有可能导致某些狗狗出现一些激烈甚至是危险的反应,只有在您能保证安全的情况下才能使用。 听到您的命令以后坐到地上,对于狗狗来说是最简单的练习之一。狗狗学习的速度,尤其是学习复杂事情的速度,是不同的,有些需要多一些时间,有些则相当迅速。您会不停地给它一些信号,而且不仅仅是那些您已经教给它的声音和视觉信号。您想让它在您吃饭的时候不要在餐桌边吠叫,就不要从餐桌上拿任何食物给它。任何您分享给它的食物或者不小心从餐桌上掉下来的食物都会让它觉得,耐心等待就会有回报,它只要坚持不懈地守在餐桌旁就一定会有好吃的。粗心马虎有可能导致狗狗出现不良行为,因此请您注意,不要让狗狗学到一些不该学的东西。 通过联系进行学习.联系的意思是把一种刺激和一种行动联系起来,这两者被放到一起就是经典条件反射(巴甫洛夫的狗)。您拿起遛狗绳,狗狗就知道马上要去散步了。您把它的牙刷拿在手上,它就知道您是想给它刷牙了。 通过发出声音信号或者视觉信号来学习,这意味着要把某种信号和一种行为联系在一起。例如,您和狗狗一起练习,当您对它说“坐下”或者举起您的食指的时候,让它坐下。如果它能正确做出您想要的行动,它就会得到表扬,甚至会得到好吃的零食作为奖励。您的积极的反应告诉它,它的行为是正确的。假设您的狗狗想要从垃圾桶里掏东西。它成功地掀开了垃圾桶的盖子,但是盖子“啪嗒”一声掉了下来,砸在了它的头上,让它感觉到了疼痛,它就会被吓跑了。它的这种行为会产生一个消极的结果,这种经历将阻止它再去翻垃圾桶。 在训练的过程中,狗狗承受的压力很大,而且在做某些练习的时候,它的注意力只能集中几分钟。 请您在练习的过程中安排一些休息时间。如果狗狗成功地完成了一个练习,那么在这次训练中就不要再重复这个练习了。(过犹不及) 请您把练习过程分成一段一段小的练习。(循序渐进) 您的狗狗只有在不被其他事物分散注意力的时候才可以专注到训练当中去。每次开始新的练习的时候,请您把练习地点安排在没有什么能分散它的注意力的地方.(集中注意力) 狗狗会把学到的东西和某一个地点联系起来。为了让狗狗能够在任何地方都能成功地完成练习,您需要变换不同的地点进行练习。(变换地点) 有些练习需要一百次甚至更多次重复。(不停地重复) 有目的性地进行奖励.只有当狗狗做出了您想要的行为时才能得到奖励。例如,在“坐下”的练习中,不要在狗狗站起身来的时候奖励它。请您在狗狗端正地坐下,触碰到地面的那一刻对它进行奖励。这样,就不会产生误解了. 每次训练结束的时候,您可以给出一个结束性的信号,例如“跑”,这可以告诉狗狗,训练结束了。 可以惩罚狗狗吗?您的目的是终止您不希望看到的行为,并且在今后尽量避免再出现这样的行为。要达到这个目的,必须使用一些您的狗狗能够理解的方式。但是这些方式不能被认为是惩罚,因为您是在纠正狗狗的错误,而不是想要报复它。纠正错误这个行为必须适度,而且要给它留下深刻的印象。对于某些狗狗来说,一个严厉的眼神、一句敦促它的话或者一声轻咳就足够了。或者,让狗狗知道主人已经做好了采取措施的准备了。如何给狗狗这样一种印象呢?例如,您可以果断地朝它走去,以这种方式来限制它的活动范围,如果有必要的话,可以在它的上方弯下腰来,盯着它看。如果它还是不改变自己的行为,那么就有必要按照狗狗的方式轻度地拍它一下或者碰它一下。在合适的情况下使用这种方法,也是非常有效的。 责罚和暴力在对狗狗的教育中毫无价值。而且您要在狗狗犯错的当时给它纠正,不要等事情都过去了才纠正。 有意义的教育辅助工具: 零食,哨子,食物包,牵引绳. 训练狗狗不随地大小便.方便的地点:请您在狗狗来到您家的第一天就指给它看,它可以在花园里或者您家门前的哪个地点大小便。每次它到固定地点去大小便的时候,您都要开心地表扬它,以此来加深它对这件事的印象。如果您能把狗狗上厕所这件事和一个口令联系起来,那么它以后一听到这个口令就会上厕所。 使用响片是一种以积极强化为基础的训练方式。首先要训练狗狗对响片的声音产生条件反射。它要知道,这个声响代表食物。响声要和积极的事物联系起来。如果它做出了您所希望的行为,那么就要伴随着响声,给它食物作为奖励。起关键作用的是响片发出响声的时机。狗狗是通过尝试和犯错来学习的。 中断指令: 可以适当和它练习一些基础的中断指令,如: 不行,停下,吐出来等.请您在时机合适的时候练习,并且使用不同的物品,也可以用食物和用来啃咬的骨头,但是练习不要太过频繁。这样,您的狗狗就能学习到:放弃一些东西是有回报的。 在狗笼里也可以很快乐.狗笼不是储藏室或者惩罚手段!它应该是狗狗的一个舒适的避风港。当周围环境对于它来说比较危险或者需要休息时,它可以短时间地待在那里。它在那里可以不被任何人打扰。请您在狗笼里铺上柔软温暖的垫子,放些装有饮用水的水盆和玩具。请您让它待在狗笼里,给它喂食,且只有它在那里的时候才喂。做这些的时候要保持狗笼的门打开.如果您的狗狗趴在笼子里啃骨头或者睡着了,您可以把笼子的门先虚掩着,几次之后,可以短暂地锁上。如果小狗没有对您锁门的行为做出反抗,那么您可以让锁门的时间变得越来越长。如果小狗看到您把门锁上就非常绝望地发出叫声,请您先让它慢慢安静下来,然后再把它放出来,否则它就会产生这样的联想:我一发出悲惨的叫声就能被放出去了。请您耐心一些,不久之后您的狗狗肯定就能自己钻进狗笼里去了。 您的狗狗必须要学着自己在家,这样它才能放松地等着您回来。请您不要在晚上让小狗独处,这对于它来说会是噩梦般的经历。当狗狗发出诉苦的叫声时,请您不要马上回来,而是要等它安静下来的时候再回来。不能让它产生这样的联想:您是听到了它的诉苦才回来的。在您离开之前,不要对狗狗表示怜悯和同情,这会让它的独处变得更困难。但是您跟它分别的时候要遵循一定的程序。这样,它就可以明白,您还是会回到它的身边的。 常用的训练指令 看这里 如果您的狗狗听到您的这个命令朝您看过来,您就成功了。集中注意力的训练非常简单,而且还很有趣:请您把一块好吃的东西藏在手里,然后把手放到背后。对狗狗说“看这里”,如果您的狗狗向您看过来,那么就在这时迅速将您手中的美食给它。请您经常做这个练习,让它快速熟悉这个口令。 到这儿来 坐下 请您把一块食物拿在手里,抬起食指,然后把它拿到狗狗的鼻子前面并举过它的头顶。狗狗为了能够继续观察您手中的食物,有可能会坐下。如果它坐下了,您就说“坐下”,同时把食物给它,并且表扬它。在这个练习结束以后,您可以给它一个结束的信号。 趴下 请您用大拇指和手掌夹住一块食物。让您的狗狗闻一闻食物,然后慢慢地把手从狗狗的鼻子前面移动到地面上。在这个过程中您的手掌向下翻转。狗狗为了追寻食物,很有可能会趴下。如果狗狗趴下了,您马上要说出“趴下”,把食物给它,对它进行表扬。如果您的狗狗并没有趴下,您可以把食物在地上向着远离它的方向移动。您还可以在椅子下面移动食物。这样,狗狗如果想继续追寻食物,就必须趴下了。练习结束的时候请您发出结束的指令。 别动 请您在开始训练前让狗狗先趴下,这样练习对于它来说会容易多。狗狗趴在那儿。然后您发出指令“不要动”,手掌竖直放在它面前,像一个禁止前进的牌子。请您后退一步。如果它还是没有动,您就向它走过去,给它一些食物并且表扬它。如果它成功完成了这个练习,那么您可以扩大距离继续进行练习,每一次成功都要给它奖励和表扬。请您变换方向和距离,绕着它走,继续重复这个练习。之后再练习,您可以在周围比较安全的地方从狗狗的视野中消失一段时间。如果在这个过程中,您的狗狗站起来了,那么请您让它安静下来,重新回到训练的原点,重复短距离或者短时间的趴着不动的练习。最后给狗狗一个结束训练的信号。 其他 狗狗狂吠、吼叫,拆卸家具或者把家具、地毯、门等挠坏,是压力大的表现。可能的原因是狗狗还没有学习如何独处。分离的恐慌经常是由不安全引起的.在让狗狗独处之前需要进行一个和它分离的仪式,以这种方式使狗狗对所处情境进行评估。 攻击性行为的原因多种多样。通常起因是一些资源,例如食物、玩具或者和主人的亲密,或者狗狗的主人太过娇纵它了。攻击性行为通常是狗狗表达不安的方式,狗狗把它视为预防性措施或者最后的出路。狗狗也有可能因为接收到的要求太多或者压力过大而变得具有攻击性,例如它们从主人那里得不到足够的关怀。对狗狗的要求太低、社交太少或者教育缺失,缺乏运动. 用水枪或者矿泉水瓶子向狗狗喷水,以此来中断它的行为,让它重新开始接受主人发出的指令。请您不要直接向狗狗的面部喷水,而是向它的耳朵或者脖子喷水。正确的时间点是喷水起作用的关键。 玩游戏的规则很简单:不要容忍任何让您感到不舒服或者给您带来疼痛的行为。如果狗狗变得疯狂起来,甚至咬您的衣服、胳膊,请您给它一个中断信号,中场休息一下或者结束游戏。 社会性行为和熟悉周边环境——这些基本的经历发生在狗狗出生后最初的几个月中.虽然狗狗在任何年龄阶段都可以学习,但是不会再像这个阶段这样容易了,现在学到的东西一辈子都不会忘记。小狗在这个时候要通过尝试和犯错学习控制在打闹中啃咬的轻重程度。游戏中学习到的东西对于小家伙以后的生活非常重要。此外,游戏还可以促进它的运动机能,增强身体素质。请为您的狗狗提供一些积极的经历,这些会让它终身受用。尽量让它熟悉那些对它今后的生活非常有用的事。 在公寓房里生活的狗狗也可以很幸福。前提是选择适合生活在室内的狗狗,除此之外还要有靠谱的主人,为狗狗提供除了散步以外的其他活动来平衡减少的室外活动。您是生活在宫殿里还是一居室里,对于狗狗来说都无所谓,它需要的是获得了身体和精神所需的活动。 对于一只狗狗来说,得到宠爱并不意味着主人给它买很多玩具。当它的主人肯抽出时间跟它一起散步、玩耍、工作,让它依偎在自己身边,这时的狗狗才算是幸福的。您能给予狗狗的最宝贵的东西就是您的关心。您的狗狗会因此而感谢您信任您、对您发出的命令无条件服从。 不能毫无限制地亲近,您和狗狗身体上的亲密对于它来说是一种资源,您需要和狗狗保持亲密的关系,但是也不能让它每次索要时都能得到。这并不是冷酷无情,而是一种正常的行为。 一只心情一般的狗狗,它的身体是放松的:尾巴不紧绷或者微微下垂,耳朵直立。如果有什么事情引起了它的注意,它的身体会紧张起来,尾巴翘起,耳朵略微探向前方。而有所期待的狗狗身体会略显紧张,尾巴水平方向摇晃,嘴巴略微张开,面部表情聚精会神。 狗狗在感到害怕或者不安时,它们的眼神是躲闪的,同时表现得非常卑微:低着头,弯曲着腿,尾巴耷拉着或者夹在两条腿中间,耳朵耷拉着。应该避免一切会增加狗狗不安的事情。 狗狗在被动地臣服于其他狗狗的时候,会仰面躺下,把自己最脆弱的地方展现给对方。同时,它还会保持等待的姿态,嘴角拉得很长,有时候还会尿尿。 主动的屈从也被称为缓和关系。如果狗狗想要缓和它们之间紧张的气氛,它会舔自己的嘴巴和鼻子,或者尝试着去舔对方的嘴角。这种行为来源于它们的孩提时代:通过舔成年狗狗的嘴角,小狗可以得到它们想要的食物。 一只想要威胁对方的狗会一直盯着对方,皱起鼻子周围的皮肤,抬起上嘴唇下垂的部分,露出牙齿。根据激烈程度的不同,它背上的毛竖起来的程度也不同。它还会发出呼噜声或者朝对方吠叫。 怎样才能小狗习惯它的名字?不要在骂它的时候叫它的名字,只有在给它喂食或者给它玩具这类好事的时候才叫它的名字 可以让10周大的小狗长时间单独4待在家吗?不可以,小狗需要学习才能单独待在家。如果现在就延长让它单独在家的时间,有可能会让它产生分离的恐惧。此外,如果让它单独在家,它就会学着在屋里大小便了,这对于训练它们不随地大小便是不利的. 怎样正确地抱起狗狗?在抱起小狗的时候必须好好支撑住它。一只手扶着它的胸部,最好是把食指放在两条前腿中间。另外一只手支撑住它的屁股。然后小心翼翼地把它放到您的胸前。一定要把狗狗固定在臂弯里,这样它就不会挣扎着摆脱您的双手,然后掉到地上.","categories":[],"tags":[]},{"title":"读《时间简史》","slug":"读《时间简史》","date":"2022-03-31T06:22:01.000Z","updated":"2022-03-31T06:24:14.251Z","comments":true,"path":"2022/03/31/读《时间简史》/","link":"","permalink":"http://yoursite.com/2022/03/31/读《时间简史》/","excerpt":"","text":"时间简史 摘录自 史蒂芬霍金 <<时间简史>> 我们的宇宙图象早在公元前 340 年,希腊哲学家亚里士多德在他的《论天》一书中,就能够对于地球是一个圆球而不是一块平板这个信念提出两个有力的论证。第一,他意识到,月食是由于地球运行到太阳与月亮之间引起的。第二,希腊人从旅行中知道,在南方观测北极星,比在较北地区,北极星在天空中显得较低。希腊人甚至为地球是球形提供了第三个论证,否则何以从地平线驶来的船总是先露出船帆,然后才露出船身? 亚里士多德认为地球是不动的,太阳、月亮、行星和恒星都以圆周为轨道围绕着地球公转。他相信这些,是因为他认为地球是宇宙的中心,而圆周运动是最完美的;他的这种看法是基于某些神秘的原因。公元 2 世纪,这个思想被托勒密精制成一个完整的宇宙学模型。地球处于正中心,8 个天球包围着它,这 8 个天球分别负载着月亮、太阳、恒星和 5 个当时已知的行星:水星、金星、火星、木星和土星。托勒密模型的系统可以相当精密地预言天体在天空中的位置。但是为了正确地预言这些位置,托勒密不得不假定,月亮遵循的轨道有时使它离地球的距离是其他时候的一半。它被基督教会接纳为与《圣经》相一致的宇宙图象。这是因为它具有巨大优势,即在固定恒星天球之外为天堂和地狱留下了大量的空间。 然而,1514 年波兰教士尼古拉·哥白尼提出了一个更简单的模型。他的观念是,太阳静止地位于中心,而地球和行星们围绕着太阳做圆周运动。后来,两位天文学家——德国人约翰斯·开普勒和意大利人伽利略·伽利雷开始公开支持哥白尼理论,尽管它所预言的轨道还不能完全与观测相符合。直到 1609 年,亚里士多德和托勒密的理论才宣告死亡。那一年,伽利略用刚发明的望远镜来观测夜空。当他观测木星时,发现有几个小卫星或月亮围绕着它转动,这表明不像亚里士多德和托勒密设想的那样,并非所有东西都必须直接地围绕着地球转动。同时,约翰斯·开普勒修正了哥白尼理论,提出行星不是沿着圆周而是沿着椭圆(椭圆是拉长的圆)运动,从而最终使预言和观察相互一致了。虽然他几乎偶然地发现椭圆轨道能很好地和观测相符合,但却不能把它和他的磁力引起行星围绕太阳运动的思想相互调和起来。 只有到更晚得多的 1687 年,这一切才得到解释。这一年,艾萨克·牛顿爵士出版了他的《自然哲学的数学原理》,牛顿不但提出物体如何在空间和时间中运动的理论,并且发展了为分析这些运动所需的复杂的数学。此外,牛顿还提出了万有引力定律。根据这条定律,宇宙中的任一物体都被另外的物体吸引。物体质量越大,相互距离越近,则相互之间的吸引力越大。正是这同一种力,使物体下落到地面。牛顿继而证明,根据他的定律,引力使月亮沿着椭圆轨道围绕着地球运行,而地球和其他行星沿着椭圆轨道围绕着太阳公转。 在 20 世纪之前从未有人提出过,宇宙是在膨胀或是在收缩,这有趣地反映了当时的思维风气。一般认为,宇宙要么以一种不变的状态存在了无限长的时间,要么以多多少少正如我们今天观察到的样子在有限久的过去创生。其部分的原因可能是,人们倾向于相信永恒的真理,也可能由于从以下的观念可以得到安慰,即虽然他们会生老病死,但是宇宙必须是不朽的不变的。 1781 年,哲学家伊曼努尔·康德发表了里程碑般的(也是非常晦涩难懂的)著作《纯粹理性批判》。在这本书中,他深入地考察了关于宇宙在时间上是否有开端、在空间上是否有限的问题。他称这些问题为纯粹理性的二律背反(也就是矛盾)。因为他感到存在同样令人信服的论据,来证明宇宙有开端的正命题,以及宇宙已经存在无限久的反命题。他对正命题的论证是:如果宇宙没有一个开端,则任何事件之前必有无限的时间。他认为这是荒谬的。他对反命题的论证是:如果宇宙有一开端,在它之前必有无限的时间,为何宇宙必须在某一特定的时刻开始呢?事实上,他对正命题和反命题用同样的论证来辩护。它们都是基于他隐含的假设,即不管宇宙是否存在了无限久。时间均可无限地倒溯回去。我们将会看到,在宇宙开端之前时间概念是没有意义的。 1929 年,埃德温·哈勃作出了一个里程碑式的观测,即不管你往哪个方向观测,远处的星系都正急速地飞离我们而去。换言之,宇宙正在膨胀。这意味着,在早先的时刻星体更加相互靠近。事实上,似乎在大约 100 亿至 200 亿年之前的某一时刻,它们刚好在同一地方,所以那时候宇宙的密度为无限大。如果在这个时刻之前有过一些事件,它们将不可能影响现在发生的东西。因为它们没有任何观测的后果,所以可不理睬其存在。由于更早的时间根本没有定义,所以在这个意义上,人们可以说,时间在大爆炸时有一开端。 时间和空间我们现在关于物体运动的观念来自于伽利略和牛顿。在他们之前,人们相信亚里士多德,他说物体的自然状态是静止的,并且只有在受到力或冲击的推动时才运动。这样,重的物体比轻的物体下落得更快,因为它受到更大的将其拉向地球的力。在伽利略之前,没有一个人想看看不同重量的物体是否确实以不同速度下落。据说,伽利略从比萨斜塔上将重物落下,从而证明了亚里士多德的信念是错的。这故事几乎不足以信,但是伽利略的确做了一些等效的事:让不同重量的球沿光滑的斜面上滚下。这种情况类似于重物的垂直下落,只是因为速度小而更容易观察而已。伽利略的测量指出,不管物体的重量多少,其速度增加的速率是一样的。一个铅锤比一片羽毛下落得更快些,那只是因为空气阻力将羽毛的速度降低。如果一个人释放两个不受任何空气阻力的物体,例如两个不同的铅锤,它们则以同样速度下降。在没有空气阻碍东西下落的月球上,航天员大卫·斯各特进行了羽毛和铅锤实验,并且发现两者确实同时落到月面上。 牛顿把伽利略的测量当做他的运动定律的基础。在伽利略的实验中,当物体从斜坡上滚下时,它一直受到不变外力(它的重量)的作用,其效应是使它恒定地加速。这表明,力的真正效应总是改变物体的速度,而不是像原先想象的那样,仅仅使之运动。同时,它还意味着,只要物体没有受到外力,它就会以同样的速度保持直线运动。这个思想首次在牛顿于 1687 年出版的《数学原理》一书中明白地陈述出来了,并被称为牛顿第一定律。牛顿第二定律给出物体在受力时发生的现象:物体在被加速或改变其速度时,其改变率与所受的外力成比例。除了他的运动定律,牛顿还发现了描述引力的定律:任何两个物体都相互吸引,其引力大小与每个物体的质量成比例。于是,如果其中一个物体的质量加倍,则两个物体之间的引力加倍。牛顿引力定律还告诉我们,物体之间的距离越远,则引力越小。 亚里士多德和伽利略-牛顿观念的巨大差别在于,亚里士多德相信一个优越的静止状态,任何没有受到外力和冲击的物体都取这种状态。特别是他以为地球是静止的但是从牛顿定律可以推断,并不存在唯一的静止标准。例如,在有轨电车上打乒乓球,人们将会发现,正如在铁轨旁一张台桌上的球一样,乒乓球服从牛顿定律,所以无法得知究竟是火车还是地球在运动。 亚里士多德和牛顿都相信绝对时间。也就是说,他们相信人们可以毫不含糊地测量两个事件之间的时间间隔,只要用好的钟,不管谁去测量,这个时间都是一样的。时间相对于空间是完全分离并且独立的。这就是大部分人当做常识的观点。然而,我们必须改变这种关于空间和时间的观念,虽然这种显而易见的常识可以很好地对付运动甚慢的诸如苹果、行星的问题,但在处理以光速或接近光速运动的物体时却根本无效。 1865 年,当英国的物理学家詹姆士·克拉克·麦克斯韦成功地将直到当时用以描述电力和磁力的部分理论统一起来以后,才有了光传播的正确理论。麦克斯韦理论预言,射电波或光波应以某一固定的速度行进。但是牛顿理论已经摆脱了绝对静止的观念,所以如果假定光以固定的速度旅行,人们就必须说清这固定的速度是相对于何物来测量的。因此有人提出,存在着一种无所不在的称为“以太”的物质,甚至在“真空的”空间中也是如此。然而,一位迄至当时还默默无闻的瑞士专利局的职员阿尔伯特·爱因斯坦,在 1905 年的一篇狭义相对论中指出,只要人们愿意拋弃绝对时间观念的话,整个以太的观念则是多余的。几个星期之后,法国第一流的数学家亨利·庞加莱也提出类似的观点。爱因斯坦的论证比庞加莱的论证更接近物理,后者将其考虑为数学问题。通常将这个新理论归功于爱因斯坦,但人们不会忘记,庞加莱的名字在其中起了重要的作用。不管观察者运动多快,他们应测量到一样的光速。这简单的观念有一些非凡的结论。可能最著名者莫过于质量和能量的等价,这可用爱因斯坦著名的方程 E=mc2 来表达(E 是能量,m 是质量,c 是光速),以及没有任何东西可能行进得比光还快的定律。当一个物体接近光速时,它的质量上升得越来越快,这样它需要越来越多的能量才能进一步加速上去。实际上它永远不可能达到光速,因为那时质量会变成无限大,而根据质量能量等价原理,这就需要无限大的能量才能做到。由于这个原因,相对论限制了物体运动的速度:任何正常的物体永远以低于光速的速度运动,只有光或其他没有内禀质量的波才能以光速运动。 相对论终结了绝对时间的观念!每个观察者都可以利用雷达发出光或射电波脉冲来说明一个事件在何处何时发生。一部分脉冲在事件反射回来后,观察者可在他接收到回波时测量时间:事件的时间可认为是脉冲被发出和反射被接收的两个时刻的中点;而事件的距离可取这来回行程时间的一半乘以光速。现在我们正是用这种方法来准确地测量距离,因为我们可以将时间测量得比长度更为准确。实际上,米是被定义为光在以铯原子钟测量的 0.000000003335640952 秒内行进的距离(取这个特别数字的原因是,因为它对应于历史上的米的定义——按照保存在巴黎的特定铂棒上的两个刻度之间的距离)。同样地,我们可以用叫做光秒的更方便的新长度单位,这就是简单地定义为光在 1 秒中行进的距离。 相对论迫使我们从根本上改变了我们的时间和空间观念。我们必须接受,时间不能完全脱离和独立于空间,而必须和空间结合在一起形成所谓的时空的客体。 这正如同将一块石头扔到池塘里,水表面的涟漪向四周散开一样,涟漪作为一个圆周散开并随时间越变越大。如果人们把不同时刻涟漪的快照逐个堆叠起来,扩大的水波圆周就会画出一个圆锥,其顶点正是石块击到水面的地方和时刻。类似地,从一个事件散开的光在(四维的)时空里形成了一个(三维的)圆锥,这个圆锥称为事件的将来光锥。以同样的方法可以画出另一个称为过去光锥的圆锥,它表示所有可以用一个光脉冲传播到该事件的事件集合。不处于 P 的将来或过去的事件被称之为处于 P 的他处。在这种事件处所发生的东西既不能影响发生在 P 的事件,也不受发生在 P 的事件的影响。例如,假定太阳就在此刻停止发光,它不会对此刻的地球上的事情发生影响,因为它们是在太阳熄灭这一事件的他处。我们只能在 8 分钟之后才知道这一事件,这是光从太阳到达我们所花费的时间。只有到那时候,地球上的事件才在太阳熄灭这一事件的将来光锥之内。当我们看宇宙时,我们是在看它的过去。 狭义相对论非常成功地解释了如下事实:对所有观察者而言,光速都是一样的,并成功地描述了当物体以接近于光速运动时会发生什么。然而,它和牛顿引力理论不相协调。牛顿理论说,物体之间相互吸引,其吸引力依赖于它们之间的距离。这意味着,如果我们移动其中一个物体,另一物体所受的力就会立即改变.爱因斯坦在 1908 年至 1914 年之间进行了多次不成功的尝试,企图找到一个和狭义相对论协调的引力理论。1915 年,他终于提出了今天我们称为广义相对论的理论。爱因斯坦提出了革命性的思想,即引力不像其他种类的力,它只不过是时空不是平坦的这一事实的结果,在时空中的质量和能量的分布使它弯曲或“翘曲”。太阳的质量以这样的方式弯曲时空,使得在四维的时空中地球虽然沿着直线的路径运动,它却让我们看起来是沿着三维空间中的一个圆周轨道运动。时空是弯曲的事实再次意味着,光线在空间中看起来不是沿着直线旅行。这样,广义相对论预言光线必须被引力场折弯。譬如,理论预言,由于太阳的质量的缘故,太阳近处的点的光锥会向内稍微弯折。这表明,从遥远恒星发出的刚好通过太阳附近的光线会被偏折很小的角度,对于地球上的观察者而言,这恒星似乎位于不同的位置。在正常情况下,要观察到这个效应非常困难,这是由于太阳的光线使得人们不可能观看天空上出现在太阳附近的恒星。然而,在日食时就可能观察到,这时太阳的光线被月亮遮住了。 广义相对论的另一个预言是,在像地球这样的大质量的物体附近,时间显得流逝得更慢一些。这是因为光能量和它的频率(光在每秒钟里波动的次数)有一种关系:能量越大,则频率越高。当光从地球的引力场往上行进,它失去能量,因而其频率下降。考虑一对双生子。假定其中一个孩子去山顶上生活,而另一个留在海平面,第一个将比第二个老得快些。在这个例子中,年纪的差别会非常小。但是,如果有一个孩子在以近于光速运动的航天飞船中作长途旅行,这种差别就会大得多。当他回来时,他会比留在地球上另一个年轻得多。这叫做双生子佯谬。在相对论中并没有唯一的绝对时间,相反,每个人都有他自己的时间测度,这依赖于他在何处并如何运动。 爱因斯坦广义相对论意味着,宇宙必须有个开端,并且可能有个终结。 膨胀的宇宙1924 年,我们现代的宇宙图象才被奠定。那一年,美国天文学家埃德温·哈勃证明了,我们的星系不是唯一的星系。对于近处的恒星,我们可以测量其视亮度和距离,这样我们可以算出它的光度。相反,如果我们知道其他星系中恒星的光度,我们可用测量它们的视亮度来算出它们的距离。恒星离开我们是如此之遥远,使我们只能看到极小的光点,而看不到它们的大小和形状。这样怎么能区分不同的恒星种类呢?对于绝大多数的恒星而言,只有一个特征可供观测——光的颜色。牛顿发现,如果太阳光通过一个称为棱镜的三角形状的玻璃块,就会被分解成像在彩虹中一样的分颜色(它的光谱)。将一台望远镜聚焦在一个单独的恒星或星系上,人们就可类似地观察到从这恒星或星系来的光谱。 当恒星离开我们而去时,它们的光谱向红端移动(红移);而当恒星趋近我们而来时,光谱则被蓝移。这个称作多普勒效应的频率和速度的关系是我们日常熟悉的例如听一辆小汽车在路上驶过:当它趋近时,它的发动机的音调变高(对应于声波的短波长和高频率);当它经过我们身边而离开时,它的音调变低.在哈勃证明了其他星系存在之后的几年里,他花时间为它们的距离编目以及观察它们的光谱,那时候大部分人都以为,这些星系完全是随机地运动的,所以预料会发现和红移光谱一样多的蓝移光谱。因此,当他发现大部分星系是红移的:几乎所有都远离我们而去时,确实令人十分惊异!1929 年哈勃发表的结果更令人惊异:甚至星系红移的大小也不是随机的,而是和星系离开我们的距离成正比。或换句话讲,星系越远,它离开我们运动得越快!这表明宇宙不能像人们原先所想象的那样处于静态,而实际上是在膨胀;不同星系之间的距离一直在增加着。 利用多普勒效应,可由测量星系离开我们的速度来确定现在的膨胀速度。这可以非常精确地实现。然而,因为我们只能间接地测量星系的距离,所以它们的距离知道得不很清楚。我们知道的不过是,宇宙在每 10 年里膨胀 5%~ 10%。我们不能排除这样的可能性,可能还有我们尚未探测到的其他的物质形式,它们几乎均匀地分布于整个宇宙中,它仍可能使得宇宙的平均密度达到停止膨胀所必需的临界值。所以,现在的证据暗示,宇宙可能会永远地膨胀下去。但是,所有我们能真正肯定的是,既然它已经至少膨胀了 100 亿年,即便宇宙将要坍缩,至少要再过这么久才有可能。 就我们而言,大爆炸之前的事件不能有后果,所以并不构成我们宇宙的科学模型的一部分。因此,我们应将它们从模型中割除掉,并宣称时间是从大爆炸开始的。很多人不喜欢时间有个开端的观念,可能是因为它略带有神的干涉的味道。如果广义相对论是正确的,宇宙可以有过奇点,一个大爆炸。然而,它没有解决关键的问题:广义相对论是否预言我们的宇宙一定有过大爆炸或时间的开端?对于这个问题,英国数学家兼物理学家罗杰·彭罗斯在 1965 年以完全不同的手段给出了回答。利用广义相对论中光锥行为的方式以及引力总是吸引这个事实,他证明了,坍缩的恒星在自己的引力作用下陷入到一个区域之中,其表面最终缩小到零。并且由于这区域的表面缩小到零,它的体积也应如此。恒星中的所有物质将被压缩到一个零体积的区域里,所以物质的密度和时空的曲率变成无限大。换言之,人们得到了一个奇点,它被包含在一个叫做黑洞的时空区域中。如果人们将彭罗斯定理中的时间方向颠倒以使坍缩变成膨胀,假定现在宇宙在大尺度上大体类似弗里德曼模型,这定理的条件仍然成立。彭罗斯定理已经指出,任何坍缩星体必定终结于一个奇点;其时间颠倒的论证则是,任何类弗里德曼膨胀宇宙一定是从一个奇点开始。 但是一旦考虑了量子效应,奇点就会消失。 不确定性原理为了预言一个粒子未来的位置和速度,人们必须能够准确地测量它现在的位置和速度。显而易见的办法是将光照到这粒子上.一部分光波被此粒子散射开来,由此指明它的位置。然而,人们不可能将粒子的位置确定到比光的两个波峰之间距离更小的程度,所以为了精确测量粒子的位置,必须用短波长的光。可是,由普朗克的量子假设,人们不能用任意小量的光;人们至少要用一个光量子。这量子会扰动这粒子,并以一种不能预见的方式改变粒子的速度。此外,位置测量得越准确,所需的波长就越短,单个量子的能量就越大,这样粒子的速度就被扰动得越厉害。换言之,你对粒子的位置测量得越准确,你对速度的测量就越不准确,反之亦然。海森伯指出,粒子位置的不确定性乘以粒子质量再乘以速度的不确定性不能小于一个确定量,该确定量称为普朗克常量。并且,这个极限既不依赖于测量粒子位置和速度的方法,也不依赖于粒子的种类。海森伯不确定性原理是世界的一个基本的不可回避的性质。 不确定性原理使拉普拉斯的科学理论,即一个完全确定性的宇宙模型的梦想寿终正寝:如果人们甚至不能准确地测量宇宙现在的状态,那么就肯定不能准确地预言将来的事件!20 世纪 20 年代,在不确定性原理的基础上,海森伯、厄文·薛定谔和保尔·狄拉克运用这种手段将力学重新表述成称为量子力学的新理论。在此理论中,粒子不再分别有很好定义的而又不能被观测的位置和速度。取而代之,粒子具有位置和速度的一个结合物,即量子态。一般而言,量子力学并不对一次观测确定地预言一个单独的结果。取而代之,它预言一组可能发生的不同结果,并告诉我们每个结果出现的概率。因而量子力学把非预见性或随机性的不可避免因素引进了科学。 在量子力学中存在着波和粒子的二重性:为了某些目的将考虑粒子成波是有用的,而为了其他目的最好将波考虑成粒子。这导致一个很重要的结果,人们可以观察到两束波或粒子之间的所谓的干涉。那也就是,一束波的波峰可以和另一束波的波谷相重合。这两束波就相互抵消,而不像人们预料的那样,叠加在一起形成更强的波。一个光干涉的熟知例子是,肥皂泡上经常能看到颜色。这是因为从形成泡的很薄的水膜的两边的光反射引起的。白光由所有不同波长或颜色的光波组成,在从水膜一边反射回来的具有一定波长的波的波峰和从另一边反射的波谷相重合时,对应于此波长的颜色就不在反射光中出现,所以反射光就显得五彩缤纷。由于量子力学引进的二重性,粒子也会产生干涉。所谓的双缝实验即是著名的例子。 爱因斯坦广义相对论制约了宇宙的大尺度结构。它没有到考虑量子力学的不确定性原理,而为了和其他理论一致这是必需的。因为我们通常检验到的引力场非常弱,所以这个理论并没导致和观测的偏离,然而,早先讨论的奇点定理指出,至少在两种情形下引力场会变得非常强:黑洞和大爆炸。在这样强的场里,量子力学效应应该是非常重要的。因此,在某种意义上,经典广义相对论由于预言无限大密度的点而预示了自身的垮台,我们还没有一个完备的协调的统一广义相对论和量子力学的理论。 基本粒子和自然的力最初,人们认为原子核是由电子和不同数量的带正电的叫做质子的粒子组成。1932 年詹姆斯·查德威克发现,原子核还包含另外称为中子的粒子,中子几乎具有和质子一样大的质量但不带电荷。直到大约 30 年以前,人们还以为质子和中子是“基本”粒子。但是,质子和另外的质子或电子高速碰撞的实验表明,它们事实上是由更小的粒子构成的。加州理工学院的牟雷·盖尔曼将这些粒子命名为夸克。现在我们知道,不管是原子还是其中的质子和中子都不是不可分的。问题在于什么是真正的基本粒子——构成世界万物的最基本的构件?由于光波波长比原子的尺度大得多,我们不能期望以通常的方法去“看”一个原子的部分。我们必须用某些波长短得多的东西。正如我们在上一章所看到的,量子力学告诉我们,实际上所有粒子都是波,粒子的能量越高,则其对应的波的波长越短。 用上一章讨论的波粒二象性,包括光和引力的宇宙中的一切都能以粒子来描述。这些粒子有一种称为自旋的性质。考虑自旋的一个方法是将粒子想象成围绕着一个轴自转的小陀螺。然而,这可能会引起误会,因为量子力学告诉我们,粒子并没有任何轮廓分明的轴。粒子的自旋真正告诉我们的是,从不同的方向看粒子是什么样子的。宇宙间所有已知的粒子可以分成两组:自旋为二分之一的粒子,它们组成宇宙中的物质;自旋为 0、1 和 2 的粒子,正如我们将要看到的,它们在物质粒子之间产生力。物质粒子服从所谓的泡利不相容原理。泡利不相容原理是说,两个类似的粒子不能存在于相同的态中,也就是说,在不确定性原理给出的限制下,它们不能同时具有相同的位置和速度。不相容原理是非常关键的,因为它解释了为何物质粒子,在自旋为 0、1 和 2 的粒子产生的力的影响下,不会坍缩成密度非常高的状态的原因:如果物质粒子几乎处在相同的位置,则它们必须有不同的速度,这意味着它们不会长时间存在于相同的位置。如果世界在没有不相容原理的情形下创生,夸克将不会形成分离的轮廓分明的质子和中子,进而这些也不可能和电子形成分离的轮廓分明的原子。它们全部都会坍缩形成大致均匀的稠密的“汤”。 直到保罗·狄拉克在 1928 年提出一个理论,人们才对电子和其他自旋二分之一的粒子有了正确的理解。预言了电子必须有它的配偶——反电子或正电子。任何粒子都有会和它相湮灭的反粒子。(对于携带力的粒子,反粒子即为其自身)。在量子力学中,所有物质粒子之间的力或相互作用都认为是由自旋为整数 0、1 或 2 的粒子携带。 携带力的粒子按照其强度以及与其相互作用的粒子可以分成四个种类。大部分物理学家希望最终找到一个统一理论,该理论将四种力解释为一个单独的力的不同方面。 第一种力是引力,这种力是万有的,也就是说,每一个粒子都因它的质量或能量而感受到引力。以量子力学的方法来看待引力场,人们把两个物质粒子之间的力描述成由称作引力子的自旋为 2 的粒子携带的。它自身没有质量,所以携带的力是长程的。太阳和地球之间的引力可以归结为构成这两个物体的粒子之间的引力子交换。实引力子构成了经典物理学家称之为引力波的东西,它是如此之弱——并且要探测到它是如此之困难,以至于还从来未被观测到过。 另一种力是电磁力。它作用于带电荷的粒子(例如电子和夸克)之间,但不和不带电荷的粒子(例如引力子)相互作用。它比引力强得多然而,存在两种电荷——正电荷和负电荷。同种电荷之间的力是相互排斥的,而异种电荷之间的力则是相互吸引的。一个大的物体,譬如地球或太阳,包含了几乎等量的正电荷和负电荷。这样,由于单独粒子之间的吸引力和排斥力几乎全被抵消了,因此两个物体之间净的电磁力非常小。然而,电磁力在原子和分子的小尺度下起主要作用。 第三种力称为弱核力。它负责放射性现象,并只作用于自旋为二分之一的所有物质粒子,而对诸如光子、引力子等自旋为 0、1 或 2 的粒子不起作用。直到 1967 年伦敦帝国学院的阿伯达斯·萨拉姆和哈佛的史蒂芬·温伯格提出了弱作用和电磁作用的统一理论后,弱作用才被很好地理解。他们提出,除了光子,还存在其他 3 个自旋为 1 的被统称作重矢量玻色子的粒子,它们携带弱力.1983 年在 CERN(欧洲核子研究中心)发现了具有被正确预言的质量和其他性质的光子的 3 个粒子,也被称为人们戏称为上帝粒子. 第四种力是强核力。它将质子和中子中的夸克束缚在一起,并将原子核中的质子和中子束缚在一起。人们相信,称为胶子的另一种自旋为 1 的粒子携带强作用力,它只能与自身以及与夸克相互作用。夸克只能存在于无色的组合之中。红、绿和蓝夸克被胶子束缚形成一个“白”中子。在正常能量下,强核力确实很强,它将夸克紧紧地捆在一起。但是,大型粒子加速器的实验指出,强作用力在高能量下变得弱得多,夸克和胶子的行为就几乎像自由粒子那样。 地球上的物质主要是由质子和中子,进而由夸克构成。除了少数由物理学家在大型粒子加速器中产生的以外,不存在由反夸克构成的反质子和反中子。我们从宇宙线中得到的证据表明,我们星系中的所有物质也是这样:除了少数当粒子和反粒子对进行高能碰撞时产生的以外,没有发现反质子和反中子如果在我们星系中有很大区域的反物质,则可以预料,在正反物质的边界会观测到大量的辐射。许多粒子在那里和它们的反粒子相碰撞、相互湮灭并释放出高能辐射。 统一电磁力和弱核力的成功,使人们多次试图将这两种力和强核力合并在所谓的大统一理论(或 GUT)之中。1956 年,两位美国物理学家李政道和杨振宁提出弱作用实际上不服从 P 对称。换言之,弱力使得宇宙和宇宙的镜像以不同的方式发展。同一年,他们的一位同事吴健雄证明了他们的预言是正确的。她把放射性原子的核排列在磁场中,使它们的自旋方向一致。实验表明,在一个方向比另一方向发射出更多的电子。次年,李和杨为此获得诺贝尔奖。 早期宇宙肯定是不服从 T 对称的:随着时间前进,宇宙膨胀——如果它往后倒退,则宇宙收缩。而且,由于存在着不服从 T 对称的力,因此当宇宙膨胀时,相对于将电子变成反夸克,这些力将更多的反电子变成夸克。然后,随着宇宙膨胀并冷却下来,反夸克就和夸克湮灭,但由于已有的夸克比反夸克多,少量过剩的夸克就留了下来。正是它们构成我们今天看到的物质,由这些物质构成了我们自身。 大统一理论不包括引力。在我们处理基本粒子或原子问题时这关系不大,因为引力是如此之弱,通常可以忽略它的效应。然而,它的作用既是长程的,又总是吸引的事实,表明它的所有效应是叠加的。所以,对于足够大量的物质粒子,引力会比其他所有的力都更重要。这就是为什么正是引力决定了宇宙的演化的缘故。甚至对于恒星大小的物体,引力的吸引会超过所有其他的力,并使恒星坍缩。 黑洞黑洞这一术语是不久以前才出现的。1969 年美国科学家约翰·惠勒,为了形象地描述至少可回溯到 200 年前的一个观念时,杜撰了这个名词。 那时候,共有两种光理论:一种是牛顿赞成的光的微粒说;另一种是光由波构成的波动说。我们现在知道,这两者在实际上都是正确的由于量子力学的波粒二象性,光既可认为是波,也可认为是粒子。在光的波动说中,不清楚光对引力如何响应。但是如果光是由粒子组成的,人们可以预料,它们正如同炮弹、火箭和行星一样受引力的影响。 1783 年,剑桥的学监约翰·米歇尔在这个假定的基础上,于《伦敦皇家学会哲学学报》上发表了一篇文章。他指出,一个质量足够大并足够致密的恒星会有如此强大的引力场,甚至连光线都不能逃逸:任何从恒星表面发出的光,在还没到达远处前就会被恒星的引力吸引回来。米歇尔暗示,可能存在大量这样的恒星,虽然由于从它们那里发出的光不会到达我们这里,我们不能看到它们;但是我们仍然可以感到它们引力的吸引。这正是我们现在称为黑洞的物体。 为了理解黑洞是如何形成的,我们首先需要理解恒星的生命周期起初,大量的气体(绝大部分为氢)受自身的引力吸引,而开始向自身坍缩而形成恒星、当它收缩时,气体原子越来越频繁地以越来越大的速度相互碰撞——气体的温度上升。最后,气体变得如此之热,以至于当氢原子碰撞时,它们不再弹开而是聚合形成氦。如同一个受控氢弹爆炸,反应中释放出来的热使得恒星发光。这附加的热又使气体的压力升高,直到它足以平衡引力的吸引,这时气体停止收缩。然而,恒星最终会耗尽它的氢和其他核燃料。貌似大谬,其实不然的是,恒星初始的燃料越多,它则被越快燃尽。这是因为恒星的质量越大,它就必须越热才足以抵抗引力。而它越热,它的燃料就被耗得越快。我们的太阳大概足够再燃烧 50 多亿年,但是质量更大的恒星可以在 1 亿年这么短的时间内耗尽其燃料,这个时间尺度比宇宙的年龄短得多了。当恒星耗尽了燃料,它开始变冷并收缩。随后发生的情况只有等到 20 世纪 20 年代末才首次被人们理解。 1928 年,一位印度研究生——萨拉玛尼安·昌德拉塞卡,算出了在耗尽所有燃料之后,多大的恒星仍然可以对抗自己的引力而维持本身。这个思想是说:当恒星变小时,物质粒子相互靠得非常近,而按照泡利不相容原理,它们必须有非常不同的速度。这使得它们相互散开并企图使恒星膨胀。昌德拉塞卡意识到,不相容原理所能提供的排斥力有一个极限。相对论把恒星中的粒子的最大速度差限制为光速。这对大质量恒星的最终归宿具有重大的意义。如果一颗恒星的质量比昌德拉塞卡极限小,它最后会停止收缩,并且变成一种可能的终态——“白矮星”。恒星还存在另一种可能的终态。其极限质量大约也为太阳质量的一倍或二倍,但是其体积甚至比白矮星还小得多。这些恒星是由中子和质子之间,而不是电子之间的不相容原理排斥力支持的,所以它们叫做中子星。另一方面,质量比昌德拉塞卡极限还大的恒星在耗尽其燃料时,会出现一个很大的问题。在某种情形下,它们会爆炸或设法抛出足够的物质,使它们的质量减小到极限之下,以避免灾难性的引力坍缩。但是很难令人相信,不管恒星有多大,这总会发生。”昌德拉塞卡指出,不相容原理不能够阻止质量大于昌德拉塞卡极限的恒星发生坍缩。但是,根据广义相对论,这样的恒星会发生什么情况呢?1939 年一位美国的年轻人罗伯特·奥本海默首次解决了这个问题。 现在,我们从奥本海默的工作中得到一幅这样的图象:恒星的引力场改变了光线在时空中的路径,使之和如果没有恒星情况下的路径不一样。光锥是表示闪光从其顶端发出后在时空中传播的路径。光锥在恒星表面附近稍微向内弯折。在日食时观察从遥远恒星发出的光线,可以看到这种偏折现象。随着恒星收缩,其表面的引力场变得更强大,而光锥向内偏折得更多。这使得光线从恒星逃逸变得。更为困难,对于远处的观察者而言,光线变得更黯淡更红。最后,当恒星收缩到某一临界半径时,表面上的引力场变得如此之强,使得光锥向内偏折得这么厉害,以至于光线再也逃逸不出去。根据相对论,没有东西能行进得比光还快。这样,如果光都逃逸不出来,其他东西更不可能;所有东西都会被引力场拉回去。这样,存在一个事件的集合或时空区域,光或任何东西都不可能从该区域逃逸而到达远处的观察者现在我们将这区域称作黑洞,将其边界称作事件视界,而它和刚好不能从黑洞逃逸的光线的那些路径相重合。 事件视界,也就是时空中不可逃逸区域的边界,其行为犹如围绕着黑洞的单向膜:物体,譬如粗心的航天员,能通过事件视界落到黑洞里去,但是没有任何东西可以通过事件视界而逃离黑洞。任何东西或任何人,一旦进入事件视界,就会很快地到达无限致密的区域和时间的终点。 在宇宙的漫长历史中,很多恒星肯定烧尽了它们的核燃料并坍缩了:黑洞的数目甚至比可见恒星的数目要大得多。仅仅在我们的星系中,大约总共有 1000 亿颗可见恒星。这样巨大数量的黑洞的额外引力就能解释为何目前我们的星系以现有的速率转动。 黑洞不是这么黑的如果从事件视界(亦即黑洞边界)来的光线永不相互靠近,则事件视界的面积可以保持不变或者随时间增大,但它永远不会减小——因为这意味着至少边界上的一些光线必须互相靠近。事实上,每当物质或辐射落到黑洞中去,这面积就会增大;或者如果两个黑洞碰撞并合并成一个单独的黑洞,这最后的黑洞的事件视界面积就会大于或等于原先黑洞事件视界面积的总和。事件视界面积的非减性质给黑洞的可能行为加上了重要的限制。 人们非常容易从黑洞面积的非减行为联想起被叫做熵的物理量的行为。熵是测量一个系统的无序的程度。常识告诉我们,如果不进行外部干涉,事物总是倾向于增加它的无序度。热力学第二定律是这个观念的一个准确描述。它陈述道:一个孤立系统的熵总是增加的,并且将两个系统连接在一起时,其合并系统的熵大于所有单独系统熵的总和。 如果黑洞具有某一特征,黑洞外的观察者因之可知道它的熵,并且只要携带熵的物体一落入黑洞,它就会增加,那将是很美妙的。紧接着上述的黑洞面积定理的发现,即只要物体落入黑洞,它的事件视界面积就会增加,普林斯顿大学一位名叫雅可布·柏肯斯坦的研究生提出,事件视界的面积即是黑洞熵的量度。由于携带熵的物质落到黑洞中时,它的事件视界的面积会增加,这样就使黑洞外物质的熵和事件视界面积的和永远不会降低。 黑洞辐射的思想是这种预言的第一例,它以基本的方式依赖于本世纪两个伟大理论,即广义相对论和量子力学。因为它推翻了已有的观点,所以一开始就引起了许多反对。然而,最终包括约翰·泰勒在内的大部分人都得出结论:如果我们关于广义相对论和量子力学的其他观念是正确的,那么黑洞必须像热体那样辐射。这样,即使我们还不能找到一个太初黑洞,大家相当普遍地同意,如果找到的话,它必须正在发射出大量的伽马射线和 X 射线。 黑洞辐射的存在似乎意味着,引力坍缩不像我们曾经认为的那样是最终的、不可逆转的。如果一个航天员落到黑洞中去,黑洞的质量将增加,但是最终这额外质量的等效能量将会以辐射的形式回到宇宙中去。这样,此航天员在某种意义上被“再循环”了。然而,这是一种非常可怜的不朽,因为当航天员在黑洞里被撕开时,他的任何个人的时间的概念几乎肯定都达到了终点!甚至最终从黑洞辐射出来的粒子的种类,一般来说都和构成这航天员的不同:这航天员所遗留下来的仅有特征是他的质量或能量。 宇宙的起源和命运从爱因斯坦广义相对论本身就能预言:时空在大爆炸奇点处开始,并会在大挤压奇点处(如果整个宇宙坍缩的话)或在黑洞中的一个奇点处(如果一个局部区域,譬如恒星坍缩的话)结束。任何落进黑洞的东西都会在奇点处毁灭,在外面只能继续感觉到它的质量的引力效应。另一方面,当考虑量子效应时,物体的质量和能量似乎会最终回到宇宙的其余部分,黑洞和在它当中的任何奇点会一道蒸发掉并最终消失。 就在大爆炸时,宇宙体积被认为是零,所以是无限热,但是,辐射的温度随着宇宙的膨胀而降低。大爆炸后的 1 秒钟,温度降低到约为 100 亿度,这大约是太阳中心温度的 1000 倍,亦即氢弹爆炸达到的温度。在大爆炸后的大约 100 秒,温度降到了 10 亿度,也即最热的恒星内部的温度。在此温度下,质子和中子不再有足够的能量逃脱强核力的吸引,所以开始结合产生氘(重氢)的原子核。氘核包含一个质子和一个中子。然后,氘核和更多的质子、中子相结合形成氦核,它包含两个质子和两个中子,还产生了少量的两种更重的元素锂和铍。大爆炸后的几个钟头之内,氦和其他元素的产生就停止了。之后的 100 万年左右,宇宙仅仅是继续膨胀,没有发生什么事。最后,一旦温度降低到几千度,电子和核子不再有足够能量去战胜它们之间的电磁吸引力,就开始结合形成原子。宇宙作为整体,继续膨胀变冷,但在一个比平均稍微密集些的区域,膨胀就会由于额外的引力吸引而缓慢下来。在一些区域膨胀最终会停止并开始坍缩。最终,当区域变得足够小,它自转得快到足以平衡引力的吸引,碟状的旋转星系就以这种方式诞生了。另外一些区域刚好没有得到旋转,就形成了叫做椭圆星系的椭球状物体。这些区域之所以停止坍缩,是因为星系的个别部分稳定地围绕着它的中心公转,但星系整体并没有旋转。恒星的外部区域有时会在称为超新星的巨大爆发中吹出来,这种爆发使星系中的所有恒星在相形之下显得黯淡无光。恒星接近生命终点时产生的一些重元素就被抛回到星系里的气体中去,为下一代恒星提供一些原料。因为我们的太阳是第二代或第三代恒星,是大约 50 亿年前由包含有更早超新星碎片的旋转气体云形成的,所以大约包含 2%这样的重元素。云里的大部分气体形成了太阳或者喷到外面去,但是少量的重元素集聚在一起,形成了像地球这样的,现在作为行星围绕太阳公转的物体。 地球原先是非常热的,并且没有大气。在时间的长河中它冷却下来,并从岩石中散发气体得到了大气。我们无法在这早先的大气中存活。因为它不包含氧气,反而包含很多对我们有毒的气体,如硫化氢。然而,存在其他能在这种条件下繁衍的原始的生命形式。人们认为,它们可能是作为原子的偶然结合,形成叫做高分子的大结构的结果,而在海洋中发展,这种结构能够将海洋中的其他原子聚集成类似的结构。第一种原始的生命形式消化了包括硫化氢在内的不同物质,而释放出氧气。这就逐渐地将大气改变成今天这样的成分,并且允许诸如鱼、爬行动物、哺乳动物以及最后人类等生命的更高形式的发展。 时间箭头当人们试图统一引力和量子力学时,必须引入“虚”时间的概念。虚时间是不能和空间方向区分的。如果一个人能往北走,他就能转过头并朝南走;同样的,如果一个人能在虚时间里向前走,他应该能够转过来并往后走。这表明在虚时间里,往前和往后之间不可能有重要的差别。过去和将来之间的这种差别从何而来?为何我们记住过去而不是将来?科学定律并不区别过去和将来。更精确地讲,正如前面解释的,科学定律在称作 C、P 和 T 的联合作用(或对称)下不变。(C 是指用反粒子替代粒子。P 的意思是取镜像,这样左和右就相互交换了。而 T 是指颠倒所有粒子的运动方向:事实上,是使运动倒退回去。) 想象一杯水从桌子上滑落下,在地板上被打碎。如果你将其录像,你可以容易地辨别出它是向前进还是向后退。如果将其倒放回来,你会看到碎片忽然集中到一起离开地板,并跳回到桌子上形成一个完整的杯子。你可断定录像是在倒放,因为在日常生活中从未见过这种行为。 为何我们从未看到破碎的杯子集合起来,离开地面并跳回到桌子上,通常的解释是这违背了热力学第二定律。它可表述为,在任何闭合系统中无序度或熵总是随时间而增加。地板上破碎的杯子是一个无序的状态。人们很容易从早先桌子上的杯子变成后来地面上的碎杯子,而不是相反。无序度或熵随着时间增加是所谓的时间箭头的一个例子:时间箭头将过去和将来区别开来,使时间有了方向。至少有三种不同的时间箭头:首先是热力学时间箭头,即是在这个时间方向上无序度或熵增加;然后是心理学时间箭头,这就是我们感觉时间流逝的方向,在这个方向上我们可以记忆过去而不是未来;最后,是宇宙学时间箭头,宇宙在这个方向上膨胀,而不是收缩。 假定上帝决定不管宇宙从何状态开始,它都必须结束于一个高度有序的状态,则在早期这宇宙很可能处于无序的状态。这意味着无序度将随时间而减小。你将会看到破碎的杯子集合起来并跳回到桌子上。然而,任何观察杯子的人都生活在无序度随时间减小的宇宙中,我将论断这样的人会有一个倒溯的心理学时间箭头。这就是说,他们会记住将来的事件,而不是过去的事件。当杯子被打碎时,他们会记住它在桌子上的情形;但是当它在桌子上时,他们不会记住它在地面上的情景。 记忆器从无序态转变成有序态。然而,为了保证记忆器处于正确的状态,需要使用一定的能量(例如,移动算盘珠或给计算机接通电源)。这能量以热的形式耗散了,从而增加了宇宙的无序度的量。人们可以证明,这个无序度增量总比记忆器本身有序度的增量大。这样,由计算机冷却风扇排出的热量表明计算机将一个项目记录在它的记忆器中时,宇宙的无序度的总量仍然增加。计算机记忆过去的时间方向和无序度增加的方向是一致的。因此我们对时间方向的主观感觉或心理学时间箭头,是在我们头脑中由热力学时间箭头决定的。 虫洞和时间旅行1949 年库尔特·哥德尔发现了广义相对论允许的新的时空。这首次表明物理学定律的确允许人们在时间里旅行。 因为时间不存在唯一的标准,而每一位观察者都拥有他自己的时间。这种时间是用他携带的时钟来测量的,这样航程对于空间旅行者比对于留在地球上的人显得更短暂是可能的。但是,这对于那些只老了几岁的返回的空间旅行者,并没有什么值得高兴的,因为他发现留在地球上的亲友们已经死去几千年了。 要打破光速壁垒存在一些问题。相对论告诉我们,飞船的速度越接近光速,用以对它加速的火箭功率就必须越来越大。对此我们已有实验的证据,但不是航天飞船的经验,而是在诸如费米实验室或者欧洲核子研究中心的粒子加速器中的基本粒子的经验。我们可以把粒子加速到光速的 99.99%,但是不管我们注入多少功率,也不可能把它们加速到超过光速壁垒。航天飞船的情形也是类似的:不管火箭有多大功率,也不可能加速到光速以上。这样看来,快速空间旅行和逆时旅行似乎都不可行了。然而,还可能有办法。人们也许可以把时空卷曲起来,使得 A 和 B 之间有一近路。在 A 和 B 之间创生一个虫洞就是一个法子。顾名思义,虫洞就是一个时空细管,它能把两个相隔遥远的几乎平坦的区域连接起来。 时空不同区域之间的虫洞的思想并非科学幻想作家的发明,它的起源是非常令人尊敬的。1935 年爱因斯坦和纳珍·罗森写了一篇论文。在该论文中他们指出广义相对论允许他们称为“桥”,而现在称为虫洞的东西。但它们不能保持畅通足够久,以使任何东西通过。 假定你回到过去并且将你的曾曾祖父在他仍为孩童时杀死。这类佯谬有许多版本,但是它们根本上是等效的:如果一个人可以自由地改变过去,则他就会遇到矛盾。看来有两种方法解决由时间旅行导致的佯谬。一种称为协调历史方法。它是讲,甚至当时空被卷曲得可能旅行到过去时,在时空中发生的必须是物理定律的协调的解。根据这个观点,除非历史表明,你曾经到达过去,并且当时并没有杀死你的曾曾祖父或者没有干过任何事和你的现状相冲突,你才能在时间中回到过去。此外,当你回到过去,你不能改变记载的历史。那表明你并没有自由意志为所欲为。解决时间旅行的其他可能的方法可称为选择历史假说。其思想是,当时间旅行者回到过去,他就进入和记载的历史不同的另外历史中去。这样,他们可以自由地行动,不受和原先的历史相一致的约束。","categories":[],"tags":[{"name":"读书 笔记","slug":"读书-笔记","permalink":"http://yoursite.com/tags/读书-笔记/"}]},{"title":"西方哲学史","slug":"西方哲学史","date":"2021-12-22T09:22:52.000Z","updated":"2022-03-23T09:49:11.569Z","comments":true,"path":"2021/12/22/西方哲学史/","link":"","permalink":"http://yoursite.com/2021/12/22/西方哲学史/","excerpt":"","text":"西方哲学史 摘录自 林欣浩 <<哲学家们都干了些什么?>> 苏格拉底与柏拉图苏格拉底在临终前说,未经审视的人生是不值得过的。不过,如果交谈的目的只在于获取定义,那么就算能反思人生,也算是失败。得到确切的定义很可能并非交谈的重点或目的,或许共同探讨本身才是目的,审视问题才是价值所在。 苏格拉底没有写过哲学方面的著作。不过,我们可以参考柏拉图的早期对话录——其中一些片段表达了苏格拉底的观点。对话录通常分为至少三个阶段,但是究竟按什么顺序排列仍然存在争议。苏格拉底在大部分对话中都是主人公。据说,所谓的“早期对话录”主要反映了苏格拉底的思想,不过到了中后期,苏格拉底其实成了柏拉图的代言人。 苏格拉底被希腊公民审判死刑, 为什么苏格拉底宁愿死,也要怀疑?因为人和动物的区别在于人要思考。而怀疑是思考的起点,也是思考成果的检验者。怀疑的最大作用在于能避免独断论,这样才能引导我们寻找正确的答案,免得我们轻信一切未经证实的结论。苏格拉底的怀疑是理性文明的开端和标尺。所有的思想都要因他的怀疑而诞生,最后还要能经得住他的怀疑才算合格。正是照着这个标准思考,西方人才有了哲学,才有了科学,才创造了现代文明。 亚里士多德的演绎推理亚里士多德是亚历山大大帝的老师,和他一样都是马其顿人.亚历山大帝国的统一使希腊文明得以传播到东欧,北非和中亚. 亚里士多德在某种程度上发明并制定了正确的推理规则,这也是哲学逻辑的开端。他是第一个研究演绎性质的人。演绎指的是某个命题遵循必要的前提,辨别各种可能的推论并将其形式化。掌握证据的一般概念后,他界定了研究万物所得到的证据与有关这些事物的真实结论之间的关系。这样一来,他就给世人指明了科学的方向.三段论是演绎推理的一般模式,包含三个部分:大前提——已知的一般原理,小前提——所研究的特殊情况,结论——根据一般原理,对特殊情况作出判断。例如:知识分子都是应该受到尊重的,人民教师都是知识分子,所以,人民教师都是应该受到尊重的。 柏拉图认为,人们不必依靠永恒不变的形式就可以认识世界。相反,亚里士多德认为,形式和共性只能蕴藏于世界万物之内,而了解事物不仅要观察形式,而且需要亲自去检查、剖析不断变化的世界上的万事万物,从而逐渐了解其成因。 保罗与基督教保罗与耶稣是同一时期的,他是犹太人,信奉犹太教,他听从教长的指示,积极迫害基督徒。据《使徒行传》的记载,有一天,保罗在追捕耶稣门徒的路上突然见到天上发光,听到耶稣对他说:“为什么要逼迫我?”保罗大惊失色,眼睛失明,直到三天后才恢复视力。之后他皈依了基督教. 保罗有深厚的哲学功底,他将哲学的思维方式应用到传教中,撰写了大量的神学文章。这些文字后来被称作《保罗书信》,成为《新约》的重要组成部分。 公元 64 年 7 月 17 日夜里发生了一件大事。西方世界的中心,全欧洲最富饶、最美丽的城市罗马突然烧起了大火。这火太大了,持续烧了六天七夜,整个罗马城的三分之二都被烧为灰烬。在那个年代,基督徒们相信世界末日就快到来,有些人到处宣传“上天将会降下巨大的火球烧毁一切”。因此当时还有些人认为,罗马的大火就是基督徒放的。或许是为了洗脱自己的嫌疑,不久以后,罗马皇帝尼禄正式宣布这场大火是基督徒所放,同时展开了对基督徒大规模的逮捕和残杀。一般认为,保罗就死于这场大火之后的审判中。基督徒们从此受到了极为残酷的迫害。 奥勒留与斯多亚学派马可·奥勒留(121—180)是一位带有哲学倾向的罗马皇帝,他的作品反映了斯多亚学派哲学的精华内容,而这些内容被他的强权实践所验证。大概在他的晚年时期,尽管罗马帝国身处混乱状况,奥勒留还是完成了《沉思录》,这本书奇迹般地留存到了现在。《沉思录》显然算不上标准的哲学著作,更像是他的私人日记。书中没有针对清晰表述的观点进行持续论证,而是有很多不连贯的感言、格言以及个人忠告。这本书创作的浪漫之处在于奥勒留结束白天的战争后,每个孤独的不眠夜都会在月光下的多瑙河边写上几行字。这很可能是事实。 斯多亚学派哲学的观点,即与自然保持和谐的人生是最好的、最道德的。有一则比喻可以很好地证明上述观点,那就是拴在马车背后的一条狗。马车前进时,狗有两种选择,既可以一边大声叫,一边朝着相反方向用力,导致自己受勒,又可以选择平心静气地随马车一起前进。不管它做出哪种选择,都会朝着马车前进的方向前进,它唯一能做的就是去应对业已注定的命运。奥勒留总是在提醒自己,斯多亚学派学者必须搞清楚哪些事物受控于自身,哪些事物不受控制。奥勒留曾引用了爱比克泰德的话:“我们可以控制的方面包括观点、冲动、欲望和厌恶情绪.不能控制的包括身体、财产、名誉和官职。 罗马的分裂和十字军东征公元 284 年戴克里先被推举为罗马第一执政官,罗马皇帝.戴克里先一直在琢磨帝国千年基业的问题。他总结了之前罗马历史上的得失,认为帝国的最大弱点在于国家面积太大了,一个皇帝管不过来。他大手一挥,把罗马分成了东西两个部分。自己不当整个罗马的皇帝,只当东罗马帝国的皇帝,给对面的西罗马帝国又找了一个新皇帝。而且他觉得光这么分还不够,还是守不过来。他又找来了两个副手皇帝,自己和西罗马皇帝一人一个,把帝国进一步分成了四份。但是戴克里先退位之后,其他的皇帝发生了斗争.最够君士坦丁成为西罗马帝国皇帝,并接纳了基督教.最后君士坦丁又击败了东罗马帝国皇帝. 基督教称为罗马帝国国教之后,开始了清除希腊哲学的行动,他们烧毁了亚历山大图书馆,关闭了柏拉图学院.在基督徒烧毁神庙的时候,一些哲学家惊慌失措地向东逃跑,此时波斯帝国覆灭,他们就到了阿拉伯帝国.虽然伊斯兰教也是一神教,但是他们展示了对异教的宽容,接纳了他们,从此希腊文化在阿拉伯帝国存在了下来.并在阿拉伯占领北非的时候,一些希腊文化的学者借道北非到了西班牙.而基督徒在数次十字军大败后,开始了着手翻译各种阿拉伯文的典籍。其中也免不了包括一些希腊哲学著作。 阿奎那与天主教哲学公元 476 年,中国南北朝的时候,西罗马帝国的最后一任皇帝被赶下台。这件事标志着西罗马帝国的灭亡,从此以后,欧洲分为多个国家,再也没有统一过.西罗马灭亡了,但基督教保留了下来,且势力越来越大.基督教神学家们为了维护自己的观点,斥责对方是异端,展开了激烈的学术辩论。这时基督教的神学家们开始用各种办法从东方获取希腊文献来寻找学术辩论的理论依据。这在欧洲又掀起了一段研究哲学的高潮。此时的哲学被称作“经院哲学”。”其中的集大成者就是阿奎那. 圣托马斯·阿奎那(约 1225—1274)是意大利天主教哲学家,也是学术传统的重要人物之一.如果不算唯一一位重要人物的话。是他创立了托马斯哲学学派,该学派在相当长的一段时期内是罗马天主教会的主要哲学支撑。直到今天,新托马斯主义还是天主教的重要思想。 阿奎那采用的亚里士多德注释方法使得他的论证非常著名。每种方法都以公认的经验事实开头,并证明上帝存在。世上万事万物都要有另一个事物作为它的原因。那么必然存在一个最初的原因,这个原因就是上帝。经院哲学家想得挺好,他们用哲学去证明宗教,为的是让宗教也能符合理性的考验。但是别忘了,怀疑是哲学的核心精神。就在这么无聊的经院哲学时期,却出现了一个对后来的科学发展极为重要的理论。它是一个教士在研究神学的时候提出来的。这个教士因为出生于一个叫作奥卡姆的地方,因此被人称为“奥卡姆的威廉”,这个理论就被后人称为“奥卡姆剃刀”,内容就是”如无必要,勿增实体”. 君主论与马基雅维利主义马基雅弗利的外交官生涯为他提供了创作《君主论》的必要经历,但是在 1512 年不幸终止。那年,美第奇家族在佛罗伦萨掌权后,解散了共和国,之后马基雅弗利也被撤职。更糟糕的是,几个月后,马基雅弗利还遭到冤枉,被指控暗算新成立的美第奇政府,并遭到严刑拷打与监禁。他正是基于这样的形势变化创作了《君主论》。马基雅弗利之所以认为君主要想高效地统治国家就必须学会怎样作恶,其根源在于他对人性的偏见。他认为,人都是“忘恩负义、心怀二志、弄虚作假、伪装好人、见死不救和利欲熏心的. 马丁路德 和 加尔文马丁路德(1483 年 11 月 10 日-1546 年 2 月 18 日). 罗马教会比较看重信徒是否遵守律法、纳税、履行仪式这些外在的行为,认为这些外在行为是信徒成为“义人”的关键,这种观点在神学上被称为“因行称义”。但是马丁・路德在阅读《圣经》中的保罗书信时,发现保罗所持的是“因信称义”的观点。“因信称义”的意思就是说,真心相信上帝,就可以成为“义人”。” 禁止普通百姓接触《圣经》的命令,是在 13 世纪开始颁布的。因为垄断《圣经》的好处太明显了。既然教会的全部权威都来自于这本书,那么把这本书束之高阁,也就没有人可以怀疑教会了,一切都必须以教会的说法为准。换句话说,垄断了对权威的解释,就等于垄断了一切。但是很快,中国的活字印刷术就传到了欧洲.因为有了印刷术,还因为有城市居民和手工业者等群众基础,所以马丁・路德的那些宗教檄文一经写完就在欧洲迅速传开,宗教革命终于遍及整个欧洲,千百万神父和知识分子卷入其中。几十年后,欧洲支持路德和罗马的两派贵族还打了一场惨烈的宗教战争。双方打了个势均力敌。从此,欧洲基督教分成了两大派:罗马一方被称为天主教;路德一方被称为新教。另外,东边的罗马帝国在此之前还搞了一个东正教。 加尔文比路德小十二岁。他和路德同样是先学法律,中途改为研究神学。加尔文认同路德的观点,因此受到了天主教的迫害,一路流浪,来到了瑞士的日内瓦。最终,加尔文在日内瓦确立了他在新教中的地位。加尔文将新教的影响扩大到了整个欧洲,并且用庞大、严格的教会系统维持他的统治。很快,加尔文像他的敌人——罗马的天主教皇那样——当上了新教的教皇,日内瓦成了新教的罗马。新教是靠着路德一篇篇雄辩的文字,从天主教的火刑架下顽强成长起来的。但加尔文和他的继承者们却在日内瓦竖起了更多的火刑架。他们烧天主教徒,烧异端分子,烧跟他神学观点不一致的人,这再次证明了,哲学和宗教的联合是行不通的。宗教只会把哲学当作获权的工具,一旦取得胜利,就会毫不犹豫地把哲学扔到一边。 荷兰共和国和笛卡尔教会最可怕的武器是宗教裁判所。在各个国家中,最严酷、最血腥的宗教裁判所在西班牙。而西班牙的历代君王中,最不可一世的是腓力二世(也被翻译为“菲利普二世”)。他的老婆是著名的“血腥玛丽”,以屠杀异教徒而闻名。腓力二世则打赢了欧洲有史以来最大规模的海战,阻止了奥斯曼帝国进攻欧洲的企图。他还拥有欧洲最强大的“无敌舰队”,把美洲变成了西班牙的后花园。腓力二世的志向如此之大,或许觉得在小小荷兰发生的一场叛乱很容易就能搞定。但结果出乎了他的意料. 荷兰起义军的首领威廉是一个令人敬佩的贵族。一是为了保护自己的财产,二是因为他与荷兰有深厚的感情,再加上他也同情被西班牙迫害的新教徒,结果威廉毅然放弃了舒适的生活,花了大笔财产组织起义军。腓力二世不愿意小小的荷兰牵扯他太多的精力和金钱,于是他换了一种镇压方式:悬赏威廉的性命。奖赏金是赦免过去一切罪行,封为贵族,还有 25000 枚金币的巨款作为赏金。威廉被刺杀的那天,距离他登基成为荷兰国王只差两天。威廉被杀后四年,具有绝对优势的西班牙“无敌舰队”被用海盗船、商船拼凑起来的英国舰队悉数歼灭。又过了八年,腓力二世被法国击败。一连串军事和政策上的失败使得西班牙实力大减。这成就了坚持不懈顽强起义的荷兰人。威廉去世二十多年后,荷兰终于赢得了独立。 因为荷兰商业发达(雅典也是商业发达),在天主教和新教比赛般绞杀异端的世界里,荷兰拥有全欧洲最开明的言论政策。不久以后,荷兰成为各类异端分子、科学家、哲学家的避难地。它成为雅典之后的哲学的第二个故乡。荷兰在近代欧洲第一个取消了独裁者,完全采用议会投票的方式处理政务。这种政体从罗马屋大维结束民主制度以来,已经很多年没有了。此时的荷兰也不能叫作“荷兰王国”了,而改叫“荷兰共和国”。荷兰虽然商业发达,但是军事不行,于是就大量雇佣雇佣兵,其中有一个人,叫笛卡尔. 勒内·笛卡儿(1596—1650)或许是被引用最多的哲学家了,最常被引用的是那句:我思,故我在。 笛卡尔比怀疑先哲著作还要更彻底,他要彻底怀疑整个世界:我眼前的这个世界是不是都是假的?我见到的一切会不会都是幻觉、都是梦境?他想,不管我再怎么怀疑,“我怀疑”这件事是确定的,它肯定存在吧?那么,只要有了怀疑的念头,就说明“我”肯定是存在的——“我”要是不存在就不会有这些念头了。“我思”和“我在”不是因果关系,而是推理演绎的关系。即:从前者为真可以推导出后者为真。也就是从“我思”为真,可以推导出“我在”为真。而不是说“我不思”的时候就“我不在”了,在不在我们不知道。 形而上学此时的哲学已经有了公认原则。即:我们的结论必须能经得起各种怀疑,这样才能保证它真实可信。这也是科学研究的原则。但是还有一个大问题。我们该用什么方法才能得出可靠的、经得住怀疑的结论呢? 笛卡尔从欧几里得的几何原本中得要灵感,几何原本由几条简单的公理和公设推导出厚厚的六卷.欧氏几何启发了笛卡尔时代的哲学家.为什么不像欧氏几何那样,建立一套严密、规整,又高于世间万物的理论体系. 笛卡尔的疑问关系到了哲学上的一个重要概念,叫作“形而上学”。我们在学校里学习马哲的时候,课本给我们的解释是:形而上学就是用孤立、静止、片面的方式看待问题。在课本上,“形而上学”被当成一个贬义词.它的来历是什么呢?众所周知,亚里士多德写了很多著作,一个叫安德罗尼柯的人用“研究有形体的事物”和“研究没有形体的事物”,把亚里士多德的著作分成了两大类。一个是物理学,一个是形而上学,意思是“那些高于物理学的、看不见、摸不着的学问。《易经》有一句话:“形而上者谓之道,形而下者谓之器。日本哲学家井上哲次郎先生在看到 metaphysics 这个词后,联想到《易经》,把 metaphysics 翻译成了“形而上学”。 我们现在所说的“形而上学”,可以简单地理解成是用理性思维去研究那些能统一世间一切问题的“大道理”。比如世界的本质是什么样子、人生的意义是什么之类的问题. 二元论 与 唯我论笛卡尔只知道自己的意识存在,不知道外面的世界存在不存在。这个结论暗含了一个前提,那就是:他把我们讨论的世界分成两个部分,一个是我们自己的心灵,一个是心灵之外的部分。这种观点就叫作“二元论”。心灵一个元,外界一个元,一共二元。这两个元是相互独立的、平等的,虽然可以互相影响,但谁也不能完全决定另一个。 唯物主义,说世界的本质是物质的,我们的精神世界不过是大脑生理活动的结果。换句话说,精神是从物质中产生的。这种观点就叫作物质一元论。当然,相应的也有唯心主义的一元论,认为世界的本质是精神的,外面的世界不过是我自己心灵的产物罢了。 在二元论的观念下,世界被一分为二:外界和内心。痛苦虽然来自于外界,但真正承受痛苦的是我的内心。因此我们虽然仍旧需要尽力去改变外物,但在客观世界这一元里的得失其实不重要,关键是固守自己的内心这一元,固守住我们获得体验的最后一关。而在内心世界里,我们自己能完全做主,这就让人产生了很大的安全感。我们想,对人伤害最大的其实不是一时的痛苦,而是对未来痛苦的恐惧。所以,在面对痛苦的时候,我们应该把自己的感受局限在此时一瞬,而不要顾及那些未到的痛苦。我们自己其实是由无数个时间瞬间组成的。我们的感受只是此一瞬的。而这一瞬的痛苦,前面从二元论的角度讨论过了,并不难忍受。至于未来尚未到来的痛苦,此时并未加诸我身,对我也就没有伤害。 从二元论进一步,还可以得到唯我论。假设我们只停留在“我在”的阶段,我们只能确认我自己存在,外界的一切存在不存在我不知道,这就叫“唯我论”。首先,和二元论一样,唯我论很难被彻底反驳掉。我们永远都可以质疑自己生活的世界是一片幻觉,或者只是一个梦。当你思考“世界的本质是什么”的时候,唯我论永远立在一旁幽幽地望着你。挥之不去。其次,唯我论对我们的普通生活也有很大的影响。它可以让我们变得更坚强。在采用唯我论的时候,我们会感到天上地下唯我独大,我们不用害怕任何事物,只要面对自己的内心就可以了。 斯宾诺莎与伦理学笛卡尔有一个很棒的想法,就是按照欧式几何学的模式来建立哲学体系。具体来说,就是先找出一些不言自明的公设,再以这些公设为基础,按照演绎推理的方法建立整个哲学体系。笛卡尔的想法不错,具体工作却做得不太好。斯宾诺莎则完美实现了这个想法。 斯宾诺莎于 1632 年 11 月 24 日出生在阿姆斯特丹,父母都是秘密的犹太人。也就是说,他们曾被迫信仰基督教,却秘密信奉着犹太教。1670 年,《神学政治论》匿名出版了,该书认为《圣经》应该是一部基于史实、文化底蕴深厚的文献,这无疑指出了《圣经》作者的狭隘认识。举个例子,斯宾诺莎否认《圣经》中的神迹属于超自然现象,声称它们只不过是自然现象,却因为《圣经》作者的知识匮乏而产生了误解。斯宾诺莎最有影响的著作叫《伦理学》,在他去世后才发表。这本书的全称是《按几何顺序证明的伦理学》。 如何证明呢? 第一步找到公设,我们就必须找到一个绝对存在的、不可能被怀疑的东西作为公设。既然这个东西绝对存在,那么它肯定不能依赖别的物体存在。斯宾诺莎把这种东西称作“实体”。这么一个永恒的、无限的、唯一的、不可分的东西,就是上帝。斯宾诺莎就是这么想的。所以,斯宾诺莎承认上帝,但他心目中的上帝不是基督教或者犹太教中人格化的上帝,而是无所不在的实体。类似于中国哲学里很多学派都追求的“天人合一”. 培根与归纳法科学家们是靠什么搞研究的呢?靠归纳法。这应该归功于培根,说过“知识就是力量”的培根,一位和笛卡尔同时代的知识分子。归纳法的意思是,人们通过观察多个个别的现象,总结出普遍的规律。比如人观察到,每一次把石头扔出去,最后石头总要落地。那么他就能总结出“空中的石头总会落地”这么条规律来。事实上,我们今天取得的所有科学成就,都是综合使用归纳法和演绎推理的结果。举例,科学家先观察到某些现象(比如木头用火一点就燃烧),用归纳法假设出一条科学规律来(是高温引起木头燃烧吗?),然后用演绎推理从这个假设中得到一些推论(那么用烧红的烙铁虽然没有火苗,也应该能点燃木头),再根据这些推论去做实验,看实验结果是不是符合假设的理论(哇,果然点燃了)。然后科学家就可以写篇《论木头燃烧的原因》发表了。这套科学方法里既有归纳法,也有演绎推理,但其基础、起关键作用的,是归纳法。科学家们“轻视”演绎推理,关键在于他们发现演绎推理有一个巨大的缺陷。这个缺陷就是,演绎推理不能给我们带来任何新知识。一本《几何原本》的全部知识其实就是开头的那几条公设和公理,后面厚厚的十三卷内容不过是在不断用其他的形式去重复那些公设和公理罢了。而科学的任务是探索自然界,获取新的知识。毫无疑问,数学是不可能完成这个任务的。归纳法是科学家们的唯一选择。 笛卡尔他们研究哲学,不都先要公设吗?问题是,这公设它有什么根据吗?斯宾诺莎说世上存在实体,你能做一实验给我证明吗?说白了,笛卡尔和斯宾诺莎构建的哲学世界,整个学说不过只是几句公设,而这几句公设还没什么根据! 洛克:现代经验主义之父第一个向数学派哲学家发起挑战的科学派哲学家叫洛克。约翰·洛克(1632—1704 英国)主要关注社会问题和认识论。他的理论激励了美国革命和法国大革命.他认为,知识主要来自经验,而非天赋观念。 洛克说,刚出生的婴儿,内心就像是一张白纸或者一块白板,什么都没有,人的思想都是靠后天学习得来的。没有什么知识是人不用学习,先天就能领悟的。在洛克看来,笛卡尔、斯宾诺莎等人号称的那些公设,全都是无根之木。洛克也承认人的本能是天生的,比如直觉之类。但洛克认为,这些本能就和动物捕食、生存的本能一样,是一种生理、心理上的习惯而已,并不是什么比客观世界高一等的理性,更不可能由此建立起一个哲学世界来。洛克在《人类理智论》在中提出了物体第一性质与第二性质的区别。第一性质与物体“密不可分”,无论物体经历任何变化,第一性质都会始终不变,它也是“物体微粒”的一部分。洛克指出,固体性、延展性、形状、体积和运动属于物体的第一性质,而第二性质则“不存在于物体内部,而是通过物体第一性质使人产生各种感觉的力量”。洛克将颜色、味道、触感、气味和声音归为了第二性质。举个例子,人们对柠檬形状的认识反映了外界世界中柠檬的本来面目,但是柠檬的味道却不一定具有同等作用。” 理性主义与经验主义洛克的学说给当时的哲学界带来了很大的影响。原本数学家一枝独秀的哲学界,出现了数学派与科学派双雄争霸的场面。由于这场争论是哲学界的一件大事,所以哲学家们给这两派学说分别起了名字。笛卡尔、斯宾诺莎代表的数学家派,被称为“理性主义”。在归纳法里,最重要的是实验数据,是观测结果,它们是科学理论的基础和证据。这些东西可以用一个词来统称:经验。所以洛克代表的科学家派被称为“经验主义”。 理性主义和经验主义之间的分歧,其实可以上溯到柏拉图和亚里士多德的分歧。他们俩对世界的看法就不一样。一个重视心灵理性,一个重视现实经验。以“人”这个概念为例。柏拉图说,“人”这个概念比“张三李四”这些具体的人更真实。“张三李四”生了又死,来去不定,只有“人”这个概念是恒久的。亚里士多德则说,“张三李四”是具体的,我们看得见摸得着。而“人”这个概念,完全是我们看过了这么多具体的人,然后在脑子中产生的。所以真实存在的是具体的事物,不是概念。亚里士多德是柏拉图的学生,但是观点和柏拉图相悖,为此亚里士多德还说了一句名言:“吾爱吾师,吾更爱真理。” 莱布尼兹与哲学乐观主义戈特弗里德·莱布尼茨(1646—1716)是数学家,在哲学上是一个理性主义者。他很快就接受了笛卡尔等人的学说,不仅发展出了属于自己的哲学体系,还和洛克展开了激烈的论战。洛克说,理性主义者们所谓的一些先于经验的公设啊,理念啊,和动物的本能没有区别。莱布尼茨针锋相对地反驳:人跟禽兽有什么区别吗?区别就是禽兽做事只凭经验,人却能根据经验总结出必然规律。禽兽不知道思考,总以为过去发生的事情,在以后相似的场合下还会发生。所以人可以利用禽兽的习性,去设计陷阱捕捉禽兽。 莱布尼茨是理性主义者,自然他也是使用先公设后推理的那套过程。莱布尼茨的公设是这样的:物质是占据空间的对吧?那么只要是能占据空间的东西,就可以被分成更小、更简单的东西。物质无限地分下去,最后剩下的,一定是不占据空间的“东西”——要是占据空间就能再分下去了。这“东西”不占据空间,所以它不是物质。所以它是精神。所以一切物质都是由精神组成的。莱布尼茨给这些不能再分了的、不占据空间的东西起名叫“单子”,他的理论也就被称为“单子论”。 但是因为理性主义者所有的结论都建立在不一定靠谱的公设上。只要公设、推理过程中有一点不可靠的东西,失之毫厘,谬之千里,整个体系就不知道扯到哪里去了。最后得出来的结论也就很难让人信服。 牛顿与机械唯物主义牛顿最重要的成就是力学。他发现了万有引力定律,正确计算了天体运行的规律。他提出的力学定律在长达几个世纪的时间里都是不可撼动的。牛顿的第一身份是科学家,自然,在哲学上他倾向于经验主义。牛顿热衷于做实验,他的成就也都来自于实验,因此他有一句名言:“我不发明假说”。牛顿不仅在哲学上倾向于经验主义,在现实中还是洛克的好朋友。洛克赏识牛顿的才华,依靠他的社交关系提携过牛顿。一看洛克被莱布尼茨欺负了,牛顿二话没说,挽袖子就上,牛顿和莱布尼茨都是数学家。牛顿灭莱布尼茨,就灭在了微积分发明权这件事上。现代历史学家普遍认为,这两个人各自发明了微积分,所以微积分的基本定理叫“牛顿-莱布尼茨公式”,用两个人的名字合在一起表示的。但是莱布尼茨的论文比牛顿早了足足三年。但当时牛顿的声望、权势都比莱布尼茨大,再加上很多英国人出于民族主义心理支持牛顿,所以两个人在学术界大吵了一番。无非就是指责对方某年某月看过自己的笔记、某年某月我给你的通信中透露了我的微积分思想之类。不久,英国皇家学会经过详细认真的调查后庄严宣布——牛顿才是微积分的发明者,莱布尼茨是个大骗子。备注:此时的英国皇家学会会长就是牛顿。而且是他本人起草的这份调查报告。 顺便一说,这不是牛顿唯一的学霸行为。他不仅对外国学者狠,对自己的同事同样毫不留情,比如对罗伯特・胡克。胡克也是历史上有名的大科学家,制作过一些很牛的机械,还发明了“胡克定律”以及“细胞”一词。牛顿能在和他人的斗争中节节胜利,归根结底,靠的是他在力学上的伟大成就。别人对牛顿什么都能质疑,唯有在学术上,谁也打不过他。但就算是牛顿这么强的对手,莱布尼茨仍旧给了他一次反击。牛顿最伟大的成就是发现万有引力。但牛顿还有一个问题,就是没能说明相隔万里的星球之间到底是怎么产生引力的。连牛顿本人都不相信,相隔这么远的星球在没有任何媒介的情况下还能发生力的作用,他说:“在我看来,这种思想荒唐至极。”这也不能怪牛顿,因为直到后来爱因斯坦发表了相对论原理,对引力才有了令人满意的解释。而在当时,聪明的莱布尼茨立刻发现了引力是一个问题,他攻击牛顿说,你如果不能解释物体之间到底通过什么媒介产生了引力,那么你这理论就是一番空话。 牛顿能给哲学留下影响,不是因为他进行了什么哲学研究,而是他在物理学上的成就实在太大,余波就把哲学给影响了。这个成就就是他的力学。用物理学来解释包括人类意识在内的整个世界,这种观点叫作“机械论”。机械论就是除掉了辩证法之后的唯物主义,也可以叫作“机械唯物主义”。就像经验主义者集中在英国、牛顿是英国人一样,机械论的急先锋也是一个英国人,他叫作霍布斯。 托马斯·霍布斯(1588—1679),虽然霍布斯也信仰基督教,但是他的著作《利维坦》实际上宣扬的是无神论思想。霍布斯拿铃声来说明他的机械论观点。他说,在“铃铛颤动直到我们听到声音”这个事件过程里,铃铛只有运动没有铃声,空气只有运动没有铃声,传到我们的耳朵里了就产生了铃声。所以真正存在的是运动,不是声音。 17、18 世纪的人们崇拜牛顿的学说,那时的机械论也被认为有着伟大的前途。机械论者希望,有一天在医学、心理学、伦理学、政治哲学等领域,都可以应用牛顿力学,或者像牛顿力学那样用几个简单的数学公式去解释。虽然人们可以通过实验证明物质能对意识产生严重的影响(比如脑萎缩会降低人的思考能力,打一棍子可以让人立刻昏厥,喝酒、嗑药可以让人产生幻觉),可以证明人类不能靠思想意念去改变物质,但是也仍旧不能严格证明意志完全由物质决定。 决定论机械论虽然可以条理清晰地解释这个世界,但是按照机械论的说法,人类不过是这个世界中可有可无的一件事物而已,和桌子板凳、花鸟鱼虫没有本质的区别。我们的意识不过是一系列物质作用的结果,随时可以消失,毫无永存的希望,更谈不上还有什么人生意义。就像世间的其他事物一样,存在就存在了,消失就消失了。这很容易推导出虚无主义和享乐主义。以及最可怕的决定论. 决定论的意思很简单,既然世间万物都可以用物理规律来解释,那么每一个事件之间必然要遵循严格的因果关系。如果人的意识是完全由物质决定的,那肯定也得服从严格的物理定律。那么,整个世界该如何发展,该走向何处,都是由自然定律决定好了的。就像人们根据力学可以预测星辰位置一样,人们也可以根据自然规律来预测未来所有的事件。 一个支持决定论的证据是,在 20 世纪之前,人们认为世界上不存在真正的随机数。没有随机,那就意味着一切都可以计算。数学家拉普拉斯曾说,只要他拥有足够多的数据,他就可以按照机械定律推出未来世界的全部面貌。这就像某些科幻小说里设想的那样,假如有一台超级计算机,就可以计算出未来的一切。可怕的地方就在于,一旦我们接受了最严格的决定论,那就意味着人类没有了自由意志。因为我们的意识是由组成我们身体的物质决定的。组成我们身体的物质又是由物理定律决定的。所以,我们头脑中的每一个念头,其实都是在几万亿年前的宇宙大爆炸的那一瞬间就被决定好了的。且不说这想法很诡异,关键是,那人生还有什么意义啊?假如人的全部意识都是事先被决定好的,人就没有自由,那不就没有道德可言了吗?人就不需要为自己的行为负责了呀。因而,从决定论——特别是从严格的决定论所导出的结论,是荒谬甚至恐怖的。如果按照决定论的观点生活,人类的社会秩序将会荡然无存,人类的一切工作都会变得没有意义,一切罪行都可以得到饶恕。这世界显然不是任何一个哲学家想要的。除了建立在机械论上的严格的决定论之外,在哲学史上更流行的是部分决定论,也就是说物理世界是被决定的,但是人有自由意志。这当然更容易让人接受。 古希腊的斯多葛学派就相信部分决定论。他们认为我们不能控制事物,但是可以控制我们自己对待生活的方式。所以这个学派提倡随遇而安的生活态度。沉思录的作者马可·奥勒留就收到了斯多葛学派的影响. 休谟与人性论挑战机械论和决定论的人,乃至挑战整个科学体系的人就是休谟. 他 23 岁完成了人性论,休谟是英国人,也是经验主义者。但休谟认为他之前的经验主义者和理性主义者都有根本缺陷。休谟认为,你们讨论“何事真实存在”之类的问题,实际上这些问题人类根本没有能力回答,所以你们才能怎么说怎么都有理,正反两面的观点都能成立。在休谟这里,经验就是人的感觉印象。我感觉到了什么就是什么,至于这感觉从哪儿来的,是真是假,我不知道。休谟认为,我们所谓的“我”,不过是一堆经验片段的集合而已,并没有一个独立于经验的、实在的“我”存在。 笛卡尔认为“我”是超越了客观世界的真实存在,在休谟看来,“我”不过是后天学习到的一堆经验片段而已。真正有没有“我”呢?不知道!不知道就不知道,没关系。我们能得到的经验就是眼前的生活,在没有明确的证据证明面前的生活都是幻觉之前,我们就照着自己平时的经验正常生活下去就可以了。我们没必要也没能力去无限地怀疑世界。反正想也想不出结果来,就别想了. 休谟打算用怀疑论来抛掉前人所有不可信的经验。休谟想,有什么知识是切实可信的呢?他找到两种。第一种是不依赖于经验的知识。比如几何学,它自身是不矛盾的,完全符合逻辑规则,而且不依赖经验存在。第二种可靠的知识是我们自己感受到的经验,摸到什么、看到什么,这些都是可信的(当然,还是那句话,这经验是不是来自于幻觉我们先不管)。第一种是纯粹的演绎推理,第二种是经验归纳.那么,因果律属于第一类知识吗?我们能不依赖于经验,只靠逻辑推导出因果律吗?显然不能。那么,因果律可以靠经验总结出来吗?休谟说,不能,因为你就算之前多次看到苹果离开树枝落到地上这个现象,你也不能保证,下一次苹果还一定会落到地上。“必然”这个东西不在我们的经验范围之内。我们之所以认为这里有“必然”性,是因为我们过去无数次地看见了这两件事连在一起发生,所以我们就想当然地认为,这两件事之间有必然的联系,在未来也会永远连在一起发生。 休谟指出,人相信因果律其实是一种心理错觉,只因为我们发现两件事总在一起发生,我们就期待它们能再次一起发生。但这其中并没有可靠的根据。举个简单的例子,假如有一个没有科学知识的原始人,他通过观察发现,公鸡打鸣之后,总伴随着太阳升起,没有一天例外。那么他会认为,公鸡打鸣是太阳升起的原因。这显然是错的。同理,农夫喂鸡也是这个道理.因为再多次的偶然累计在一起也不可能把偶然变成必然。 康德与理性批判我们说哲学的一切都是从怀疑开始的。 近代哲学从笛卡尔的怀疑开始,这个怀疑让人们踌躇满志,觉得有一个广阔的空间可以施展拳脚。然而一路怀疑下去,到了休谟的怀疑,把人类所有的知识都怀疑没了,只剩下荒诞。这个时候有两个会严重摧毁生活的哲学观点。一个是休谟的怀疑论,一个是科学的决定论。可怕的是,这两个观点正好是互相矛盾的两个极端。反对一个就等于拥护另一个,采取中庸之道的那些结论,更像是诡辩论而不是严谨的推理。 接下来是属于德国的时代。康德终于来了。包括康德,以及后面的谢林、黑格尔、费尔巴哈、叔本华、尼采、马克思、胡塞尔、海德格尔,还有对哲学影响颇大的爱因斯坦、海森堡。这个超豪华阵容全部都是德意志人。1781 年,康德的杰作《纯粹理性批判》出版了,由此打破了沉寂。 我们先集中关心一下他到底是怎么解决决定论和休谟怀疑论这两个大问题的吧。康德说,当年大家都以为地心说正确,可是天文学家根据地心说怎么也计算不出正确的结果。哥白尼大胆地把地心说掉过来,改成日心说,一下子解决了问题。那过去的哲学家呢,都认为我们的认识要符合客观世界,但是讨论了半天都没有结果。康德认为,我们应该把主客观世界的关系颠倒过来!在康德的哲学世界里,所有的知识都要先经过人类心灵的加工,才能被人类认识。在他的哲学里,不是心灵去感受经验,而是心灵加工经验,心灵生产了经验。有一个最常用的比喻,有色眼镜。这个比喻说,假设每个人终身都必须戴着一副蓝色的有色眼镜。这个世界上所有的事物,必须都通过有色眼镜的过滤才能被人看到。那么所有人看到的就是一个蓝色的世界,而世界真实的面貌是人永远看不到的。在这个比喻里,有色眼镜是先天认识形式,事物原本的颜色是物自体,人类看到的蓝色的世界,是表象。康德认为,这世界(物自体)是人类永远无法真正认识的,人类看到的只是表象的世界。但是由于每个人对真实世界的表象方式(先天认识形式)都是相同的,所以人类看到的同一个东西的感受还是一样的,因此我们察觉不到真实的事物是否被扭曲了。所以这个世界观并不和我们的生活经验相悖。 康德之前的哲学危机,是休谟对因果律,乃至对人类理性能力的怀疑。康德的解决方法是,他把世界分成了两个部分。一个部分完全不可知,另一个部分则可以用理性把握。不可知的那部分因为永远不可知,所以对我们的生活没有什么影响。只要我们在可把握的世界里生活,理性就又恢复了威力。今天我们在谈论哲学的时候,无论讨论多么时髦、多么先锋的理论,都无法绕过康德。用叔本华的话说,任何人在读懂康德之前都只是一个孩子。按照学术史的发展规律,面对康德这么一座高峰,后来人要常年生活在他的阴影之下,只能做一些修修补补的工作。事实上这工作有人做了。他们就是费希特和谢林。 黑格尔与辩证法存在即合理。- 格奥尔格·黑格尔(1770—1831). 就在法国大革命这一年里,黑格尔开始阅读康德的作品。过去的哲学家们,也就是形而上学家们,他们认为真理是固定不变的,静等着人类去发现。说白了,这些形而上学家们都觉得这个世界上存在一个叫“真理”的固定的东西,就跟事先写好的考试答案一样,等我们去找到它。因此我们的教科书才说形而上学是孤立、静止地看待世界。黑格尔认为,这么想就太幼稚了。我们今天对辩证法有一种庸俗的理解,说辩证法就是“看待事物要分两个方面”。别人批评一个现象,你非要说“要辩证地看这件事,这件事也有好的一面嘛”。这是对辩证法的极大误读。这不叫辩证法,这叫诡辩法,它的唯一作用是把所有的事实都捣成一片糨糊,逃避一切有意义的结论。 这当然不是黑格尔的辩证法。黑格尔的辩证法是什么意思呢? 传统的逻辑,也就是我们一般人能接受的逻辑,都要遵守“矛盾律”。“矛盾律”的意思是,一件事不能自相矛盾,事物和事物之间也不能互相矛盾。“我长得漂亮”和“我长得丑”两者只能有一个为真,不可能同时为真。”黑格尔认为:“矛盾就是世界的本质。矛盾的双方可以共存,但是处在互为差异、甚至互相冲突的动态之中。事物的正题和反题会发生强烈的冲突,这个冲突的结果并不是一方消灭另一方,而是正题和反题最终化为“合题”达到了协调,升华了。新的合题产生之后,它的反题也随之产生,这样就又产生了新的矛盾,又要有新的冲突和升华,再产生新的合题。变化到最后是什么呢?是黑格尔的终极真理,黑格尔给它起个名字,叫做“绝对精神”。黑格尔说,之前的哲学家错就错在认为人的理性世界和客观世界是对立的、矛盾的两个事物。理性经过不断的辩证,就可以完全符合客观世界的真实面貌。理性就是世界的本质,世界的本质就是理性。所以说,宇宙的本质是精神,而且是一种理性精神。这个理性精神,就是黑格尔的“绝对精神”。 因为重视历史过程,黑格尔是第一个重视研究哲学史的人。黑格尔的历史观后来被马克思批判性地继承,变成了辩证唯物主义历史观——马克思也认为,历史的进程是有方向的,不可逆转、不可阻止的,但是可以预测的。马克思预测历史通向的是共产主义,黑格尔的历史通向绝对精神。 叔本华和生命意志黑格尔是形而上学的巅峰,他创造了一个史上最完备、最庞大的形而上学世界。然而,这时候突然有一个人站出来对黑格尔破口大骂,而且骂得超级狠。他说黑格尔是“一个平庸、无知、愚蠢、令人讨厌恶心的江湖骗子”。这人叫叔本华,他崇拜康德并且鄙视黑格尔。 到了 30 岁的时候,叔本华最重要的著作《作为意志和表象的世界》出版了。又过了一年,叔本华得到了一个难得的机会,他被柏林大学聘用(没薪水)。叔本华终于可以和自己的宿敌黑格尔当面对决了,于是他要求校方把自己的课和黑格尔的课安排在同一个时间。那时候黑格尔的学生不算多,一堂课也就只有三百个人。那么叔本华的课上有多少人呢?基本不超过五个。只干了半年叔本华就崩溃辞职了。 56 岁的时候,《作为意志和表象的世界》第二版出了,结果只卖了不到三百本。等到他 63 岁的时候,他出了一本《附录与补遗》。这本书以格言体写成——仿佛我们今天的人生小感悟.不过,还是人生小感悟的力量比较大。过了一段时间,这本《附录与补遗》终于让人们接受了叔本华。 叔本华骂黑格尔,自然也不会喜欢黑格尔的哲学。叔本华是康德思想的继承者。前面讲康德时那个“蓝色眼镜”的例子,可能会让很多人认为物自体是有很多个的,而且和表现出的事物一一对应。比如桌子有一个它对应的物自体,“我”也有一个“我”对应的物自体。叔本华说,这是不对的。因为我们在区别两个事物的时候,离不开空间概念。比如两个东西形状不同,摆放的位置不同,等等。可是物自体不具备空间属性啊,所以我们不可能把物自体区分成一个一个不同的样子。叔本华认为,万物的物自体是统一的,只有一个。这个物自体,叔本华给它起了个名字,叫作“生命意志”。 那么生命意志是个什么东西呢? 简单地说,是一股永不停歇的力量。这股力量驱使着万物去运动,去发展。比如人和动物的食欲性欲,比如植物破土而出的欲望。举例子说,我们以为自己生活、恋爱、结婚、工作是根据我们的理性选择的。而叔本华认为,真正驱动你的都是种种欲望:生殖的欲望、享乐的欲望、征服的欲望,等等。你以为你在靠理性生活,实际上躲在理性背后的是生命意志,生命意志在驱动你作出种种选择。 说到这里,你可能会有疑惑:叔本华的理论有什么意思呢?他不就是把康德的物自体给解释成生命意志了吗?这和康德有多大差别呢? 就是这一点点小差别,导致叔本华和康德的世界观、人生观相差十万八千里。我们还记得,康德的形而上学,目的是干掉休谟的怀疑论。休谟说我们的理性根本无法认识这个世界的真相。康德点点头:休谟说得没有错,我们的确无法认识物自体,可是我们生活在表象的世界里。在这个表象世界里,一切都先经过先天认识形式的加工。而先天认识形式我又用理性给分析得清清楚楚了,那表象世界还是被理性统治的呀。所以在康德看来,理性就是我们这个世界的统治者。没错,理性确实管不了物自体,但是物自体也不影响我们的世界呀。叔本华说,不,物自体能影响我们的世界。不仅能影响,而且影响力超大,我们用理智控制不了。在康德那里,这个世界的基础是井井有条的理性。在叔本华这里,这个世界的基础是无法控制的生命意志。 因此康德对世界的看法是乐观的。叔本华对世界的看法是悲观的。叔本华认为,欲望本质上是痛苦之源。因为满足不了欲望,人会痛苦。满足了欲望,人又会产生新的、更高的欲望,还是会痛苦。如果人满足了全部的欲望,而且没产生新的欲望,人会幸福吗?不会,人会感到空虚和无聊,这也是痛苦。所以快乐只是暂时的,痛苦才是永恒的。人生就好像在痛苦和无聊之间不停摆动的钟摆。叔本华认为,应当增强自己克制欲望的意志力。我们可以提高自己对这世界的认识(当然是去认识叔本华所理解的那个世界),增强我们的理性,用理性抑制和控制感性冲动。然后,把自己的感情和欲望上升为全人类的感情和欲望,这样就可以尽量消除个人的欲望。接下来,我们要强迫自己不去做想做的事,反而去做不想做的事,抛弃一切现实的理想。像苦行僧一样地修行,通过苦行来抑制生命意志。不反对别人损害自己,欣然接受任何损失,把这当作考验自己战胜生命意志的机会。最终欣然接受死亡。当我们实现了这一目的后,就可以达到物我两忘的境界,就算降服生命意志了。除了苦修禁欲之外,叔本华还给出了另一个方法:欣赏艺术。他认为,人在欣赏真正的艺术的时候,内心是非功利、不带欲望的,也就脱离了生命意志的控制。因为最伟大的艺术家都是在努力审视自己的内心,表达自己内心深处最真实的感受。而且艺术是感性的、非理性的。这就是说,最伟大的艺术品反映的是“生命意志”的真相。在所有的艺术中,叔本华最推崇音乐。 在宗教方面,叔本华认为,基督教教义中的原罪就是生命意志,基督教鼓励的赎罪精神就是要求人们去克服生命意志。所以基督教比其他宗教在欧洲更受欢迎,因为它认识到了生命意志的悲观主义精神,而其他宗教都是乐观主义的。佛教比基督教更重视禁欲。基督教在某种程度上并不禁欲,比如鼓励人多生养,鼓励人勤奋工作。佛教不同,佛教认为欲望是痛苦的来源,主张彻底摒弃一切欲望。这和叔本华的观点很像。这不是巧合,叔本华的哲学观点深受印度佛教的影响。据说他的书桌上经常摆放的是一尊康德像和一尊佛像。 尼采和权力意志对于哲学史来说,叔本华最大的价值不在于悲观主义的世界观,而在于他暗示了一个巨大的危机。理性没落的危机。 之前的哲学家们为什么都要坚持理性呢?理由很简单,理性是知识最好的载体。哲学是求“真”的。这个“真”是一个逻辑判断,是一个理性概念,离开了理性,就无所谓“真”不“真”。光谈“感觉很棒”,那样无法经受苏格拉底的怀疑,那我们也就没有必要再研究哲学了,当叔本华认为生命意志高过了理性的时候,他等于告诉人类,理性的能力实在太小了,理性既不能揭露世界的本质,也难以对抗本能的欲望。理性这东西实在没用。 叔本华去世五年后,他的思想随着他的作品遍布欧洲大陆。在德国莱比锡市的一家旧书店里,一个青年鬼使神差地拿起了一本《作为意志和表象的世界》。这个年轻人就是尼采。这时他才 21 岁.毫无疑问,在哲学上,尼采是站在叔本华那边儿的。尼采是一个充满激情的人,他喜欢音乐,喜欢爬山,崇拜音乐家瓦格纳,他大喊“上帝死了”,还自比是太阳.但富于激情的尼采基本上度过的是悲剧的一生。他一生不被人理解,著作无人问津。他最重要的著作《查拉图斯特拉如是说》印完之后,别说卖了,送也只送出去七本。在他写作生涯的最后三年,他前后花了五百个银币出版自己的著作,没拿到半分稿费。讽刺的是,在尼采疯了以后,财富和荣誉接踵而来。他出了名,人们像对待圣人一样崇拜他,王公贵族争相拜见,就好像只要看一眼这位已经失去了神志的可怜人,就可以沾上一点哲人的仙气一样。 在尼采发疯以后,他妹妹利用整理尼采著作的权利,任意增删、篡改尼采的作品及信函,编成了适合她自己口味的《权力意志》。因为她还撰写和尼采有关的著作,她还两次被提名诺贝尔文学奖。尼采的妹妹把尼采的学说变成了种族主义、国家主义的武器,最终成了纳粹主义的一部分.因为尼采妹妹的这些行为,尼采的学说成了法西斯国家官方鼓吹的哲学家。第二次世界大战结束,尼采的名声也随之一落千丈,直到后来才慢慢恢复。 尼采继承了叔本华的形而上学。叔本华说物自体是“生命意志”,尼采给改造成了“权力意志”。“权力意志”一词中的“权力”容易引起误解。这并不是政治权力的意思,而是指要让自己变得更强大、更强壮、更富创造力的欲望。尼采把人分成了强者和弱者。强者体现了权力意志,他们的特征是积极向上、勇于进取、勇于牺牲、善于创造。弱者相反,特点是胆小、保守、善妒、虚伪。他认为,同情弱者没错。但弱者不能以此为理由,去要挟、榨取强者,去拖强者的后腿,这样做是可耻的。他谈的第一种道德是属于弱者的道德,尼采叫它奴隶道德(又叫“畜群道德”)。表面的内容是同情、仁慈、谦卑、平等。其实本质上,是弱者为了掩盖自己对强者的恐惧、嫉妒和自私,借助奴隶道德去限制强者。弱者对强者感到恐惧,因此奴隶道德强调“仁慈”“谦卑”,把强者和特立独行的人看作是危险人物,要求社会限制他们的能力。尼采说的第二种道德是强者的道德,它可以叫作贵族道德。这种道德鼓励人们积极进取,特立独行,崇尚强大,鄙视软弱,追求创新,拒绝平庸,它代表了生命积极的一面。尼采认为,奴隶道德和贵族道德最明显的区别在于:奴隶道德总是在禁止,不许人们做这做那;贵族道德则是在鼓励人们自由创造。 尼采的道德观不是会造成弱肉强食吗,不是会造成强者欺凌弱者吗?尼采的回答是,人的本性就是残忍的。尼采的道德观和基督教道德有明显的矛盾。在尼采生活的社会里,基督教道法就和咱们这里的儒教道法一样,是全社会广为接受的道德规范。《圣经》里说什么事情是善的,那全社会的人都不用多想,都认为这件事是善的。但尼采认为,基督教道德是典型的奴隶道德,本质是伪善的。基督教鼓励人们变得谦卑,其实就是鼓励人们做弱者。所以尼采大喊“上帝死了!”意思是,他想去掉上帝。如果没了上帝,人们也就不需要无条件地遵守基督教道德了。 尼采发现,大部分强者都被奴隶道德压抑着,不能摆脱弱者对他们的束缚。因此,尼采希望“超人”出现。“超人”这个词在尼采的理论里不是指拥有强大权力的人,而是指能够完全按照自己的意志行动、能充分发挥自己的创造力,并且能摆脱奴隶道德、不被弱者束缚的强者,简而言之,尼采推崇的是一种精英主义。 进化论对哲学的影响科学是坚持纯理性的。科学使用的是归纳法和演绎推理。所有的科学理论,都必须用理性的文字表达,都必须经得住严谨的实验。科学创造的奇迹,就是理性创造的奇迹。形而上学必须使用理性工具——否则无法进行苏格拉底式的怀疑。那么,形而上学的溃败,其实就只是理性的溃败。理性工具不好用了嘛。然而与此同时,科学正在用一个接一个的奇迹来捍卫理性的尊严。物理学,进化论和心理学的发展让人们对科学的自信心变得出奇的高。人们相信,只要假以时日,科学可以解决一切问题。就算是艺术、哲学那些过去被认为科学难以碰触的领域,将来运用心理学也可以解释了。 科学的发展给哲学带来了两个影响:第一个影响是把宗教完全打趴下了。第二个影响是,随着科学的触角越来越广,机械论和决定论必然重新抬头。唯物主义出现了升级版辩证唯物主义。不过在讲这个我们很熟悉的理论之前,我们必须先讲一讲进化论。它对于重新认识科学和哲学,乃至我们自己,都有很大的意义。 进化论的关键内容有这么几条:第一,生物的基因信息可以遗传给下一代:第二,在遗传的时候,基因会发生不可控制的随机变异;第三,整个生物种群都面临着巨大的生存压力,每一代新生物的数量却大于自然资源能够供养的数量,因此每一代新生物中的大部分都会死掉。第四,生物后天的变化在大部分情况下不能改变基因。生物进化的过程是这样子的:因为每次遗传都会产生一系列变异,因此每一代新生物都会有一些个体的生理特征不同于父母辈。或者说,总有些个体长得“怪”。又因为生存压力特别大,每一代里的大部分都会死掉,因此假如这些长得“怪”的地方正好能适应当时的环境,那么拥有这些“怪”基因的生物就有更大的概率存活下来,这些“怪”基因也会保留下来,从而成为这个生物基因中的一部分,生物就完成了一次微小的“进化”。进化论仅仅阐述了一套基因变化的规律,这中间并没有任何道德含义。而且正是进化论把神创说从生物界赶走了,才把生物学中的道德元素降到最低的程度。 进化论对哲学有什么影响呢? 第一个影响,是严重打击了基督教的权威。第二个影响,是进一步消除了人类的神圣性,并且可以根据进化论产生一些哲学推论.推论一,叔本华说生物有生命意志,其中生殖是最重要的。生物的一切行为都是为了生殖。这个观点正好符合进化论。进化论也认为,为了能将基因保留下来,一切都应该以生殖为目的。推论二,尼采说权力意志,说每个人都想成为强者,这也在一定程度上符合进化论。 因为生物在进化中必须变得越来越强,才能不断增强种族延续的可能。 相对论与量子力学对哲学的影响我们来简单谈一下爱因斯坦的相对论. 首先说狭义相对论,我们看看两个最直观的结论。第一,光速是永恒不变的。我们在前进的自行车上打手电筒发出的光速,和我们站着不动打手电筒的光速一样。任何物体的移动速度都不能超过光速。第二,说一个宇宙飞船接近光速,飞船之外的人去看这个飞船,会发现飞船的时间变慢了,长度也缩短了。然而飞船内部的人却没有感觉。准确点说是这样,相对论说的是,两个运动状态不同的观测者,在看同一个物体的时候,他们看到的这个物体的时间、长短、质量都是不同的。在牛顿时代(也是咱们普通人的概念),时间和空间都是独立的,互相没有关系。就像“5 分钟”和“3 厘米”根本没法放在一起计算一样。但是狭义相对论认为,时间和空间不是互相独立的,可以互相影响,不同运动状态的人观察同一个物体,观测到的时间、大小都不相同。因此时间和空间得放在一起研究,统称为时空。质量和能量也不是互相独立的,统称为质能。这也是核武器的理论基础。 而广义相对论,它解释的是万有引力。在相对论之前人们已经知道了万有引力的存在,但是不知道引力是如何产生的。万有引力能够让两个星球相隔万里还有相互作用“力,这点连牛顿都不太说得明白。广义相对论的意思是说,当空间中存在物质和能量的时候,空间就会受到影响而弯曲,质能越大,空间弯曲得越厉害。引力就是这种空间弯曲产生的。有一个非常形象的比喻。好比我们的空间是一张抻平的床单,当我们往上放一个木球的时候,床单会被压下去,那么木球周围其他更轻的小球就会滚向木球,看上去就好像小球被木球吸引了一样。假如放的是铅球呢,床单会被压得更严重,造成的空间扭曲更大,引力也就更大。 相对论对于哲学的意义在于,这进一步打击了人们对先验理性的信心。当年的理性主义者、形而上学家们自信满满地追求绝对真理、先验理性,此时看来,他们自信的真理就不一定是绝对的了。相对论还有一个衍生的结论:我们对整个宇宙的认识有很大的局限性。 不止相对论,物理学家们在研究量子的时候发现了一个奇怪的现象。物理学家观测一个电子,越是精确地确定其位置,就越无法确定它的动量;越是想更精确地测定它的动量,就越测量不到它的位置。这并不是因为科学家的观测技术不行,而是由严格的理论决定的。这个规律叫作“海森堡测不准原理”或者“海森堡不确定性原理”。类似的怪事,还有电子的“波粒二象性”。从传统意义上说,电子不可能既是波又是粒子。然而科学家在实验中发现,电子既能显示波的特性,又能显示粒子的特性,关键看科学家们用什么方法去检测它。用一种方式观测就是波,用另一种方式观测就成了粒子了。科学家们对于一个电子的运动状态只能预测出一个概率,只能说大约、可能在哪儿。物理学成了一门缺乏确定性的学说。爱因斯坦有一句名言:“上帝不掷骰子。”意思是说,世界不可能真正是随机的,一切都是确定的。然而,这回是爱因斯坦错了。这意味着,人类对世界的认识能力又受到了进一步的限制,而且只要量子力学不被推翻,这限制就永远无法超越。 但量子力学彻底打败机械论和决定论。这对于坚持人有自由意志的学者来说是一件好事。 罗素与逻辑实证主义时间进入 20 世纪,理性还没有被打倒,还有很多哲学家在为理性的尊严而奋斗。这次冲到前面的,是罗素老师。罗素高寿,活了 98 岁。他出生的时候是中国同治十一年,颐和园还没开始建造。他去世的时候是 1970 年,互联网已经诞生了。 罗素和怀特海花了十年时间,合写过一本数学原理.虽然销量不好,但是罗素在写作《数学原理》的时候受到了启发。他想,既然能用逻辑来建立整个数学大厦,那么,是不是也能用类似的方法建立整个哲学大厦呢?严谨的哲学研究要靠理性思维。那么,什么样的语言能够最严谨地表达理性思维呢?那就是严格符合逻辑的语言。更准确地说,是逻辑符号。就好比我们在数学题中写的那些数学符号,它们是最严谨、最符合逻辑的。 逻辑实证主义者发现,过去的哲学家们不是很重视语言的严谨性,他们经常使用大白话来表达自己的哲学思想。哲学家们常自己提出一些特定的术语——就像我们前面说过的“生命意志”“权力意志”——他们又不给这些术语下特别严谨的定义,只是用粗浅的大白话解释一番,很容易引起读者的误解。“逻辑强调的是演绎推理,即从一个真的前提,推理出一个真的结论。可是,光有演绎推理的话,那得出的只能是重复的命题,得不出新的知识来。要扩展知识,我们要从经验中吸收。“实证”的意思就是说,逻辑实证主义得出的新结论,必须能有经验实实在在地证明。 逻辑实证主义的理想很好,要坚持绝对的理性、绝对的正确,可是最后发现,这个绝对的理性却得不到任何有意义的结论,连一个普遍的理论都得不出来。 实用主义实用主义和逻辑实证主义基本上是同一个时期的学说。他们遇到了和逻辑实证主义一样的问题:科学在飞速发展,然而哲学却一直在原地打转,这问题到底出在哪里? 实用主义和逻辑实证主义的思路不一样,逻辑实证主义看到的是科学的严谨性,希望哲学也能和科学一样严谨。实用主义则看重科学的实用性,看到科学家没哲学家那么多废话,在科学研究中什么理论好用就相信什么。实用主义者觉得,哲学也得像科学这样,不再说空话,不再讨论空泛的大问题,而是重视哲学的实用性。 实用主义在美国很受欢迎,实用主义哲学家也大多是美国人。有人说,这是因为实用主义正好契合了美国人的务实精神——这是好听的说法,难听的说法是美国人世俗功利。比如美国的司法采用判例法。意思是,过去类似的案子是怎么判的,这回的案子就参考着判。或许有人认为这过于儿戏了,难道国家制定的法律不是最大的吗?但判例法认为,一次性制订的司法是很难完善的。那么我们就通过每一次的审判,来不断纠正、完善国家的法律。你看,这不正好和实用主义者的真理观吻合吗?一些有社会主义倾向的政党在西方国家兴起。他们不搞武装革命,也不想消除阶级差别,而是搞工会,搞社会福利。不像马克思那样试图从根本上解决问题,而是从小地方一点一点改良,遇到什么问题就解决什么问题。比如资本家对工人压榨得太厉害了,国家就制定法律保护工人。垄断企业影响市场竞争,就制定反垄断法律限制垄断企业。工人购买力下降,就设置最低工资,增加社会福利。实际上,马克思当年为了维护工人阶级利益提出的很多要求,大部分都被资本主义国家接受并且实现了。如今这种改良式的资本主义在西方颇受欢迎. 波普尔和证伪主义我们大都了解一点弗洛伊德对梦的分析,这里的问题是,无论患者梦见了什么,医生都会进行解释,都会说这符合弗洛伊德的理论。换句话说,无论患者出现任何情况,弗洛伊德都不可能是错的。这样的理论,的确不会和现实产生任何矛盾,但是,能说它是现实的真实反映吗? 波普尔看出了其中的问题,提出了一个检验科学理论的重要标准:证伪。什么是科学理论,什么不是?其中关键的标准,是看这个理论有没有可以被证伪的可能。具体来说:科学理论必须能提出一个可供证伪的事实,假如这个事实一经验证,便承认该理论是错的。如果暂时没有人能证明它是错的,那它暂时就是真的。换句话说,所有的科学理论都是一种假说,科学家没有办法证实任何一种科学理论。但是科学理论可以给别人提供验错的机会。在没被检验出错误之前,我们就姑且相信这个科学理论是正确的。“在现实生活里,这个标准可以很方便地把巫术、迷信和科学区分开。 证伪主义对社会哲学也有影响。现在,世界大部分国家的刑事司法都接受“无罪推定”原则。意思是说,假如没有足够的证据证明一个人是犯罪嫌疑人,那么就应认为他是无罪的。 波普尔还根据证伪主义提出了自己的政治观。 有一种社会观念,认为历史的发展轨迹是必然的,这种观念叫作“历史主义”,黑格尔和马克思都持这样的观点。波普尔不同意这样的看法。没有永恒不变的真理,所有的理论都可能是错的。所以,也就不存在什么“历史的必然规律”。而且科学理论未来的发展方向也是难以预测的。就比如在牛顿时代,没人能够预测相对论的出现,也没人能预测牛顿理论将会在哪里出问题。因此,预测未来的历史规律,一劳永逸地设计一种绝对正确的政治制度,也是不可能的。波普尔因此主张应当建立“开放社会”,要求执政者能够广泛接受意见,赋予大众质疑政策的权利。因为执政理论和科学理论一样,永远都可能是错的。必须要不断地接受证伪,才能保证理论的正确。证伪主义的政治观,最关心的不是谁制定的政策,而是无论谁制定的政策,都不能成为绝对真理。不管是美国总统下的命令还是全世界人民投票的结果,都要给别人留出修改、推翻它的机会。 那证伪主义是怎么终结形而上学的呢?演绎推理的规则是,从一个绝对为真的命题出发,推出一个绝对为真的结论。只有这样,才能保证每一个结论都是正确的。但是证伪主义反对的,恰恰就是绝对为真的命题——因为绝对为真的命题是不可证伪的!是毫无意义的,它和巫术、宗教理论都是处于一个地位,毫无讨论的必要. 人生的意义形而上学走不通,形而上学的问题都没有答案。 形而上学的任务,是用理性思维去研究世界本质等“大问题”。形而上学走不通,也就是说,理性不可能回答世界的本质是什么,有没有终极真理,终极真理是什么,人生的意义是什么等大问题。实际上,所有的形而上学都会陷入无法证明自身的困境。既然形而上学的问题都没有答案,那么就意味着我们不知道人类的一切知识是否可靠,这个世界就没有了终极真理,没有了本质,终极问题没有答案,最聪明的人们追求到最后,不约而同地发现这是一条绝路。 那么接下来该怎么办呢?该怎么回答“人生意义是什么”的问题呢? 首先说一下我对这个世界的看法:这个世界的本质是什么呢?什么知识是真实可信的呢?对于客观经验领域,也就是对于我们能看得见、摸得着的物质世界,最好的研究方法是“基于经验主义和实用主义的、可证伪的理论”。说白了,就是科学。作为现代人,拒斥科学方法和科学成果是不可能的。 对于“世界的本质到底是什么”的问题,这里没有标准答案,愿意相信什么都可以。也可以这么说:世界的本质就是我的信念。我相信世界的本质是什么,它就是什么。 那么,“人生的意义是什么”该怎么回答呢?这也是一个信仰问题。没有固定答案的世界才更美好. 那么,该如何找到自己的人生意义?我认为最有效的办法,是逼迫自己直面死亡。我们问人生的意义是什么,其实就是在给自己的人生找一个目标,就是在问:我为什么活着?这也就等于在问:我为什么不立刻自杀?假如你能顺利地回答“我为什么不自杀”的问题,如“我不想死,是因为我还想到处旅游,吃好吃的,我不想死是因为我不能让父母伤心”。那么,这些答案就是你现在的人生意义。假如你的回答是“我不觉得活着有什么意义,我只是怕死”,那就请你想象一下死亡来临时的感觉吧。一个无神论者在面临死亡的巨大恐惧的时候,有时,求生的本能会让头脑拼命地给自己寻找活下去的理由。这个理由,也就是每个人的人生意义。有些和死神擦肩而过的人说,经过这一场磨难,自己大彻大悟,对人生有了更高层次的看法了。可是,我们每个人都知道自己早晚会死,为什么非要死到临头的时候才会大彻大悟呢?那就是因为绝大部分人平时从不愿意直面死亡,潜意识里认为自己可以永远逃避死亡。所以只有死到临头,才会开始反省人生。我们既然知道了这个道理,那就不妨早一点直面死亡,早一点把这件事想明白。以人类现有的经验而言,死亡是宇宙中少有的一个不可逆的事情。人死以后,再也回不到原来的生活中,想后悔也来不及了。如果死亡确实是意识的永远终结,那么就意味着我们失去了一切探索世界的机会。我们甚至可以感性地说,那就意味着我们自愿放弃了这世界给我们最大的恩赐,而这恩赐就只有这一次,放弃了就没有了。","categories":[{"name":"读书","slug":"读书","permalink":"http://yoursite.com/categories/读书/"}],"tags":[{"name":"哲学史","slug":"哲学史","permalink":"http://yoursite.com/tags/哲学史/"}]},{"title":"读<<穷爸爸富爸爸>>","slug":"读<<穷爸爸富爸爸>>","date":"2021-10-19T03:47:30.000Z","updated":"2021-08-31T03:51:19.126Z","comments":true,"path":"2021/10/19/读<<穷爸爸富爸爸>>/","link":"","permalink":"http://yoursite.com/2021/10/19/读<<穷爸爸富爸爸>>/","excerpt":"","text":"读<<穷爸爸富爸爸>>第一课 富人不为钱工作生活推着我们所有的人,有些人放弃了,有些人在抗争。少数人学会了这门课程,取得了进步,他们欢迎生活来推动他们,对他们来说,这种推动意味着他们需要并愿意去学习一些东西。他们学习,然后取得进步。但大多数人放弃了,还有一部分人像你一样在抗争。 有些人因为他们和他们的家庭需要钱而接受这份工资。但他们所做的也只是等待,等待加薪,因为他们认为更多的钱能解决问题。大部分人接受这样的工资,还有一些人会再找一份工作,仍旧干得很努力,但仍只能得到很少的报酬。 正是出于恐惧的心理,人们才想找一份安稳的工作。这些恐惧有:害怕付不起账单,害怕被解雇,害怕没有足够的钱,害怕重新开始。为了寻求保障,他们会学习某种专业,或是做生意,拼命为钱而工作。大多数人成了钱的奴隶,然后就把怒气发泄在他们老板身上。 大多数人都希望有一份工资收入,因为他们都有恐惧和贪婪之心。一开始,没钱的恐惧会促使他们努力工作,得到报酬后,贪婪或欲望又让他们想拥有所有用钱能买到的好东西。于是就形成了一种模式。起床,上班,付账,再起床,再上班,再付账他们的生活从此被这两种感觉所控制:恐惧和贪婪。给他们更多的钱,他们就会以更高的开支重复这种循环。 当你长大后,你想要的玩具会更贵,会变成要让你的朋友羡慕的汽车、游艇和大房子,恐惧把你推出门外,欲望又开始召唤你,诱惑你去触礁。这就是陷阱。钱就是驴子面前的胡萝卜,是幻象。如果驴子能了解到全部事实,它可能会重新想想是否还要去追求胡萝卜。 你们越快忘记你们的工资,你们未来的生活就会越轻松,继续用你们的头脑思考,不求回报地工作,很快就会发现比拿工资更挣钱的方法。你们会看到别人看不见的东西。机会就摆在人们面前,但大多数人从来看不到这些机会,因为他们忙着追求金钱和安定,所以只能得到这些。如果你们能看到一个机会,就注定你们会在一生中不断地发现机会。 第二课:为什么要学习财务知识我想有太多人过多地关注钱,而不是关注他们最大的财富——所受的教育。如果人们能灵活一些,保持开放的头脑不断学习,他们将在时代的变化中一天天地富有起来。如果人们认为钱能解决一切问题,恐怕他们的日子就不会太好过。只有知识才能解决问题并创造财富,那些不是靠财务知识挣来的钱也不会长久。从长远来看,重要的不是你挣了多少钱,而是你能留下多少钱,以及能够留住多久。 你必须明白资产和负债的区别,并且购买资产。这就是第一条规则,也是唯一一条规则。 资产是能把钱放进你口袋里的东西。负债是把钱从你口袋里取走的东西。富爸爸和穷爸爸在对待房子问题上的不同观念,一个认为他的房子是资产,另一个则认为是负债。拥有房子后带来的附属支出,房子越大支出就越大,现金就会通过支出不断地流出。我很清楚那不是资产,而是负债,因为它把钱从我们口袋中掏走了。 对于房子,我要指出大多数人一生都在为一所他们从未真正拥有的房子而辛苦地工作。 即使人们住房按揭贷款的利息是免税的,他们还是要先还清各期贷款后,才能以税后收入支付各种开支。 财产税。 房子的价值并不总是在上升。 最大的损失是致富机会的损失。如果你所有的钱都投在了房子上,你就不得不努力工作,因为你的现金正不断地从支出项流出,而不是流入资产项,这是典型的中产阶级现金流模式。 失去了用其他资产增值的时机。 本可以用来投资的资本将用于支付房子高额的维修和保养费用。 失去受教育的机会。 为什么富人会越来越富。资产项产生的收入足以弥补支出,还可以用剩余的收入对资产项进行再投资。资产项不断增长,相应的收入也会越来越多。弄清资产与负债的区别,一旦你明白了这种区别,你就会竭尽全力只买入能带来收入的资产,这是你走上致富之路的最好办法。坚持下去,你的资产就会不断增加。同时还要注意降低负债和支出,这也会让你有更多的钱投入资产项。很快,你就会有钱来考虑进行一些投资了,这些投资能给你带来100%,甚至是无限的回报。 第三课:关注自己的事业存在财务问题的人经常耗费一生为别人工作,其中许多人在他们不能工作时就变得一无所有。请注意,你的职业和你的事业有很大的差别。我经常问人们:你的事业是什么?他们会说:我在银行工作。接着我问他们是否拥有一家银行,他们通常回答:不是的,我只在那儿工作。 对成年人而言,把支出保持在低水平、减少借款并勤劳地工作会帮你打下一个稳固的资产基础。 富人与穷人一个重要的区别就是:富人最后才买奢侈品,而穷人和中产阶级会先买下诸如大房子、珠宝、皮衣、宝石、游艇等奢侈品,因为他们想让自己看上去很富有。真正的奢侈品是对投资和积累真正资产的奖励。例如,当我通过房地产生意获得了额外的收入时,去买了辆奔驰汽车。这不是因为增加工作量或是冒着风险才买下的,是在房地产生意上的收益支付了这辆车。 第四课:税收的历史和公司的力量事实上富人并未被征税,是中产阶级尤其是受过良好教育的高收入的中产阶级在为穷人支付税金。税收的初衷是惩罚有钱人,而现实却是它惩罚了对它投赞同票的中产阶级和穷人。政府对钱的胃口越来越大,以致中产阶级也要被征税,且税收的范围不断向穷人扩展。 财商是由4个方面的专门知识构成的:第一是会计,也就是我说的财务知识。第二是投资,我把它称为钱生钱的科学。第三是了解市场,它是供给与需求的科学。这要求了解受感情驱动的市场的技术面。第四是法律。例如:了解减税优惠政策和公司法的人会比雇员和小业主更快致富。 第五课:富人的投资我意识到过分的害怕和自我怀疑是毁掉我们才能的最大因素。看到有些人明明知道该做什么,却缺乏勇气付诸实际,我就感到十分悲哀。在现实生活中,人们往往是依靠勇气而不是智慧去取得领先的位置的。 300年前,土地是一种财富,所以,谁拥有土地,谁就拥有财富。后来,美国依靠工厂和工业产品上升为世界头号强国,工业家占有了财富。今天,信息便是财富。问题是,信息以光一样的速度在全世界迅速传播,这种新的财富不再像土地和工厂那样具有明确的范围和界限。变化会越来越快,越来越显著,百万富翁的人数会极大地增加,同样,也会有许多人被远远地抛在后面。 如果你清楚自己在做什么,就不是在赌博;如果你把钱投进一笔交易然后只是祈祷,才是在赌博。在任何一项投资中,成功的办法都是运用你的技术知识、智慧以及对于这个游戏的热爱来减少意外、降低风险。当然,风险总是存在的,但你的财商可以提高你应付意外的能力。常常有这样的情况,对一个人来说是高风险的事情,对另一个人来说则可能是低风险的。这就是我不断鼓励人们多关注财商教育而不只是投资股票、房地产或其他市场的原因。你越精明,就越能应付意外情况。 第六课:学会不为钱工作一位药品贸易的商务顾问曾经告诉我,有许多医生、牙医和按摩师在财务上困难重重。以前我总是认为他们一毕业,美元就会滚滚而来。这位商务顾问还跟我说了一句话:他们只有一项技能,所以挣不到大钱。这句话的意思是说,大部分人需要学习和掌握不止一项技能,只有这样他们的收入才能获得显著增长。以前我提到过,财商是会计、投资、市场和法律等各方面知识和能力的综合。将上述4种技能结合起来,以钱生钱就会容易得多。当涉及钱的时候,只有一项技能的人不得不努力工作。 一种可怕的管理理论是这样说的:工人付出最大努力以免于被解雇,而雇主提供最低工资以防止工人辞职。如果你看一看大部分公司的工资支付额度,就会明白这一说法确实在某种程度上道出了真相。最终的结果是大部分人从不敢越雷池一步,他们按照别人教他们的那样去做:找一份稳定的工作。 克服困难掌握财务知识的人有时候还是不能积累丰厚的资产项,其主要原因有5个: 对可能亏钱的 恐惧心理。 如果你讨厌冒险,担心会亏钱,就早点动手积累资产吧。如果你有致富的愿望,就必须集中精力。把你大部分的鸡蛋放在较少的篮子里,别像穷人和中产阶级那样:把很少的鸡蛋放在许多篮子里。 愤世嫉俗。 我认为疑虑和愤世嫉俗的心态使大多数人安于贫困。生活等着你去致富,可就是这些疑虑使人们无法摆脱贫穷。未经证实的怀疑和恐惧会使人们成为愤世嫉俗者。愤世者抱怨现实,而成功者分析现实。如果大多数人懂得股票市场上横盘(预定低点抛售)是什么意思的话,就会有更多的人为了赢利而投资,而不是为了避免损失而投资。横盘好比一个计算机指令,当股价开始下跌时自动卖出股票,帮助你将损失最小化、收益最大化。对于那些害怕受到损失的人来说,这是一项极好的工具。 懒惰。 我可付不起带来的悲哀和无助感会使人们失望、迟钝以至意志消沉。我怎样才能付得起则打开了充满可能性的快乐和梦想之门。世界之所以发展是因为我们都渴望生活得更好,新发明的诞生是因为我们渴望更好的东西,我们努力学习也是因为我们想要了解更好的东西。因此,每当你发现自己在逃避你内心清楚应该去做的事情时,就应该问问自己:我还能得到什么?稍稍贪婪一点,这是治愈懒惰的灵丹妙药。 不良习惯。 自负。 开始行动我建议你采取以下步骤来开发上帝赐予你的才能,这种才能只有你才可以控制: 你需要一个超现实的理由 每天作出自己的选择 从理财的角度来说,我们每挣到一美元,就得到了一次选择自己是成为富人、穷人还是中产阶级的机会。我们花钱的习惯反映了我们是什么样的人,穷人之所以贫穷是因为他们有着不良的消费习惯。 慎重地选择朋友 首先,我不会把理财状况作为挑选朋友的标准。我既有穷困潦倒的朋友,也有每年都有数百万美元进账的朋友,因为我相信三人行,必有我师,我愿意努力向他们学习。不要听贫穷的或是胆小的人的话。他们总会告诉你一件事为什么不可行。在积累财富的过程中,最困难的事情莫过于坚持自己的选择而不盲目从众。因为在竞争激烈的市场上,群体往往会反应迟钝,成为被宰割的对象。 掌握一种模式,然后再学习一种新的模式——快速学习的力量。 如果你对自己所做的工作感到厌倦或是你挣的钱不够多,那么很简单,改变你的挣钱模式吧。在今天这个快速变化的社会中,你学到的东西再多都不算多,因为当你学到时往往就已经过时了。问题在于你学得有多快,这种技能是无价之宝。如果你想赚钱,寻找一条捷径是非常关键的。 首先支付自己——自律的力量。 如果你控制不了自己,就别想着致富。能否自律是将富人、穷人和中产阶级区分开来的首要因素。不要背上数额过大的债务包袱。要保持低支出。首先增加自己的资产,然后,再用资产项产生的现金流来消费.储蓄只能用于创造更多的收入,而不是用来支付账单。","categories":[{"name":"读书","slug":"读书","permalink":"http://yoursite.com/categories/读书/"}],"tags":[{"name":"读书","slug":"读书","permalink":"http://yoursite.com/tags/读书/"}]},{"title":"Webpack 配置指南","slug":"Webpack配置指南","date":"2021-09-08T02:52:36.000Z","updated":"2022-03-23T09:58:04.073Z","comments":true,"path":"2021/09/08/Webpack配置指南/","link":"","permalink":"http://yoursite.com/2021/09/08/Webpack配置指南/","excerpt":"","text":"webpack 配置指南 搭建环境 123npm init -y # 生成 package.jsonnpm i webpack --save-dev # 引入 webpacknpm i webpack-cli --save-dev # 引入 webpack-cli 123\"scripts\": { \"build\": \"webpack\",}, webpack 旧版本必须在 webpack.config.js 通过 entry 属性定义入口,现在默认为./src/index.js 新建./src/index.js文件.执行命令后,dist/main.js为默认输出. 生产和开发模式webpack 分为 ‘development’ 或 ‘production’ 或 ‘none’.production 可以开箱即用地进行各种优化。 包括压缩,作用域提升,tree-shaking 等development 针对速度进行了优化,仅仅提供了一种不压缩的 bundle. 1234\"scripts\": { \"build\": \"webpack --mode production\", \"dev\": \"webpack --mode development\"}, 默认覆盖 entry 和 outputwebpack 支持 ES6, CommonJS, AMD 规范 12345678910111213141516/** * webpack 支持 ES6、CommonJs 和 AMD 规范 */// ES6import sum from './vendor/sum'console.log('sum(1, 2) = ', sum(1, 2))// CommonJsvar minus = require('./vendor/minus')console.log('minus(1, 2) = ', minus(1, 2))// AMDrequire(['./vendor/multi'], function (multi) { console.log('multi(1, 2) = ', multi(1, 2))}) webpack.config.js 是 webpack 默认的配置文件名,在根目录下创建.we 123456789101112const path = require('path')module.exports = { entry: { app: './app.js', // 需要打包的文件入口 }, output: { publicPath: __dirname + '/dist/', // js 引用的路径或者 CDN 地址 path: path.resolve(__dirname, 'dist'), // 打包文件的输出目录 filename: 'bundle.js', // 打包后生产的 js 文件 },} path.resolve() 方法会把一个路径或路径片段的序列解析为一个绝对路径。__dirname: 当前模块的文件夹路径。 执行 build 后会生成多个 bundle 文件,这跟 AMD 的引入方式有关,在实际写代码的时候,最好使用 ES6 和 CommonJS 的规范来写. // CleanWebpackPlugin 插件可以自动删除 dist 旧文件在打包. 1npm install clean-webpack-plugin --save-dev 1234const { CleanWebpackPlugin } = require('clean-webpack-plugin')plugins: [ new CleanWebpackPlugin(), // 默认情况下,此插件将删除 webpack output.path目录中的所有文件,以及每次成功重建后所有未使用的 webpack 资产。] babel 转译 webpack 使用 loader 进行转译.babel-loader 可以将 es6 转译为 es5.我们使用 babel7 版本.@babel/core@babel/preset-env: 包含 ES6、7 等版本的语法转化规则@babel/plugin-transform-runtime: 可以重复使用 Babel 注入的程序代码来节省代码,减小体积.避免 polyfill 污染全局变量,减小打包体积@babel/polyfill: ES6 内置方法和函数转化垫片,所谓垫片也就是垫平不同浏览器或者不同环境下的差异babel-loader: 负责 ES6 语法转化 12npm i @babel/core babel-loader @babel/preset-env @babel/plugin-transform-runtime --save-devnpm i @babel/polyfill @babel/runtime 在项目根目录创建.babelrc配置 babel 1234{ \"presets\": [\"@babel/preset-env\"], \"plugins\": [\"@babel/plugin-transform-runtime\"]} webpack 配置 loader. 1234567891011module: { rules: [ { test: /\\.js$/, // 使用正则来匹配 js 文件 exclude: /node_modules/, // 排除依赖包文件夹 use: { loader: 'babel-loader', // 使用 babel-loader }, }, ]} 在入口文件处全局引入import @babel/polyfill,执行 build 命令打包.全局引入 @babel/polyfill 的这种方式可能会导入代码中不需要的 polyfill,从而使打包体积更大.更改 .babelrc,只转译我们使用到的. 1234567891011{ \"presets\": [ [ \"@babel/preset-env\", { \"useBuiltIns\": \"usage\" } ] ], \"plugins\": [\"@babel/plugin-transform-runtime\"]} 注释入口文件全局引入的import @babel/polyfill,执行 build 命令打包.打包体积明显变小. .browserslistrc用于在不同前端工具之间共享目标浏览器和 Node.js 版本的配置,或在 package 文件中加入 12345\"browserslist\": [ \"> 1%\", \"last 2 version\", \"not ie <= 8\"] 代码切割 code splitting 1npm i lodash 新建src/index.js并添加 lodash 相关代码,更改 webpack 配置 123456789entry: { main: './src/index.js'},output: { publicPath: __dirname + '/dist/', // js 引用的路径或者 CDN 地址 path: path.resolve(__dirname, 'dist'), // 打包文件的输出目录 filename: '[name].bundle.js', // 代码打包后的文件名 chunkFilename: '[name].js' // 代码拆分后的文件名}, 运行 build 打包我们会发现,业务代码和第三方框架一起被打包,业务代码更新频繁,而第三方框架不会变,这会造成资源浪费和低效率.浏览器是有缓存的,如果文件没变动的话,就不用再去发送 http 请求,而是直接从缓存中取,这样在刷新页面或者第二次进入的时候可以加快网页加载的速度。所以我们可以利用 webpack 的代码分割功能,将第三方代码与业务代码分开,这样及时业务变动,浏览器也可以读取第三方框架的缓存. webpack4 使用 内置的 splitChunksPlugins 进行代码分割. all 表示分割所有代码, async 为默认值,表示分割异步代码. 12345optimization: { splitChunks: { chunks: 'all' }}, 执行 build 命令,发现文件为dist/**.bundle.js dist/vendors-***.js,表示代码分割成功.对分割名称进行更改.添加 cacheGroups 对象. 1234567891011121314151617181920212223optimization: { splitChunks: { chunks: 'all', cacheGroups: { vendors: { name: 'vendors', chunks: 'all', // test 正则过滤 // priority 优先级 // minChunks 最小公用多少次才打包 // minSize 超过多少进行压缩 // reuseExistingChunk: true // 公共模块必开启,如果当前块已从主模块拆分出来,则将重用它而不是生成新的块 }, default: { chunks: 'all', minChunks: 2, priority: -20, reuseExistingChunk: true } } }} 懒加载,Prefetching 预加载 懒加载就是通过 import 去异步的加载一个模块,类似于 vue-router 中的懒加载component: () => import('***.vue').懒加载并不是 webpack 里的概念,而是 ES6 中的 import 语法,webpack 只是能够识别 import 语法,能进行代码分割而已。页面加载时,异步的代码不会执行但是确下载下来了,浪费了页面性能,我们希望 webpack 可以把异步代码放到一个模块中.当我们需要时,才会去加载这些代码.这也是为什么 webpack 的 splitChunks 中的 chunks 默认是 async,异步的. 问题又来了,如果异步的代码模块较大,而当我们需要加载时就要等待,体验不好.webpack 的 Prefetching/Preloading 预加载功能可以解决这个问题.它会在网络带宽空闲的时候预先帮我们加载. webpackPrefetch: 等待核心代码加载完之后,有空闲之后再去加载webpackPreload: 和核心的代码并行加载,不推荐 使用方式import(/* webpackPrefetch: true */, '......js').then... 自动生成 html 文件并自动引入相关资源 1npm i html-webpack-plugin html-loader --save-dev 更改 webpack 配置 123456789101112131415const HtmlWebpackPlugin = require('html-webpack-plugin')plugins: [ new HtmlWebpackPlugin({ // 打包输出HTML title: '自动生成 HTML', minify: { // 压缩 HTML 文件 removeComments: true, // 移除 HTML 中的注释 collapseWhitespace: true, // 删除空白符与换行符 minifyCSS: true, // 压缩内联 css }, filename: 'index.html', // 生成后的文件名 template: 'index.html', // 根据此模版生成 HTML 文件 }),] 由于使用了 title, 在 index.html 中加<title><%= htmlWebpackPlugin.options.title %></title>,执行命令,dist 中生成 index 的 html 文件.我们发现其中 js 引入为绝对路径,需要更改为相对路径.找到 output 输出配置,更改 publicPath 公共路径,修改为 ./ 处理 css 文件 这次我们需要用到 css-loader,style-loader 等 loader.css-loader:负责解析 CSS 代码,主要是为了处理 CSS 中的依赖,例如 @import 和 url()等引用外部文件的声明.style-loader 会将 css-loader 解析的结果转变成 JS 代码,运行时动态插入 style 标签来让 CSS 代码生效。 1npm i css-loader style-loader --save-dev 更改配置文件 123456rules: [ { test: /\\.css$/, // 针对 .css 后缀的文件设置 loader use: ['style-loader', 'css-loader'], },] 可以发现在 style 标签中出现了相关 css,如果要单独打包成文件,需要 mini-css-extract-plugin 插件 1npm i mini-css-extract-plugin --save-dev 1234567891011121314151617const MiniCssExtractPlugin = require('mini-css-extract-plugin')new MiniCssExtractPlugin({ filename: '[name].css', chunkFilename: '[id].css',})rules: [ { test: /\\.css$/, use: [ { loader: MiniCssExtractPlugin.loader, }, 'css-loader', ], },] 执行 build,生成了单独的 css 文件,但是并没有压缩,引入 optimize-css-assets-webpack-plugin 插件来实现 css 压缩.webpack5 使用 css-minimizer-webpack-plugin 插件. 1npm install css-minimizer-webpack-plugin --save-dev 更改配置 12345const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')optimization: { minimizer: [new CssMinimizerPlugin()]} 处理 scss 或 less. 1npm i node-sass sass-loader --save-dev 更改 loader: 12345678910{ test: /\\.(scss|css)$/, use: [ { loader: MiniCssExtractPlugin.loader }, 'css-loader', 'sass-loader' ] } 根据 webpack 规则:放在最后的 loader 首先被执行,从上往下写的话是下面先执行,从左往右写的话是右边先执行。 为 css 加上浏览器前缀. 1npm install postcss-loader autoprefixer --save-dev 新建 postcss.config.js ,并修改 loader 配置 1234567891011121314module.exports = { plugins: [require('autoprefixer')]}{ test: /\\.(scss|css)$/, use: [ { loader: MiniCssExtractPlugin.loader }, 'css-loader', 'postcss-loader' 'sass-loader' ] } Tree Shaking字面意思是摇树,项目中没有使用的代码会在打包的时候丢掉。JS 的 Tree Shaking 依赖的是 ES6 的模块系统 import 和 export.对于经常使用的第三方库,要安装 ES 写法的版本,比如 lodash-es webpack5 可以通过 package.json 的 “sideEffects”(意为副作用) 属性,通知 webpack 安全的删除未使用的 export.而 optimization.usedExports: true,依赖于 terser 去检测语句中的副作用. css tree shaking: 略. 图片字体资源的处理 1npm install url-loader file-loader --save-dev 更改配置 1234567891011121314151617181920212223242526272829rules: [ { test: /\\.(png|jpg|jpeg|gif)$/, use: [ { loader: 'url-loader', options: { name: '[name]-[hash:5].min.[ext]', outputPath: 'images/', //输出到 images 文件夹 limit: 20000, //把小于 20kb 的文件转成 Base64 的格式 }, }, ], }, { test: /\\.(eot|woff2?|ttf|svg)$/, use: [ { loader: 'url-loader', options: { name: '[name]-[hash:5].min.[ext]', limit: 5000, // fonts file size <= 5KB, use 'base64'; else, output svg file publicPath: 'fonts/', outputPath: 'fonts/', }, }, ], },] 处理第三方 js 库 12345678910resolve: { alias: { jQuery$: path.resolve(__dirname, 'src/vendor/jquery.min.js') }}, new webpack.ProvidePlugin({ $: 'jquery', // npm jQuery: 'jQuery' // 本地Js文件 }) webpack-dev-server 1npm i webpack-dev-server --save-dev 1234\"scripts\": { \"dev\": \"webpack-dev-server --open\", \"build\": \"webpack --mode production\" }, 配置 123456mode: 'development', // 开发模式devtool: 'source-map', // 开启调试devServer: { port: 8080, //...} DllPlugin 加快打包速度 新建 webpack.dll.js 文件, 1234567891011121314151617181920const path = require('path')module.exports = { mode: 'production', entry: { vendors: ['lodash', 'jquery'], }, output: { filename: '[name].dll.js', path: path.resolve(__dirname, 'dll'), library: 'dll_[name]', }, plugins: [ new webpack.DllPlugin({ name: 'dll_[name]', path: path.join(__dirname, 'dll', '[name].manifest.json'), context: __dirname, }), ],} json 文件中新增命令 1\"build:dll\": \"webpack --config ./build/webpack.dll.js\" 在 webpack 配置中添加 123456789101112131415161718192021222324252627282930const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin') // dll自动引入// dll配置const dllReference = (config) => { config.plugin('vendorDll').use(webpack.DllReferencePlugin, [ { context: __dirname, manifest: require('./dll/vendor.manifest.json'), }, ]) config .plugin('addAssetHtml') .use(AddAssetHtmlPlugin, [ [ { filepath: require.resolve( path.resolve(__dirname, 'dll/vendor.dll.js') ), outputPath: 'dll', publicPath: '/dll', }, ], ]) .after('html')}chainWebpack: (config) => { dllReference(config)} 多页面打包在 webpack.base.conf.js 中配置 entry,配置两个入口.然后多次调用 new HtmlWebpackPlugin({})插件.或写个函数自动生成配置. 其他 webpack-merge 用来合并 webpack 配置 workbox-webpack-plugin 用来配置 PWA ts-loader 以及 ts.config.js 用来配置 ts eslint-loader 以及 .eslintrc.js 编写 loader 新建一个 js 文件 123module.exports = function (source) { return source.replace('world', this.query.name)} source 参数就是我们的源代码,这里是将源码中的 world 替换成我们自定义的 字符串. 修改 webpack 配置添加自定义 loader. 12345678910111213rules: [ { test: /.js/, use: [ { loader: path.resolve(__dirname, './loaders/replaceLoader.js'), options: { name: 'xh', }, }, ], // 引入自定义 loader },] 编写 plugin 新建 xxx-webpack-plugin 的 js 文件,plugin 是一个类, 调用的时候要 new 这个类生成实例. 12345678910class CopyrightWebpackPlugin { constructor(options) { console.log('插件被使用了', '参数为', options) } apply(compiler) { // compiler 是 webpack 的实例 }}module.exports = CopyrightWebpackPlugin 调用: 123456const CopyrightWebpackPlugin = require('./plugins/copyright-webpack-plugin')plugins: [ new CopyrightWebpackPlugin({ name: 'xx' })], webpack 原理 初始化阶段: - 初始化参数: 从配置文件或 shell 获取参数 - 创建编译器对象: 根据参数创建 compile 对象 - 初始化编译环境: 注入内置插件,rule 规则,加载的插件. - 开始编译: 执行 compile 的 run 方法 - 确定入口: 根据配置的 entry 找到入口,将入口文件转换为 dependence 对象 构建阶段: - 编译模块(make): 根据 entry 对应的 dependence 创建 module 对象,调用 loader 将模块转译为标准 JS 内容,调用 JS 解释器将内容转换为 AST 对象,从中找出该模块依赖的模块,再 递归 本步骤直到所有入口依赖的文件都经过了本步骤的处理. - 完成编译: 得到了每个模块被翻译后的内容以及它们之间的 依赖关系图 生成阶段: - 输出资源: 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表 - 写入文件系统(emitAssets): 在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统.","categories":[{"name":"前端","slug":"前端","permalink":"http://yoursite.com/categories/前端/"}],"tags":[{"name":"webpack 配置","slug":"webpack-配置","permalink":"http://yoursite.com/tags/webpack-配置/"}]},{"title":"读<<国富论>>","slug":"读<<国富论>>","date":"2021-05-05T06:11:54.000Z","updated":"2022-03-23T09:54:01.176Z","comments":true,"path":"2021/05/05/读<<国富论>>/","link":"","permalink":"http://yoursite.com/2021/05/05/读<<国富论>>/","excerpt":"","text":"国富论论劳动生产力改善的原因,并论劳动产出自然而然在各社会阶级间分配的次序 论分工 然而任何一种行业,若能引进分工,都会因分工而使劳动生产力得到相当比例的提高。而且不同行业与职业之所以相互分离出来,似乎也是由于分工有这种好处。一般来说,产业最发达进步的国家,通常也是分工程度最高的国家 我们不能将农业所运用的各种劳动完全清楚分开、相互独立出来,也许就是农业劳动生产力改善的速度跟不上制造业的主要原因。 贫国土地耕种的情况,尽管次于富国,但贫国生产的小麦,在质量和价格方面,却在相当程度上能与富国竞争;但在制造业方面,尤其是那些适宜富国土壤气候与位置的制造业,贫国最好别想和富国竞争。 分工之后,同样人数的工人所能生产的数量大为提高,主要是基于三种不同理由;第一,每个工人手脚灵巧的程度提高了;第二,工人不再需要从一种工作转换到另一种工作,节省了一些时间;第三,由于发明了许多机器,简化与节省了人力,使一个人能够完成许多人的工作。 目前那些分工最细密的制造业所使用的机器,大部分原本是某些普通工人的发明;他们每个人都只操作某种简单的工序,自然而然会把心思花在设法找出较简便的操作方式。 论促成分工的原理 分工的形成,是因为人性当中有某种以物易物的倾向;这种倾向的作用虽然是逐步而且缓慢的,也完全不问分工是否会产生广泛的效用;然而分工却是这种倾向必然产生的结果。 我们每天有吃有喝,并非由于肉商、酒商或面包商的仁心善行,而是由于他们关心自己的利益。 一如我们利用相互约定、交换或购买的方式,从他人身上取得绝大部分自己所需的帮忙,追根究底来看,导致目前这种分工状态的,也正是我们这种相互要求交换的倾向。 每个人都发觉有把握随时根据自己的需要,拿自己生产出来的剩余部分(即超出自己需要消费的部分),交换别人生产出来的剩余部分;人们就是因为有了这种确实的把握,才各自致力于某种特殊的行业,从而将每个人身上所有的才能或天分,发挥与磨练至最完美且适合各特殊行业的境界。 由于缺乏相互交换的能力与倾向,这些动物的不同天分与才能必然会产生的各种效果,不能集结在一起,因此对于整个种族的生活与便利,丝毫没有贡献,每只动物现在仍然必须个别独立仰赖自己与保卫自己。所以,虽然它们天生具有许多不同的才能,但是从彼此天生的差异中,它们得不到任何好处。人类的情况正好相反,极不相似的才能可以彼此帮忙;他们个别生产出来的不同物品,好像都被人类这种相互交换的倾向,集结在一起,让每个人可以根据自己的需要,随时在那里买到利用他人的才能生产的物品。 分工受限于市场范围 正因为交换的力量产生了分工,所以分工的程度必然受制这种力量的大小,或者说受制于市场的广狭。如果市场非常小,完全致力于一种行业的人,便得不到多少激励,因为他本身劳动产出的剩余部分,无法全部用来交换他人劳动产出的剩余部分。由于每一种产业,通过水运都比单靠陆运接触的市场更加广大,所以每种产业自然在海滨或适宜航行的河流两旁,开始进行产业内分工与生产力改善;而且往往需要经过很长的一段时间,分工与改善的现象才会延伸至内陆地带。各种工艺与产业的改良也是如此. 论金钱的起源与应用 分工的势态一旦彻底确立,任何人本身劳动的产品,便只能供应自己极小部分的日常需要。他用来供应自己绝大部分需要的办法,是以本身劳动产出的剩余部分,向他人换得自己恰好需要的部分。于是每个人都得靠交易过活,或者说,都在一定程度内变成了商人,而整个社会也就真正变成了所谓的商业社会。 每一个审慎考虑将来的人,不管他活在哪种社会发展阶段,在有了初步的分工之后,自然都会努力安排自己的日常事务,使自己不管在什么时候,手上除了本业特殊的产品之外,还持有一定数量的特殊商品或物品;那些商品或物品,是他认为当自己需要用它们来交换他人的劳动产品时,很少有人会拒绝的东西。 然而,无论在哪个国家,人们似乎最后都基于无法抗拒的理由,而选择了金属,取代其他东西当做交易工具。几乎没有其他东西比金属更不易腐败,因此贮存期间比其他东西更长。金属不仅耐久保存,而且不管把它分割成几个部分,或者再把那几个部分融在一起,都不会造成损失。也正因为具有这种特性,所以金属才特别适合作为买卖与流通的工具.如此粗糙的金属当做金钱使用,会有两个不算小的麻烦。第一个麻烦发生在要称它们重量的时候;第二个麻烦发生在要评鉴成分的时候。 为了防杜这种诈欺恶习,也为了方便进行交易,以刺激工商业发展,那些相当文明进步的国家才觉得,需要将国内人民常用来购买东西的金属,选取若干定量,盖上一个象征公信力的戳印。铸币,以及那些称作铸币厂的政府机关就这样产生了 我相信,世界上每一个角落,都曾经有过贪婪与不义的君主或主权国家,滥用人民对它们的信任,一点一滴偷减钱币里起初含有的真正金属数量。 必须注意“价值”一词有两个不同的意思,有时它表示某一特别物品的效用;有时则表示该物品给予占有者购买其他物品的能力。前者也许可称之为“使用价值”,而后者或许可称之为“交换价值”。那些具有最大使用价值的物品,往往几乎或完全没有交换价值;相反的,那些具有最大交换价值的物品,却往往几乎或完全没有使用价值。没有什么东西比水更有用,可是水却几乎买不到任何东西。相反的,钻石几乎没有使用价值;但拿钻石去交换,往往可以得到大量的其他物品。 论商品的真实价格(劳动价格)与名义价格(金钱价格) 因此他是贫是富,就要看他能够支配或购买多少别人的劳动数量而定。对于任何商品的占有者来说,当他不打算自己使用或消费,只想用来交换其他有用的物品时,它的价值就等于能够用来购买或支配的劳动数量。所以劳动是测量一切商品交换价值的真正标准。世上所有的东西,追根究底都不是用金银买来的,而是用劳动取得的。对于任何物品的占有者来说,当他想用它交换某些新产品时,它的价值就等于它能购买或支配的劳动数量。 虽然劳动是测量一切商品交换价值的真正标准,但平常测量商品的价值,却无法使用这种标准。要确定两种不同劳动数量的比例,时常会有困难。于是事情慢慢演变成商品的交换价值,通常以金钱的数量来估计,而不以劳动或其他商品的数量来估计。虽然每一种商品,通过交换,最后都可换得劳动或其他商品。然而金银的价值总是会随着矿脉的蕴藏丰富与否而起伏不定。 同一数量劳动可买到的商品,有时较多,有时较少;但这是那些商品价值变动的结果,而不是因为购买那些商品的劳动价值起了变化。只有劳动本身的价值绝不会改变,所以,在任何时间与任何地点,只有劳动才是测量与比较一切商品价值的基本真实标准。以劳动单位表示的价格是商品的真实价格,而以金钱表示的只是商品的名义价格罢了。雇主购买劳动所付出的物品数量有时多有时少,所以在他看来,劳动价格有时贵有时便宜,也和其他商品一样会变动。实际上,那是商品有时便宜有时贵造成的结果。 在产业发展的过程中,商业化的国家发现,为了交易方便,需要同时利用几种金属铸造钱币。例如用金币支付大笔的金额,用银币购买中等价值的东西,用铜币或其他粗贱金属购买价值更小的物品。”“但是,那些国家总是认为,其中某一种钱币比其他两种更特别,而把它当做测量价值的标准。一般来说,被视为价值标准的金属,往往就是最先用作交易媒介的金属。在所有的国家,法定的支付工具,起初一定只有一种钱币,而且正是用价值测量标准的金属铸成的。事实上,不管法律如何规定各种钱币金属的价值比例,只要规定没有更改,所有钱币的价值便会取决于最贵重金属的价值。 论商品价格的构成部分 在商品价格的构成部分中,利润与劳动工资完全不同,而两者也确实取决于截然不同的原则。在这种情形下,劳动的全部产出不一定完全属于劳动者本人。在大多数场合,他必须和雇主分享自己的劳动产出。通常取得或生产任何商品所需的劳动数量,不再是调节该商品应当购买、支配或换得多少劳动数量的唯一依据。任何商品显然都必须换得额外的劳动数量,才使垫付工资与提供材料的资本可以获得适当的利润。 无论在哪一个国家,一旦土地全部成为私有财产,像所有人类一样喜欢不劳而获的地主,便会开口索取地租. 需要注意的是劳动不仅是测量拆解成劳动所得这一部分商品价格的价值标准,也是测量拆解成地租和利润部分的价值标准。 在每一个社会里,任何商品的价格,最后可以拆解成(地租,工资,利润)这三种成分当中的某一种或两种,或全部三种。工资、利润与地租是一切收入,以及一切交换价值的三个根本来源。其他一切收入,追根究底,都源于这三种收入当中的某一种。不管是谁,想从自有资源得到收入,都只有三个办法:用自己的劳动、自己的资本,或自己的土地去取得。 当这三种收入分别属于不同人时,它们很容易分辨。可是,当它们属于同一个人时,则时常会被纠缠在一起。农夫不雇用他人帮忙而亲自辛苦耕种,如此节省下来的工资,其实就是他自己赚得的工资。所以说,农夫在这里便混淆了工资与利润。 但是,从来没有一个国家,能把每年的产出完全用在维持勤劳工人的生活。无论在什么地方,游手好闲的人总会消费掉社会每年产出的大部分。任何社会每年平常或平均产出的价值,究竟是年年增加,或年年减少,或呈现停滞状态,要视该社会的每年产出,在前述两种社会阶级间的分配比例而定。 论商品的自然价格和市场价格 当某种商品的价格,按照自然报酬率,不多不少刚好足够支付所有栽培、制作,直到上市的土地地租、劳动工资与资本利润时,那么该商品可说是按它的自然价格出售。对于任何商品来说,通常实际卖得的价格称为它的市场价格。这个价格也许高于、低于、或恰好等于它的自然价格。对于任何商品来说,市场价格的高低取决于两种数量间的比例,一是实际上市的商品数量,二是那些愿意支付自然价格的买者,他们需求的商品数量。 那些愿意支付自然价格的买者可称为有效需求者,而他们的需求数量可称为有效需求量,因为这种需求量,足以促使商品实际被带到市场来卖。它和绝对需求量不同。只要某一商品的上市数量超过有效需求量,则其价格当中某些成分的收入,便会低于它们的自然报酬率。相反的,一旦上市数量低于有效需求量,则商品价格当中某些成分的收入,便会高于它们的自然报酬率。 对任何商品来说,偶尔与暂时性的市价波动,在价格的诸构成要素之中,主要影响工资与利润的部分。地租部分虽然也受到影响,但是程度比较小。然而,究竟影响工资或利润,便要看市场当时发生存量过剩或不足的物品,究竟是商品或劳动。 论劳动工资 无论在什么地方,一般工资水平,取决于雇主和工人通常会达成的契约。 由于人数比较少,所以雇主们比较容易团结起来.此外,法律也允许雇主团结起来,至少不会禁止。然而,法律却禁止工人团结。 对工人的需求,必然都会跟随国家的财富而增加;如果国家的财富没有增加,对工人的需求便不可能增加。劳动工资之所以升高,不是因为一国拥有大量财富,而是因为一国的财富不断增加。因此,工资最高的国家,不是在最富有的国家,而是在最欣欣向荣,或者说财富成长最迅速的国家。 对任何国家来说,要知道它的繁荣程度,最简单可靠的办法,莫过于看人口增加的状况。 劳动获得宽裕的报酬,不仅是一国财富不断增加的必然结果,同时也是一国财富不断增加的自然症候。另一方面,贫穷的劳动阶级生活捉襟见肘,是一国财富停滞的自然症候,而该阶级人民濒临饿死,是一国财富迅速萎缩的自然症候。 对人口的需求,也像对其他任何商品的需求一样,就依照这种方式自然而然的调节人口的成长速度;当人口的成长进行得太慢时,超额需求会使它加速;当人口成长速度得太快时,需求不足会使它放慢。调节与决定世界各地人口成长速度的关键正是劳动需求。 绝大部分人民,处境最快乐也最舒服的时候,似乎是在社会不断进步,也就是社会持续累积财富的时候,而非已经取得了所有财富的时候。贫穷的劳动阶级,在社会停滞时,处境艰难;在社会退步时,处境悲惨。事实上,对所有社会各阶层来说,进步中的社会最令人感到欢乐。停滞的社会,令人感到单调无聊;退步的社会,令人忧郁感伤。 论资本利润 利润和工资的升降,取决于同一个原因,即是取决于社会财富增加或减少。 无论在哪一国,当寻常的市场利率发生变动时,就可以推定资本的平常利润也有同样的变动。利率降时,它也降,利率升时,它也升。 一般来说,在大城市经营任何行业,比在乡村需要更多的资本。城市里,除了各种行业都运用比较大量的资本外,有钱的竞争者人数也比较多,因此一般来说,城市的利润率会被压低到乡村的利润率以下。在每一个欣欣向荣的城市里,有大量资本可用的人,时常达不到他们想要雇用的劳工人数,所以会互相竞价争取工人,从而提高了工资,也降低了利润。在偏远的乡村地区,时常没有足够的资本可以雇用全部的劳工,所以劳工会互相竞价争取工作,从而降低了工资,也提高了利润。 当财富、各方面进步和人口增加之后,利率便会跟着下降。劳动工资不会跟随资本利润下降。不管资本的利润如何,只要资本增加,劳动需求就会跟着增加。 论劳动与资本在不同行业的工资与利润 行业本身性质不同所产生的工资差异:一、工作本身讨人喜欢(工资低)或令人厌恶的程(工资高)度。二、学得工作技巧的过程,是否既容易又便宜(工资低),或是既困难又昂贵(工资高)。三、工作机会是否稳定(工资低),或是时有时无(工资高)。四、执行工作的人是否必须特别值得信赖(工资高),或是一般人(工资低)即可。五、行业经营(或职业生涯)获得成功的几率大(工资低)小(工资高)。 在前述五个影响劳动工资的因素当中,只有两个因素对资本利润有影响,也就是行业本身讨人喜欢或令人厌恶的程度,以及投资获利风险或安全的程度. 在小城镇或乡村地区,由于市场狭小,即使扩大应用资本,生意不见得一定跟着扩大。因此,在这种地方,尽管业者的利润率很高,利润总额却不可能很大,从而每年累积的资本也不可能很多。相反的,在都市地区,生意随着资本增加而扩大,而且生活节俭、事业蒸蒸日上的生意人能够获得的信用,比他自己的资本增加得更快。他的生意按资本和信用增加的比例扩大,利润总额按生意扩大的比例增加,每年累积的资本按利润总额增加的比例提高。 政策导致的差异:一、限制加入某些行业竞争的人数,使它小于自由的情况下愿意加入的人数。二、增加某些行业的就业人数,使它超过自由的情况下愿意加入的人数;三、阻挠劳动和资本在不同行业之间以及不同区域之间的自由流通。 论地租 略 论物品积蓄的性质、累积与运用一旦社会分工彻底实施了以后,每个人随时需要的各种物品,他自己的劳动产出只能供应极小的一部分。只能用自己的劳动产品价格购买。不过,这个购买动作,必须等到自己的劳动产品不仅已经完成。因此,必须有一定数量的各种物品事先积蓄在社会的某个角落,至少足够维持个人的生活. 如果说物品积蓄的累积必须在分工之前,否则劳动生产力便不会有重大的进步,那么我们同样可以说,物品积蓄的累积自然会导致生产力进步。这种积蓄累积成各种不同的资本. 论物品积蓄(资本)的种类 当某个人拥有的物品积蓄仅仅足够维持几天或几个礼拜的生活时,他不太会想到利用这项积蓄来衍生任何收入。可是当他拥有的物品积蓄足够维持几个月或几年的生活时,他便自然会尽力利用大部分的积蓄来衍生收入,而只保留一小部分的积蓄,所以,他的全部积蓄可区分成两部分。其中他期望衍生收入的部分,可以说是他的资本。另外一部分则是直接供应他消费使用的积蓄。 资本有两种不同的运用方式,可以让运用它的人获得收入或利润。一是流动资本(卖出前的商品),二是固定资本(土地,机器). 一个国家或社会全部的物品积蓄,等于所有居民或成员物品积蓄的总和,因此它也自然分成同样的三部分.第一部分是留作直接消费使用的部分,特征是它不会提供收入或利润。第二项就是固定资本,它的特征是毋须循环或易主,便可以提供收入或利润。最后,也是第三项,就是流动资本。它的特征是,唯有通过循环或来回易主,才可能提供收入。 固定资本和流动资本两者最终和唯一目的,就是要维持和增加留作直接消费使用的物品积蓄。正是后面这部分物品积蓄让人们有得吃、有得穿、有得住。人民的贫富取决于这两种资本提供了多少物品,可以让人民保留,当作直接消费使用的积蓄。 论货币作为社会全部积蓄中的一个特殊种类或论国家资本的维持费用 就每个国家全部土地与劳动每年生产的所有商品总和来看,也必然如此。换句话说,每年产出的全部价格或交换价值,必定自然同样分解成这三种成分,以劳动工资、资本利润或土地的租金等三种形式,分配给该国各阶层的居民。 虽然固定资本全部的维持费必须从社会的净收入中剔除,但是,流动资本全部的维持费却不一样。流动资本包含四个部分:钱币、食物、原材料和制成品。前文已经指出,后面三项经常从流动资本中抽离出来,成为社会的固定资本,或是成为人们留在身边供作直接消费使用的物品积蓄。在社会全部的流动资本当中,唯有钱币这部分的维持费,才会导致社会净收入有所减少。 我们虽然通常是以每年支付给某人的钱币数量来表示他的收入,这是因为这些钱币数量会影响他的购买力大小,或影响他每年能够消费价值多少的物品。但是,我们仍然会认为,他的收入在于购买力或消费能力,而不在于传送购买力的钱币。 自由竞争也使得每一个银行家和客户打交道时更为慷慨大方,唯恐客户会被其他竞争对手抢走。一般说来,任何一种行业或任何一种劳动部门,只要是对大众有利的,那么竞争愈是自由并且愈为普遍,对大众就愈为有利。 论资本的累积,兼论生产性和非生产性劳动 有一种劳动施加在物品上,会增加物品的价值,另外有一种劳动没有这种效果。前者或许可以称为“生产性劳动”,因为它会产生价值。相对的,后者可以称为“非生产性劳动”。一般来说,制造工人的劳动,在他加工的材料上,增加了一部分价值,可以提供本身生活所需和雇主利润。相反的,侍奉主人的奴仆不会增加任何东西的价值。 制造的物品可以在日后有需要的时候,拿来驱动某一数量的劳动工作,这个数量和当初生产该物品的劳动相等。相反的,侍奉主人的奴仆,他的劳动并不附加或体现在任何物品. 一国的产出,不管数量多大,绝不可能是无穷无尽的,而是一定会有某个限度。所以,在任何一年内,如果用比较多的产出去维持非生产性劳动者,剩下来维持生产性劳动者的产出就会比较少,从而来年的产出也会比较少。相反的,维持生产性劳动者的产出比较多,来年的产出也会比较多。 勤劳与懒惰的比例高低,不管在什么地方,似乎都随资本与收入的相对比例而起伏。资本比例高的地方,勤劳的比例也高;收入比例高的地方,懒惰的比例也高。因此,资本的增加或减少,自然会倾向增加或减少勤劳的实际数量,增加或减少生产性人员的数目,从而增加或减少一国土地和劳动每年产出的交换价值。也就是说,增加或减少该国所有居民的实质财富和收入。 资本因节俭而增加,因浪费和错误运用而减少。由于节俭扩大了准备用来维持生产性人员的财源,所以节俭有助于提高生产性人员的数目。而生产性人员的劳动,则会使施工的物品价值增加。所以节俭有助于增加一国土地和劳动每年产出的交换价值。它驱动了额外数量的勤劳,而这额外的勤劳将增添每年产出的价值。资本增加的直接原因是节俭,而非勤劳。不错,勤劳提供物品让节俭得以累积。但是,不管勤劳可以取得多少物品,如果没有节俭来储蓄或贮存,资本便不可能增加。 当国内的产出价值减少时,在国内流通的消费品价值必然也会跟着减少,从而需要用来流通消费品的钱币数量也会跟着减少。 论贷出取息的积蓄 贷出取息的积蓄,有时候确实会被用来供应日常的生活消费,但实际上更常被当作资本使用。借钱来消费的人,很快会变得一贫如洗,以致借钱给他的人,终将因自己当初的愚蠢而懊悔不已。 我们可以这样看待贷出取息的资本:贷款者将相当可观的一份产出转让给借款者使用,回报的条件之一是,在借款存续期间内,借款者必须每年将比较小的一份产出转让给贷款者,比较小的这一份产出称作利息;条件之二是,在借款期满时,借款者必须将一份大小和当初转让给他的那一份相等的产出转让给贷款者,这一份产出称作还本。货币借贷的利息,它总是和资本的利润同步升降. 论资本的各种用途 资本有四种不同的用途:第一,生产社会每年需要使用和消费的初级产品。第二,对这些初级产品进行加工制造,供应直接使用和消费。第三,将初级产品或制成品,从供给充裕的地方运送到供给不足的地方。第四,将一部分初级产品或制成品,分装成方便人们偶尔需要使用的数量单位。所有从事土地改良或耕作、采矿和渔捞的资本,全都属于第一种用途。各种制造业主的资本,全都属于第二种用途;各种批发商的资本,全都属于第三种用途。各种零售商的资本,全都属于第四种用途。 同一数量的资本用于国内贸易,通常会比用于外国消费品贸易,激励和支持更多的本国生产性劳动,同时增加更大的本国产出价值。而同一数量的资本用于外国消费品贸易,又比用于海外贩运贸易,更有利于本国生产性劳动和产出价值的提升。每一个国家的财富,以及只要国家的力量取决于财富,必然和最后支付一切税收的财源成正比。增加本国的财富和国力,几乎是每个国家经济政策的主要目的。 当一国的资本积蓄,增加到超过供应本国消费和支持本国生产性劳动全部所需的数量时,多余的资本自然会流向海外贩运贸易,在他国发挥同样的功能。海外贩运贸易是一国财富极大的自然结果和象征,但似乎不是一国致富的自然原因。 论不同国家财富增加的过程 论国家财富增加的自然过程 城市和乡村的交易其实是互助互惠的,两者之间的劳动分工,就像其他所有的分工那样,对所有参与分工、从事各种不同职业的人,都是有利的。城市居民的数目和收入愈多,乡村居民在那里享有的市场规模便愈大;而那里的市场规模愈大,总是对大多数人都愈有利. 如果各种人为扭曲的制度从未骚扰自然的事态发展,那么在每个国家,城镇财富和人口的发展,不仅在时间上会较晚,而且在规模上也会受限于周围土地或乡村的改良和耕种发展。 按照自然的道理,每个成长中的社会都将大部分资本优先投入农业,然后是制造业,最后才是国外贸易业。虽然每个成长中的社会,都必然在某个程度内有这种自然的发展次序;然而在许多方面,欧洲所有的现代国家,它们的发展次序却完全颠倒过来。这些国家的某些城市率先经营国外贸易,然后引进各种适合销售到远方的精致制造业;然后这些制造业再和国外贸易业,一起形成农业方面的重大进步。这些国家原来的政府所建立的各种风俗习惯,在政府经历了很大的变革之后,仍然被保留下来;正是碍于这些风俗习惯,所以这些国家的财富发展才会有这样不自然的相反顺序。 论罗马帝国灭亡后欧洲古代国家农业发展的阻碍 古罗马人就是遵行这种自然的继承法。他们在土地继承上对于长幼和性别的区分,不会比我们现在分配动产时分得更仔细。但是,当土地被认为不仅是提供食物的手段,更是掌握权力和保障生命财产的重要手段时,人们便觉得土地最好只传给一个人,不要分割。因此,一处地产的安全,或地产的主人对于地产上的居民所能提供的保障,便取决于地产的大小。要是把土地分割,便等于是要让它毁灭。因为成本的原因,这种庞大的地产对土地改良很不利。很难指望大地主进行耕种改良,那么,更不能指望他辖下的承租人进行耕种改良. 逐渐取代这种佃农的,是真正配称为农夫的耕种者。他们运用自己的积蓄耕种土地,支付一定的地租给地主。当这种农夫握有期限长达好几年的土地租约时,他们有时候会发现,拿出自己部分的资本进一步改良土地,对他们自己有利。因为他们有时候可以在土地租约到期以前将投资完全收回来,同时还可以获得可观的利润.当然最重要的是制定法律排除任何一种土地继承人的影响,以保障土地租约享有最长的时效. 论罗马帝国灭亡后城镇的兴起和进步 彼此的利益便使得镇民倾向支持国王,也使得国王倾向支持镇民抵抗封建领主。镇民是国王敌人的敌人,所以尽可能让镇民安全独立,避免他们受到共同敌人的挟持,便是国王的利益所在。国王准许镇民选任自己的行政和司法长官,让他们有权订定内规治理自己内部的事务,有权建筑城墙保卫自己,以及有权迫使所有居民服从某种军事纪律,等于是把国王能够授与的手段全部都给了镇民,让他们用来对抗封建领主,保护自己的安全和独立。 在某些国家,譬如像意大利和瑞士,或许是由于距离中央政府的主要根据地太过遥远,也或许是由于乡村地区本身自然的力量,或者由于某些其他原因,君主逐渐完全丧失了权威。在这样的国家里,各个城市通常变成了独立的共和国,并且征服了所有邻近的诸侯贵族,迫使诸侯拆除在乡下的城堡,到城市里与禀性和平善良的居民住在一起。十二世纪末至十六世纪初,这种共和国兴起和灭亡的数目非常多。 社会秩序和优良政府,以及随之而来的个人自由和安全,就这样在各个城市里确立起来。逃出来的农奴,如果能够在城镇里躲上一年,而不被他的主人逮到,那么他便永远自由了。因此,凡是在勤劳的乡村居民手中累积起来的积蓄,都自然会逃到城市. 十字军东征,虽然浪费了大量的社会积蓄,也摧毁了无数生命,必定减缓了欧洲大部分地方的进步。然而,十字军东征对意大利的某些城市极为有利。从欧洲各地出发征讨巴勒斯坦圣地的各路大军,让威尼斯、热那亚和比萨的船运业生意兴隆。商业城市的居民,可以从比较富有的国家进口各种改良的制品和昂贵的奢侈品,来满足大地主们的虚荣心。对各种比较精致进步制品的嗜好,就这样经由国外贸易,导入了各个从来没有做过这种制品的国家。当这种嗜好变得相当普遍,以致市场的需求很大时,商人为了节省运输费用,自然会尽力在自己的国家设立同一种类的制造业。这似乎是罗马帝国灭亡后,西欧诸省开始以远方为销售市场而建立各种制造业的由来. 论城镇商业活动如何促进乡村改良 商业和制造业城市的繁荣,以三种不同的方式,对城市所属国家的乡村改良和农业进步,产生正面的影响。第一,城市为乡村地区的初级产物提供了广大和现成的市场,鼓励乡村地区的农业耕作和土地改良。第二,城市的居民累积了财富以后,往往会在乡村地区购买一些等待出售的土地,这种土地多半无人耕种。商人通常有强烈的企图心,希望变成乡绅,提高自己的社会地位,而当他们得遂所愿以后,往往成为最佳的土地改良者。第三,也是最后一种方式,商业各种精致先进的制造业,逐渐把社会秩序和优良政府,以及随之而来的个人自由和安全导入乡村地区。 论政治经济学的思想体系略 论君主或国家收入略","categories":[{"name":"读书","slug":"读书","permalink":"http://yoursite.com/categories/读书/"}],"tags":[{"name":"读书 笔记","slug":"读书-笔记","permalink":"http://yoursite.com/tags/读书-笔记/"}]},{"title":"Vite 使用教程","slug":"Vite 使用教程 ","date":"2021-02-22T05:39:21.000Z","updated":"2021-08-30T09:54:36.241Z","comments":true,"path":"2021/02/22/Vite 使用教程 /","link":"","permalink":"http://yoursite.com/2021/02/22/Vite 使用教程 /","excerpt":"","text":"vite 教程安装12npm init @vitejs/app # npm 安装yatn create @vitejs/app # yarn 安装 特点 热启动快 模块热重载vite 提供一套原生 ESM 的 HMR(即时热更新 hot module replacement) API 实现热重载,无需重新加载页面和应用. 按需编译 Vite 要求项目完全由 es module 模块组成,因此不能再生产环境使用,打包依旧由 rollup 打包工具,目前vite 更像是一个类似于 webpack-dev-server 的开发工具. es module 和 commonJses modules 是浏览器支持的一种模块化方案.import HelloWorld from './Helloworld.vue',当浏览器解析这条语句的时候会往当前域名发送一个请求获取对应的资源.我们平时在 webpack 上写的 esmodule 代码会被打包成 commonJs 的方式运行,所以运行速度较慢.目前 90% 的浏览器都已支持 基于 web 标准的 es module.浏览器对于带有type=”module”的<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。有多个该标签会依次执行. CommonJs 是 NodeJs 中的规范,每个模块都有一个 exports 私有变量,exports 指向 module.exports.require 命令可以读入并执行一个 js 文件然后返回该文件的 exports 对象.require 被导出的值的拷贝.commonJs 是动态分析,动态加载.先整体加载模块,再从对象上读取里面的方法,因为只有运行中才能得到对象所以没有办法在静态编译时做静态优化. esModule 的导入和导出都是声明式的,必须位于模块的顶层作用域,在 es6 代码编译阶段就可以分析模块依赖.通过静态分析未被调用的模块不会被执行和打包,确保模块之间传递的值和接口类型正确.import 导入的模块是只读的,不允许在导入后直接对其修改.如果是一个对象,可以对对象的属性进行修改.由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。 不同: cjs 输出的是值拷贝,mjs 输出的是值引用. cjs 是运行时加载,mjs 是编译时记载.cjs 中 this 指向顶层对象,mjs 指向 undefined. esModule 如果想要动态记载,可以使用 2020 提案中的 import()函数.该函数的参数可以是动态的.与 commonJS 中 require 的区别就是 require 是异步加载,import()是同步加载.加载成功后使用 then 方法从参数中获取模块. node 要求 es6 模块使用 mjs 后缀,如果不想改后缀需要在 package.json 中加 'type' : 'module',这时 commonjs 需要加 cjs 后缀了.两者尽量不要混用. esModule 加载路径必须给出脚本的完整路径,不能省略后缀名. 而使用webpack时 由于在resolve.extensions: ['.js','.jsx','.vue']配置过,可省略后缀名.而vite可以通过resolve.extensions :['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json']配置. webpack VS vite我们经常会遇到我们改动一小行代码,webpack 会耗时数秒来重新打包.因为 webpack 需要将所有模块打包成一个或多个模块. 1234567891011121314151617// a.jsexport const a = 1;// b.jsexport const b = 2;// main.jsimport {a} from 'a.js';import {b} from 'b.js';export const getNumber = () =>{ return a + b;}// 打包成 bundle.jsconst a = 1;const b = 2;const getNumber = () =>{ return a + b;}export {getNumber}; 当我们修改一个子模块 bJs,整个 bundleJs 都需要重新打包.随着项目增大,重新打包的时间越来越长.热更新的速度越来越慢.webpack 之所以慢,是因为 webpack 会将很多资源构成一个或多个 bundle.如果跳过打包过程,当需要某个模块的时候再通过请求去获取就完美的解决了这个问题.Vite 做到了. Vite 实现原理 请求拦截.Vite 的基本原理就是启动一个 node 服务器拦截浏览器请求 es module 的请求,通过 path 找到对应文件做一定的处理,然后以 es module 的方式返回给浏览器. esbuild.Vite 对 js/ts 的处理没有经过 gulp/rollup 等传统打包工具,而是使用 esbuild,esbuild 是一个全新的 js 打包工具,支持如 babel,压缩等功能,它要比 rollup 等传统工具快上几十倍.原因是它使用了 go 语言作为底层语言. node_modules 模块的处理当我们在日常开发时引用 node_modules 的时候,我们会这样引用.import vue from 'vue';然后 webpack 等打包工具会帮我们找到模块的路径.但是浏览器只能通过相对路径去找,vite 为了解决这个问题,对其做了一些特殊处理.当浏览器请求vue.js时,请求路径是@modules/vue.js.在 vite 中约定若 path 的请求路径满足/^\\/@modules\\//格式时,就被认为是 node_modules 模块.那么如何将代码中的vue.js变为/@modules/vue 呢? Vite 对 es module 形式的 js 文件模块处理使用了 Es module lexer 处理, Lexer(词法分析器)会返回 js 文件中导入模块以数组形式返回.然后通过该数组判断是否为一个 node_modules 模块,若是则重写其 path. 然后当浏览器发生 path 为/@modules/xx的对应请求时,会被 Vite 服务端做一层拦截,最终找到对应模块进行返回.Vite 对 script 标签导入的模块也会有对应的处理. vue 文件的处理当 Vite 遇到 vue 文件时,它会被拆分成 template,css,script 三个模块进行处理.最后会对 script,template,css 发送多个请求获取.比如 App.vue?type=template 获取 template,type=style 获取 css. 静态文件的加载当请求的路径符合 imageRE,mediaRE,fontsRE 或 JSON 格式时会被认作静态资源.然后处理成 es module 模块返回. 1234567// src/node/utils/pathUtils.tsconst imageRE = /\\.(png|jpe?g|gif|svg|ico|webp)(\\?.*)?$/const mediaRE = /\\.(mp4|webm|ogg|mp3|wav|flac|aac)(\\?.*)?$/const fontsRE = /\\.(woff2?|eot|ttf|otf)(\\?.*)?$/iexport const isStaticAsset = (file: string) => { return imageRE.test(file) || mediaRE.test(file) || fontsRE.test(file)} HMR(即时热更新 hot module replacement)的原理Vite 的热更新原理就是在客户端和服务端建立了一个 websocket 链接,当代码修改时服务端发送消息通知客户端重新请求信代码,完成更新. 服务端原理 websocket 客户端原理vite 在处理 html 时写入 websocket相关代码.123456789101112131415161718192021222324 export const clientPublicPath = `/vite/client`const devInjectionCode = `\\n<script type=\"module\">import \"${clientPublicPath}\"</script>\\n` async function rewriteHtml(importer: string, html: string) { return injectScriptToHtml(html, devInjectionCode) } // // Listen for messagessocket.addEventListener('message', async ({ data }) => { const payload = JSON.parse(data) as HMRPayload | MultiUpdatePayload if (payload.type === 'multi') { payload.updates.forEach(handleMessage) } else { handleMessage(payload) }})async function handleMessage(payload: HMRPayload) { const { path, changeSrcPath, timestamp } = payload as UpdatePayload console.log(path) switch (payload.type) { case 'vue-reload': // ... break // ....} Vite 的一些优化es module 如果包含相互依赖的话,页面初始化会发送大量请求. Vite 为了优化这个问题,给了一个 optimize 指令.它类似于 webpack 的 dll-plugin 插件,提前将 package.json 中的依赖打包成一个 esmodule 模块,这样在页面初始化就能减少大量请求. Vite 只是一个用于开发环境的工具,上线仍会打包成一个 commonJs 文件进行调用.","categories":[{"name":"前端","slug":"前端","permalink":"http://yoursite.com/categories/前端/"}],"tags":[{"name":"Vite","slug":"Vite","permalink":"http://yoursite.com/tags/Vite/"}]},{"title":"Vue3 迁移指南","slug":"Vue3 迁移指南","date":"2020-09-29T05:42:05.000Z","updated":"2021-08-30T09:57:27.756Z","comments":true,"path":"2020/09/29/Vue3 迁移指南/","link":"","permalink":"http://yoursite.com/2020/09/29/Vue3 迁移指南/","excerpt":"","text":"vue3.0 迁移指南 全局 api 更改为 应用程序实例.vue2.0 有很多全局的 api 和配置,比如 Vue.component 创建全局组件,Vue.directive 创建全局指令,Vue.mixins 和 Vue.use 等等..因为Vue2.0 通过 new Vue(…)来创建根 Vue 实例,从同一个 Vue 构造函数创建的根实例共享相同的全局配置.1234567// 这会影响两个根实例Vue.mixin({ /* ... */})const app1 = new Vue({ el: '#app-1' })const app2 = new Vue({ el: '#app-2' }) vue3.0 提供了 一个全新的全局 API - creatApp,调用它返回一个应用实例.应用实例拥有当前全局 API 的子集.1234import { createApp } from 'vue'const app = createApp({})// app.component,app.directive,app.mixin,app.use...app.mount('#app'); 全局和内部 api 以及重构为可 tree-shaking (删除无用代码,不打包到 bundle)vue2.0 时再用 Vue.nextTick() 或它的简单包装形式$nextTick()时,webpack 的 tree-shaking 不可摇动.Vue3.0 对全局和内部 api 进行了重构,考虑到 tree-shaking 的支持,全局 api 现在只能作为 es 模块侯建的命名导出进行访问.例如:12import {nextTick} from 'vue'nextTick(...); 受影响的 api 有: Vue.nextTick VUe.observable(用 Vue.reactive 替代) Vue.version Vue.compile Vue.set Vue.delete v-model 用法更改在 vue2.0 中,v-model 用来双向绑定数据,但一个组件只能用于一个 v-model,如果需要多个双向绑定只能用.sync.123456// 2.x<ChildComponent :title=\"pageTitle\" @update:title=\"pageTitle = $event\" />// 简写<ChildComponent :title.sync=\"pageTitle\" />// 子组件内部触发this.$emit('update:title', newValue) 在 vue3.0 中,v-model 通过后面要绑定的属性名来实现绑定多个值.原理: vue2.0 的 v-model 通过绑定一个 value 属性和 input 事件,将输入e.target.value映射到 绑定的变量 值上.而 vue3.0 相当于传递了modelValue 的 prop 并接收了抛出的 update 事件.123456789101112// 用在组件上<custom-input v-model=\"searchText\"></custom-input>// 组件内部的 input 必须将属性绑定在 modelValue 上且 事件触发通过 update:modelValue 抛出.app.component('custom-input', { props: ['modelValue'], template: ` <input :value=\"modelValue\" @input=\"$emit('update:modelValue', $event.target.value)\" > `}) key 属性用法更改对于 v-if,v-else,v-else-if 的 key 不再必须,Vue3.0 会自动生成 唯一的key,不建议手动赋予 key 值.vue2.0 中 template 标签不能有 key 值,通常在它的子节点设置 key.在 Vue3.0 中,key 值应该设置在 template 标签中. 1234567891011<!-- Vue 2.x --><template v-for=\"item in list\"> <div v-if=\"item.isVisible\" :key=\"item.id\">...</div> <span v-else :key=\"item.id\">...</span></template><!-- Vue 3.x --><template v-for=\"item in list\" :key=\"item.id\"> <div v-if=\"item.isVisible\">...</div> <span v-else>...</span></template> v-if 和 v-for 优先级调整.vue2.0 中,v-for 的优先级最高.而在 Vue3.0 中,v-if 的优先级最高. 但都建议避免他们在同一元素上使用. v-for 中的 ref 数组.vue2.0 中 v-for 使用 ref 会用 ref 数组填充相应的 $refs.当 v-for 存在嵌套 v-for 时,这是不明确和效率低下的.Vue3.0 中.这样的用法将不再自动创建数组,需要将 ref 绑定到一个灵活地函数上. 只能使用普通函数创建功能组件(函数式组件).vue2.0 函数式组件示例: 12345678910111213141516171819202122// Vue 2 函数式组件示例export default { functional: true, props: ['level'], render(h, { props, data, children }) { return h(`h${props.level}`, data, children) }}// 或者<template functional> <component :is="`h${props.level}`" v-bind="attrs" v-on="listeners" /></template><script>export default { props: ['level']}</script> Vue3.0 不需要定义 functional,接收两个参数,props 和 context.(同 setup)123456import { h } from 'vue'const DynamicHeading = (props, context) => { return h(`h${props.level}`, context.attrs, context.slots)}DynamicHeading.props = ['level']export default DynamicHeading 异步组件Vue2.0 通过将组件定义为返回 promise 的函数来创建的.1const asyncPage = () => import('./NextPage.vue') Vue3.0 由于函数式组件被定义为纯函数,因此异步组件需要包装在新的 defineAsyncComponent 方法显示定义.12import { defineAsyncComponent } from 'vue'const asyncPage = defineAsyncComponent(() => import('./NextPage.vue')) h 渲染函数更改. 1234567891011121314// 2.x 语法 render 函数接收 h 之类的参数export default { render(h) { return h('div') }}// 3.x 语法 h 函数全局导入,不作为参数传递,可以用作 setup 的返回值函数import { h } from 'vue'export default { render() { return h('div') }} slot 统一在 3.x 中,将所有 this.$scopedSlots 替换为 this.$slots 自定义指令自定义指令的钩子函数更改为与组件声明周期统一的事件钩子.bind => beforeMount, inserted => mounted, 新增 beforeUpdate, update 与 componentUpdated => updated, 新增 beforeUnmounte, unbind => unmounted. watch 和 $watch 不再支持.分隔符字符串路径,请改为计算函数作为参数. destroyed 重命名为 unmounted, beforeDestroy 重命名为 beforeUnmount prop 默认值函数中不再能访问 this,可以把组件接收到的原始 prop 作为参数传递给默认函数.或使用 inject. 1234567891011121314import { inject } from 'vue'export default { props: { theme: { default (props) { // `props` 是传递给组件的原始值。 // 在任何类型/默认强制转换之前 // 也可以使用 `inject` 来访问注入的 property return inject('theme', 'default-theme') } } }} data 组件选项不再接受纯 js Object,而必须是 function.而组件和 mixins 或 extends 基类合并是,现在将浅层次合并. 123456789101112131415161718192021222324252627282930313233const Mixin = { data() { return { user: { name: 'Jack', id: 1 } } }}const CompA = { mixins: [Mixin], data() { return { user: { id: 2 } } }} // vue 2.0 合并后是 { user: { id: 2, name: 'Jack' }}// Vue 3.0 合并后是{ user: { id: 2 }} 迁移建议: 对于依赖 mixin 的深度合并行为的用户,我们建议重构代码以完全避免这种依赖,因为 mixin 的深度合并非常隐式,这让代码逻辑更难理解和调试。 过渡 class 名更改过渡类名 v-enter 修改为 v-enter-from、过渡类名 v-leave 修改为 v-leave-from。. 移除功能 不再支持使用数字键吗作为 v-on 的修饰符 不再支持 config.keyCodes 12345678Vue.config.keyCodes = { f1: 112}<!-- 键码版本 --><input v-on:keyup.112=\"showHelpText\" /><!-- Vue 3 在 v-on 上使用 按键修饰符, 建议对任何要用作修饰符的键使用 kebab-cased (短横线) 大小写名称 --><input v-on:keyup.delete=\"confirmDelete\" /> $on,$off 和 $once 实例方法(全局事件侦听器)已被移除,应用实例不再实现事件触发接口。$emit 仍然是现有 API 的一部分,因为它用于触发由父组件以声明方式附加的事件处理程序 Fileter 已删除,不再受支持,建议使用计算属性替代 123456789<p>{{ accountBalance | currencyUSD }}</p>export default{ filters: { currencyUSD(value) { return '$' + value } }} 删除 inline-template 内联属性 删除$destroy 实例方法,用户不应再手动管理 Vue 组件的生命周期 vue3新特性 setupsetup是Vue3.0提供的一个新的属性,可以在setup中使用Composition API.setup函数有两个参数,分别是props和context。props 是组件外部传入进来的属性,contextcontext是一个对象,里面包含了三个属性attrs,slots,emit.attrs 与 vue2.0 的 this.$attrs 一样,是外部传入未在 props 中定义的属性.slots 对应 vue2.0 的 this.$slots 代表组件插槽.emit 对应 vue2.0 的 this.$emit,对外暴露的事件.setup 返回一个对象,对象中包含了组件使用到的 data 与一些函数或事件,也可以返回一个函数,对应 vue2.0 的 render 函数在里面可以使用 jsx.不要在 setup 中使用 this,通过 props 和 content 基本可以满足开发需求. 1234567891011export default { props: { value: { type: String, default: \"\" } }, setup(props) { console.log(props.value) }} composition API在 vue2.0 中,我们在 data()函数中定义数据,在 methods,computed,watch 等等地方使用数据,书写逻辑.但随着功能增加,代码越来越难阅读和理解,因为现有的 api 迫使我们通过选项写代码,但有时候通过逻辑写代码更有意义.但 vue2.0 缺少一种简介的机制来提取和重用多个组件间的逻辑. 了解 composition api 前,想了解下 reactive 和 ref. reactive在 vue2.6 中有一个 新的 api, Vue.observer,通过这个 api 可以创建一个响应式对象.而 reactive 和 observer 功能基本一样. 12345678910111213141516171819202122<template> <div>{{ state.name }}</div></template><script>import { reactive } from \"vue\";export default { setup() { // 通过reactive声明一个可响应式的对象 const state = reactive({ name: \"test\" }); setTimeout(() => { state.name = \"test123\"; }, 1000 * 5); return { state }; }};</script> vue2.x 时,经常出现更改数据后页面没有刷新,需要使用 Vue.set()来解决.vue3.0 抛弃了 2.0 使用的 Object.defineProperty .使用 proxy 来监听.我们可以直接在reactive声明的对象上添加新的属性.reactive 返回的不是原对象,而是 proxy 实例的一个全新对象. ref假如现在我们需要在一个函数里面声明用户的信息,那么我们可能会有两种不一样的写法12345678// 写法1let name = 'vue'let version = '3.0'// 写法2let info = { name: 'vue', version: '3.0'} 对于写法1我们直接使用变量就可以了,而对于写法2,我们需要写成info.name的方式。我们可以发现info的写法与reactive是比较相似的,而Vue3.0也提供了另一种写法,就像写法1一样,即ref。123456789101112131415161718192021<template> <div> <div>名称:{{ name }}</div> </div></template><script>import { ref } from \"vue\";export default { setup() { const name = ref(\"vue\"); console.log('名称',name.value) // 5秒后修改name为 react setTimeout(() => { name.value = \"react\"; }, 1000 * 5); return { name }; }};</script> reactive传入的是一个对象,返回的是一个响应式对象,而ref传入的是一个基本数据类型(其实引用类型也可以),返回的是传入值的响应式值.reactive获取或修改属性可以直接通过state.xxx来操作,而ref返回值需要通过xxx.value的方式来修改或者读取数据。但是需要注意的是,在template中并不需要通过.value来获取值,这是因为template中已经做了解套。 toRefs会把一个响应式对象的每个属性都转换为一个ref,在 setup 返回值中…roRefs(data),在模板中引用不需要再加上 data 前缀,可以直接使用变量.1234567891011import { toRefs, reactive } from 'vue'export default { setup() { let data = reactive({ count: 0 }) return { ...toRefs(data) } }} watchvue2.0 中使用 watch 来监听结果的变化 12345678910111213export default{ watch:{ name: { handler(newVal,oldVal){ ... }, deep: true, immediate: true } }}// 或使用this.$watch('name',() => {...},{deep:true}) vue3.0 兼容 2.0 的写法,也提供了新的 api.分别是 watch 和 watchEffect.watch 与2.0 $watch 用法一样.但可以监听单个值和函数的返回值,还可以监听多个数据源(放在一个数组中).watchEffect会传入一个函数,然后立即执行这个函数,对函数中的响应式依赖进行监听,当依赖变化时,重新调用传入的函数.1234567891011121314import { ref, watchEffect } from 'vue'export default { setup() { const id = ref('0') watchEffect(() => { // 先输出 0 然后两秒后输出 1 console.log(id.value) }) setTimeout(() => { id.value = '1' }, 2000) }} vue2.0 中$watch 会返回一个函数用于停止监听.vue3.0 中 watch 和 watchEffect 也会返回一个用于 unwatch 的函数. computedvue 3.0 中 computed 与 vue2.0 一样. 12345678910111213141516171819202122// 2.0computed:{ getName(){ return this.firstName + this.lastName; }}// vue3.0const info = reactive({ firstName: 'xx', lastName: 'xxx'})// getterconst getName = computed(() => info.firstName + info.lastName)// 或 getter + setterconst getName = computed({ get: () => info.firstName + info.lastName, set(val){ const name = val.split('-') info.firstName = name[0] info.lastName = name[1] }}) readonly获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。只读代理是深层的:访问的任何嵌套 property 也是只读的。 provide 和 injectprovide 和 inject 启用依赖注入。只有在使用当前活动实例的 setup() 期间才能调用这两者。","categories":[{"name":"前端 Vue","slug":"前端-Vue","permalink":"http://yoursite.com/categories/前端-Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"http://yoursite.com/tags/Vue/"}]},{"title":"认识基金","slug":"认识基金","date":"2020-07-15T11:47:48.000Z","updated":"2021-08-30T11:49:57.529Z","comments":true,"path":"2020/07/15/认识基金/","link":"","permalink":"http://yoursite.com/2020/07/15/认识基金/","excerpt":"","text":"认识基金基金的种类按照投资品种分类,可以分为:1.股票基金,2.债券基金,3.混合基金,4.货币基金 股票基金,他主要投资的品种是股票和债券,股票品种必须占总品种的80%以上,而债券则必须低于20%。债券基金,一般投资的是股票和债券,他叫债券基金所以他投资品种的80%必须是债券,也就是说债券必须占大多数。货币型基金,一般对于投资的债券其实是没有要求的,他的投资产品一般为短期债券和银行收益比较稳定的债券等。我们常用的余额宝其实就是货币基金的一种。最后来说混合基金。一般混合型基金就是股票和债券的混合品种,由于是混合型,所以对股票和债券的占比并没有要求。基金经理可以随意自行买入不同比例的股票和债券。比如市场好的时候就买入多一点的股票来获取更多的利润,市场不好的时候就买入多一点的债券来分摊风险。当然啦,混合基金也分为偏股票型混合与偏债券型混合,按照名字来说他的股票和债券的占比也会有所不同。 按照交易渠道来分,又可以分为两种,分别是场内基金和场外基金。 场内基金说白了就是在证券公司内部才能购买的基金。购买这种基金一般来说需要去券商网点开通相应的证券账号。而场外基金呢,是由第三方的机构帮助你去购买基金。比如在支付宝或微信上都能购买基金,这就是所谓的场外基金。场外基金的好处是不用开户,也不用去下载app,并且还能给你自动设置投资的额度和周期。 按照运作方式来分可以分为开放式基金和封闭式基金。 我们市面上绝大部分基金都是开放式基金。所谓开放式基金,就会随拿随取的,一般来说没有任何的年限要求。而封闭式基金则有一定的年限要求 按照投资方式来分可以分为主动性基金和被动型基金两种。 主动基金一般由基金经理自行判断买入和卖出时间,还有自定决定买入哪些股票债券他们的持仓比例是多少,这都是由管理这只基金的基金经理来决定。因此这就很考验基金经理的投资和选择产品的水平了。而被动型基金,也就是我们所天天念叨的指数型基金。他的买入卖出的时机和持仓的成分股等都跟基金经理的主观判断没关系,他只跟国家的经济和大盘的点数有关。 辨识基金公司名称 + 投资方向/基金特点 + 基金类型,基金的名字大多是由这3部分组成的。国泰沪深 300 指数 A ,就是指 国泰基金管理有限公司 出品的 追踪“沪深 300”这个指数的 基金。关于名字中的“优选”“灵活配置”“价值”“量化”,选基金的时候必须遵守一个原则,一切带有修饰词的名称都是纸老虎。 基金名字中的ABC货币基金 ABC 的区别在于申购门槛不同。A 的后缀的呢,一般申购门槛比较低,面向的是普通投资者. B 和 C的呢,一般申购门槛都比较高,主要面向的是资金量大的用户或者机构投资者。 债券基金的 ABC 主要在于收费方式的不同。债券基金中 A 类,一般是前端收取申购费,也就是在买的时候,不管你打算持有这只基金多长时间,都马上收取申购费。B 类呢,一般是后端收取申购费,就是买的时候先不扣你的申购费,等赎回的时候才一次性收取。那关于后缀为 C 的,一般会免去申购费,短期投资选 C,长期投资选A 或 B. 指数基金的 A和C,A 类不收销售服务费,但是会收申购费,赎回费,根据持有时长变化。C 类不收申购费,但要比 A 类多收一定的销售服务费,按日计提。持有时间大于7 天,赎回费率为 0。一般持有一年以内买 C,持有一年以上就买 A。 场内基金和场外基金的具体区别1.费率的区别。场内交易费率更便宜一些。2.基金购买或申购时价格的区别。场内基金购买的时候它的价格是实时波动的,就像股票一样。比如上午10点的时候它的价格是1块钱,这个时候你可以以1块钱的价格买入。到了下午2点,他的价格变成了2块钱的时候,你这时想买就必须花2块钱去买了。外基金他的价格是不受价格实时波动的。不管你如何波动,只要你是在下午三点收盘前申购,它都是以下午三点收盘的价格购入。3.购买份数有区别。场内基金对购买份数有要求。跟股票一样,最少需要买1手,也就是100股。而场外就没有这个要求啦,最低好像是十块钱起投资,不管多少手都行,当然也没有多少手这个概念。4.投资方式的区别。场外基金相比较场内基金在这块操作起来更便捷。比如,场外基金可以设置自动定投操作。5.到账时间。场内基金买入后T+1日就可以卖出,而资金在成功交易后即可使用。场外基金一般申购后T+2日才可以赎回,资金到账时间一般为T+1到7个工作日。 货币基金货币基金的选择1.成立时间;最好是在3-5年以上。首先他的历史收益没办法进行很有效的参考2.基金的规模;规模适中的最好。最低不能低于20亿3.流动性;一般来说有T+0和T+1两种交割方式。具体选择哪种看个人,我更倾向于选择T+0的方式。4.收益率.正常的收益率情况,我们可以通过“万分收益率”和“7天年化收益率”来计算。万份收益了就是按照上一日或者上一个交易日的收益来算一万块本金能赚多少钱。而7天年化收益率则是,根据过去7天的收益总合计算出年化收益率。 债券基金通常来说 1 年之内要用的钱,用货币基金打理是非常合适的,流动性好,安全性高,收益也远高于银行活期利息。投资股票类基金,比如之后会讲到的指数基金,用的是 3 年以上用不到的闲钱。那么如果是 1-3 年用不到的钱怎么办?放货币基金有点太可惜啦,此时纯债基金就是非常好的选择,通常来说纯债基金可以有 6%-7%的年化收益率。还有一种情况,就是整个股票市场都涨疯了的牛市,此时低估的指数基金已经找不到了。但是我们的投资还是要继续呀,这时我们就可以卖出已经高估的指数基金,买入纯债基金。 主动型基金的选取指标主动基金最大的好处其实也是他最大的毛病就是过于依赖了基金经理的个人能力。因为决定你购买的基金的成分股是哪些的,就是这个基金经理。如果这个基金经理个人能力牛逼,那么可能他选的成分股就厉害。就会使得这只基金涨的多,但是反之如果这只基金经理的能力一般般或者很水,那么他这一篮子股票尽选一些比较垃圾的股票作为成分股,那么可想而知你买的基金也够呛。这也说明一只基金经理的能力的重要性,他决定了这只基金业绩到底是牛逼还是垃圾。 到底该如何去挑选一只主动基金和如何去判断一只基金的基金经理到底如何。第一,看收益率。一般来说看累积3年以上的收益率,然后选择收益率高的基金。第二,看基金的成立时间。一般也是选成立3年以上的基金最好。第三点,手续费。第四点,基金经理的更换频率越低越好。第五点,看基金公司的盈利能力。最重要的是看该基金的持仓. 指数基金在中国,主要的指数我们分为四大类型。上证50,中证500,沪深300和创业板指数。 上证50就是指在上海交易所,也称之为上交所上市的所有公司里面挑选市值最高的前50只股票,按照一定的权重比例和计算公式算出来的一个值。而沪深300指的是在上海交易所和深圳交易所上市的所有公司中,挑选出市值和业绩最好的前300家公司,按照一定的权重比例和计算公式算出来的一个值。他反应的就是整个中国上市公司的一个总体表现。接着中证500是由全部A股中剔除沪深300指数成份股及总市值排名前300名的股票后,总市值排名靠前的500只股票组成,综合反映中国A股市场中一批中小市值公司的股票价格表现。创业板指数,就是以起始日为一个基准点,按照创业板所有股票的流通市值,一个一个计算当天的股价,再加权平均,与开板之日的“基准点”比较。 ETF,ETF 联接,LOFETF 基金。交易型开放式指数基金,通常又被称为交易所交易基金.特点一:跟踪指数的效果更好。特点二:相对场外的指数基金,购买成本更低。 ETF 联接。它其实就是为了方便我们普通投资者购买 ETF 基金而专门设计的。对于普通投资者而言,ETF 基金只能通过证券交易软件进行场内买卖,但又有 100份起售的限制。ETF 联接就是买 ETF 基金的基金,一般以不低于 90%的仓位投资于该标的 ETF 基金。 用一句话总结就是:ETF 是指数的跟屁虫,ETF 链接是 ETF 的跟屁虫。LOF 基金是上市交易型开放式基金,大家看到名字里带 LOF 的指数型基金,就知道它既可以场内交易也可以场外交易了。","categories":[{"name":"基金","slug":"基金","permalink":"http://yoursite.com/categories/基金/"}],"tags":[{"name":"基金","slug":"基金","permalink":"http://yoursite.com/tags/基金/"}]},{"title":"Truffle 框架开发区块链智能合约","slug":"Truffle 框架开发区块链智能合约","date":"2020-07-07T11:33:27.000Z","updated":"2021-08-30T11:34:54.626Z","comments":true,"path":"2020/07/07/Truffle 框架开发区块链智能合约/","link":"","permalink":"http://yoursite.com/2020/07/07/Truffle 框架开发区块链智能合约/","excerpt":"","text":"Truffle 框架开发区块链智能合约Truffle是针对基于以太坊的Solidity语言的一套开发框架。本身基于Javascript。是基于 node.js 和 web3.js 的框架进行合约的编译,发布和调用.如果熟悉 node 开发,可以直接使用 web3.js 进行开发.如果擅长 java 语言,可以使用 web3j 开发. 安装开发及测试中需要安装Ethereum客户端,以支持JSON RPC API调用开发环境,推荐使用EthereumJS TestRPC。如果使用 vscode 编辑器还可以安装 solidity 插件.123npm install -g trufflenpm: npm install Web3npm install -g ganache-cli # ethereumJs TestRPC 以太坊有很多客户端,基于 Go 语言开发的以太坊客户端 Geth 提供了 js 的运行环境可以基于 console 和 script.而 EthereumJS TestRPC 是一个完整运行在内存中的区块链 可以再开发设备上适时返回,快速验证.等测试完成后在使用真实客户端发布. EthereumJS TestRPC12npm install -g ethereumjs-testrpctestrpc # 启动 搭建私有链编写创世区块配置文件,然后执行初始化操作,完成后就可以启动私有链了.12345touch geth/gensis.json # 配置文件mkdir dbgeth --datadir \"./db\" init gensis.json # 执行初始化命令geth --datadir \"./db\" --rpc --rpcaddr=0.0.0.0 --rpcport 8545 # 启动geth --datadir \"./db\" attach # 进入 js 控制台 创建项目1truffle init 初始化之后会出现几个目录: test 用来测试应用和合约文件. truffle.js 是 truffle 的配置文件. contract(意为合同)contract 是默认合约文件存放的地址.合约后缀是.sol表示 solidity,执行 truffle compile --compile-all编译合约.文件名和代码中的合约名要一致,区分大小写.通过 import 来什么依赖,会安装正确顺序来依次编译和关联库.编译输出在 build/contracts 文件中. migrations(意为迁移)migrations存放发布脚本的地址.移植是由一些 js 文件协助发布到以太坊网络.主要目的是用来缓存你的发布任务,当你的工程发生了一些重要改变,你将创建新的移植脚本来讲这些变化移植到区块链上.之前运行移植的记录历史会通过一个特殊的 migrations 来记录到链上.truffle migrate命令会很自信所有该目录下的移植脚本.文件名以数字开头描述结尾比如1_initial_migration.js,deployer 是部署器,你可以按照一定顺序发布任务,会按照从上到下依次执行.或使用 promise 来做出一个队列.,要实现不同条件的不同部署,可以给脚本添加第二个参数 network.部署器有很多函数,deploy 函数来发布指定合约或合约数组,如果合约依赖某个库应该先部署这个依赖库.1234const ZhouCoin = artifacts.require(\"ZhouCoin\"); // 类似于 node 中的 node 中的 commonJSmodule.exports = function(deployer){ deployer.deploy(ZhouCoin)} link 函数用来连接一个已经发布的库或合约.then 函数是 promise 语法糖.exec 函数来执行外部脚本. 构建应用app 目录是文件运行默认目录.truffle 默认构建有一些特性,在浏览器内自动初始化应用包括引入编译合约,部署合约,配置以太坊客户端信息.包含常见的依赖如 web3 和 ether Pudding,内置 es6 和 jsx, sass 支持, uglifyjs 支持.app - javascripts / app.js - stylesheets / app.css - images - index.html 然后在配置文件中配置123456789101112{ \"build\":{ \"index.html\": \"index.html\", \"app.js\": [ \"javascripts/app.js\" ], \"app.css\": [ \"stylesheets/app.scss\" ], \"images/\": \"images/\" }} 然后truffle build 命令来创建前端工程.需要注意当前不支持 import 和 require,不能使用 webpack 和 commonJS 等工具来管理依赖. 合约交互以太坊网络把数据的读写做了区分,写数据被称为 交易 Transaction, 读数据被称为 调用 Call. 交易: 交易的接收地址如果是 合约地址会触发智能合约的函数运行,需要花费 gas.交易需要时间,函数执行后并不能立刻得到执行结果,大多数情况下很自信交易不会返回值,而是返回一个交易的 ID. 调用: 调用可以再网络上执行代码,但不改变数据(也许仅仅是临时变量被更改).调用执行是免费的,典型行为就是读取数据,通过调用执行合约函数,你会立即得要结果.不花费 gas 也不改变网络化妆,立即执行且有返回结果. 接口(abstract)12345678910111213141516171819202122232425import \"ConvertLib.sol\";contract MetaCoin { mapping (address => uint) balances; event Transfer(address indexed _from, address indexed _to, uint256 _value); function MetaCoin() { balances[tx.origin] = 10000; } function sendCoin(address receiver, uint amount) returns(bool sufficient) { if (balances[msg.sender] < amount) return false; balances[msg.sender] -= amount; balances[receiver] += amount; Transfer(msg.sender, receiver, amount); return true; } function getBalanceInEth(address addr) returns(uint){ return ConvertLib.convert(getBalance(addr),2); } function getBalance(address addr) returns(uint) { return balances[addr]; }} 合约有三个方法和一个构造方法.三个方法都可以执行交易和调用.其中只有 sendCoin 函数对网络造成了更改,所以它作为一个交易来执行.getBanance 函数是一个典型的调用函数,从网路中读取数据.合约可以触发事件,事件与 web3 一样.合约接口都有一个 deployed()方法,表示部署到网络合约对应的抽象接口实例.或者通过 at(‘0x..’) 由地址得要接口实例.new()函数用来部署一个全新的合约到网络中,这是一个交易会改变网络状态. 测试合约truffle 使用 mocha 测试框架来做自动化测试,使用 Chai 来做断言.Truffle 只会运行js,es,es6,jsx 结尾的测试文件.命令truffle test xx.js来测试某文件. 控制台在测试时与合约交互是很频繁的,Truffle 提供了一个交互式控制台可以用更简单的方式来和合约交互.truffle console来启动控制台,会自动连接到一个运行中的以太坊客户端,控制台支持 truffle 命令,migrate --reset 与外部执行truffle migrate --reset效果是一样的. 外部脚本你也行会经常使用外部脚本与你的合约进行交互,Truffle 提供了一个简单的方式进行这个truffle exec xx.js,为了外部脚本执行正常,truffle 需要他们能通过 js 的模块方式导出一个函数,且有一个回调函数作为参数.123module.exports = function(callback){ // ...} 工作流truffle 提供truffle watch和truffle serve命令,一个是用作修改后重构合约,一个用做修改后重编译部署构建. truffle.js 配置文件123456789101112131415module.exports = { build: {}, // 构建 network: { development:{ // host,port,network_id: \"*\" }, staging:{ // host,port,network_id: \"*\" }, ropsten: { // host,port,network_id: \"*\" } }, // 指定移植时使用哪个网络 mocha: {} // 测试框架的配置选项} DAPP前端交互为了在前端 js 代码中能后使用 Truffle 提供的合约抽象,我们需要 truffle-default-builder,它可以帮助我们在把合约抽象整合到 js 中.1npm install --save [email protected] 然后在 truffle.js 中增加配置 network.development 配置.然后执行 truffle build 命令","categories":[{"name":"Truffle DAPP","slug":"Truffle-DAPP","permalink":"http://yoursite.com/categories/Truffle-DAPP/"}],"tags":[{"name":"Truffle DAPP","slug":"Truffle-DAPP","permalink":"http://yoursite.com/tags/Truffle-DAPP/"}]},{"title":"搭建 MOCK 服务","slug":"搭建 MOCK 服务","date":"2020-06-30T09:57:43.000Z","updated":"2021-08-30T09:59:14.927Z","comments":true,"path":"2020/06/30/搭建 MOCK 服务/","link":"","permalink":"http://yoursite.com/2020/06/30/搭建 MOCK 服务/","excerpt":"","text":"搭建 MOCK 服务安装 mock.js12npm run mockjsnpm install supervisor -g supervisor是一个用来运行node程序监控程序。(类似于 pm2)它是Nodejs的一个很小的监控脚本。它运行在你的程序中,并且监控你的代码变化,所以你可以进行代码热更新,而不用担心内存泄漏和确保你清理所有模块间的引用。 123\"scripts\":{ \"mock\": \"supervisor -w mock ./xxx/http.js\",} 文档Mock.mock(rurl,rtype,data) rurl: 表示需要拦截的 URL,可以是 URL 字符串或 URL 正则。例如 /\\/domain\\/list.json/、’/domian/list.json’ rtype: 可选.表示需要拦截的 Ajax 请求类型。例如 GET、POST、PUT、DELETE 等。 data: 对象,字符串, 函数. Mock.setup( settings )配置拦截 Ajax 请求时的行为。支持的配置项有:timeout。Mock.setup({ timeout: ‘200-600’}) Mock.valid(template, data)校验真实数据 data 是否与数据模板 template 匹配。 Mock.toJSONSchema(template)把 Mock.js 风格的数据模板 template 转换成 JSON Schema。 Mock.Random是一个工具类,用于生成各种随机数据。Mock.Random 的方法在数据模板中称为『占位符』,书写格式为 @占位符(参数 [, 参数]) 。1234567var Random = Mock.RandomRandom.email()// => \"[email protected]\"Mock.mock('@email')// => \"[email protected]\"Mock.mock( { email: '@email' } )// => { email: \"[email protected]\" } 提供的占位符有以下类型:|type|method||Basic|boolean,interger,float,string,date,time,now…||Image|image,dataImage||Color|color||Text|parafraph,sentence,word,title,cparafraph…||Color|color||Name|first,last,name,cfirst,clast,cname||Web|url,email,ip,tld,domain||Address|area,region||Other|id,picl…| 使用 node 的http模块1234567891011121314151617181920212223242526272829303132333435const http = require('http')const Mock = require('mockjs')http .createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'application/json;charset=utf-8', 'Access-Control-Allow-Origin': req.headers.origin || '*', 'Access-Control-Allow-Methods': '*', 'Access-Control-Allow-Headers': '*', 'Access-Control-Allow-Credentials': true, 'Cache-Control': 'no-cache,no-store', // clear cache }) if (req.method === 'OPTIONS') { res.end(null) } if (req.method === 'POST') { let postData = '' req.addListener('data', (dataBuffer) => (postData += dataBuffer)) req.addListener('end', () => { postData = JSON.parse(postData) const data = [] setTimeout(() => { res.end(JSON.stringify(data)) }, parseInt((Math.random() - 0.5 + 1) * 500, 10)) // 随机数 }) } if (req.method === 'GET') { setTimeout(() => { res.end(Mock.mock('@cname')) }, parseInt((Math.random() - 0.5 + 1) * 500, 10)) // 随机数 } }) .listen(1111)console.log('listening port 1111') 使用concurrently同时开启前端服务和后端服务 安装npm install concurrently --save-dev 更改 package.json 的 script `”xx”: “concurrently \\”npm run xxx\\” \\”npm run xxx2\\””","categories":[{"name":"前端 Mock","slug":"前端-Mock","permalink":"http://yoursite.com/categories/前端-Mock/"}],"tags":[{"name":"Mock","slug":"Mock","permalink":"http://yoursite.com/tags/Mock/"}]},{"title":"JS 函数式编程指南","slug":"JS 函数式编程指南","date":"2020-06-16T05:19:21.000Z","updated":"2021-08-30T09:51:01.505Z","comments":true,"path":"2020/06/16/JS 函数式编程指南/","link":"","permalink":"http://yoursite.com/2020/06/16/JS 函数式编程指南/","excerpt":"","text":"JavaScript 函数式编程第一章 走进函数式 例子1234567891011121314document.querySelector(\"#msg\").innerHTML = '<h1>hello world</h1>';// 用函数封装这段代码function printMessage(elementId,format,message){ document.querySelector(`#${elementId}`).innerHTML = `<${format}>${message}</${format}>`;}printMessage('msg','h1','hello world');// 以上仍然不是一段可复用的代码,用函数式编程如下var printMessage = run(addToDom('msg'),h1,echo);printMessage('hello world');// 将程序分解为多个函数,再将他们组合起来完成一系列的操作.// 当需求更改为在控制台打印 3 遍文本信息,就可以改为一下代码var printMessage = run(console.log,repeat(3),echo);printMessage('hello world'); 函数式编程的特征: 声明式编程,纯函数,引用透明,不可变性. 声明式编程目前更主流的是命令式编程和面向对象编程.我们来看一个命令式的例子.假设你需要计算一个数组中所有数的平方.12345var array = [0,1,2,3,4];for (let i = 0; i < array.length; i++) { array[i] = Math.pow(array[i],2);}array; // [0,1,4,9,16] 命令式编程是很具体的告诉计算机如何执行某个任务.而声明式编程是将程序的描述和求值分离开来.它关注与如何使用各种表达式来描述程序逻辑.你可以在 SQL 语句中找到声明式编程的例子.可以将 es6 的 lambda 表达式和箭头函数将循环抽象成函数,减少代码的书写.1[0,1,2,3,4].map(num => Math.pow(num,2)); // [0,1,4,9,16] 为什么要去掉代码循环?因为循环是命令控制结构,很难重用,并且很难插入其他操作中.并且要尽量做到无副作用无状态变化,既纯函数. 副作用带来的问题和纯函数函数式编程基于一个前提,既使用纯函数构建具有不变形的程序.考虑以下函数1234var counter = 0;function increment(){ return ++counter;} 以上函数不是一个纯函数,它在读取外部资源时会产生副作用.还有一个例子 Date.now,它的输出是不可预见和不一致的.另一个副作用是通过 this 关键字访问实例数据时,由于 js 语言的特性,它决定了一个函数在运行时的上下文,这往往导致很难去推理代码.以下行为都可能导致副作用: - 改变一个全局的变量,属性,或数据结构 - 改变一个函数参数的原始值 - 处理用户输入 - 抛出异常又被当前函数捕获 - 屏幕打印或记录日志 - 查询 html 文档,浏览器数据或访问数据库 以下案例是一个命令式程序,它听过 SSN 号码找到一个学生的记录渲染到页面上.123456789function showStudent(ssn){ var student = db.get(ssn); if(student != null){ document.querySelector(`#${elementId}`).innerHTML = `${student.ssn} - ${student.name}`; }else{ throw new Erroe('student not found!') }}showStudent('444-44-4444'); 该函数副作用有: 访问外部数据 db,全局变量 elementId 可能随时会改变,直接修改可外部共享的全局资源 html,抛出的异常会导致整个程序栈回退并结束.如何使用函数式编程应对这种情况呢? 首先将长函数分离成多个且单一的短函数,其次通过显式的将外部依赖都定义为函数参数来减少副作用. 1234567891011121314151617var find = curry(function(db,id){ var obj = db.get(id); if(obj === null){ throw new Error('object is not found') } return obj;})var csv = (student){ return `${student.ssn} - ${student.name}`}var append = curry(function(elementId,info){ document.querySelector(elementId).innerHTML = info;})// 调用以上函数var student = run(append('#student'),csv,find(db));student('444-44-4444'); 这个程序仍然有些问题,find 函数有一个检查 null 的分支.当一个函数能够确保有相同的返回值,它使得函数的结果一致且可预测,这就是纯函数的一个特质,引用透明. 引用透明和可置换性如果一个函数对于相同的输入始终产生相同的结果,它就是引用透明的.1var increment = counter => counter + 1; 它不仅能使代码易于测试,还更容易推理整个程序. 储存不可变数据不可变数据指那些创建后不能更改的数据,js 的所有基本类型本质上不可变,但数组或对象都是可变的.1234567var sortDesc = function(arr){ return arr.sort(function(a,b){ return b-a; })}var arr = [1,2,3,4]sortDesc(arr);// [4,3,2,1] 乍一看这段代码正常,但是 array.sort 函数是有状态的,会导致排序过程中产生副作用,因为原始的引用被修改了. 总结函数式编程是指为创建不可变程序,通过消除外部可见的副作用,来对纯函数的声明式求值过程. 第二章 高阶 JavaScript 一等函数函数是函数式编程的工作单元和中心,函数只有在返回一个有价值的结果(而不是 null和 undefined)时才有意义.同时,我们需要区分表达式(返回一个值的函数)与语句(不返回值得函数).函数式编程完全依赖表达式,无值函数在函数式编程下没有意义.在 js 中,任何函数都是 Function 类型的一个实例,函数 length 属性可以获取形参的长度,apply 和 call 可以调用函数并加入上下文,不同的是 apply 函数接收一个参数数组,而 call 接收一系列参数.但函数式编程不建议这样做,因为它永远不会依赖于函数的上下文状态. 1234567891011// 创建一个函数,接受一个函数参数,并返回取反其结果的函数function negate(func){ return function(){ return !(func.apply(null,arguments)); }}function isNull(val){ return val === null;}var isNotNull = negate(isNull);isNotNull(null); // false 闭包和作用域在 js 之前闭包只存在于函数式编程语言中,js 是第一个在主流开发中应用闭包的语言.闭包是一种能后在函数声明过程中将环境信息和所属函数绑定在一起的数据结构.从本质上讲,闭包就是函数继承而来的作用域.123456789function makeAddFunction(amount){ function add(number){ // add 函数可以通过词法作用域访问 amount return number + amount; } return add;}var addTenTo = makeAddFunction(10);addTenTo(1); // 11 闭包会在其声明时记住其作用域内的所有变量,并防止他们被垃圾回收机制回收.js 的作用域分为全局作用域,函数作用域,伪块作用域. - 全局作用域,window 或 global,会有副作用,尽量避免使用 - 函数作用域,可以嵌套,由内而外向上查找,直到全局作用域,推荐使用. - 伪块作用域,如 for,while,id,switch 语句,with,try..catch..等.无法从块外部访问 123456function dowork(){ if(!myVar){ var myVar = 10; } console.log(myVar); // 10} js 有一个内部机制,将所有变量和函数提取至作用域的顶部.es6 提供了 let,const 等关键字定义的变量不会进行提升. 闭包的实际应用 模拟私有变量js 并没有一个 private 修饰符来限定对象中私有变量和函数的访问,我们可以使用闭包来完成.闭包还可以管理全局的命名空间,既模块模式,它采用立即执行函数表达式IIFE,在 封装内部变量的同时,有效减少了全局引用.123456789101112// 一个模块框架的示例var myModule = (function myModule(export){ // 给 IIFE 一个名字方便栈追踪 let _myprivateVar = ...;//无法从外部访问这个变量,但对内的方法可以访问. export.method1 = function(){ ... } export.method2 = function(){ ... } return export;}(myModule || {})); 对象 myModule 在全局作用域创建,之后传递给一个 IIFE 函数表达式并立即执行.由于 js 的函数作用域,变量_myprivateVar 和其他变量都是函数的局部变量,闭包使得返回的对象能够安全的访问模块中的所有内部属性. 异步服务端调用 js 中的函数可以作为回调函数传递给其他函数,假设需要对服务器发起一次请求,并在响应时得到通知,常用的方式就是提供一个回调函数.123456getJson('/student',(student) =>{ getJson('/students/grades', grades => processGrades(grades), error => console.log(error)), error => console.log(error)}) getJson 是一个高阶函数,它接收两个回调作为参数,一个处理成功的函数,一个处理失败的函数.如果需要多次请求很容易进入回调地狱. 第三章 轻数据结构,重操作 理解程序的控制流程序为实现业务目标进行的路径就是控制流.命令式程序需要通过暴露所有的必要步骤才能详细的描述其控制流,这里面通常涉及大量的循环和分支以及各种变量.然而函数式程序多使用简单拓扑链接的黑盒操作组合成较小的程序化控制流,这些链接在一起的操作只是一些能够将状态传递给下一个操作的高阶函数. 1opta().optb().optc()... // 链式结构 链接方法 1'function programing'.substring(0,10).toLowerCase() + 'is fun'; 通过一系列变换后的结果与原字符串毫无引用关系,无副作用.如果用更加函数式的写法如下:1concat(toLowerCase(subString('function programing',1,10)),'is fun'); 这样虽然跟复合函数式的定义,但是较难阅读,需要一层层剥离外部函数,就行剥离洋葱一样. 函数链面向对象将继承作为代码重用的主要手段,比如在 java 中有继承与基础接口 List 的 ArrayList,LinkedLise 等.但在函数式编程中是使用如数组这样的普通类型并施加在一套高阶操作上,通常接收函数作为参数,减少副作用等等. lambda 表达式,也被称为箭头函数.源自函数式编程,可以用较简介的语法声明一个匿名函数.它总是返回一个值.且能够与 map,reduce 等高阶函数配合使用.我们在接下来用 lodash 函数库来演示,它为了能够替换 underscore 采用了和它一样的 API. 12// 用 map 做数据变换_.map([1,2,3],v=> 2*v); //2,4,6 我们不需要再写循环的代码,也不用处理奇怪的作用域问题了.由于其不可变,因此输出一个全新的数组.函数式库可以辅助我们开发,写出纯函数式的代码.map是一个只会从左到右遍历的操作,对应重右到左遍历必须反转数组,但 js 中的 Array.reverse() 会改变原数组,所以我们可以配合 lodash 中的 reverse 配合 map 进行操作.1_([1,2,3]).reverse().map(v => 2*v); // 6,4,2 高阶函数 reduce 将一个数组中的元素精简为一个值,该值是每个元素累计而得.1_([1,2,3]).reduce( (memo,v) => memo+v,0 ) 除此之外,lodash 还提供了 every,some,filter 等辅助函数. 代码推理函数式编程中每个函数只完成一部分功能,但组合在一起就可以解决很多问题,下面介绍一种能够连接一组函数来构建程序的方法(声明式惰性计算函数链).假设需要对一组姓名进行读取,去重,排序等操作,命令式代码如下:1234567891011121314151617181920var names =['alozno church','Jaskell cjrl','Terjdf','asdfgg']function handleName(names){ var result = [] for(let i=0;i<names.length;i++){ // 遍历数组 var n = names[i] if(n !== null && n !== undefined){ // 检查是否合法 var ns = n.replace(/_/,' ').split(' ') // 规范数据 for(let j=0;j< ns.length;j++){ var p = ns[j] // 处理数据 p = p.charAt(0).toUpperCase() + p.slice(1); ns[j] = p; } if (result.indexOf(ns.join(' '))< 0) { // 去除重复元素 result.push(ns.join(' ')) } } } result.sort(); // 数组排序} 用函数式代码实现如下:1234567_.chain(names) // 初始化函数链 .filter(isValid) // 去除非法值 .map(s => s.replace(/_/,' ')) // 规范数据 .uniq() // 去重 .map(_.startCase) // 大写首字母 .sort() // 排序 .value(); // 返回封装对象的最终值 对一个对象使用 chain 方法会封装这个对象,并之后的每次方法调用都返回这个封装的对象,当完成计算使用 value()函数取得最终值.使用 chain 链式调用的好处是可以创建具有惰性计算能力的复杂程序,在调用 value()之前并不会真正的执行任何操作. 链中的每个函数都以一种不可变的方式来处理换上一个函数构建的新数组.这有助于过渡到 point-free 编程风格的理解. 类 SQL 的数据:函数即数据.12select p.firstname,p.birthYear from person where p.birthYear > 1903 and p.country IS Not 'US'Group By p.firstname,p.birthYear lodash 支持一种称为 mixins 的函数,可以为核心库拓展新的函数.123456_.mixin({ 'select' : _.pluck, 'from': _.chain, 'where': _.filter, 'groupBy': _.sortByOrder}) 应用此 mixin 对象后就可以编写类 sql 的程序12345_.from(persons) .where(p => p.birthYear > 1903 && p.country !== 'US') .groupBy(['firstname','birthYear']) .select('firstname','birthYear') .value(); 递归递归是一种通过将问题分解为较小的自相似问题来解决问题本身的技术,递归函数主要包含两方面,一是终止条件,二是递归条件.来解决一个简单的问题,对数组中所有的值进行求和.1234567//老规矩,先命令式,再函数式.var acc = 0;for(let i=0;i<nums.length;i++){ acc += nums[i]}// 函数式_(nums).reduce((acc,current) => acc + current, 0); 递归和迭代是一个硬币的两面,在不可变条件下递归提供了一种更强大的迭代替代方法.纯函数式语言甚至没有标准的循环结构,如 for,while 等,因为所有循环都是递归完成的.123456789// 递归求和function sum(arr){ if(_.isEmpty(arr)){ // 终止条件 return 0; } return _.first(arr) + sum(_.rest(arr)); // 递归条件}sum([]); // 0sum([1,2,3])// 6 从底层看,递归调用会在栈中不断堆叠,但算法满足终止条件时,运行时会展开调用栈并执行加操作,因此所有返回语句都将被执行,递归就是通过这种机制代替循环.但是注意编译器在处理循环的优化问题是很强大的,比如 es6 带来了尾调用优化,可以使递归和迭代的性能更加接近.123456function sum (arr,acc=0){ if(_.isEmpty(arr)){ // 终止条件 return 0; } return sum(_.rest(arr),acc+_.first(arr));//发生在尾部的递归调用} 我们之前已经利用函数式技术解析过一些扁平化数据,比如数组.但这些操作对树形数据结构是无效的. 因为 js 没有内置的树形对象,所以需要基于节点,创建一种简单的数据结构.节点包括当前值,父节点引用,以及子节点数组的对象. 树是包含了一个根节点的递归定义的数据结构. 12345678910111213141516171819202122class Tree{ constructor(root){ this._root = root; } static map(node,fn,tree = null){ // 使用静态方法避免与 Array.prototype.map 混淆 node.value = fn(node.value); if(tree === null){ tree = new Tree(node); } if(node.hasChildren()){ _.map(node.children,function(child){ Tree.map(child,fn,tree); }) } return tree; } get root(){ return this._root; } } 第四章 模块化且可重用的代码Unix 的脚本程序的编写如下1tr 'A-Z' 'a-z' < words.in | uniq | sort 这行代码对字符进行可一系列的变换,大小写转换,去除排序等.管道操作符 | 用于连接这些命令. 方法链接函数管道的比较在 Haskell 中私有一种符号::来描述函数,如下:1<function-name> :: <Input*> -> <output> 在函数式编程中,函数是输入和输出类型之间的数学映射.如 isEmpty 函数接收一个字符串并返回一个布尔值,使用该符号表示为:1234// haskell 描述isEmpty :: String -> Boolean// js lambda 描述const isEmpty = s => !s || !s.trim(); 链式调用xxx.xxx().xxx()虽然相比命令式代码提高了可读性,但是它与方法所属对象耦合在一起,只能使用由 Lodash 提供的操作,无法将不同函数库或自定义函数链接在一起.而管道是松散结合的有向函数序列,一个函数的输出会作为下一个函数的输入. 管道函数的兼容条件 类型: 函数的返回类型必须与接收函数的参数类型相匹配. 元数: 接收函数必须声明至少一个参数才能处理上一个函数的返回值.函数的参数长度和其复杂度成正比,只有一个单一参数的纯函数是最简单的,建议使用.但如何返回两个不同的值呢,函数式语言通过一个被称为元祖的类型达成.元组是不可变结构,将不同数据类型元素打包在一起,以便传递到其他函数中.如(false,'error message').但 js 并不原生的支持 tuple 类型,在 es6 的解构赋值特性下可以简明的键元祖值映射到变量中.123[first,last] = [false,'error message'];first // falselast // error message 元祖是减少函数元数的方式之一,但还可以引入函数柯里化来实现降低元数的同时,增强代码模块化和可重用性. 柯里化的函数求值js 允许在确实产生的情况下对常规或非柯里化函数进行调用,js 会将缺少的参数设置为 undefined ,这或许也是 js 并不原生支持柯里化的原因.如果不设置行参,仅仅依靠 arguments 对象问题会更糟糕.再看柯里化函数,它要求所有参数都被明确定义,当使用部分参数调用时,它会返回一个新的函数,在真正运行之前等待外部提供剩余参数.柯里化是一种在所有参数提供之前,挂起或延迟函数执行,将多参函数转换为一元函数序列的技术.1234// 具有三个参数的柯里化定义curry(f) :: (a,b,c) -> f(a) -> f(b) -> f(c);const add = x => y => z => x + y + z; 以上代码表明,curry 是一种从函数到函数的映射,将输入(a,b,c)分解为多个分离的单参数调用.在纯函数式语言中,柯里化是原生特性,是任何函数定义中的组成部分.由于 js 不支持自动柯里化函数,需要编写一些代码来启用它. 12345678// 二元参数的手动柯里化function curry2(fn){ return function(firstArg){ return function(secondArg){ return fn(firstArg,sencondArg); } }} 如上所示,柯里化是一种词法作用域(闭包),其返回的函数只不过是一个接受后续参数的简单嵌套函数包装器.像 lodash 一样,ramda.js 是一个函数式编程辅助库,之所以使用它是因为它很容易实现参数柯里化,惰性应用,和函数组合.1234567891011const checkType = curry2(function(typeDef,actualType){ if(R.is(typeDef,actualType)){ // 使用 ramda 中 is()检查类型信息 return actualType; }else{ throw new TypeError('type mismatch') }})checkType(String)('Curry'); // StringcheckType(String)(42); // type mismatch 通过 R.curry 或 lodash 的curry 可以对任意数量参数的函数进行自动的柯里化.可以将自动柯里化想象为基于声明参数的数量而人工创建对应嵌套函数作用域的过程. 部分应用(partial 偏函数)和函数绑定部分应用是一种通过将函数的不可变参数子集初始化为固定值来创建更小元数函数的操作.简单说就是,如果存在一个具有五个参数的函数,给出三个参数后就会得到一个具有两个参数的函数.柯里化的函数本质上也是部分应用的函数.他们主要的区别在于参数传递的内部机制和控制.123456//体积计算函数的部分应用function volume(l) { return (w, h) => { return l * w * h }} 柯里化在每次分布调用时都会生成嵌套的一元函数,在底层函数的最终结果由这些一元函数逐步组合产生,所以可以完全控制函数求值的时间和方式.123456789101112131415161718// partial 的实现function partial (){ let fn = this, boundArgs = Array.prototype.slice.call(arguments); let placeholder = <<partialPlaceholderObj>> // 占位符,lodash 使用下划线对象作为占位符,其他实现使用 undefined 来表示应略过该参数 let bound = function(){ // 使用部分参数创建新函数 let position = 0,length= args.length; let args = Array(length); for(let i=0;i< length;i++){ args[i] = boundArgs[i] === placeholder ? arguments[position++]: boundArgs[i] } while(positoion < arguments.length){ args.push(arguments[positoion++]) } return fn.apple(this,args) } return bound;} 部分应用将函数的参数与一些预设值绑定(赋值),从而产生一个拥有更少参数的新函数.该函数的闭包中包含这些已经赋值的参数,在之后的调用中完全被求值.一种类似的 js 原生技术被称为函数绑定,即 Function.prototype.bind() 12345678_.partial(finc,[params...])// 创建一个函数,该函数会调用 func,并传入预设的参数,与 _.bind 不同的是,它不会绑定 this.// 例子var greet = function(greeting,name){ return greeting + ' ' + name;}var sayHelloTo = _.partial(greet,'hello');sayHelloTo('fred'); // hello fred 组合函数管道我们来看一个例子: 12345678const str = `we can only see a shortdistancethree`const explode = str => str.split(/\\s+/);const count = arr => arr.length;const countWords = R.compose(count,explode);countWords(str); // 8 这段程序有趣的地方在于,直到countWords被调用才会触发求值,用其名称传递的函数 explode 和 count 在组合中是静止的.这种将函数的描述和求值的行为分开正是函数式编程的强大之处.我们来看一下 compose 的实现: 123456789101112function compose(){ let args = arguments; let start = args.length -1; return function(){ let i = start; let result = args[start].apply(this,arguments); while(i --){ result = args[i].call(this,result); } return result; }} 使用 Ramda 这种函数库的好处就是所有函数都已经正确的柯里化,在组合函数管道时更具有通用性.我们注意到 compose 函数是从参数最右到最左的顺序,而unix管道符 | 是从左到右执行的.我们可以使用 compose 的镜像函数 pipe 来获得 管道符一样的效果.不必像原来那样正式的声明参数来创建新的函数,函数式鼓励这种风格,它被称为 point-free.point-free 使得 js 代码更接近 haskell 和 unix 的理念.柯里化能够灵活地定义一个只差最后一个参数的内联函数,这种编码风格被称为 Tacit 编程. 使用函数组合子来管理程序的控制流.命令式代码能够加 if-else 和 for 语句这样的过程控制机制,而函数式则不能.组合器是一些可以组合其他函数和组合子,作为控制逻辑运行的高阶函数.除了 compose 和 pipe,常见的组合子如下: identity,意为身份,特性.它是返回与参数同值得函数. identity :: a -> a 它广泛用于函数数学特性的检验 tap,意为轻拍.它能够将无返回值的函数嵌入到函数组合中,而无需创建其他代码. tap:: (a -> *) -> a -> a该函数接受一个输入对象a 和一个对 a 执行操作的函数,使用提供的对象调用给定的函数,然后在返回该对象. alternation,alt 组合子又叫 OR 组合子,能够在提供函数响应的默认行为时执行简单的条件逻辑. 12345const alt = function(func1,func2){ return function(val){ return func1(val) || func2(val) }} sequence,seq 组合子用于遍历函数序列,它以两个或以上的函数作为参数并返回一个新函数,会用相同的值顺序调用这些函数.实现如下: 123456const seq = function(){ const funcs = Array.prototype.slice.call(arguments); return function(val){ funcs.forEach(fn => fn(val)) }} seq 不会返回任何值,只会一个一个的执行一系列操作. fork(join) 组合子fork 用于需要以两中不同的方式处理单个资源的情况,该组合子需要以单个函数作为参数,即以一个 join 函数和两个 fork 函数来处理提供的输入,两个分叉函数的结果传递给join 函数. 第五章 针对复杂应用的设计模式 命令式错误处理的不足在命令式编程中,异常都是通过 try-catch 处理的.将可能出现问题的代码放在 try 代码块中,通过 catch 捕获异常.但是,这样的代码将不能组合或连在一起,这将严重影响代码设计.函数式程序不应抛出异常,因为抛出异常会导致难以与其他函数组合,违反了引用透明原则,会引起副作用. 一种更好的解决方案Functor(函子).通常我们在判断 null 和 undefined 时会写啰嗦且重复的的判断代码.函数式以一种完全不同的方法应对软件系统的错误处理,其思想就是创建一个安全的容器来存放危险代码.functor 和 map 很类似,它会首先打开容器,应用函数到值,最后把返回的值包裹到一个新的同类型容器中.这种函数类型被称为 functor. 12345678910// 用 functor 完成 2 + 3 = 5const plus = R.curry((a,b) => a+b);const plus3 = plus(3);// 将 2 放到warp容器中const two = warp(2);// 调用 functor 把 plus3 映射到容器上const five = two.fmap(plus3); // Warpper(5) 返回一个具有上下文包裹的值five.map(R.identity) ; // 5// fmap 函数返回同类型的类型,可以链式调用two.fmap(plus3).fmap(R.tap(infoLogger)); // 在控制台打印以下信息 functor 是无副作用且可组合的,其实际目的只是创建一个上下文或一个抽象,以便可以安全的应用操作到值而不改变原始值.函子是函数编程中最重要的数据类型,也是基本的运算单位和功能单元.一般约定,函子的标志就是容器拥有 map 方法,该方法将容器中的每一个值映射到另一个容器. 123456789101112class Functor{ constructor(val){ this.val = val; } map(f){ return new Functor(f(this.val)) }}(new Functor(2)).map(function(two){ return two + 3;}) // Functor(5) 还有一个更具体化的函数式数据类型 Monad,可以将电话代码中的错误处理,更流畅的进行函数组合,其实 Monad 就是 functor “伸入” 的容器.我们曾经写个这样的 jQuery 代码$("#student").fadeIn(3000).text(student.fullname()).jQuery 可以很安全的将 fadeIn 和 text 行为应用到 DOM 上,如果 student 的 id 不存在,方法会应用到空的 jQuery 对象上且什么也不发生,也不会抛出任何异常.Monad 在于安全的传送错误,这样代码才有较好的容错性. Monad 函数式的出路错误我们来了解一下 functor 的局限性,当把两个 warp 包裹函数组合在一起的时候需要用两次 R.identity 函数来提取值,如果层数再多的话,monad 是更好的解决方案. 假设有一个函数half: number -> number,1Warpper(2).fmap(half); // warpper(1) functor只管应用到值并将结果包裹起来,并不能加额外的逻辑,如果想限制 half 只应用到偶数,而输入是一个奇数该怎么办. 123const isEven = n => Number.isFinite(n) && (n%2 == 0);const half = (val) => isEven(val) ? Wrap(val/2) : empty();// half 如果是一个奇数则返回一个空的容器 Monad 用于创建一个带有一定规则的容器,而 Functor 不需要了解其容器内的值.使用 Monadic 类型需要了解以下定义: 创建 Monadic 类型(类似于 Warpper的构造函数) unit 函数,可将特点类型的值放入 Monadic 结构中,类似于 empty 函数. bind 函数,可以链式操作,functor的 fmap join 函数,将两层 monadic 结构合并为一层.用于逐层扁平化嵌套结构,无需多次提取.Monad 函数的fmap 函数也叫 flatmap 函数,在大多数函数库里 flatmap 叫做 chain . 下面来看丰富的 Monad 实例,maybe,Either 和 IO.函数式编程通常使用 maybe 和 either 来隔离不纯,合并判空逻辑,避免异常,支持函数组合,中心化逻辑提供默认值.简单说 maybe 函子的 map 方法里设置了空值检查.Either 一般用来提供默认值.either 函子内部有两个值,left 和 right,right 是正常情况的值.left 是 right 不存在时的默认值.总之就是 right 有值用 right,否则用 left.12345var addOne = function(x){ return x + 1;}either.of(5,6).map(addOne); // either(5,7)either.of(1,null).map(addTOne); // either(2,null) Either 另一个用途就是代替 try…catch,使用 left 表示错误.123function parseJson(json){ return Either.of(null,JSON.parse(json))} 第六章 可测试的函数式略 第七章 函数式优化 函数执行机制js 中,每个函数调用都会在函数上下文堆栈中创建记录(帧),它负责管理函数执行以及关闭变量作用域.全局的上下文帧永远在堆栈的底部,函数体声明的变量越多,就需要越大的堆栈帧.函数柯里化过度使用会导致其占有大量的堆栈空间,进而导致程序运行速度显著降低.递归也会导致堆栈的溢出.因为递归时函数调用自己也会创建新的函数上下文,如果你见过range error: Maximum call stack exceeded or too much recursion就知道是递归出问题了.堆栈大小跟硬件也有关系.既然大量函数推入堆栈会增加程序的内存占用,为什么不避免不必要的调用呢? 使用惰性求值推迟执行函数式语言 Haskell 内置了惰性函数求值,惰性求值的方法有很多,但目的都是尽可能的推迟求值,直到依赖的表达式被调用.但是 js 使用的是更主流的函数求值策略 - 及早求值,它会在表达式绑定到变量时求值,不管结果是否用到,也称贪婪求值. 2.1 使用函数式组合子避免重复计算.alt 组合子类似于 || 运算,先计算 func1 如果返回值为 假,在调用 func2.这是避免不必要计算的简单方法,还有一个更强大的方法 memoization.2.2 函数式编程的 shortcut fusion(意为: 捷径 融合),是一种函数级别的优化,它通过合并函数执行,并压缩计算过程中使用的临时数据结构有效降低内存占用.之所以可以这样做事因为函数式编程引用透明带来的数学和代数的正确性.比如 compose(map(f),map(g))可以由 map(compose(f,g))完全代替. 1234567891011121314151617181920212223242526272829303132333435const square = x => Math.pow(x,2)const isEven = x => x%2 === 0const numbers = _.range(200)const result = _.chain(numbers) .map(square) .filter(isEven) .take(3) // 仅处理前三个 .value() // [0,4,16]// map 和 filter 可以通过 compose 融合在一起``` 2.3 记忆化 memorization加快程序执行的方法之一就是避免计算重复值,在传统的面向对象中,通过将函数结果赋予给唯一的键值对并持久化到缓存中.而在函数式中记忆化是一种很好的方式.它基于函数的参数创建与之对应的唯一的键,将结果存储到键上,当再次遇到相同的参数的函数时,立即返回储存的结果.给 FUnction 添加记忆化```jsFunction.prototype.memoized = function(){ let Key = JSON.stringify(arguments);//将参数字符串化以获取当前函数调用的键值 this._chace = this.cache || {}; // 为当前函数实例创建一个内部缓存 this._chace[key] = this._chace[key] || this.apply(this,arguments)// 先试图读取缓存,通过输入判断是否计算过,找到就离开返回,没找到这开始计算 return this._chace[key]}Function.prototype.memoize = function(){ // 激活函数记忆化 let fn = this; if(fn.length === 0 || fn.length>1){ return fn; // 只尝试记忆化一元函数 } return function(){ return fn.memoized.apply(fn,arguments) }} 设计多个参数的函数即使是纯函数也很难缓存,因为复杂度增加了,柯里化是解决方案之一.递归和尾递归优化,es6 添加的尾部调用消除,可以再递归调用时不依赖当前帧,创建一个新的帧并回收旧的帧.","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yoursite.com/categories/JavaScript/"}],"tags":[{"name":"JavaScript 函数式编程","slug":"JavaScript-函数式编程","permalink":"http://yoursite.com/tags/JavaScript-函数式编程/"}]},{"title":"读<<上帝的骰子>>","slug":"读<<上帝的骰子>>","date":"2020-06-15T11:43:05.000Z","updated":"2021-08-30T11:46:55.861Z","comments":true,"path":"2020/06/15/读<<上帝的骰子>>/","link":"","permalink":"http://yoursite.com/2020/06/15/读<<上帝的骰子>>/","excerpt":"","text":"读<<上帝的骰子>>量子力学的前夜中学时我们就学过牛顿三定律。牛顿第一定律即惯性定律:不受外力的物体将在惯性系中保持静止或匀速直线运动的状态不变。接着,他又给出,说明力、质量和运动之间的定量关系:物体的加速度与它所受的外力成正比,与它的质量成反比。牛顿第三定律则指出:两个物体间的作用力和反作用力大小相等,方向相反,作用在一条直线上。也就是说,牛一定律说明了力是改变物体运动状态的原因;牛二定律指出了力使物体获得加速度;牛三定律揭示了力是物体间的相互作用。除了牛顿三定律,再加一个万有引力定律。牛顿完成了经典力学架构,统一了万物运行背后的道理。 大伙儿都相信牛顿定律就是宇宙的终极真理,宏观世界很快热闹了起来。科学家们真干实干加巧干,一直干到20世纪,终于建成了一座宏观物理学大厦。经典力学、热力学、光学、电磁学等在大厦里各司其职。眼看科研经费一年比一年少,前途一片黯淡。这时,哥本哈根学派的一帮年轻科学家开始掀桌子。宏观世界是没什么活儿可以干了,但是还有微观世界啊!牛顿力学只适用于宏观世界,可一旦深入微观世界,比如原子级别,这套理论就完全找不着北了。那量子力学到底是怎样诞生的呢?人类在研究光的过程中偶然邂逅了无辜的量子。因此我们的故事得追溯到一个古老的问题——光是什么? 从光的本质说起很久很久以前,人类祖宗的祖宗就在思考:这世界到底是由什么构成的?古希腊哲人再一次展示出他们惊人的物理直觉:光由一粒一粒非常小的光原子所组成。就这样,微粒说一直统治着上古科学界。直到17世纪初,它才迎来宿敌——波动说。它率先由失恋的数学教授格里马第提出,这个失恋的男人躲在小黑屋里疯狂地做实验。让一束光穿过两个小孔后,他恍惚看到旧情人眼里水波的流动。一瞬间他顿悟了,这不正是一种衍射现象吗?最后,他哭闹着向全世界宣布:光是一种波。1663年左右,英国科学家胡克加入波动学说的阵营中。一开始波动派挺高兴,总算盼来一位猛将。一向视胡克为死对头的牛顿发话了:既然你胡克支持波动说,那——1672年,牛顿发布光的色散实验,矛头直指波动说要害。1704年,他发大招出版《光学》一书。并在序言中写下:为了避免对这些论点的无谓争论,我推迟了这部书的公开发行。波动说阵营群龙无首,无人应战。 直到一个世纪后——才有一位少年天才敢站到牛顿的对立面,为波动说站台。托马斯·杨。作为物理学五大经典实验之一,在一个月黑风高之夜,天才杨开始了表演:他点燃了一支蜡烛。直接点燃了量子革命的火种,留下了一条历史性的干涉条纹…… 旧量子论的奠基麦克斯韦预言光是电磁波的一种,迈出了史诗级的一步。可这预言是对是错,终究得有个人来给它证明。这个人,就是麦克斯韦的弟子—— 赫兹。1887年,赫兹通过一个高频振荡回路,证明了电磁波的存在。这个实验确认了光的波动性。电磁理论的一体化,标志着经典物理达到了顶峰。揭示电磁波存在的同时,赫兹的实验还出现了一个奇怪的现象:光电效应。 什么是光电效应?就是在高于某频率的电磁波的照射下,某些物质的电子会被光子激发出来,从而形成电流,即光生电。光能转化成电能,物质的电性质由此发生变化……宏观世界的理论无法解释光电效应,后来我们才知道:光电效应的背后,是科学家要研究的新方向。那是一个人类一直不曾进入的世界——微观量子世界。普朗克、爱因斯坦和玻尔这三位奠基者,也马上要登场了。 1900年,普朗克在研究黑体辐射时大胆假定:能量在发射和吸收时,不是连续不断的,而是一份一份的。这个不连续假设,正是量子理论最初的萌芽。就这样,普朗克稀里糊涂地提出了量子概念。它推翻了微积分几百年的连续基础,开始挖牛顿世界的墙角。大家普遍将1900年12月14日,普朗克发表《论正常光谱的能量分布定律的理论》的这一天当作量子物理学诞生的日子。然而,能量子的概念太激进了!面对这样一个骇人的真相,这个老派绅士被自己吓得魂飞魄散。但提出者无心,研究者有意。 1905年,听墙角的爱因斯坦开始收割普朗克的劳动果实。天才的直觉告诉爱因斯坦,对于光来说,量子化可能是一种必然的选择。他在普朗克的假设上提出,光以量子的形式存储能量,不累积。一般情况下,一个量子打出一个电子,这就是著名的光量子效应。按照爱因斯坦的理论,光又成了粒子,具有不连续性。但比起旗帜鲜明地站队光到底是微粒还是波,爱因斯坦更在乎自己的直觉。光具有波粒二象性。 对于20世纪初的科学家来说,你说光既是波又是粒子,这怎么可能?粒子是单个存在的个体,而波则是集体运动的结果,这两者根本不可能统一啊。借此机会,微粒说率先开始了绝地反击。1923年,康普顿看到了光,开始带领微粒军大举反攻。他大胆引入光量子假设,完成了X射线散射实验,光的粒子性被证实。可微粒派还没来得及露出得意的笑容。1923年,法国贵族王子德布罗意出场了。为了阻止微粒说和波动说一触即发的大战,德布罗意从光量子理论中顿悟到:正像光波可以表现为粒子一样,粒子也可以表现为波!不仅仅是光,一切物质都具有波粒二象性。这就是物质波理论。全世界的物理大师都保持沉默,只有爱因斯坦一个人点赞支持德布罗意。就这样,场面一度僵持。 结果,还没等大家从德布罗意的物质波理论冲击中回过神来——1925年4月,戴维逊和革末进行的电子衍射实验发现:电子居然表现出波动性质!电子居然是个波!这下,波动和微粒双方阵营都炸开了锅。1925年,正当物理学陷入十字路口时,24岁的海森伯出现了,他被认为是微粒派的代表。他试图用数学来解释微观粒子运动。最后,他选择了一种不符合交换率的古怪矩阵来描述量子理论。在玻恩、约尔当和狄拉克的助攻下,很快,海森伯的矩阵力学就在旧量子系统废墟上建立了起来。可好景不长,薛定谔加入了战斗。他被认为是波动派的代表。他嫌矩阵力学太装,故弄玄虚让大家都看不懂。他认为,是微粒还是波,这根本没那么复杂,量子性不过是微观体系波动性的反映。只要把电子看成德布罗意波,用一个波动方程表示电子运动即可。 他就这样提出了名震20世纪物理学史的薛定谔波函数。看到熟悉的微分方程,那些被海森伯矩阵整得晕头转向的大佬,个个热泪盈眶。毫不犹豫,他们转身就把矩阵力学打入了冷宫。一边是骄傲的海森伯,一边是好胜的薛定谔。一边是以微粒说为基础的矩阵力学,一边是以波动说为基础的波函数。 矩阵力学和波动力学,从此成了生死天敌。尴尬的是,1926年4月,薛定谔、泡利、约尔当各自证明:两种力学在数学上来说是完全等价的! 搞了半天,不过是同一理论的不同表达形式而已。两座大厦其实建立在同一地基上:微观粒子的波粒二象性。但旧量子论真正的集大成者,不是普朗克,也不是爱因斯坦,而是来自丹麦的玻尔。1913年,他发表了三篇论文:《论原子和分子的构造》《单原子核体系》《多原子核体系》。这三篇论文成为物理学经典之作,被称为玻尔模型三部曲。用对应原理算出了氢原子能级。经过普朗克、爱因斯坦、玻尔三大先行者的接力,旧量子论终于从牛顿宏观理论的阴影里爬了出来。但这时的人们最多只是刚爬到微观世界的门口,新量子论(即真正意义上的量子力学)仍处于混沌之中。 量子力学的建立普朗克、爱因斯坦、玻尔三人接力救了旧量子论。但真正建立量子力学(新量子论)国度的开国元勋却来自哥本哈根学派。他们的主将有三个:玻恩、海森伯、玻尔(没错,又有玻尔)。 玻恩算是海森伯的半个老师。他是一名地地道道的物理教授,在哥廷根开了个理论班。海森伯就是在那里跟着玻恩搞科研的。1926年,海森伯哭着跑回家说,他被薛定谔欺负了。在矩阵力学和波动力学被证明等价后的尴尬中,他们两人表面休战,薛定谔却暗中使绊子,到处骂矩阵力学变态。本就高冷难追的矩阵力学,风头远远被薛定谔的波函数盖了过去。玻恩气得肝疼,发誓一定要替自家弟子报仇。他找上了远在哥本哈根的大哥玻尔,准备联合起来找回面子。1926年7月,薛定谔接受玻尔的邀请前往哥本哈根,正春风得意的薛定谔,并未察觉这是一场鸿门宴。 在他赞美着自己的波函数时,护徒心切的玻恩出手了——玻恩先假仁假义地夸赞了对方一番,再挖了个坑:阁下波函数中的ψ,代表什么?毫无警觉的他,笑呵呵地解释:ψ函数代表电子电荷在空间中的实际分布。玻恩反驳,不,电子本身不会像波那样扩展,而是它的概率分布像一个波。ψ函数代表的不是实际位置,而是电子在某个地点出现的一种随机概率。玻恩很淡定。他以子之矛,攻子之盾,用对方的一个波动实验给出了最好的证明:电子双缝干涉实验。 电子穿过两道狭缝后,便形成了一个明暗相间的图案,也就是干涉条纹。一个电子究竟出现在哪儿,我们无法确定。连这个世界都是以概率形式存在的,我们只能预言概率。一切都只是随机的?玻恩,你这是在挑战整个科学的决定论根基!借助电子双缝干涉实验,玻恩狠狠扇了薛定谔一个大耳光。但还没等玻恩开心多久,哥本哈根学派自家后院先着火了。 1927年,大哥玻尔改变了对波动力学的看法。当初为了赢薛定谔,他也没少研究波动说,可里里外外解剖完,玻尔突然觉得,这也是个好东西。 要不试试,把波动说当做量子论的基础,看能不能搞个新理论出来?1927年,闹别扭的海森伯还在跟矩阵较劲。他试图用矩阵来对抗薛定谔方程。在绞尽脑汁的思考过程中,他突然想起:矩阵其实是不符合小学的乘法交换律的!最后研究得出不确定性原理.玻恩的随机概率解释已经让人头大了。这次海森伯更狠,他直接否定了物理学。这是一种哲学上的原则问题。不仅是你波动说,不管你创立什么理论,都必须服从不确定性原理! 可外界还是不服气。照你们的说法,电子是波也是微粒,不确定性是电子在波和微粒之间的一种随机表现。可你们又没同时见过电子波和电子粒,谁能做证?玻尔急中生智,直接抢白:谁说电子是波又是微粒,就一定能同时观察到两种状态了?为了听上去更有说服力,玻尔还进行了官方陈词总结,这就是互补原理。波和粒子在同一时刻是互斥的. 概率解释、不确定性原理、互补原理就这样颠覆了人们对宇宙的终极认识。它们共同构成了量子论哥本哈根解释的核心。概率解释与不确定性原理摧毁了世界的因果性,不确定性原理和互补原理合力干掉了世界的绝对客观性。 爱因斯坦和波尔的战争爱因斯坦认为,量子这熊孩子已经长歪了,哥本哈根学派的解释,根本就没有办法说服他。这个当初提出光量子理论的男人,是因果律和客观性的坚定拥护者,却对量子力学(新量子论)嗤之以鼻孔。哥本哈根学派欺负了自己的小弟薛定谔,爱因斯坦决定找个机会好好教训一下他们。哥本哈根派与爱因斯坦总共约架三次。正是这三次约架,奠定了量子力学在物理学上的重要地位,使它成为20世纪最伟大的两大理论之一。 1927年10月24日,第五届索尔维会议召开。这是他们的第一次约架。看热闹的不少,整个物理学界能排得上号的人基本都来了。爱因斯坦、玻尔、薛定谔、德布罗意、玻恩、普朗克、朗之万,狄拉克、居里夫人……29个人,其中有17个人是诺贝尔奖的获得者!这群人组成了一支物理学全明星梦之队,留下了堪称人类历史上智商巅峰的一张合影。就算不是绝后,也一定是空前的。这支全明星梦之队分为三个阵营:一个是哥本哈根学派,以玻尔为首。成员有海森伯、玻恩、泡利、狄拉克……第二个阵营是他们的老对手,以爱因斯坦为首的反对派。麾下有抱大腿的薛定谔、小王爷德布罗意等几员大将。还有一个闲云野鹤派,他们不在乎你们谁和谁打架,只关心实验结果。最前头站着的是布拉格和康普顿,身后还站着居里夫人、德拜等一群看热闹不嫌事儿大的人。 德布罗意小王爷一马当先,提出导波的概念,试图推翻概率解释,用因果关系解释波动力学。他说,我虽然提出了物质波,但你们都没搞懂。粒子是波动方程的一个奇点,就像波上的一个包,它必须受波的引导。而这个波,其实就是物质的运动轨迹。导波没有物质波幸运,它遭到了泡利的猛烈反击。被称为上帝之鞭的泡利从小就是个暴脾气。身为海森伯的师兄,他对他们的老师也照样尖刻。极具个性的他,一言不合就丢出一个泡利不相容原理(在费米子组成的系统中不能有两个和两个以上的粒子处于完全相同的状态)如果波是物质的运动轨迹,那你倒是说说,这个运动到底是怎么回事,德布罗意小王爷羞红了脸,下不来台。薛定谔想来助阵,结果自身难保。他的电子云理论被玻恩和海森伯两师徒前后夹击。薛定谔认为,波是真实存在的,电子在空间中的实际分布如波般扩散,就像一团云。可海森伯很嚣张:对不起啊,从你的计算中,我看不到任何可以证明你理论的东西。薛定谔自知自己的计算还不完善,便硬着头皮还击,那你们提出的什么波本征态叠加更胡扯!以一敌二,薛定谔直接被玻恩、海森伯怼到怀疑人生。 眼看自己的两大亲兵节节败退,在一阵可怕的沉默中,爱因斯坦终于爆发了。他直接提出一个模型:一个电子通过一个小孔得到衍射图像。假设一片隔板中间有一条狭缝,朝着这隔板的狭缝发射一个电子,发射的方向垂直于隔板,电子穿过了狭缝,再移动一段距离后,抵达感应屏障。没错,你们的概率分布是比薛定谔的电子云完备。但你们说,电子在到达感应屏前都不确定,到达的一瞬间概率就变成了100%?这种随机性不是要以超距作用为前提吗?这是违背相对论的!爱因斯坦是神一般的人物,是大当家的玻尔的偶像。面对身为反方带头大哥的爱因斯坦,玻尔勇敢地站了出来。你这个模型,同样不能避免测量时仪器对电子不可控的相互作用,即电子与狭缝边沿的相互作用,电子在通过A缝时如果不超距怎么感知旁边没有其他的缝呢? 也就是说,其实你这个模型也是符合量子理论的,你还要反驳我们吗?玻尔出招,虽然重剑无锋,但直取对方致命弱点。爱因斯坦想反驳,可憋了半天,愣是没憋出一个字。会场鸦雀无声……第一个回合,哥本哈根学派胜出。低估了对手实力,爱因斯坦很不服气。他又提出一个模型:电子双缝干涉实验。若控制装置,让某一时刻只有一个粒子穿过,并分别关闭狭缝,就可以测出电子的准确路径和位置。而由干涉条纹又可计算电子波的波长,从而可精确确定电子的动量。怎么样,这下你们的测不准关系被否定了吧?爱因斯坦自以为这局一定稳胜,可玻尔却古怪地笑了:爱因斯坦先生,如果你关上其中任何一个狭缝,实验的状态就完全改变了!双缝开启干涉现象也不再出现,实验又回到了单缝状态,等于又多了一次不确定因素!这个实验,不但没反驳成功互补原理,反而用互补原理说明了波粒二象性!第二回合,还是哥本哈根学派胜!六天的会议,变成了这两个人的对台戏。爱因斯坦屡战屡败却越挫越勇。最后,他恼羞成怒,扔下了一句物理学名言:玻尔,上帝不掷骰子!玻尔此时也已经豁出去了,他毫不留情地回呛:爱因斯坦,别去指挥上帝该怎么做!第一次爱玻之战,以爱因斯坦的惨败告终。 1930年,第六届索尔维会议召开。这是他们的第二次约架。这次,爱因斯坦有备而来。他先发制人,快准狠地打出一张实验牌:光箱子。箱子里有n个光子,时间间隔Δt之后打开箱子,每次只放出一个光子,Δt确定。再用理想的弹簧秤测出箱子的质量,发现轻了Δm,将Δm代入质能方程E=mc2,ΔE也确定。既然ΔE和Δt都确定,那你们家不确定性原理,ΔEΔt>h,也就不成立!玻尔毫无思想准备,当场蒙了。第二天一大早,一夜没合眼的玻尔,顶着两个浓重的黑眼圈出现在台上。好,你说一个光子跑了,箱子轻了Δm,这没问题。那怎么测量这个Δm呢?广义相对论中的红移效应,即光频率降低的现象。引力场可以使原子的频率变低,也就是红移,等效于时间变慢。你想要准确测量Δm或ΔE,可你其实根本没办法控制光子逃出的时间Δt,它测不准。爱因斯坦哑口无言。苦心孤诣三年,他和薛定谔、德布罗意在小黑屋反复沙盘推演,原以为万无一失、可以一招制敌。可自己精心设计的实验,又一次成了不确定性原理的一个绝佳例证。第二次约架,爱因斯坦又输了! 1933年,第七届索尔维会议召开。可彼时,爱因斯坦正被纳粹逼得在异国他乡流浪,他缺席了。缺了爱因斯坦,会议变得索然无味。丢了主心骨的薛定谔、德布罗意两人,在新量子论的喧闹中沉默不语。1935年,孤独的爱因斯坦又找到了两个同盟军,波多尔斯基和罗森,他们联合发表了一篇论文。论文的名字特别长,叫《量子力学对物理实在的描述可能是不完备的》。 这一次,是双方的第三次约架。爱因斯坦吸取了之前血的教训。他不再攻击量子力学的正确性,而准备改说它是不完备的。对于量子力学,爱因斯坦心理上有两个坎儿过不去。一个是,怎么可能有超光速信号的传播?爱因斯坦称之为定域性。另外一个是实在性:你不去看,难道天上的月亮就不存在了吗?爱因斯坦准备了一个实验,来说明量子力学违背了定域实在论,大意是:一个母粒子分裂成两个自旋方向相反的子粒子A和B。这两个粒子是互相影响的。如果粒子A为左旋,那B一定是右旋,以保持总体守恒,反之亦然。 按照量子力学的解释,这两个粒子相互之间是有联系的。那么,如果这两个粒子分开足够远——比如,粒子A在银河系的这头,粒子B在银河系的那头,相隔10万光年以上。你对粒子A吹口气,难道粒子B也会在一瞬时做出相对的反应吗?(这两个纠缠态的粒子,薛定谔成为量子纠缠,)因此,量子力学并不完备! 综上所述,这就是整篇论文的论据。这个思想实验,也被称为EPR佯谬,命名灵感来自三人名字的缩写。玻尔淡定地给出了反击——你二话不说就先假定了两个粒子在观察前,分别都有个客观的自旋状态存在。这两个客观存在的粒子是哪儿来的?根据量子力学的理论,在没有观测前,一个客观独立的世界并不存在,更不存在客观独立的两个粒子。它们本就是一个相互联系、相互影响的整体。在被观测之后,粒子A、粒子B才变成客观真实的存在。我们两个前提都不一样,量子力学仍然是完备、逻辑自洽的。不得不说,爱因斯坦是一个伟大的反对派。作为一代科学巨匠,他的反对成了量子力学最好的试金石,每一次他提出的问题,都推动量子力学前进了一大步。甚至有人怀疑他是量子力学派来的卧底。1962年,玻尔去世后的第二天——人们在他的黑板上,发现了当年爱因斯坦光箱子的实验草图。他对爱因斯坦的反对是如此眷恋,至死还萦绕于心。而此时的爱因斯坦,已经去世了7年。 薛定谔的猫在爱因斯坦的光环下,薛定谔虽然只是小弟,但自身同样也是实力一流的大科学家。哥本哈根学派第一条核心原理——概率诠释,就是用薛定谔方程来描述量子行为。虽然不怎么喜欢他这个反对党,但哥本哈根派也不得不承认薛定谔是量子力学的奠基人之一。除此之外,薛定谔还是分子生物学的开山鼻祖,他写的《生命是什么》一书畅销至今。 薛定谔的猫是怎么来的呢?爱因斯坦落败后,老薛心里极度憋屈又扭曲。他又一次复习了EPR理论,觉得没毛病啊!薛定谔认为爱因斯坦没有错,错的是哥本哈根学派,这一派个个都是诡辩高手。他得再做一个实验,这个实验要让每个人一眼就看懂。正想着实验怎么做的薛定谔扫了一眼周围——他的猫正在撕咬他的论文《量子力学的现状》!气不打一处来的薛定谔灵光乍现:这么皮,把你拿去做实验好了!薛定谔把猫放进一个不透明的盒子里。盒子连接到一个包含放射性原子核和有毒气体的实验装置中。可怜的猫被活生生关在里面。如果原子衰变了,毒气瓶会被打破,盒子里的猫会被毒死。要是原子没有衰变,猫就好好地活着。根据量子力学理论,原子核处于衰变和未衰变的叠加态。那么这只猫理所当然也随着原子核叠加进入一种又死又活的状态。这样一只猫,与我们的常识是如此相悖。 薛定谔得意地大笑:玻尔,你们见过一只又死又活的猫吗?薛定谔的猫思想实验的高超之处在于:它将看不见的微观世界与可视化的宏观世界联系了起来。这只猫,成了行走于宏观世界和微观世界的灵宠。你们不是欺负人们看不到吗?我现在就让全世界看到你们哥本哈根学派的丑陋!你们非要将我的波函数方程解释成粒子的一种叠加概率波。你看,现在搬起石头砸自己的脚了吧!叠加态不是微观世界量子论的核心吗?现在我将它带到宏观世界了,你们自己看看,它是多么可笑!薛定谔的猫实验否定的是哥本哈根学派的概率解释。如果量子力学的三大基石之一被毁掉了,那科学家进军微观世界的梦想将彻底破灭。 首先给出解释的,还是哥本哈根学派。哥本哈根学派其实心里也有点虚,但他们只能硬着头皮上:你的实验盒子里,有一个计数器是用来测量原子是否衰变的。从这一步测量开始,波函数的叠加态就已经坍缩了。后面的猫是生是死,完全是属于经典世界的,不存在叠加态。可不久,现代应用计算机鼻祖,年青的冯·诺伊曼就一针见血地指出:不对!计数器本身也是由微观粒子组成的!你用B去测量A,用C去测量B,只不过是A的叠加态转移到了B,B的不确定又转移到了C……到最后,整个大系统的波函数还是没有坍缩。到最后,波函数之所以坍缩,还是因为人的意识参与。只要没有被意识到,猫就是又死又活的。可究竟什么是意识?大脑?灵魂?思想?这种解释太唯心主义了。 暗中窥视的爱因斯坦一派伺机而动。看到量子力学大厦被意识决定论搞得摇摇欲坠,他们悄悄带来了第二种解释,也就是反哥本哈根学派的诠释。他们不反对量子力学,只想在量子力学的世界抢班夺权,掠取哥本哈根学派打下来的量子江山。它的代表人是玻姆。1952年,玻姆创立了一个完整的隐变量体系。在玻姆看来,哥本哈根学派含糊混淆的那些现象,主要是因为存在着一个隐形变量。为此,他用高超的数学手法复活了导波。写下了一个复杂得让许多科学家觉得生无可恋的隐函数。玻姆说,这个隐变量,就是爱因斯坦寻找的神秘力量。但因为我们还没有发现,也发现不了,所以微观粒子才表现出不确定,才会有叠加态。(奥拉姆剃刀原则,即简单有效原则,如果同一种现象有两种或多种不同的假说,我们应该采取最简单或可证伪的)虽然看上去特别有道理,但不能证伪,玻姆的隐函数同样难以服众!这明显违反了奥卡姆剃刀原则。 1957年,又一个不走寻常路的家伙出现了:埃弗莱特。他带来了荒谬又可笑的第三种解释。他大大咧咧地说,别多愁善感了,根本没有什么又死又活的叠加猫,猫也不是你看一眼就死了的。本来就有两只猫,一只是活着的,另一只死了。只不过这两只猫各自在两个世界里,两个你看到了不同的猫。埃弗莱特眼中有一个量子世界:整个宇宙是一个总体的波函数叠加系统,里面包含了很多个完全孤立、互不干涉的子世界。从宇宙大爆炸以来,这些世界就各自演化着,谁也看不到谁。这就是平行宇宙解释.(Many Worlds Interpretation,简称MWI)。 一群继承了多宇宙思想的科学家。他们在MWI基础上发展出了一种新的解释:退相干。这种新解释,就是第四种解释,也是目前的主流解释。它解释了MWI中为何平行世界没有在宏观中显示叠加态。通俗点来说,就是解释了为什么我们感受不到另外一个平行世界。退相干的理论研究者首先指出,不可能有同时又死又活的猫。如果猫是活的,那一步步反推回去,毒气瓶就没有碎,放射性原子也没有衰变,反之同理。也就是说,如果猫不生死叠加,那放射性原子也是不叠加的,波函数早就坍缩了。那波函数是什么时候坍缩的?又是什么东西导致它坍缩的?这群人给出的答案是:无论是薛定谔的盒子,还是整个宏观世界,都是由无数微观粒子组成的。它们的叠加性其实也是一种相干性。但量子的相干性会因外部环境的干涉而逐渐消失。说白了,就是其他粒子影响了盒子里的放射性原子,最后变成宏观性质了。量子退相干是德国学者汉斯在1970年提出的。但和可怜的埃弗莱特一样,当时并没有多少人注意到它。直到1984年,哈特尔的关注才让退相干理论正式发展壮大起来。退相干历史认为在宇宙中世界只有一个,但历史有很多个,分为粗粒历史、精细历史。精细历史是量子历史,无法求解概率,粗粒历史是经典历史,在宏观上显示,类似于路径积分,可以计算概率。每一个粒子都处在所有精细历史的叠加中,比如放射性原子。但一旦涉及宏观物体,我们所能观察到的就是一些粗粒化的历史,比如打开盒子后看到的猫。因为量子退相干了,这些历史永久地失去了联系,只剩一种被我们感知到。本该是粒子叠加态的薛定谔实验,打开盒子后,就只能看到一种状态的猫(生/死)。虽然退相干并不是十全十美,但无论是从数学上还是哲学上,它都让三维世界的我们好受一点。现在它已经成为量子力学的主流理论之一。不少科学家正利用它来建立真正的现实应用。量子计算与量子通信就正在与退相干做斗争。 薛定谔本来想让他的猫恶心哥本哈根学派,嘲讽一下量子力学。结果他到死也没想到,他的猫竟然成了量子世界的鲇鱼。只能说,薛定谔不愧是爱因斯坦的小弟,连给量子力学送助攻,都和爱因斯坦一模一样。爱因斯坦提出的EPR佯谬像不可攻破的堡垒。尽管在量子风暴中饱受摧残,它的定域实在论仍然牢牢把守着经典世界的大门。哪怕爱因斯坦曾三次落败。可直到去世,他心底里其实也没被玻尔说服。这两个伟大科学家之间的较量,早就超越了个人之间的战争,是一场关于世界本质的辩论。 微观世界到底符合定域实在论(经典),还是量子不确定性?最终一定要做一个了断。1964年,爱因斯坦的信徒——贝尔,重温了EPR佯谬。把定域实在论转化为另一种令所有科学家心服口服的语言。他提出了一个不等式——(|pxz-pzy| <= 1+pxy)这个不等式用超越了宇宙文明维度的数学语言铸就而成,被称为科学史上最深刻的发现。既然在物理世界没办法决出高下,我们就转战到更本质的数学领域,用数学来判断究竟谁对谁错。 贝尔不等式贝尔不喜欢量子力学听上去主观又唯心的一套。他想要的是一个确定的、客观的世界。贝尔有自己隐藏的绝招,那就是1952年玻姆提出的隐函数。在新一代大神冯·诺伊曼的禁锢中,隐变量举步维艰。可贝尔坚持认为,隐变量是反击哥本哈根学派的大杀器。玻姆的隐变量抛弃了定域性,但它至少恢复了世界的实在性。只要他在这基础上再证明一个定域隐变量的存在,就证明了量子力学的非定域性也是错的。他撸起袖子,研究起了爱因斯坦的老实验:EPR佯谬。在EPR佯谬理论中,一个母粒子分裂成了两个自旋方向相反的子粒子A和B。按照爱因斯坦一派关于隐变量的思想,两个子粒子A和B,就像南北极的两只手套。不管你观测不观测,它们是左手还是右手,从分开那时起就已经确定了。既然宇宙中不存在超距作用那么,在观测的一瞬间,两个纠缠的粒子必然在经典世界存在某种极限。假设Pxy是粒子A在x方向上和粒子B在y方向上的相关性,Pzy、Pxz同理,则可得出:(|pxz-pzy| <= 1+pxy),这个不等式对宇宙的本质做出了最后的裁决。 它意味着,如果我们的世界同时满足:1.定域的,也就是没有超光速信号的传播。2.实在的,也就是说,存在着一个独立于我们观察的外部世界。那么两个具有相反自旋方向的粒子,它们的运动,必定受限于不等式。简单来说,就是——如果微观世界是经典的,那么不等式成立。反之,则不成立。这个由隐变量理论推导出来的式子,它打破了一直以来的僵局,隐变量重见天日,一个定域又实在的世界近在眼前。物理学家开始骚动起来,他们按捺不住,想要亲身参与到大结局中。在数学与好奇心的撩拨下,他们纷纷动手改造起了EPR佯谬思想模型,做起了贝尔不等式实验。1972年,有个叫克劳泽的小厮成功实现了实验。这是史上第一个验证贝尔不等式的实验。不过,结果让贝尔魂飞天外——那两个纠缠的粒子,竟然突破了贝尔不等式??!这意味着,真的存在鬼魅般的量子纠缠?贝尔心心念念的微观世界经典性竟然是错的。 1982年,在巴黎奥赛光学研究所,又一场惊心动魄、万众瞩目的实验正在进行,这一次所有人都屏住了呼吸。这次的实验领导人是正在读博士的阿斯派克特。不同于克劳泽的幼稚版装置,阿斯派克特的技术非常成熟。借助激光的强信号源,一对对光子从钙原子中冲出,朝着偏振器奔去,它们关乎整个量子力学的命运。在令人窒息的24个小时的等待后,结果 出来了:实验再一次与贝尔想要的结果相反,玻尔是对的,爱因斯坦又一次输了!世界再也不可能回到那个美好的经典时代了。 数学是物理学的基石,贝尔不等式用严谨的数学手段覆灭了整个爱因斯坦军团,EPR实验最终成了EPR佯谬。数学的降维打击助力量子力学取得了胜利。在克劳泽和阿斯派克特之后,还有一大批追求完美的科学家也进行了实验。从5倍偏差,到9倍偏差,再到30倍偏差……模型越来越完备,技术越来越精密,都证明了玻尔是对的。多年爱玻之争,终于在宇宙判决书贝尔不等式中画上了句号。 此后量子力学的追随者开始分成两拨继续探索。一拨是勤耕不辍的理论派。他们一直试图深入微观世界,甚至想统一整个宇宙。为了达成这个长期目标,理论派把宇宙划分为4种力:电磁作用力、强相互作用力、弱相互作用力、引力。通过这4种力,一切物理现象都可以得到解释。天才科学家们找到了一种大一统理论,先用它将前三种属于量子力学的基础作用力都装进去,剩下一种属于广义相对论的引力,他们寄希望于更前沿的弦理论。弦理论认为,自然界的基本单元不是传统意义上的点状粒子。而是很小很小的橡皮筋一样的线状弦。当我们用不同的方式弹橡皮筋,它就会振动,产生自然界中的各种粒子,可能是电子、光子,也可能是引力子。这样,引力就有望被微观量子化描述,和前三种力统一在一起。微观(量子力学)和宏观(广义相对论)也就有望统一了。除了有着远大抱负的理论派外,另外一拨量子力学的追求者是实践派。这是一群实用主义者,他们挖掘出一项又一项伟大的量子应用。没有它,我们就不会有CD、DVD、蓝光影碟播放器;没有它,也不会有晶体管、智能手机、电脑、卫星导航;没有它,更不会有激光、电子显微镜、原子钟、核磁共振显示装置…… 有了量子力学,人类便进入了一个新时代。","categories":[{"name":"读书","slug":"读书","permalink":"http://yoursite.com/categories/读书/"}],"tags":[{"name":"科普 物理 量子力学","slug":"科普-物理-量子力学","permalink":"http://yoursite.com/tags/科普-物理-量子力学/"}]},{"title":"Chrome 插件开发指南","slug":"Chrome 插件开发指南","date":"2020-05-10T03:47:49.000Z","updated":"2021-08-30T09:48:58.468Z","comments":true,"path":"2020/05/10/Chrome 插件开发指南/","link":"","permalink":"http://yoursite.com/2020/05/10/Chrome 插件开发指南/","excerpt":"","text":"Chrome 插件开发指南开发与调试chrome插件没有严格的项目结构要求,只有保证本目录有一个 manifest.json 即可,从浏览器菜单-更多工具-扩展程序可以进入插件管理页面。或直接输入地址 chrome://extensions访问。 勾选开发者模式可以用文件夹的形式直接加载插件,否则只能安装.crx 格式的文件。mac 系统下插件安装目录为: ~/Library/Application Support/Google/Chrome/Default/Extensions 核心介绍 manifest.json 用来配置插件相关的配置信息,必须放在根目录。且以下属性是必不可少的。完整属性可以查看官方文档。 12345{ \"manifest_version\" : 2, \"name\" : \"test\", \"version\" : \"1.0.0\"} content-scripts (插件与页面交互) 是 chrome 插件向页面注入脚本的一种形式,我们可以通过manifest.json配置轻易的向页面注入 js 和 css,最常见的是广告屏蔽,页面样式定制等等. 12345678910{ \"content-scripts\" : [ { \"matches\" : [\"http://*/*\", \"<all_urls>\"], \"js\": [\"js/xxx.js\",\"....js\"], \"css\": [\"css/xx.css\"], \"run_at\": \"document_start\" // 可选 document_start/end/idle(默认空闲) } ]} content-scripts 与原始页面共享DOM,但不共享JS,如果想要访问页面JS某个变量,只能通过 injected js 来实现,content-scripts不能访问绝大部分chrome.xxx.api, 除了以下四种。 - chrome.extension(getURL,inIncognitoContext, lastError,onRequest,sendRequest) - chrome.i18N - chrome.runtime(connect,getManifest,getURL,id, onConnect,onMessage,sendMessage) - chrome.storage background 后台是一个常驻的页面,它随着浏览器的开关而开关.通常把需要一直运行的代码放在 background 里面. 他的权限非常高,可以调用 chrome 的扩展 API(除了 devtools), 而且它可以无限制跨域.配置中,background 可以通过 page 指定一个页面,也可以通过 scripts 指定一个 js,chrome 会自动为这个 js 生成一个默认页面. 123456{ \"background\":{ \"page\": \"xxx.html\" // scripts: [\"js/xxx.js\"] }} event-pages 鉴于 background 生命周期和浏览器同步,长时间挂载后台影响性能,而 event-pages 与 background 唯一的区别就是多了一个 persistent 参数,它会在需要时被加载,空闲时被关闭.一般 background 用的比较多. popup popup 是点击插件图标时打开的一个窗口网页,焦点离开网页就关闭,一般做一些交互使用. popup 可以包含任意你想要的 HTML,并且会自适应大小,可以通过 default_popup 来指定页面,也可以调用 setPopup()方法. 12345\"browser_action\" : { \"default_icon\": \"img/xx.png\", \"default_title\": \"悬停时的标题\", \"default_popup\": \"xx.html\"} popup 的生命和周期很短,需要长时间运行的代码不要放在 popup 里.popup 中可以通过 chrome.extension.getBackgroundPage() 获取 background 的 window 对象. injected-script content-script 无法访问页面中的 js,虽然它可以操作 dom,但 dom 却不能调用它,也就是在 dom 的事件中无法调用 content-script 中的代码,但是在页面中添加一个按钮并调用插件的 api 是很常见的需求,我们可以再 content-script 中通过 DOM 方式向页面注入 inject-script. 12345678910// content-scriptfunction injectCustomJs(jsPath){ jsPath = jsPath || 'js/inject.js'; var temp = document.creatElement(\"script\"); temp.src = chrome.extension.getURL(jsPath); // 类似于 chrome-extension://xxxx/js/inject.js temp.onload = function(){ this.parentNode.removeChild(this); } document.head.appendChild(temp);} 代码会报错,因为在 web 中直接访问插件中的资源必须显示声明才行,在配置文件中增加以下配置: 1234{ // 普通页面能够直接访问的插件资源列表,不设置无法直接访问. \"web_accessible_resources\": [\"js/inject.js\"]} inject-script 如何调用 content-script 中的代码,要用到消息通信. homepage_url 开发者网站 Chrome 插件的 8 种展示形式 browserAction 浏览器右上角图标 1234567{ \"browser_action\" : { \"default_icon\": \"img/xx.png\", // 19*19 \"default_title\": \"悬停时的标题\", \"default_popup\": \"xx.html\" }} 通过 setIcon 更改 icon, setTitle()更改鼠标 hover 时的标题.setBadgeText()来更改图标上的文本信息. pageAction 地址栏右侧 当某些特定页面打开时会在地址栏右边显示的图标.(新版吧位置放到了浏览器右边,可以把它看成置灰的 browserAction. 例如当打开百度时才显示图标. 1234567891011121314// background.jschrome.runtime.onInstalled.addListener(function(){ chrome.declarativeContent.onPageChanged.removeRules(undefined, function(){ chrome.declarativeContent.onPageChanged.addRules([ { conditions: [ // 只有打开百度才显示pageAction new chrome.declarativeContent.PageStateMatcher({pageUrl: {urlContains: 'baidu.com'}}) ], actions: [new chrome.declarativeContent.ShowPageAction()] } ]); });}); 右键菜单 通过 chrome.contextMenus API,右键菜单可以出现在不同的上下文. 1234567// manifest.json{\"permissions\": [\"contextMenus\"]}// background.jschrome.contextMenus.create({ title: \"测试右键菜单\", onclick: function(){alert('您点击了右键菜单!');}}); 常见 API 参考 覆盖特定页面 使用 override 页可以将 chrome 默认的一些特定页面替换掉.一个插件只能替代一个默认页. 123456\"chrome_url_overrides\":{ \"newtab\": \"newtab.html\", \"history\": \"history.html\", \"bookmarks\": \"bookmarks.html\"} devtools(开发者工具) 例如 vue.js devtools , chrome 可以再 devtools 上新增一个面板. devtools 页面会随着开发者工具的开关而开关.可以访问 Devtools API ,而其他比如 background 无权访问. chrome.devtools.panels: 面板相关 chrome.devtools.inspectedWindow: 获取被审查窗口的信息 chrome.devtools.network: 获取有关网络请求的信息 123{ \"devtools_page\": \"XXX.html\"} 这个 html 一般什么都没有,只有一个 script 标签引用 js 文件<script src='js/devtools.js'></script>,再看一下 devtools 的代码: 123456789101112131415161718192021// 创建自定义面板,同一个插件可以创建多个自定义面板// 几个参数依次为:panel标题、图标(其实设置了也没地方显示)、要加载的页面、加载成功后的回调chrome.devtools.panels.create('MyPanel', 'img/icon.png', 'mypanel.html', function(panel){ console.log('自定义面板创建成功!'); // 注意这个log一般看不到});// 创建自定义侧边栏chrome.devtools.panels.elements.createSidebarPane(\"Images\", function(sidebar){ // sidebar.setPage('../sidebar.html'); // 指定加载某个页面 sidebar.setExpression('document.querySelectorAll(\"img\")', 'All Images'); // 通过表达式来指定 //sidebar.setObject({aaa: 111, bbb: 'Hello World!'}); // 直接设置显示某个对象});// 访问被检查的页面DOM需要使用inspectedWindowchrome.devtools.inspectedWindow.eval(\"jQuery.fn.jquery\", function(result, isException){ var html = ''; if (isException) html = '当前页面没有使用jQuery。'; else html = '当前页面使用了jQuery,版本为:'+result; alert(html);}); 选项页 option 选项页是插件的设置页面,有两个入口,一个是右键图标菜单,一个是插件管理页面. 12345678{ // \"options_page\": \"options.html\" ,(老版本写法) \"options_ui\": { \"page\": \"optionxxx.html\", \"open_in_tab\": true, // 在当前 tab 打开 \"chrome_style\": true // 添加了一些默认样式 }} 不能使用 alert, 数据存储建议使用 chrome.storage, 因为会随用户自动同步 omnibox 注册某个关键字触发插件自己的搜索建议界面. 1234{ // 向地址栏注册一个关键字以提供搜索建议,只能设置一个关键字 \"omnibox\": { \"keyword\" : \"go\" },} 然后在 background 注册监听事件: 12345678910chrome.omnibox.onInputChanged.addListener((text, suggest) => { console.log('inputChanged: ' + text); if(!text) return; if(text === 'xxx'){ suggest([ {content: '百度搜索 ' + text, description: '百度搜索 ' + text}, {content: '谷歌搜索 ' + text, description: '谷歌搜索 ' + text}, ]); }}) 桌面通知: chrome 提供了一个 chrome.notification API 方便插件推送桌面通知. 123456chrome.notifications.create(null,{ type: \"basic\", iconUrl: \"img/xx.ping\", title: \"标题\", message: \"内容\"}) 消息通信 popup 和 background popup 可以直接调用 background 的 js 方法,也可以访问 background 的 DOM. 12345678// background.jsfunction test(){ alert(\"background\");}// popup.jsvar bg = chrome.extension.getBackgroundPage();bg.test();bg.document.body.innerHTML; // dom popup 或 bg 向 content 发消息 12345678910111213// bg,popup.jsfunction sendMessageToContent(message,callback){ chrome.tabs.query({active:true,currentWindow: true},function(tabs){ chrome.tabs.sendMessage(tabs[0].id,message,function(res){ if(callback) callback(res); }) })}// content-script.js 接收chrome.runtime.onMessage.addListener(function(req,send,res){ if(req.cmd == 'test') alert(req.value); send(\"我收到了你的消息\")}) content 向 bg/popup 主动发消息 12345678910// content-script.jschrome.runtime.sendMessage({ greeting: \"hello\"}, function(res){ console.log(\"收到回复\" + res)})// bg 或 popup.jschrome.runtime.onMessage.addListener(function(req,send,res){ send(\"我收到了你的消息\")}) injected script 和 content script content-script 和页面内 脚本(injected-script)之间唯一共享的就是页面 DOM 元素.有两种方式实现通信,一是通过 window.postMessage 和 window.addEventListener 实现消息通信(推荐),二是通过 自定义 dom 事件. 123456// injected - scriptwindow.postMessage({\"test\": 'hello'},'*');// content- scriptwindow.addEventListener(\"message\",function(e){ console.log(e.data)},false) 长连接和短连接chrome 插件中有两种通信方式,一种是短连接(chrome.tabs.sendMessage和 chrome.runtime.sendMessage),一个是长连接(chrome.tabs.connect 和 chrome.runtime.connect). 12345678910111213141516// 长连接// popup.jsvar port = chrome.tabs.connect(tabId,{name: 'test-connect'});port.postMessage({xxx:'xxx'});port.onMessage.addListener(function(msg){ alert('收到消息' + msg.answer) // ...})// content-scriptchrome.runtime.onConnect.addListener(function(port){ if(port === 'test-connect'){ port.onMessage.addListener(function(msg){ alert(\"收到长连接\",msg) }) }}) 补充 获取当前窗口 id 123chrome.windows.getCurrent(function(cw){ console.log(cw.id)}) 获取当前标签页 id 12345function getCurrentTabId(callback){ chrome.tabs.query({active:true,currentWindow:true},function(tabs){ if(callback) callback(tabs.length ? tabs[0].id : null) })} 定期执行代码 12345// 配置\"permissions\": [\"alarms\"]// 创建方法chrome.alarms.create(name,info);// name 为任务名, info 包含以下属性 when 何时,dalayInMinutes 延迟时间, periodInMinutes 非 null表示时间间隔,单位 minchrome.alarms.onAlarm.addListener(xxx); // 触发事件 本地存储chrome.storage 是针对插件全局的,即使在 background 中保存的数据,在 content-script 也能获取到.chrome.storage.sync 可以跟随当前登录用户自动同步.需要声明 storage 权限,有 sync 和 local 两种方式选择. 12345678// 读取数据,第一个参数是要读取的 key 以及默认值chrome.storage.sync.get({color: 'red',age:18},function(items){ console.log(items)})// 保存数据chrome.storage.sync({color:'blue'},function(){ console.log('save success')}) 快捷键唤醒 popup 1234567\"commands\": { \"_execute_browser_action\":{ \"suggested_key\":{ \"default\": \"Alt+Shift+J\" // 快捷键唤醒 } }} webRequest通过 webrequest API 可以对 HTTP 请求进行修改 1234567891011121314151617181920//权限申请\"permissions\": [ \"webRequest\", // web请求 \"webRequestBlocking\", // 阻塞式 web 请求 \"storage\",// 插件本地储存 \"http://*/*\" //可以通过 executeScript 或 insertCss 访问的网站]// web 请求监听chrome.webRequest.onBeforeRequest.addListener(details =>{ let showImage = false ; // 不展示图片 if(!showImage && details.type === 'image'){ return { cancel: true } } if(details.type === 'media'){ //... }},{urls: [\"<all_urls>\"]},[\"blocking\"]); 国际化插件根目录新建一个_locales 的文件夹,在新建一些语言文件夹如 en,zh_CN,zh_TW,然后在每个语言文件夹放入一个 messages.json,同时在文件中设置 default_locale.测试时,通过给chrome建立一个不同的快捷方式chrome.exe –lang=en来切换语言 123456789101112131415161718// en/message.json{ \"pluginDesc\": {\"message\": \"A simple chrome extension demo\"}, \"helloWorld\": {\"message\": \"Hello World!\"}}// zh_CN/message.json{ \"pluginDesc\": {\"message\": \"一个简单的Chrome插件demo\"}, \"helloWorld\": {\"message\": \"你好啊,世界!\"}}// 在 manifest.json 和 css 文件中通过 __MSG_messagename__引入{ \"description\": \"__MSG_pluginDesc__\", // 默认语言 \"default_locale\": \"zh_CN\",}// js 中使用chrome.i18n.getMessage(\"helloWorld\")","categories":[{"name":"前端","slug":"前端","permalink":"http://yoursite.com/categories/前端/"}],"tags":[{"name":"chrome 插件","slug":"chrome-插件","permalink":"http://yoursite.com/tags/chrome-插件/"}]},{"title":"solidity 区块链编程入门","slug":"solidity 区块链编程入门","date":"2020-04-11T11:31:16.000Z","updated":"2021-08-30T11:32:44.517Z","comments":true,"path":"2020/04/11/solidity 区块链编程入门/","link":"","permalink":"http://yoursite.com/2020/04/11/solidity 区块链编程入门/","excerpt":"","text":"solidity 区块链编程入门编译器Remix编译器 可以在线使用或离线使用.使用 node 可以安装 solidity 编译器 solcjsmac 可以通过homebrew 安装 编译器 solidity 源文件结构pragma solidity ^0.5.2; 表示版本号及编辑器版本import * as symbolName from "filename";导入其他源文件 值类型 整数类型分为 int/uint 定义. 可以显式设置占用空间大小,默认是 int256/uint256. 定长浮点类型: fixed/ufixed 表示各种大小有符号和无符号的定长浮点型.分别是 fixed128x19 和 ufixed128x19 的别名,第一个数字表示占用的位数,必须是 8 的倍数,第二个数字是可用的小数点位. 布尔型bool 分为 true 和 false 运算符与 javascript 相同,除 === 之外. 以太坊地址为 160 位即 20 字节大小.用 address 表示地址类型.地址有两种 address payable 可以接受以太币,而 address 则不行.前者可以隐式转换为普通地址,但普通地址要想转换为 payable 必须通过 payable()函数. 12345address public owner; // 定义地址owner.balance; //查看余额addressA.transfer(1 ether)// 像 A 转 1 eth,地址无效或余额不足会抛出异常.addressA.transfer.gas(120000)(1 ether) // 转账 附带 gas 的写法owner.send(1 ether) // send 是 transfer 的低级版本,有风险,合约失败返回 false,建议使用 transfer 每一个 contract 合约都有自己的类型,可以显式的转换为 adress 类型,只有当合约具有 receive(接收) 函数或 payable 回退函数时,才能显式和 address payable 类型相互转换.转换仍然使用 address()执行,如果没有接收函数和回退函数需要用 payable(address(x))转换为 address payable. 对于合约可以使用 type(xx) 来获取合约的类型信息. 固定长字节数组,以 bytes 加数字表示,如 bytes2 表示 两个字节长度的数组,数组范围为 1-32.默认是 1. 动态长度字节数组分两种,bytes 和 string(不支持索引访问) 引用类型 如果使用引用类型,必须明确数据存储在哪个位置. 变量的储存位置有三种,memory 修饰的变量储存在内存中仅在函数运行期有效不能外部调用, storage 修饰的变量存储在区块链上只有合约存在就有效,calldata 指调用数据,用来保存函数参数,是一个只读位置. 函数返回值默认是 memory,函数局部变量的默认数据是 storage,状态变量的默认数据是 storage. 数组截图在声明时指定长度,也可以动态调整.push()添加一个元素,返回对它的引用. 同理还有 pop 函数. bytes 和 string 也是数组.string 不能使用索引, bytes 等同于 byte[] 但 gas 消耗更低.可以使用 new 关键字创建内存数组,但不能改变其内存数组的大小. solidity 提供数组切片 x[start:end],仅仅可用于 calldata 1234567uint[][5] x = [1,2,3,4,5];x[0] = 6; // x 为[6,2,3,4,5]x.length = 5;uint[] y = [1,2] // 动态长度xxxtype[] public xxxx; // 自定义 xx 类型数组bytes memory b = new bytes(9)uint[3][5] x; //与大多数编程语言相反,为 5 行 3 列. 结构体是自定义数据类型,可以是字符串整型等基础类型,也可以是数组映射结构体等复杂类型.可以使用关键字 struct 定义.123456789101112131415//定义struct Bank{ address owner; uint balance;}// 初始化方法一Bank b = Bank({ owner: msg.sender, balance: 5})// 方法 2Bank c = Bank(msg.sender, 7)// 重新赋值c.balance = 1;delete b;//重置 b 的所有值为 0,除了 mapping 类型. 映射/字典 定义方式为 mapping, key 值最好是基础类型.Solidity 没有提供其迭代的方法. 1234mapping(string => uint) public balances; // public 会自动创建一个 getter 函数.balances['charles'] = 1;balances['ada']; // 没有设置 key 的返回 0delete balances[\"John\"]; // delete 不会删除元素,只会重置其初始值. delete 用来初始化类型的值,对映射无效. 枚举可用来创建一定数量的”常量值”够成的自定义数据类型,可以显式转为整型,但不能隐式转换.一般当做状态机使用.长度不能超过 256 位. 12345678// 定义状态机 enum State {Created, Locked,Inactive}; // 声明 state 变量 State public state; // 赋值 state = State.Created; // 显式转换 uint createdState = uint(State.Created); 类型转换和类型推断 隐式转换: int和 int,uint 和 uint 可以相互转换,但 int 和 uint 不能转换,整数类型可以转换为 bytes,但反过来不行,任何可以转换为 uint160 的变量都可以转换为 address 地址类型. 显示转换: uint8(a) 类型推断: var 会在第一次赋值时推断变量类型,不可以用于函数参数,使用时小心,有时候会推断出错误类型. 单位和全局变量 货币单位 wei, gwei, ether,默认后缀是 wei. 1ether = 10 的 18 次方 wei. 11 ether = 10 ** 18 wei; 时间单位 seconds,minutes,hours,days,weeks 都可作为后缀,默认以 seconds 为单位.(years 因为闰年的原因已去除). 这些单位不能直接用在变量后面,要用变量 乘 1seconds/其他单位 来使用. 1uint a = 1 * 1 days; // 值为 86400 秒 全局变量solidity 提供的通用函数或变量. block 区块信息 msg 消息信息 tx 交易信息 abi 编码及解码函数 错误处理 throw 抛出异常,require检查由输入和外部引起的错误,assert检查内部错误,revert终止运行回撤状态并提供一个解释性字符串 数学密码学函数 addmod,mulmod,keccak256,sha256,repemd160,ecrecover address 地址成员,包含 balance 余额,code 代码,transfer,send,call,delegatecall,staticcall 合约相关: this 表示当前合约,selfdestruct 销毁合约. 类型信息: type(x) 检索类型信息.属性包含 name,runtimeCode 等等… 表达式和控制语句Solidity 支持 js 中大部分语句 if,else,while,do,for,break,continue,return, 三元表达式,不支持 switch 和 goto 语句.tryCatch语句只能用于外部函数调用和合约创建调用.Solidity 没有 js 中的非 boolean 类型自动转换的特性.使用循环时注意 gas 的数量,防止合约失败.在合约中优先使用循环而不是递归,EVM 的最大调用栈的深度是 1024.solidity 内部允许使用元祖(tuple)类型.12345678function g() public { //基于返回的元组来声明变量并赋值 (uint x, bool b, uint y) = f(); //交换两个值的通用窍门——但不适用于非值类型的存储 (storage) 变量。 (x, y) = (y, x); //元组的末尾元素可以省略(这也适用于变量声明)。 (index,,) = f(); // 设置 index 为 7} solidity 作用域规则可以参考 javascript. 合约合约类似于编程语言中的类,可以通过 new 关键字来创建一个新合约,在合约可以调用另一个合约的方法.调用另一个合约时会很自信一个 EVM 函数调用,这会切换执行时的上下文,这样前一个合约的状态变量就不能访问了. 1234567891011121314contract infoFeed{ ...}contract Consumer{ InfoFeed feed; // 指向一个已经部署的合约; function setFeed(address addr){ // 传入部署合约的区块链地址 feed = Infofeed(addr) // 显示进行类型转换,不会调用构造函数 } // 创建合约实例 function createNewFeed(){ feed = new InfoFeed() // 调用构造函数 }} 编译器自动为所有 public 状态的变量创建 getter 函数.外部访问时被认作一个函数.状态变量声明为 constant (常量)或者 immutable (不可变量)合约之外的函数(也称为“自由函数”)始终具有隐式的 internal 可见性。 它们的代码包含在所有调用它们合约中,类似于内部库函数。 函数函数也是一种值类型,可以将函数传递给另外一个函数作为参数,可以再函数中返回一个函数. 123456// 定义 ,可以由多个返回值function increment(uint x) returns (uint a, uint b) { a = x+1; b = a*2; return (a,b);} 函数类型分为两类: 内部 internal 函数类型和外部 external 函数类型.如果函数不需要返回,可以省略 returns xx.一个函数默认是内部函数. 函数有四种可见性,public(公开),private(私有,定义的合约内部访问),external(不能再合约内部调用),internal(只能从内部访问).在public函数中,solidity会立刻把函数数组参数拷贝到内存中,而external函数可以直接从calldata中读取数据。内存分配是昂贵的,直接从calldata中读取是便宜的. constructor 是构造函数,在创建合约时执行,并在内部初始化 代码 和状态变量.构造函数运行后将合约最终代码部署到区块链. View 视图函数: 减函数声明为 view 类型,这种情况下要保证不修改状态(包括修改状态,产生事件,创建合约,发送 eth,调用任何没有标记 view 和 pure 的函数,销毁合约等). Constant 之前是 view 的别名,0.5.0 移除. Pure 纯函数: 承诺不读取也不修改状态.访问 address 和 block 等其他信息都属于读取状态.纯函数能适应 revert()和 require()在发生错误是还原状态. 一个合约最多有一个接收函数 receive(),声明为`receive() external payable {...}`不需要 function 关键字,也没有参数和返回值,必须用 external 和 payable 修饰.如果它不存在就会调用有 payable 的 fallback 回退函数.如果两个都没有就会在交易时抛出异常. 函数修饰符: modifier(修改器) 用于在函数执行前检查某种前置条件是否满足 1234modifier onlyOwner{ require(msg.sender == owner); // 判断调用合约的是不是合约所有者 _;// 下划线表示私有修改符的函数的方法体的替换位置} 回退函数 fallback: 每个合约最多只有一个,这个函数无参数也无返回值.一般有两种情况对调用回退函数,一是调用合约时没有匹配到任何函数,二是给合约发送 eth 时,交易中没有附带任何其他数据,也会调用回退函数.新版本不再推荐,推荐使用 receive 函数. 12345contract Test{ fallback() external payable{ throw; // 执行失败返回 eth 给发送者. payable 修饰符用来接收 eth }} 自毁函数: selfdestruct(address)用来摧毁合约并将 eth 转移到给定地址.当你发现合约有问题不想让其他人使用时就可以摧毁这个合约了.摧毁之后再有人发送eth 到这个地址就会消失. solidity 支持函数重载和函数重写 overriding.父合约标记为 virtual 函数可以再继承合约里重写.重写的函数要用 override 修饰. 继承: solidity 合约可以通过 is 关键字实现从父合约中继承. 12345678910contract A{}contract B is A{}// 多重继承contract C is A,B{} 接口:接口 interface 是 solidity 在版本 0.4.11 版本后引入的,接口所有函数都是抽象函数, 关键字 abstract定义抽象函数. 合约中有的函数没有函数体只有函数定义的是抽象合约. 库:库是一中不同类型的合约,没有存储,不拥有 eth.库中的代码可以被其他合约调用而不需要重新部署,这样可以节省大量 gas.库中没有可支付的函数(payable),没有 fallback 回退函数, 库的调用通过 DELEGATECALL(委托调用,除此之外好友 call,staticcall都是低级的函数,破坏了 solidity 的类型安全性,谨慎使用) 实现,不切换上下文. Using for:using for 的声明方式是 using lib for a,意为库 lib 中所有函数默认接收 a 实例作为第一个参数.`using Balances for *`引入库 Balances 中的函数被附加在任意的类型上。 12345678910library C{ funtion a() returns (address){ return this; }}contract A{ function test() returns(address){ return C.a(); // 返回 A 合约的地址 }} 事件真实环境中我们需要发送交易(Transaction)来调用智能合约,我们无法立即获得合约的返回值,此时调用返回值只是该交易的 txid 或 tx hash 值.当事件真正发生时,合约将事件写入区块链时,前端才能进行响应. 12345678910 //定义事件event Sent(address,indexed from...);// 触发事件emit Sent(address,...)// js 调用事件var ClientReceipt = web3.eth.contract(xx);var event = ClientReceipt.Sent();event.watch(function(error,result){...})","categories":[{"name":"solidity ETH","slug":"solidity-ETH","permalink":"http://yoursite.com/categories/solidity-ETH/"}],"tags":[{"name":"solidity ETH","slug":"solidity-ETH","permalink":"http://yoursite.com/tags/solidity-ETH/"}]},{"title":"认识以太坊","slug":"认识以太坊","date":"2019-09-18T11:29:39.000Z","updated":"2021-08-30T11:30:52.053Z","comments":true,"path":"2019/09/18/认识以太坊/","link":"","permalink":"http://yoursite.com/2019/09/18/认识以太坊/","excerpt":"","text":"认识以太坊 以太坊不同于比特币,它是一种图灵完备的系统.以太坊能够执行存储在区块链之上的程序的能力,是由被称为EVM的状态机完成的,创建了一个分布式的单体状态世界计算机。 根据图灵的理论,在真正运行合约之前,以太坊实际上无法预先判断一个合约是否会运行终止,或者它需要运行多久,也许这个合约会陷入死循环一直运行。无论是程序中的瑕疵,还是故意为之,智能合约都可能在一个节点试图验证它的时候永远不停地执行下去,这也就造成了一种DDoS攻击的后果。为了应对这个挑战,以太坊引入了名为gas的计量机制。以太坊的货币是 eth,不同于比特币,eth 的总量没有上限,它的最小单位是 wei,1eth 等于 10 的 18 次方 wei. DAPP 去中心化应用,代表更为广泛的智能合约.它需要一个智能合约和web 用户界面,是一个区中心化的 web 应用程序. Web3 是一种去中心化的互联网协议. 选择一款以太坊钱包(推荐MetaMask可以运行在 chrome 中),并保管好你的私钥.在交易时矿工会收取一部分手续费,手续费是由矿工决定的.交易完成后可以通过以太坊区块浏览器 查询到. 钱包创建的账户是外部账户,用户掌握私钥.而合约账户掌握智能合约代码,没有私钥,它由代码所控制.合约具有地址,可以接收和发送以太币.合约账户无法启动交易,但合约可以互相调用,对交易做出反应. 以太坊支持多种编程语言,最常用的是Solidity,有以太坊创始人之一Gavin Wood 创立.Solidity 编译器会把代码编译成EVM 字节码然后可以再区块链上的 EVM 中所执行.在次之前需要给合约注册到以太坊链上,需要通过一个特殊的交易,这个交易的目标地址是0x0000000000000000000000000000000000000000,被称为零地址,用来告诉以太坊区块链用户希望通过这样的交易来注册合约.注册成功后合约有了自己的地址. 有人向合约地址发送交易就会触发合约在 EVM 上的执行,交易的本身就是合约函数的输入参数.合约发起的交易称内部转账或消息. 请注意,JavaScript的限制,大于10的17次幂 的数值无法处理,我们需要单独处理. 以太坊协议分别有六种实现,分别是以 rust,pathon,Go,Java,cpp,scala 实现的.开发以太坊并不需要在主网上有运行一个全功能节点,可以再测试网络上的节点完成开发.测试网络节点连接的是一个公共测试区块链. 以太坊客户端的API是一组远程过程调用(RPC)命令,并采用JSON格式编码。这被称为JSON-RPCAPI。最基本的,JSON-RPCAPI作为一个接口,允许程序员编写代码,把以太坊客户端作为进入以太坊网络和区块数据的大门-网关.RPC接口使用8545端口以HTTP协议的方式对外服务。出于安全的目的,在默认情况下对这个端口的访问是受限的,只允许来自本地网络的链接.RPC 接口提供了很多服务,比如“管理钱包中的私钥和以太坊地址。创建、签名并广播交易。通过交易内数据载荷的方式与智能合约交互。浏览并使用DApp。提供外部服务的链接,例如区块浏览器。转换以太币的单位,向浏览器注入一个 web3 实例.移动钱包都属于远程客户端. 不要尝试用编程的方式 创建随机数,使用密码学安全的伪随机数生成器(如CSPRNG),并且使用熵源充足的随机源做种子,可以确保它在密码学上是安全的. 椭圆曲线程序库, openSSL和libsecp256k1(bitcoinCore). 加密哈希函数是一种单向的哈希函数,可以将任意长度的数据映射为固定长度的比特序列。这种“单向性”意味着,如果我们只知道哈希函数的输出值,还原出输入数据在计算上是不可能的。以太坊使用的哈希算法是Keccak-256(爱德华斯诺登爆出美国国家安全部门在该算法的随机数生成器中留有后门), 可能会更改为FIPS-202 SHA-3. 不同于比特币,以太坊的地址没有校验信息,在交易时注意地址是否正确.EIP55 提案增加了校验,但只有支持 EIP55 的钱包才能使用. 以太坊的交易信息中有一个 nonce值,它是一个记数器,如果同时发起多笔交易,矿工是以任意顺序接收交易的,交易信息中有 nonce 的比没有 nonce 值的交易优先级要高.如果你按顺序创建了一系列交易,但其中一个没有得到确认,那么之后的所有交易都会“堵”住,等待这个缺失的交易。如果某个 nonce 的值不对或者没有足够的 gas,交易都会被堵住. 交易信息中还包含 gas 和 gasLimit 这两个信息,gas 不是以太币,但它跟以太币有汇率关系会上下波动.gas 是用来支付给矿工的手续费,高额的 gas 可以让你的交易快速完成.gasLimit 是付款人为了完成交易愿意付出的最大 gas 量.由于每次调用合约,合约的计算量会根据代码决定,为了应对突发状况,你需要多付出一些 gas.以太坊没有找零机制. 向一个错误的地址 发送交易会导致 eth 被销毁(进入黑洞),因为错误的地址没有私钥,无法完成签名去使用它. 交易的核心数据是 value 和 data 两个字段,他们都可以为空.接收者为外部账户,不管对方是否存在该金额都会转移到接收地址.如果接收者是合约地址,就会执行 EVM 合约,data为空时执行合约的回退函数.data 有内容时,data字段的内容会被EVM解读为针对合约的函数调用,调用data中指定的函数,并把需要的参数传递给这个函数。 一但被部署之后,智能合约就不能被更改,更改的唯一方式就是部署一个新的合约实例.但合约实例可以被删除,当把合约地址和内部存储信息清空之后合约就成了一个空地址,系统会提供 gas 退款,用来激励大家释放资源.需要注意,只有合约的开发者在代码中编写了对应的删除功能,SELFDESTRUCT字节码才会起作用。否则无法删除合约实例. 智能合约为程序员设定了一个很高的门槛:如果有bug,可能会损失大量的金钱。因此,编写智能合约就需要极力避免任何可能的副作用。 gas的费用由交易的发起方支付,因此我们需要避免调用那些可能引发高额gas的合约或者函数。程序员的最佳策略就是设法避免合约可能产生的 gas 消耗.比如避免对动态数组所执行的循环操作,避免调用未知的合约. Vyper是在以太坊虚拟机上执行的、面向合约的实验性编程语言,旨在提供更好的可审计性,并帮助程序员更轻松地完成清晰易懂的代码.OpenZeppelin项目完成了一项伟大的工作,它为以太坊社区构建并审计了很多安全的库合约,可以避免漏洞. 有很多可重用的代码可以供我们编写自己的合约,包括已经部署到链上的可调用合约以及可以作为代码模板使用的库合约代码.有OpenZeppelin 和 zeppelinOS 等 ERC20 标准是在以太坊上构建代币的标准.工具型代币是指用来支付某个服务、应用或资源的代币,比如 gas。权益型代币可以被设定为没有投票权的,用来分红和分配利润的代币,也可以承载一个去中心化自治组织的投票权,这类组织的管理由持有代币的所有人共同投票决定. ERC 合约必须实现如下函数和事件,总发行量 totalSupply,balanceOf 余额,transfer 转账,transform 转账信息,approve 审核,allowance配额,Transfer 转账事件,Approval 审核事件,name 名称,其他的都是可选函数.symbol 符号等. ERC20支持两种转账。第一种是单一式转账,直接使用transfer函数。这个流程被钱包软件用来发送代币给其他地址。大多数的代币转账都是通过transfer函数完成的。第二种方式是使用了approve和transferFrom的两步式交易。这个流程允许代币的持有人授权其他地址操纵他们的代币。这通常用于授权给某一个合约地址,进行代币的分发,但也可以用于交易所的场景。 以太币是使用带有目标接受人地址的交易进行转账的,而代币的转账是通过代币合约内相关的状态转换进行的,使用合约作为地址,而不是接收方的地址。代币合约跟踪余额并发出事件。在代币的转账过程中,并没有任何针对接收方地址的交易。接收方的地址被加入代币合约的余额映射表中。即使是支持ERC20代币的钱包软件,也不会自动发现对应地址上的代币余额变化,触发用户主动把某个代币合约添加到钱包的关注列表。大多数ERC20代币都像是垃圾邮件一般毫无价值。 以太币使用send函数发送,任何合约中的可支付函数或者外部账户都可以接收以太币。代币使用transfer或approve&transferFrom函数发送,这些函数只存在于创建这个代币的合约中.并不会触发接收方合约中的任何可支付函数。 预言机(oracle),它是可以为以太坊智能合约提供外部数据源的系统。为了保持共识,EVM的执行过程必须完全确定,并且仅基于以太坊状态和签名交易的共享上下文。这产生了两个特别重要的后果:一个是EVM和智能合约没有内在的随机性来源;另一个是外部数据只能作为交易的数据载荷引入。我们可以引入外部信息,包括随机性、价格信息、天气预报等,作为发送到网络的交易的数据部分。但是,这些数据本身无法信任,因为它的来源无法核实。我们使用预言机尝试解决这个问题.理想状态下预言机可以无信任的获取外部信息,用于智能合约,还可以把信息安全的中继到 dapp 前端. 预言机的设计模式: 所有的预言机都提供了一些关键功能。这些能力包括:从链外的数据源收集数据。使用签名消息在链上传输数据。将数据放入智能合约的存储空间,使数据可用。其他智能合约再调用预言机智能合约的检索功能来访问它.预言机的三种主要方式可以分为请求与响应、发布与订阅和立即读取。数据证明:让链外方法可以证明返回数据的完整性是非常关键的。两种常见的数据认证方法是真实性证明以及可信执行环境,上面列举出的所有机制描述的都是中心化的预言机系统,都需要依赖可信的权威。 去中心化预言机可以用于保证数据可得性,还可搭配链上数据汇总系统创建独立数据提供者网络。比如 chainLink,它分为三部分分别是声誉合约、订单匹配合约、数据汇总合约. DAPP 通常是指具有 web 前端的智能合约.Dapp 的后端部署在区块链上,无法篡改.因为在智能合约中执行计算非常昂贵,所以应该让它尽量小.当需要计算量特别大时,一般选做外部计算或使用外部数据源.但前提是你的用户必须信任这些外部源.Dapp 的前端与传统前端并无不同,通过 web3.js 连接以太坊. 不仅是计算,储存数据也通常储存在链下.可以是中心化的数据存储平台,也可以说去中心化数据存储平台IPFS和以太坊自有平台 Swarm.星际文件系统(IPFS)是一个去中心化的、内容可寻址的存储系统,它可以在P2P网络中分配存储的对象。Swarm是另外一个内容可寻址的、类似于IPFS的P2P存储系统。 任何应用都会包含的重要组件是进程之间的通信。传统情况下,这可以通过依赖中心化的服务器进行。但是,也有许多基于P2P网络的替代方案。对DApp来说,最值得关注的P2P消息协议是Whisper. 一些DApp可能设置一个或者多个具有特殊功能的特权账户,如终止DApp合约,覆盖或者更改合约配置,甚至“否决”某些操作。通常,在DApp中引入这些治理功能是为了避免某些未知的问题而引起的bug。在构建一个DApp时,你必须决定是让其完全独立,一旦部署之后就无法被控制,还是创建一个特权账户,承受受到攻击的风险。任何一种选择都会带来风险,但是长远来看,真正的DApp不应该存在能进行特权访问的特权账户,因为这不是去中心化。 以太坊名称服务:ENS不仅仅是一个智能合约,它本身是一个基础的DApp,提供去中心化的名称服务。是以太坊的域名(DNS服务商 ,域名是.eth,它通过一个拍卖系统被分发,并且没有保留列表或者优先级,获得名称的唯一办法就是使用该系统。ENS的顶层是另一个超级简单的合约,目的只有一个:持有资金。如果所有者不再需要这个名称,他们可以将其卖给系统并拿回他们的以太币(资金只能转给所有者).这种方法大大降低了因为bug引起的攻击而给资金带来的风险。 以太坊虚拟机 EVM 用来处理智能合约的部署和执行.EVM有一个基于栈的架构,在一个栈中保存了所有内存数值 。EVM的数据处理单位被定义为256位的“字”(这主要是为了方便处理哈希运算和椭圆曲线运算操作),并且它还具有以下数据组件:一个不可变的程序代码存储区ROM,加载了要执行的智能合约字节码。一个内容可变的内存,它被严格地初始化为全0数值。一个永久的存储,它是作为以太坊状态的一部分存在的,也会被初始化为全0.它仅仅是一个计算引擎,仅提供对计算和存储的抽象,就像Java虚拟机(JVM)那样。从高级视角来看,JVM的设计提供了一个无须知晓底层宿主OS或硬件的运行环境,从而提供了跨不同系统平台的兼容性.EVM 的执行顺序由以太坊发起的交易决定,就像是 js 中的单线程. EVM既没有任何“系统接口”,也没有“硬件支持”,因为并没有任何物理机器需要与之交互。以太坊世界计算机是完全虚拟化的。EVM 的任务是基于以太坊协议、根据智能合约代码的执行来计算合法的状态转换,用以更新以太坊的状态。 由于停机问题,以太坊世界计算机就有了一个被程序要求永远执行下去的风险。这可能是由于某些意外情况或者恶意的目的。不过在有了gas之后,也就有了一个解决方案:如果在一个预先指定的最大计算量被用尽的时候计算还没有结束,那么所有处理都会无条件地停止。这个限制并不是固定的,你可以通过支付费用来将它提高到最大值. 与比特币协议中仅仅以交易数据的字节大小来计算交易费不同,以太坊协议中计算交易费Gas时需要计量由交易和智能合约代码执行所引发的所有计算步骤。比如相加两个数值需要消耗3 gas,发送一个交易需要消耗21000 gas.Gas 是以太币和矿工奖励之间的缓冲,增加攻击者的攻击成本.如果智能合约执行完成,gas 作为交易费给矿工,矿工费= 合约实际消费的gas * gas 的价格(与以太币的汇率)最后得到一笔以太币,交易剩余的 gas 兑换成以太币返还. 交易未成功的话也需要支付交易费,因为矿工为发生错误的操作执行了计算. 在一个敌意环伺、没有中心化控制者的分布式网络中达成共识的能力是所有开放性公有区块链的核心。比如工作量证明POW是支撑比特币的最重要的发明,奖励只是工具,它的目的是保护比特币系统的安全和去中心化.PoW共识意味着一套风险与收益的细致权衡,驱动着参与者出于自利而遵循共识规则行动。 POW 提出之前就已经出现了权益证明共识 POS.以太坊也正在由 pow 逐渐向 pos 过渡.PoS算法的运作流程大体如下: 持有以太币的人会有投票权,但是需要质押自己的资产充作保证金,投票的权重和保证金成正比,投票的区块通过则按比例获取奖励,不通过则失去保证金.另外还有一种 DPOS 它是分布式的 POS. POW 的币种都要考虑有 ASIC 矿机造成的算力集中,GPU短缺,电力浪费等问题. THE DAO 去中心化的自治组织,在ICO众筹自己发行的代币 DAO 时遭到黑客攻击,损失了 360 万 ETH. 以太坊社区针对是否回滚发生争议,硬分叉为 ETC(不回滚) 和 ETH (回滚)两条链.根据社区投票,ETH 为客户端默认选择. Truffle框架由若干个Node.js包构成。用来开发以太坊智能合约.相似的框架还有 embark和 OpenZeppelin,zeppelinOS.web3 提供了与以太坊连接的接口.Ganache 提供了一个本地测试区块链用来测试智能合约. 以太坊共包含流程,从下往上分别是 数据层(区块和区块链),网络层(p2p),共识层(pow/pos),激励层(挖矿和 gas),合约层(EVM 和智能合约),应用层(DAPP,钱包)","categories":[{"name":"ETH","slug":"ETH","permalink":"http://yoursite.com/categories/ETH/"}],"tags":[{"name":"ETH","slug":"ETH","permalink":"http://yoursite.com/tags/ETH/"}]},{"title":"认识比特币","slug":"认识比特币","date":"2019-08-30T11:27:11.000Z","updated":"2021-08-30T11:29:18.377Z","comments":true,"path":"2019/08/30/认识比特币/","link":"","permalink":"http://yoursite.com/2019/08/30/认识比特币/","excerpt":"","text":"认识比特币(BTC) 比特币地址以 1 和 3 开头. 1 开头的地址,比特币只能通过私钥签名和公钥哈希后才能消费,3 开头的是P2SH 地址,代表多重签名. 钱包是多个地址和对应密钥的集合. 没有交易过的地址是无效地址,交易过的地址就已经在全网公布了,可以再全账本或区块链浏览器中查到交易记录.交易后该记录是未确认状态,由矿工打包到区块中并链接到链上后变为确认状态. 在交易时,钱包会从多个地址找到有余额的地址(UTXO 意为未花费的交易输出),然后组合成可消费当前金额的组合(不一定相等,但一定是不少于可消费金额),然后创建一个找零地址,为了让这笔交易快速确认再付一笔矿工费,再发送出去.矿工费可能是设置的找零金额和实际找零金额的差值,所以要确认这个金额是否相差过多. ASIC矿机是一个集成可上百个挖矿专用算法硬件且能同时在一个单独芯片上并行工作的专用集成电路.而矿池是多个矿工共享算力和依靠贡献分成的. 交易发送到区块链网络时,区块链网络上的节点会把这些交易放到未验证的交易池当中.矿工构建新区块时会从交易池中放到新区块中,以矿工费为优先级进行排序,然后计算哈希开始挖矿.矿工一旦从网络上收到新区块时,会立刻放弃当前挖的区块,重新生成新区块.当生成新快成功后,区块奖励的比特币会支付到自己的比特币地址,如果是矿池会根据工作量进行分配.每生成一个区块,对于上一个区块就多了一个证明.按照惯例,6 个确认之后就被认为是不可撤销的. 比特币核心客户端是一个完整的账本,随着时间发展,账本会越来越大.按照后可以使用 bitcoin-cli 工具,包含了很多工具和功能.比如钱包设置和加密,备份,文本导出和恢复.接收交易等等 拥有比特币的秘钥就拥有该账户的控制权,秘钥是非对称加密的,公钥向外展示是比特币地址,私钥用来签名不对外公开.可以根据私钥计算出公钥,一般存储时只储存私钥.私钥是一个数字随机选出(256 位的二进制,2 的 256 次幂),然后根据椭圆加密曲线这个单向加密函数产生一个公钥.有了公钥就可以根据哈希函数生成比特币地址. 素数幂和椭圆曲线算法是不可逆的,很容易向一个方向计算但不能逆向去推算.使得数字签名称为可能.比特币使用椭圆曲线算法作为其公钥加密的基础算法. 比特币交易时会使用私钥对公钥进行签名,每次签名都不同,网络中的节点可以根据公钥和签名进行验证,确认该笔交易是否拥有比特币的所有权. 椭圆曲线算法:是一种基于离散对数问题的非对称加密算法,它是单向的函数.比特币使用了一种 secp256k1 标准所定义的一条特殊椭圆曲线和一系列数学常数.以一个随机生成的私钥 k 为起点,将其与曲线上已经定义的生成点 G相乘 获得曲线上的另外一个点 K 即公钥.生成点 G 是 secp256k1 标准的一部分,比特币的生成点都是一样的, K = k*G,公钥和私钥关系是固定的,但只能单向运算,所以暴露公钥并不会威胁到私钥的安全.(椭圆曲线之上的算术运算跟常规的数学运算是不一样的。一个点(G)可以与一个整数(k)相乘来获得另外一个点(K)。但是椭圆曲线的世界里没有除法的概念。因此不可能简单地通过计算公钥K对G点的除法来计算私钥。这就是本章前面提到的单向数学函数。 哈希加密算法在比特币中广泛运用,包括比特币地址的生成,工作量证明等等.比特币私钥 SHA256 哈希加密算法. 根据公钥进行 sha256 和RIPEMD 160 两次哈希运算的到160bit或20字节的公钥哈希,然后根据 base58check编码获取比特币地址(前缀为 0x00). 因为 256 位二进制过长,交易不便,base58 编码和 base64 不同的是, base58不仅实现了数据压缩和易读性还具有错误诊断功能.它不包含 base64 中的数字0 ,大写 o,小写 l,大写 I 这些容易混淆的字符.base58check增加了错误校验,数据中自带校验码,错误的比特币地址不会被认为有效地址从而造成损失. 早期比特币钱包只是随机生成的私钥集合,称非确定性(随机)钱包,难以管理备份和导出.这与避免地址重复使用原则相违背(每个地址最好只使用一次,可以增加安全性),后面出现的确定性钱包现在是主流,确定性钱包可以从公共的种子生成大量私钥,创建备份导出时只关注种子就可以了.助记词是英文单词序列用作种子所对应的确定性钱包的随机数私钥.单词的顺序就是钱包的备份.用来恢复钱包.助记词被定义在比特币改进协议 BIP0039 中作为草案而不是标准方案.通过 BIP0039 生成的钱包是 HD(分层确定性)钱包,由种子生成主密钥,再生成子密钥,子密钥又可以生成孙密钥. BI0038提出一个通用标准,使用一个口令加密私钥并使用Base58Check对加密的私钥进行编码,这样加密的私钥就可以安全地保存在备份介质里,安全地在钱包间传输,保持密钥在任何可能被暴露情况下的安全性。其本质就是私钥和密码混合形成加密私钥. 比特币地址以 1 和 3 开头. 1 开头的地址,比特币只能通过私钥签名和公钥哈希后才能消费,3 开头的是P2SH 地址,代表多重签名.多重签名需要从 N 个密钥中需要 M 个私钥签名才可以消费这笔金额. 比特币开发者针对比特币客户端进行交易的脚本类型设置了一些限制,然后编译为一个叫standard 的函数,该函数定义了五种类型的交易分别是P2PKH、P2PK、MS(限15个密钥)、P2SH(多重签名)和OP_Return” 并非所有的节点都有能录存储完整的区块链账本,通过简化的支付验证(SPV)的方式可以使它们在不必存储完整区块链的情况下进行工作.SPV 节点只需要下载区块头而不用下载交易信息.一个拥有完整区块链的节点会构造一条验证链,这条链是由沿着区块链按时间倒序一直追溯到创世区块的数千区块及交易组成。而一个SPV节点会验证所有区块的链(但不是所有的交易),并且把区块链和有关交易链接起来.但它不是绝对安全的,如果要保证万无一失的安全性,最可靠的方法还是运行完整区块链的节点。 在SPV节点里,Bloom 过滤器被用来向对等节点发送交易信息查询请求,同时交易地址不会被暴露。 比特币网络中几乎每个节点都会维护一份未确认交易的临时列表,被称为内存池或交易池.内存池中的交易如果长时间未处理就会消失,交易人可以重新发起交易或提高矿工费以提高优先级. 一个区块出现多个子区块的情况被称为“区块链分叉。但这只是暂时的,最长链原则决定了分叉的问题.区块越长,篡改的成本越大,区块链越安全,这也是比特币的一个特征. 一个区块的大小是 1M,一笔交易最小是250 字节,也就是一个区块最多包含 500 笔交易.一个区块有区块头,交易额记数,交易信息三部分组成. 挖矿保护了比特币系统的安全,并且实现了在没有中心机构的情况下,也能使整个比特币网络达成共识。铸造新币的奖励和交易费是一种激励机制,它可以调节矿工行为和网络安全,同时又完成了比特币的货币发行.比特币每出 210000 个区块奖励就会减半.2140 年奖励为最小单位,停止奖励,总金额是 2100w 个.区块奖励不同于其他交易,没有付款人,只包含一个 coinbase 的输入,仅仅是用来创建新的比特币,叫创币交易. 工作量证明的难度单位为 1TH/s 表示每秒可以处理 1 万亿次哈希运算.比特币每 10 分钟出一个区块,但并不是没 10 分钟进行一个难度调整,而是 2016 个块会调整一次难度,会根据 2016 个区块的总共花费的时长与 20160 分钟进行比对,之后再决定该如何调整. 51%攻击,当一个或者一群拥有了整个系统中大量算力(一些安全模型认为 30%的算力就可发动 共识攻击)的矿工出现之后,他们就可以通过攻击比特币的共识机制来达到破坏比特币网络的安全性和可靠性的目的.共识攻击不能从其他的钱包那里偷到比特币、不签名地支付比特币、重新分配比特币、改变过去的交易或者改变比特币持有纪录。共识攻击能够造成的唯一影响是影响最近的区块(最多10个)并且通过拒绝服务来影响未来区块的生成。这也是惯例 6 个区块确认才算做安全交易的由来. 莱特币的工作量证明与比特币不同,比特币是基与 SHA256,而莱特币为了避免 ASIC 矿机导致算力过于集中,采用了scrypt 算法,便于 CPU 挖矿.","categories":[{"name":"BTC","slug":"BTC","permalink":"http://yoursite.com/categories/BTC/"}],"tags":[{"name":"BTC","slug":"BTC","permalink":"http://yoursite.com/tags/BTC/"}]},{"title":"Docker使用总结","slug":"Docker使用总结","date":"2019-07-30T09:34:47.000Z","updated":"2021-08-30T09:39:13.463Z","comments":true,"path":"2019/07/30/Docker使用总结/","link":"","permalink":"http://yoursite.com/2019/07/30/Docker使用总结/","excerpt":"","text":"Docker常用命令12345678910111213141516171819202122232425# 镜像相关docker images # 查看镜像docker search xxx # 搜索互联网镜像docker pull xxxx # 拉取镜像docker rmi xxxx # 删除镜像# 容器相关docker ps # 查看运行的容器 docker ps -a # 查看所有容器 docker run # 运行容器,-i运行容器,-t创建后进入命令行,-d后台运行,-p端口映射,-v目录映挂载,--name命名#交互式 docker run -it --name=mycentos centos:7 /bin/bash# 守护式 docker run -di --name=mycentos1 centos:7docker exex -it xxxxx /bin/bash # 进入容器 docker stop xxx # 停止容器docker start xxx # 启动容器# 文件拷贝docker cp 需要拷贝的目录 容器名:容器目录docker cp 容器名:容器目录 需要拷贝的目录# 通过目录挂载将目录与容器目录进行映射 通过--privileged=true来解决目录权限问题docker run -di -v /xx/YY:/xx/YY --name=mycentos2 cnetos:7# 查看容器相关数据 docker inspect xxx# 查看容器ipdocker inspect --format='{{.NetworkSrtting.IPAddress}}' xxx# 删除指定容器docker rm xxx 应用部署123456789101112131415161718192021222324252627282930#mysqldocker pull centos/mysql-xxxdocker run -di --name=testmysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysqldocker exex -it testmysql /bin/bashmysql -uroot -pxxx#nginxdocker pull nginxdocker run --name nginx-test -p 8080:80 -d nginxdocker exec -it nginx-test /bin/bash#tomcatdocker pull tomcatdocker run --name myTomcat -p 8080:8080 -v $PWD/xxx:/usr/local/tomcat/webapps/ -d tomcat #目录映射docker exec -it myTomcat /bin/bash# mongodocker pull mongodocker run -itd --name myMongo -p 27017:27017 mongo --authdocker exec -it myMongo mongo admin# 创建一个名为 admin,密码为 admin 的用户。> db.createUser({ user:'admin',pwd:'admin',roles:[ { role:'userAdminAnyDatabase', db: 'admin'}]})# 尝试使用上面创建的用户信息进行连接。> db.auth('admin', 'admin')# RabbitMQdocker search rabbitmqdocker pull rabbitmq:3.8.3-managementdocker run -di --name myRabbitmq -p 5671:5617 -p 5672:5672 -p 4369:4369 -p 15671:15671 -p 15672:15672 -p 25672:25672 rabbitmq:management# 访问 http://127.0.0.1:15672/ 备份123456#容器保存为镜像docker commit 容器名称xxxx 镜像名称YYY# 镜像备份docker save -o xxx.tar 镜像名称YYY# 镜像恢复与迁徙docker load -i xxx.tar DockerFiledockerfile是有一系列命令和参数构成的脚本,这些命令应用于基础镜像并最终创建一个新的镜像。 dockerFile常用命令 1234567FROM image_name:tag 定义使用的基础镜像MAINTAINER user_name 什么镜像创建者ENV key calue 设置环境变量RUN command 命令执行ADD 原目录/文件 目标目录/文件 将文件移动到容器COPY 原目录/文件 目标目录/文件 将文件复制到容器WORKDIR 目录 设置工作目录","categories":[{"name":"后端 docker","slug":"后端-docker","permalink":"http://yoursite.com/categories/后端-docker/"}],"tags":[{"name":"docker","slug":"docker","permalink":"http://yoursite.com/tags/docker/"}]},{"title":"vim入门指南","slug":"vim入门指南","date":"2019-05-09T11:24:13.000Z","updated":"2021-08-30T11:40:09.864Z","comments":true,"path":"2019/05/09/vim入门指南/","link":"","permalink":"http://yoursite.com/2019/05/09/vim入门指南/","excerpt":"","text":"vim入门指南快捷操作- di" 删除本行""内的文本 - cw 替换一个词 - yaw 复制当前词 - f和 t来查找当前行字符,`;`重复查找,`*`查找当前词,n 跳往下一个. - ctrl + d/u 向下/上 滚动半屏 - 分隔符 a/i 带空格/不带空格 ,例如 a(/i( ,一般 d 和 a 配合,c 和 i 配合, w/词 -> s/句子->p/段落. - u 撤销修改,U 撤销所有修改(undo), 撤销最后一次修改 ctrl + R - i/I,a/A,o/O 插入 - mm 命令可以标记一个位置,然后~m回到标记点 - 全局替换`:%s/{pattern}/{string}/{flags}` - 宏: q a-z 命名, 操作 q 离开完成.调用 @ a-z 具体的宏. 字符跳动 h,j,k,l 左下上右 模式所有命令都需要在normal模式下使用 i切换到insert模式,按esc回到normal模式 v 可视化模式,按v或V进入可视模式v可以进入面向字符的可视模式,V 是针对行的可视模式.ctrl +v可以进入针对列的可视模式.可视模式下按 o 可以调整选择区边界. :wqa! w为保存,q为离开,!为强制执行 ,a表示all所有 行内移动 0 到行头 ^ 第一个不是空的位置 $ 行尾 g_ 最后一个不是空的位置 w 下一个单词开头 b 当前或上一个单词开头 wi/bi 在开头前插入 e 当前或下一个单词结尾 (end) ge 上一个单词结尾 ea,gea 在结尾后插入 fa 下一个字符为a的位置 ta 字符a前的第一个字符 页移动 H 跳转到当前屏幕第一行 (head) M 当前屏幕中间一行 (middle) L 最后一行 (last) 2H 移动到当前屏幕第2行 3L 移动到当前屏幕倒数第三行 ctrl + f 前移一页 (forward) ctrl + b 后移一页 (back) ctrl + d 往下滚动半屏 (down) ctrl + u 往上滚动半屏 gg 调到首行 相当于:1 G 调到尾行 nG 调到n行 相当于:n % 调到另一边括号 ({[]}) 配合查找字符方式移动 fa 正向移动到第一个字符a处, ta 正向移动到下一个a字符之前 Fa 逆向移动到第一个字符a处, Ta 逆向移动到下一个a字符之前 ;可以重复查找上次 f 命令查找的字符,不用再重复使用 f 命令.如果按了太多次,可以按,回到上次位置. *命令可以查找光标当前所在词,按 n 跳往下一个. f/t 只能在行内查找且查找时只能查找一个字符,而不是单词.查找时尽量选择频率比较低的字符从而快速移动到目标处. 非相邻单词或字符间移动 8w 正向移动到相隔8个单词的首字符 4Fa 逆向移动到第4个a字符 搜索匹配 /text 向后搜索 ? text 向前搜索 :g/word 全局搜索 n 搜索下一个同样的内容 N 搜索上一个同样的内容 查找 :{作用范围}s/{目标}/{替换}/{替换标志} :%s/old/new/gc 全局替换 作用范围 当前行:s,全文:%s,选区在visual 选择区域后输入:后 vim 自动补全:’<,>’s 或手动指定:4,6s 或.,+2s 接下来 2 行 替换标志 i 表示大小写不敏感,I 表示敏感,等于与模式中的\\c :%s/foo/bar/i === :%s/foo\\c/bar g 全局替换 c 表示需要确认,每次替换系统按键确认 y 替换/n 不替换/a 替换所有/q 退出查找模式/ l 替换当前位置并退出 在normal模式下按下*即可查找光标所在单词(word) \\c表示大小写不敏感查找,\\C表示大小写敏感查找 # 匹配当前光标所在的单词 是下一个 #是上一个 光标移动可以与其他命令联动,比如 0y$ 表示从行首拷贝到最后一个字符 拷贝不一定要用y,还可以使用d(删除),v(可视化选择),gU(变大写),gu(变小写) 插入 i在光标前插入(insert),I 在当前行开头插入 a在光标后插入(append),A在当前行结尾进行插入,相当于执行了$a操作 o在当前行后插入新行, O在当前行前插入新行 ea 在当前行结尾插入(end append) cw 当前光标到单词结尾清空并插入 在插入模式删除除了使用退格键,还可以使用 ctrl+w 删除前一个单词或使用 ctrl + u 删除至行首. 替换和删除 r 替换一个字符 replace,R 可以替换多个字母. cc 替换一行 cw 替换一个 word c$ 替换到行尾 s 修改一个字符 S 修改一整行 x 删除当前光标所在的一个字符 dl 删除当前字符 === x dw 删除到下一个单词开头 d0 删除至行首 dd 删除当前行 dj 删除上一行 dk 删除下一行 gg dG 删除全部内容 :1,10d 删除1-10行 :11,$d 删除11行及以后所有行删除的内容存到剪贴板. 操作d,c,y等操作符加 一个动作命令组成一次操作.例如 daw. 操作符有 - c 修改 (change) - d 删除 (delete) - y 复制, (yank) - p 粘贴 (paste/put) - g~ 反转大小写 - gu 转换为小写 - gU 转换为大写 - `>` 增加缩进 - `<` 减少缩进 - `=` 自动缩进 复制粘贴 yy 拷贝当前行(yank) 按v或V进入可视模式,然后上下左右选择,再按y即可复制,按d即可剪贴 p 粘贴到下一行或右侧 P 粘贴至上一行或左侧 调换文本行可以使用 ddp(先删除当前行再粘贴),同理复制当前行 yyp 交换两个词. 先用 d 删除一个词,然后 mm 标记,在用 v 选中另一个词按 p替换,然后 ~m 回到标记位置再按 p 即可完成替换. 针对字符的复制或删除操作如 x,diw等将创建面向字符的寄存器,粘贴时会放在光标后.针对行的复制删除操作如 dd,yy 或 dap 会创建针对行的寄存器,粘贴时会放在当前行的下一行. 撤销和重复 u撤销最近一次修改(undo) .命令可以再普通模式下重复上次修改,如果想要在命令模式下重复上次命令可以用@: U撤销所有 ctrl + r 取消最后一次的撤销 文件管理 :e 打开一个文件 :saveas 文件另存为 :x 或 zz 或:wq 保存并推出 :bn 切换到下一个文件(n表示next,b表示切换blocked-out) :bp 切换到上一个文件(p表示prev) :n 切换到下一个 命令行模式技巧 DTc 删除光标的c之间的所有字符 Rc 将光标的字符替换为c nDD 删除n行数据 nYY 复制n行数据 nX 删除n个字符 命令模式按:进入命令模式. :n 光标移动到多少行 :1,5w file 将第一行至第五行写入文件 :.,$w file 当前行至结束写入文件 :w 保存 :wq 保存退出 :q 退出 :q! 强制退出 :/str/ 正向搜索,光标移动到下一个结果 :?str? 逆向搜索 正则: :/^xxx/ :/xxx$/ :s实现替换 delete 删除 yank 复制 put 粘贴 copy 拷贝 move 移动 join 连接 normal 可以执行普通模式下的命令 substitute/{pattern}/{string}/{flags} 在范围内出现{pattern}的地方替换为{string} x,y 表示 x-y 行,.表示当前行,%表示当前文件所有行 使用:t和:m命令复制和移动行.其中:t是 copy 命令的简写(可以理解为copyTo),:m是 move 命令的简写 vim 实用技巧 分隔符文本对象. vim 的文本对象由两个字符组成,第一个字符是 i/a, i 会选择分隔符内部文本,a 会选择包括分隔符在内的整个文本.可以理解为 inside/all. 第二个字符是分隔符如)]}>'",t 表示标签 a)/i) a”/i” at/it文本对象前可使用 d/c/y/g~/gu/gU/>/</= 等操作符 范围文本对象 aw 当前单词及一个空格/iw 当前单词 as 当前句子及一个空格/is 当前句子 ap 当前段落及一个空行/ ip 当前段落一般 d 和 a 配合, c 和 i 配合. 标记位置 mm 命令可以标记一个位置,然后~m回到标记点 可以回到上次修改位置. 比如我们想要修改一对大括号,可以先用%匹配大括号,这时 vim 会自动为发生跳转的地方设置一个标记,在我们使用 r]修改后大括号后,按 ~~ 调回到前大括号,然后 r[ 修改完成. 自定义宏 创建宏 q a,操作,q 结束. 其中 a 为命名的寄存器. 查看宏 :reg a 调用宏 @a, @@可以回放上次调用的宏. 当前行多次调用宏. 3@a ,调用三次宏 a 多行调用宏, 按 v 进入可视模式,选中执行区间,:normal @a就可以在每一行执行宏了. 用迭代求值的方式给列表编号 12345:let i = 1 #定义变量qa # 定义宏和寄存器I ctrl+r =1 ctrl+R ) esc # 对行进行操作:let i += 1 # 变量递增q # 完成录制 搜索引擎\\v表示模式开关,可以让搜索时不用正则写过多的转义字符,比如要匹配 css 中的颜色可以使用/\\v #[0-9a-fA-F]{6}|[0-9a-fA-F]{3}\\V表示原义开关,按照字符进行匹配,即便字符中有像.这样的符号也会按照原义查找. substitude命令该命令可以提供查找模式以及替换字符串,还可以指定执行的范围.具体语法:s/{pattern}/{string}/{flags} 在范围内出现{pattern}的地方替换为{string},如果想要全局配置要使用:%s/xxx/xxx/gflags 为标志位,其中 g 是全局范围,n 为统计个数而不执行替换,c 让我们确认每一次修改.e 可以屏蔽错误提示.i 表示大小写不敏感,I 表示大小写敏感. 分屏 :vsp 左右分屏 :sp 上下分屏 :tabnew 新建标 z和fzf脚本123456789101112j() { if [[ -z \"$*\" ]]; then cd \"$(_z -l 2>&1 | fzf +s | sed 's/^[0-9,.]* *//')\" else _last_z_args=\"$@\" _z \"$@\" fi } jj() { cd \"$(_z -l 2>&1 | sed 's/^[0-9,.]* *//' | fzf -q $_last_z_args)\" }","categories":[{"name":"VIM","slug":"VIM","permalink":"http://yoursite.com/categories/VIM/"}],"tags":[{"name":"VIM","slug":"VIM","permalink":"http://yoursite.com/tags/VIM/"}]},{"title":"入门shell编程","slug":"入门shell编程","date":"2019-01-11T11:20:45.000Z","updated":"2021-08-30T11:21:42.942Z","comments":true,"path":"2019/01/11/入门shell编程/","link":"","permalink":"http://yoursite.com/2019/01/11/入门shell编程/","excerpt":"","text":"入门shell编程第一个shell脚本1234567#!/usr/bin/env bashmkdir codecd codefor ((i=0; i<3; i++)); do touch test_${i}.txt echo \"shell很简单\" >> test_${i}.txtdone 让我们看一下以上代码都做了什么: 从系统path中寻找指定脚本的解释程序 创建 名叫code文件夹 进入创建的文件夹 for循环3次 创建文件 往创建的文件中写入信息 结束循环 mkdir, touch,cd,touch,echo都是系统命令,在命令行下可以直接执行 for, do, done 是shell脚本语言 for循环的语法. 编写shell新建一个文件,扩展名为sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好,如果你用php,扩展名为php,如果你用Python,扩展名为python. 第一行一般是这样: 1234#!/usr/bin/php#!/usr/bin/env python3#!/usr/bin/env bash#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行 /env 是系统的PATH目录中查找 运行shell脚本有两种方式 作为可执行程序 12chmod +x op_base.sh //设置op_base.sh可执行权限./op_base.sh //执行op_base.sh文件 作为参数 1/bin/sh op_base.sh 变量定义变量时,变量名前不需要加符号和Python一样,但是在PHP语言中变量需要加$.如my_name="jack" 变量名和=之间不能有空格且变量后不能有;号. 使用变量对于已经定义过的变量,使用的时候在前面添加$,如echo $my_name,或echo ${my_name} .花括号是可选的,但建议加上.因为花括号是为了帮助解释器识别变量的边界. 注释单行注释使用#,解释器会忽略该行 sh 里没有多行注释,只能每一行加一个#号. 字符串shell 不像其他语言如 php,python 有很多数据类型,在 shell 中常用的数据类型即字符串和数字. shell中的引号和 php 类似,可以用单引号或双引号,双引号中可以有变量,可以出现转义字符,但单引号中的变量是无效的.python 中引号没有区别,且存在三个引号.三个引号不会被转义. 字符串操作 拼接字符串 123a=\"hello\"b=\"world\"echo $a $b 获取字符串长度 1echo ${#a} 截取字符串 1echo ${a:0:2} shell 数组定义数组在 shell 中用括号表示数组,数组元素用空格分隔开.如name=(name1 name2 name3)还可以单独定义数组的各个分量如arr[0]=name1 读取数组读取数组的一般格式是${数组[下标]},比如echo ${name[0]},使用@符号可以获取数组中所有元素.例如echo $name[@] 获取数组长度获取数组长度与字符串类似,例如: 123456789#获取数组元素个数length=${#name[@]}echo length# 或者length=${#name[*]}echo length# 取得数组单个元素的长度length=${#name[n]}echo length shell 传递参数我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……123./test.sh 1 2 3$0 # ./test.sh$1 # 1 ... shell 运算符原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。 123456789101112131415161718192021222324252627282930# 算术运算符# + - \\* / % = == !=val=`expr 2 + 2`echo \"两数之和为 : $val\"#关系运算符# -eq 相等 , -ne 不相等, -gt 大于, -lt 小于, -ge 大于等于, -le 小于等于if [ $a -eq $b ]then echo \"$a -eq $b : a 等于 b\"# 布尔运算# ! 非. -o 或, -a与if [ $a -lt 100 -a $b -gt 15 ]then echo \"$a 小于 100 且 $b 大于 15 : 返回 true\"# 逻辑运算# && ||# 字符串运算# =, !=, -z 长度是否为 0, -n 长度是否不为 0, $ 是否为空# 文件测试运算符# -e 是否存在# -d 是否存在且为目录# -f 是否存在且为常规文件# -r 是否存在且可读# -w 是否存在且可写# -x 是否存在且可执行# -s 文件大小是否大于 0 test 命令 和 [[]]test命令用来测试某条件是否成立,可以用[]代替, 但[]内部不能使用||和&&以及!,[[]]支持这种写法,1234567read telif [[ $tel =~ ^1[0-9]{10}$ ]]then echo \"你输入的是手机号码\"else echo \"你输入的不是手机号码\"fi echo 和 printfecho 和 printf是用于字符串的输出.12echo \"$1\" > xx.txtprintf \"%-10s %-8s %-4s\\n\" 姓名 性别 体重kg shell 流程控制和其他编程语言流程控制不同,sh的流程控制不能为空 如 js 流程控制 123456var a = trueif a{ console.log(\"a 为 true\")}else{ //什么也不做} 在 sh/bash 里不能这么写,如果 else 分支没有执行语句就不要写 else. 如: 123456789101112131415#!/usr/bin/env sha=1b=2if [$a == $b]then echo 'a 等于 b'elif [$a -gt $b]then echo 'a 大于 b'elif [$a -lt $b]then echo 'a 小于 b'else echo \"没有符合的条件\"fi for 循环shell 的 for 循环和 python 类似. python 的 for 循环: 12for item in 1,2,3,4,5: print(item) shell 的 for 循环写法一: 1234for item in 1 2 3 4 5;do echo 'i='$idone shell 的 for 循环写法二: 1234for ((i=0;i<5;i++)); do echo \"i=\"$idone while语句while 循环用于不断执行一系列命令,也用于从输入文件中读取数据,命令通常为测试条件. 123456int=1while (($int<=5))do echo $int let \"int++\"done 在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数 shell 函数123456789funWithReturn(){ echo \"这个函数会对输入的两个数字进行相加运算...\" echo \"输入第一个数字: \" read aNum echo \"输入第二个数字: \" read anotherNum echo \"两个数字分别为 $aNum 和 $anotherNum !\" return $(($aNum+$anotherNum))} 输出/输入重定向1234# > 输出重定向到 file# < 输人重定向到 file# >> 追加# n>&m n 和m 合并到 m shell 结合系统命令shell脚本结合系统命令会更加强大,在字符串处理领域,有 grep,awk,sed 三剑客, grep 负责找出特定的行, awk 能将行拆分成多个字段, sed 则可以实现更新插入删除等写操作.例如定时检测 nginx,mysql 是否被关闭. 1234567891011121314151617181920path=/var/loglog=${path}/httpd-mysql.logname=(apache mysql)exs_init[0]=\"service httpd start\"exs_init[1]=\"/etc/init.d/mysqld restart\"for ((i=0; i<2; i++)); do echo \"检查${name[i]}进程是否存在\" ps -ef|grep ${name[i]} |grep -v grep if [ $? -eq 0 ]; then pid=$(pgrep -f ${name[i]}) echo \"`date +\"%Y-%m-%d %H:%M:%S\"` ${name[$i]} is running with pid $pid\" >> ${log} else $(${exs_init[i]}) echo \"`date +\"%Y-%m-%d %H:%M:%S\"` ${name[$i]} start success\" >> ${log} fidone#检测 nginx、mysql进程是否存在,如果不存在了会自动重新启动。 脚本每次运行会写日志的,没事可以去看看该日志文件,如果进程是不是真的经常性不存在,恐怕就要排查一下深层原因了。","categories":[{"name":"Shell","slug":"Shell","permalink":"http://yoursite.com/categories/Shell/"}],"tags":[{"name":"Shell","slug":"Shell","permalink":"http://yoursite.com/tags/Shell/"}]},{"title":"关于 JavaScript 隐式转换的一道题","slug":"关于 JavaScript 隐式转换的一道题","date":"2018-07-14T11:16:05.000Z","updated":"2021-08-30T11:20:16.919Z","comments":true,"path":"2018/07/14/关于 JavaScript 隐式转换的一道题/","link":"","permalink":"http://yoursite.com/2018/07/14/关于 JavaScript 隐式转换的一道题/","excerpt":"","text":"关于 JavaScript 隐式转换的一道题让我们先看一道面试题123当 a==1 && a==2 && a==3 为 true 时,a = ?简要分析 a 多次 == 一个不同的值成立,由此可以推断 a 是一个引用类型的值且比较时用到 == 而不是 === ,可以判断这道题考察的是 js 的隐式转换问题 其他值转换为字符串 当值是原始类型时,直接转换为字符串类型 1234null => 'null'undefined => 'undefined'true => 'true'1 => '1' 当值是引用类型时,先转换为原始类型,再转换为字符串.String方法背后的转换规则与Number方法基本相同,只是互换了valueOf方法和toString方法的执行顺序。 123String([1,2,3]) // 1,2,3 因为[1,2,3].toString() 是'1,2,3',再调用 valueOf 是'1,2,3'String([]) // ''{a:1} // [object object] 其他值转换为数字类型 当值是原始类型的转换规则 123456// 数字类型转数字类型Number(123) // 123// 字符串转数字类型Number('123a') // NaN Number 比 parseInt 严格,当有字符串无法转换时返回 NaN,而 parseInt 返回已经转换的值, 空字符串转成0// 布尔值:true 转成 1,false 转成 0// undefined:转成 NaN ,null 转成0 当值是引用类型时的转换规则 12// 先调用 valueOf(),如果不是原始类型的值再调用 toString,然后NumberNumber({x: 1}) // NaN 其他值转换为布尔值只有 null,undefined,0,空字符串,NaN 转换为 false,其他所有值都转为 true 引用类型转换为原始类型先调用 valueOf(),如果未返回原始类型的值,再调用 toString() == 比较时的隐式转换规则用一句话概括就是原始类型转数字,引用类型转原始类型1234true == 2 (布尔值先转换为数字类型再参与比较)11 == ‘11’ (字符串转换为数字类型再参与比较){a:1} == ‘a:1’ (复杂类型转换为原始类型再参与比较)null,undefined 互相==,与自身==,但与其他所有值都不== 解析面试题我们可以推测出 a 是引用类型,引用类型与原始类型比较时会先转换成原始类型参与比较,所以 a 每次参与比较都会先后调用自身的 valueOf()和 toString()方法,所以在最后调用 toSring 时让自身递增,就可以让条件成立.所以 a 的值就是. 1234567a = { i:1, toString: function(){ return a.i++ }}a == 1 && a==2 & a==3 // true","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yoursite.com/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yoursite.com/tags/JavaScript/"}]},{"title":"Sass和Less的总结","slug":"Sass和Less的总结","date":"2018-06-22T07:27:55.000Z","updated":"2018-06-22T07:28:52.000Z","comments":true,"path":"2018/06/22/Sass和Less的总结/","link":"","permalink":"http://yoursite.com/2018/06/22/Sass和Less的总结/","excerpt":"","text":"Sass的安装 及编译12345678910111213# 安装brew install rubybrew install sass# 编译(sass提供4种编译风格)# nested: 嵌套缩进,默认值# expanded: 没有缩进的扩展的 css 代码# compact: 简洁格式的 css 代码# compressed: 压缩后的 css 代码sass --style compressed test.sass test.css# 监控某个文件,一旦变动,自动生成编译后的版本sass --watch input.scss:output.css# 监控某个文件夹sass --watch app/sass:publc/stylesheets Sass 的使用 使用变量 sass 使用$符号来标识变量,因为它不会导致现存和未来的 css 语法冲突,且深受程序员喜爱. 变量声明 和 css 属性声明类似:$custom-color:#f90;,声明后的变量只有引用这个变量的时候才会生效.与 css 属性不同,变量可以在 css 规则块定义之外存在.当变量定义在 css 规则块内,那么该变量只有在这个规则块内使用才会生效.声明变量时,变量值还可以引用其他的变量. 变量引用 凡是 css 属性的标准值可存在的地方就可以使用变量.background-color: $custom-color; 变量镶嵌在字符串中必须写在#{}中 1234$side: left;.div{ border-#{$side}-radius: 5px;} 变量名用中划线还是下划线分割 这两种用法相互兼容,但是推荐使用中划线,这也是 compass 使用的方式. 计算功能 sass 允许在代码中使用算式 12345body{ margin: (14px/2); top: 50px + 100px; right: $var * 10%;} 嵌套 css 规则 sass 可以再规则块中嵌套规则块,使样式可读性更高.且 sass 在输出时会帮你把这些规则处理好,避免你的重复书写. 1234567.container{ .header{ p{ font-size: $font-size; } }} 大多数这种简单的嵌套都没有问题,但如果你现在嵌套的选择器中使用类似于:hover的伪类,为了解决这种情况, sass 提供了一个特殊结构& 父选择器的标识符& 如果不使用&,下面的 sass 则无法工作 1234567article a{ color: blue; :hover{ color: red; }}//他会被编译为 article a :hover{...} article 元素内所有的子元素在 hover 都会变成红色.这是不正确的.你想把这条规则应用到超链接自身,而后代选择器的方式无法帮你实现. sass 提供&表示父选择器,它不会像后代选择器那样进行拼接,而是直接替换 1234567article a{ color: blue; &:hover{ color: red; }}//他会被编译为 article a:hover{...} 群组选择器的嵌套 1234nav, aside{ a {color: blue}}//编译为: nav a , aside a {color: blue} 子组合器和同层组合选择器 >,+,~ 123456789article{ ~ article{border-top: 1px solid #ccc} > section {color: red} nav + & {margin: 0}}//编译为article ~ article{...}article > section{...}nav + article {...} 嵌套属性 在 sass 中,除了 css 选择器可以嵌套,属性也可以嵌套,比如 border 的诸多属性反复写比较痛苦,我们可以这样写: 1234567nav{ border{ style: solid; width: 1px; color: #ccc; }} 导入 sass 文件 css 中有一个@ import规则,允许在一个 css 文件中导入其他 css 文件,然而只有执行到@ import 时,浏览器才回去下载其他 css 文件,导致文件加载特别慢. sass也有一个@ import 的规则,不同的是, sass 的@ import 在生成 css 文件时就把相关文件导入进来,在导入时你可以省略. sass 或. scss 的后缀 使用 sass 的部分文件 当通过@ import把 sass 的样式分散到多个文件时,你通常只想生成少数几个 css 文件,有些 sass 文件并不需要生成对应的独立 css 文件,这些 sass 文件称为局部文件.sass 约定局部文件名以下划线开头,这样 sass 就不会再编译时单独编译这个文件输出 css, 而只把这个文件用作导入. 默认变量值 如果反复声明一个变量,后者会覆盖前者,加入你引入一个他人的 sass 库文件,你可能希望导入者可以定制修改这个文件中的某些值.使用sass 的!default标签可以实现这个目标,它很像 css 的!important的对立面,不同的是!default表示如果这个变量被声明赋值了,则用它的声明,否则就用这个默认值 嵌套导入 sass 允许@import可以写在 css 规则中. 123aside{ @import 'test'} 原生的 css 导入 由于 sass 兼容原生的 css, 所以支持原生的@ import, 因为 sass 语法完全兼容 css, 所以你可以把原始的 css 文件改名为. scss后缀即可直接导入了. 静默注释 sass 提供了一种不同于 css 标准注释格式的/*...*/的注释语法,即静默注释,其内容不会出现在生成的 css 文件中,他的语法和 js ,java 等类 C 的语言中单行注释相同即以//开头,注释内容到行末 123body{ color: #333;//不会出现在生成的 css 中} 高级用法 条件语句@if ... @else ... 12345@if $color == 'red'{ background-color: red;}@else{ background-color: yellow;} 循环语句 1234567891011121314151617181920/*for 循环*/@for $i from 1 to 10{ .border-#{$i}{ border: #{$i}px solid blue; }}/*while 循环*/$i: 10;@while $i > 0 { .item-#{$i}{ width: 2px * $i; } $i: $i -2;}/**each 命令,作用和 for 类似*/@each $member in a,b,c,d { .#{$member}{ background-image: url("/image/#{$member}.jpg"); }} 自定义函数 sass 允许用户编写自己的函数. 123456@function double($n){ @return $n * 2;}.sidebar{ width: double(5px);} 混合器 当你的样式越来越复杂时,大量使用变量并不是一个很好的方式,你可以通过混合器来实现大段样式的重用.混合器使用@ mixin标识符定义.这看上去很像 css 的标识符,比如@media或@font-face.混合器标识符可以把混合器中所有的样式提取出来放在@include中被调用的部分. 1234567891011121314@mixin custorm{ border: 1px solid #ccc; font-size: 16px;}notice{ color: #eee; @include custorm}//编译为notice{ color: #eee; border: 1px solid #ccc; font-size: 16px;} 如果你发现在很多地方都在使用同一样式,则应该把它改造为混合器. 给混合器传参 混合器并不一定总是生成相同的样式,可以给混合器传参来定制精确的样式,这种方式和 JS 中的function很像. 123456789101112131415@mixin link-color($normal,$hover){ color: $normal; &:hover {color: $hover}}a{ @include link-color(blue,red)}//混合器支持默认参数值@mixin color( $normal, $hover: $normal){ color: $normal; &:hover {color: $hover}} 继承 使用选择器继承来精简 css 使用 sass 时,最后一个减少重复的主要特征就是选择器继承.就是说一个选择器可以继承另一个选择器的所有样式,这个通过@extend语法实现. 12345678.error{ border: 1px solid #ccc; color: red;}.seriousError{ font-size: 14px; @extend .error} @extend不仅会继承它自身的所有属性,任何跟它有关的组合选择器样式也会以组合选择器的形式继承.比如: 1234.error a{ font-weight: 100;}//同样应用到 .seriousError a{...} 何时使用继承 继承是基于类的,所以继承应该建立于语义化的关系上,当一个元素拥有类,表明它属于另一个类,这时使用继承再适合不过了.使用继承时的最好方法就是不要在后代选择器的时候去继承 css 规则,因为这样被继承的 css规则通过后代选择器修饰的样式,生成 css 中选择器的数量很快就会失控. 继承与混合器的比较 继承比混合器生成的 css 代码更少,因为继承仅仅是重复选择器,而不会像混合器那样重复书写,所以继承会提升站点的速度.继承遵从 css 的层叠规则,同一属性根数层叠的权重高低而选择权重高的胜出,而混合器本身不会引起 css 层叠的问题,因为混合器把样式直接放到 css 规则块中. Less 的安装及编译1234# 通过 node 环境全局安装 lessnpm install -g less# 编译 less 文件lessc styles.less styles.css Less 的使用 和 Sass一样,Less 作为一种 css 扩展,它同样兼容 css, 且学习成本比 Sass 要小一些,这使得学习 Less 更轻松. 变量 12345@nice-blue : #5B83AD;color: @nice-blue;/*变量插值*/@dicretion: left;margin-@{dicretion}: 20px; 注意: 变量只能定义一次,实际上它们就是常量. mixin( 混合) 1234567891011121314.nice-color{ color: #5B83AD;}/*只需要在规则中访问我们定义的类名即可*/p{ .nice-color}/*带默认参数的混合*/.nice-color(@beauty:#5B83AD){ color: @beauty;}p{ .nice-color();} 嵌套规则 123456789101112p{ color: #5B83AD; /*配合媒体查询*/ @media (min-width:768px) { color: red; } /*&为当前选择器的父选择器*/ &:after{ content: ''; clear:both; }} 运算 123/**Less 能够推断颜色和单位之间的区别*/color: #888 / 4;width: 1px + 5; 继承 1234.inline{ color: red;}p:extend(.inline) 函数 1234567.a(@color,@size){ color: @color; font-size: @size;}p{ .a(#ccc,16px)} 作用域 123456789/**Less 中的作用域与编程语言中的作用域概念非常相似。首先会在局部查找变量和混合,如果没找到,编译器就会在父作用域中查找,依次类推。*/@var: red;#page { @var: white; #header { color: @var; // white }} 注释 12345/* 可以使用块注释 */@var: red;//也可以使用行注释@var: white; 导入 123/**导入工作与你预期的一样。你可以导入一个 .less 文件,然后这个文件中的所有变量都可以使用了。对于 .less 文件而言,其扩展名是可选的。*/@import \"library\"; // library.less@import \"typo.css\";// css import Sass 和 Less 的区别 扩展名 Sass 以.sass和.scss后缀结尾,而 Less 以less后缀结尾. 变量声明 Sass变量必须是以$开头的,然后变量和值之间使用冒号(:)隔开.而 Less变量以@开头. 变量嵌入字符串 Sass 使用${xx}使变量嵌入字符串,而 Less 使用@{xx} 继承 Sass 以@extend继承,而 Less 以:extend继承. 混入 Sass 的混入 123456@mixin error($borderWidth: 2px) { border: $borderWidth solid #F00;}div{ @ include error(); } Less 的混入 123456.error(@borderWidth: 2px) { border: @borderWidth solid #F00;}div{ .error();} 高级语法 Sass 支持很多高级语法,而 Less 对高级语法的支持比较少.","categories":[{"name":"前端 css","slug":"前端-css","permalink":"http://yoursite.com/categories/前端-css/"}],"tags":[{"name":"sass less","slug":"sass-less","permalink":"http://yoursite.com/tags/sass-less/"}]},{"title":"浅谈 requestAnimationFrame 请求动画帧","slug":"浅谈 requestAnimationFrame 请求动画帧","date":"2018-05-03T11:14:12.000Z","updated":"2021-08-30T11:15:39.837Z","comments":true,"path":"2018/05/03/浅谈 requestAnimationFrame 请求动画帧/","link":"","permalink":"http://yoursite.com/2018/05/03/浅谈 requestAnimationFrame 请求动画帧/","excerpt":"","text":"浅谈 requestAnimationFrame 请求动画帧 JavaScript 中有 setTimeout 和 setInterval 两种定时器,但这两种定时器虽然设定了精确的时间,但 JS 却不能保证恰好在那个时间点运行.一是因为大多数浏览器并没有精确到毫秒级别的触发时间,多少会有时间差,二是因为JS 的运行机制是 EventLoop 无法保证时间精确. requestAnimationFramerequestAnimationFrame是一种时间精确的定时器,原理是由系统决定回调函数的执行时间,而执行频率紧紧跟随浏览器的刷新频率.如果刷新频率通常为60HZ,即一秒钟重绘60次,那么requestAnimationFrame也会执行60次.即使什么都不做,显示器依然会以相应频率不断刷新屏幕上的图像. setTimeout 和 setInterval 实现的动画在某些设备上会有卡顿,抖动的效果.这是因为刷新频率与设置的时间间隔不同步导致的.而动画的本质就是让人眼看到的图像被绘制而引起变化的视觉效果,这个变化要以连贯平滑的方式进行过渡. requestAnimationFrame的调用123456789let progress = 0function render(){ progress += 1 //修改图像位置 if(progress < 100){ // 递归渲染 window.requestAnimationFrame(render) }}window.requestAnimationFrame(render) // 第一帧渲染","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yoursite.com/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yoursite.com/tags/JavaScript/"}]},{"title":"<CSS世界>读书笔记","slug":"CSS世界-读书笔记","date":"2018-04-12T03:11:01.000Z","updated":"2018-04-12T03:22:42.000Z","comments":true,"path":"2018/04/12/CSS世界-读书笔记/","link":"","permalink":"http://yoursite.com/2018/04/12/CSS世界-读书笔记/","excerpt":"","text":"最近在看张鑫旭的<<CSS世界>>,对里面说的一些干货做了点总结,如果希望看到更多知识,请自行购买. 概论何为流?“流”实际上是CSS世界中的一种基本的定位和布局机制,可以理解为现实世界的一套物理规则,”流”和现实世界的”水流”有异曲同工之妙.就好像我们把水流入一个容器,水面一定是平整的,我们在水中放入物体,水面升高物体依次排列.所以”流”就是CSS世界中引导元素排列和定位的一条看不见的水流. 流,元素与基本尺寸块级元素“块级元素”和display:block;不是一个概念.比如<li>元素默认的display是list-item,<table>对应的display是table,但它们都是块级元素,因为它们符合块级元素的特征,也就是一个水平流上只能单独显示一个元素,多个元素则换行显示.正是由于块级元素的换行特性,理论上它都可以配合clear属性来清除浮动. 12345.clear:after{ content: ''; display: table;//或block等. clear: both;} 外部尺寸和内部尺寸当一个元素的尺寸由外部元素决定就称为外部尺寸,当一个元素的尺寸由内部元素决定就称为内部尺寸. 深藏不漏的width: auto 正常流宽度 在页面中随便扔一个div元素,它就会具备block容器的流特性. 1234a{ display: block; width: 100%;} a元素的display 默认是 inline,当它设置为 block的时候使其具有块状化.但width设置为100%则完全没有必要.”鑫三无原则”,即”无宽度,无图片,无浮动”.为何无宽度?因为表现为”外部尺寸的”块级元素一旦设置了宽度,流动性就失去了.所谓流动性,并不是看上去宽度为100%那么简单,而是一直margin/border/padding以及content内容区域自动分配水平空间的机制. 格式化宽度 格式化宽度值出现在”绝对定位模型”中,也就是当position为absolute或fixed元素中,默认情况下,绝对定位元素的宽度是由内部尺寸决定的,但是有一种情况宽度由外部尺寸决定.对于非替换元素(…)当left/right或top/bottom对立的属性值同时存在时,元素的宽度为格式化宽度 123456789div1{ width: 1000px; position: relative; div2{ position: absolute; left: 20px; right: 20px; }} 由以上样式可知,div2的宽度为1000-20-20 = 960 内部尺寸和流式特性 button按钮是CSS极具代表的inline-block元素,是展示”包裹性”最好的例子,具体表现为:按钮文字越多宽度越宽(内部尺寸特性),但文字足够多,在容器宽度处自动换行(自适应特性). 包裹性在实际开发中的用处: 页面某个模块文字内容是动态的,希望文字少的时候居中显示,文字多的时候居左显示.代码如下: 1234567.box{ text-align: center;}.content{ display: inline-block; text-align: left;} width值作用的细节 CSS盒模型的组成模式有两种: 一种是border-box.即padding和border也被包含在width和height中的怪异下的盒模型,还有一种是content-box为标准模式下的盒模型. 给width属性赋一个值,该值作用在content上,且一旦设定width值,该元素就没有了流动性,所以提出”无宽度准则”,这样会更灵活,容错性更强. CSS流体布局下的宽度分离原则(便于维护) 也就是CSS中的width属性不与其他影响宽度的padding/border/margin属性共存.width属性独占一层标签,而padding/border等属性利用流动性在内部自适应实现. 12345678.father{ width: 180px; .son{ margin: 0 20px; padding: 20px; border: 1px solid; }} 相对简单单纯的height: autoCSS的默认流是水平方向的,宽度是稀缺的,高度是无限的,所以宽度的分配规则比较复杂,高度显得比较随意. 关于height: 100% 对于width属性就算父元素的width是auto,其百分比值也是支持的.但是,对于height属性,如果父元素的height为auto,只要子元素在文档流中,其百分比的值就被忽略了.我们发现百分比高度值想要生效,其父级必须有一个可以生效的高度值. 要明白其中的原因要先了解浏览器渲染的基本原理.首先下载文档内容,加载头部的样式资源,然后从上而下,自外而内的顺序渲染DOM内容.因此,当渲染到父元素的时候,子元素的width:100%;并没有渲染,宽度就是内容的宽度,等渲染到子元素时,父元素宽度已经固定,此时width:100%;就是父元素的宽度.宽度不够溢出就好了.overflow属性就是为此而生的.为什么宽度支持,高度不支持呢?规范中给出了答案,如果包含块的高度没有显式指定(即高度由内容决定),并且该元素不是绝对定位,则计算值为auto.一句话总结就是,高度没有显式指定则解释为auto,再和百分比计算结果为NaN 想要他生效只有如下设置: 123html,body{ height:100%;} 1234div{ height:100%; position: absolute;} max-width/height,min-width/height与width/height区别 width/height的默认值是auto,而min-width/height 的默认值是0,max-width/height的默认值是none. 他们三者也有一套相互覆盖的规则:超越!important,超越最大 1234<img src=\"1.jpg\" style=\"width: 480px!important\"/>img{ max-width: 260px;//max-width会覆盖width.} 12345div{ min-width: 1400px; max-width: 1200px; //当min比max还要大的时候,遵循'超越最大'原则} 内联元素 inline和block是流式布局的本质所在.从作用上来讲,块级负责结构,内联负责内容.且内联元素设计的属性非常多,且往往具有继承属性. 从定义看,内联元素与display:inline不是一个概念.因为display:inline-block;与display:inline-table;也是内联元素,因为他们的外在盒子都是内联元素. 从表现上,内联元素的典型特征就是可以和文字显示在一行.因此,文字是内联元素,按钮也是内联元素,输入框下拉框都是. 盒尺寸四大家族深入理解content 什么是替换元素? 根据”外在盒子”是内联还是块级,我们把元素分为内联元素和块级元素.而根据是否具有可替换内容我们把元素分为替换元素和非替换元素.举个例子,<img src="1.jpg">但我们把src换为2.jpg图片就会替换,这种通过修改某个属性值就可以被替换的元素称为”替换元素”.所以的替换元素都是内联水平元素. 替换元素有三种尺寸,分别为固有尺寸,HTML尺寸,CSS尺寸. 1234567<img src=\"1.jpg\"> /如果没有HTML尺寸和CSS尺寸,则使用固有尺寸即图片的尺寸/<img src=\"1.jpg\" width=\"128\" height=\"96\"> /如果没有CSS尺寸则使用HTML尺寸/<img src=\"1.jpg\">img{ width:200px; height:100px;/如果有css尺寸则使用css尺寸/} CSS世界中的替换元素有一个很重要的特性,那就是’我们无法改变这个替换元素内容的固有尺寸’.那为什么我们设置width和height会影响图片尺寸呢?那是因为图片中的content替换内容的默认适配方式是”填充”,不管设置的尺寸有多大,就填充多大.在CSS3之前这种适配方式是不能修改的.CSS3我们可以通过object-fit属性修改该方式,例如<img>元素的默认声明是object-fit:fill;如果我们设置为object-fit:none;那么图片的尺寸就完全不受控制.如果我们设置为object-fit: contain;则图片保持比例尽可能的利用HTML的尺寸但又不会超出的方式显示. 替换元素和非替换元素的区别主要在于src和content. content生成辅助元素 实际项目中,content属性主要用于::before以及::after这两个伪元素中.此应用的核心点不在content上,而在伪元素上,所以我们通常会写content:''; 生成辅助元素后再实现特定布局或实现图形效果.辅助元素最常用的应用是清除浮动.如下: 12345.clear:after{ content: ''; display: block; clear: both;} content字符内容生成 content字符内容生成就是直接写入字符内容,中英文都可以,比较常见的就是配合@font-face规则实现图标字体效果. content图片生成 content图片生成指的是直接使用url功能符显示图片. 123div:before{ content: url(1.jpg);} 但我们对于图片的宽高不好控制,无法改变图片的固有尺寸.所以我们通常使用background-image. 1234div:before{ content: ''; background: url(1.jpg)} content计数器 主要是两个属性和一个方法. counter-reset: 计数器名字 计数器的默认值; counter-increment: 计数器名字 递增的值(省略的话默认是1); 方法:counter(name) 显示计数. 温和的padding属性 padding和元素的尺寸 因为CSS默认的box-sizing是content-box,所以使用padding会影响元素的尺寸.但对于内联元素(不包括替换元素)而言,padding影响水平方向,而不影响垂直方向,这样的说法也不完全准确.由于内联元素没有可视宽度和可视高度的说法,垂直方向完全受line-height和vertical-align的影响,所以从视觉上来说,padding在垂直方向上没有起作用,但是我们给它加个背景色会发现其尺寸确实受到影响了.在实际开发中,我们可以在不影响布局的前提下,优雅的增加链接或按钮的点击区域大小. padding的百分比取值 padding的值和margin值不同之处在于,padding的值不可以是负数.还有,padding的值如果是百分比,不论是水平方向还是垂直方向都是相对于宽度来说的. padding与图像绘制 padding和background-clip属性配合可以在有限的标签下实现一些CSS绘制效果. 比如如何用一个标签绘制”大队长”三道杠的分类图标效果. 12345678910.menu{ display: inline-block; width: 140px; height: 10px;//中间的杠 padding: 35px 0; border-top: 10px solid;//上面的杠 border-bottom: 10px solid;//下面的杠 background-color: currentColor; background-clip: content-box;} 激进的margin属性 相关概念 元素尺寸: border + padding + content 元素内部尺寸: padding + content 元素外部尺寸: margin + border + padding + content margin与元素尺寸以及相关布局 一旦宽度设定,margin就无法改变元素尺寸. 1234.div{ width: 100px; margin: 0 -20px;//元素宽度还是100px} 只要元素的尺寸表现符合’充分利用可用空间’无论是垂直方向还是水平方向都可以通过margin改变尺寸. 123456.father{ width: 100px;}.son{ margin: 0 -20px; //该元素空间为140px;} 正是这种具有流体特性下的改变尺寸特性,margin可以很方便的实现很多流体布局效果,比如说一侧定宽,一侧自适应. 123456789.left{ width:100px; height:100px; float: left;}.right{ height: 100px; margin-left: 100px;} margin的百分比值 和padding一样,margin的百分比值无论是水平方向还是垂直方向都是相对于宽度计算的. margin的合并 块级元素的margin-top和margin-bottom通常会合并为一个margin.我们可以捕获两点重要信息,一是块级元素,二是垂直方向. margin合并的计算规则:正正取大值,正负值相加,负负最负值. 深入理解margin: auto; 我们首先要知道,有时候元素就算没有设置height和width,也会自动填充和自动填充对应的方位.假设一个外部的容器宽度是300px, 而内部的容器因为设置宽度为200px而导致原本应该自动填满的空间现在有100px闲置了,margin: auto就是为了填充这个闲置的尺寸的.margin:auto;的填充规则是: 如果一侧定值,一侧auto,则auto为剩余的空间大小.如果两侧都是auto,则平分剩余空间. 当我们想要某个块级元素右对齐时,脑子里不要就一个float:right;,很多时候margin-left:auto;才是最佳实践.浮动毕竟是个”魔鬼”,margin属性的auto计算就是为了块级元素左右对齐而设计的,和内联元素的text-align控制左右对齐相呼应. 我们可能会发现margin:auto;并不能实现垂直居中,但是我们可以利用绝对定位实现这个需求.因为绝对定位后,top/bottom/left/right会自动填充,但又因为设置了宽高,导致多余的空间闲置,这时margin:auto;就可以计算空间了. 123456.son{ position: absolute; top:0; right: 0; bottom: 0: left:0; width:200px; height:100px; margin: auto;//实现垂直居中.} 功勋卓越的border属性 border与透明边框技巧 优雅的增加点击区域大小. 12345.click{ width:16px; height: 16px; border: 11px solid transparent;} 三角形图形绘制 12345div{ width: 0; border: 10px solid; border-color: #f30 transparent transparent;} 内联元素与流字母x是CSS中隐匿的举重若轻的角色各种内联相关模型中,凡是涉及垂直方向的排版或是对齐的,都离不开最基本的基线.字母x下的下边缘就是基线. vertical-align:middle;并不是绝对的垂直居中对齐,这里的middle是基线往上1/2 x-height的高度,也就是字母x的交叉点的位置.所以middle得垂直居中只是一种近似效果. 字母X衍生了x-height(字母x的高度)的概念,并进一步衍生出了ex,ex是css中的一个尺寸单位,是一个相对单位,指的是小写字母x的高度.其实就是x-height. 内联元素的基石 line-height思考下面的问题,一个默认的空div高度是0,里面写上几个文字后高度就有了,这个高度从何而来? 不少人认为是由文字把内容撑开的,但本质上是由line-height属性所决定的,对于文本这样的纯内联元素,line-height就是高度计算的基石. 为什么line-height可以让内联元素垂直居中?这是一个误区,要想让单行文字垂直居中只要设置line-height大小就可以了,和height没有任何关系. 1234567.title{ height: 24px; line-height: 24px;}.title2{ line-height:24px;} 多行文本和替换元素的垂直居中和单行文本不一样, 需要用到vertical-align:middle的帮助. 123456.content{ display: inline-block; line-height: 20px; margin: 0 20px; vertical-align: middle;} line-height的值一般为固定长度值,也可以是数值和是百分比,两者的值都是与font-size相乘后的值. 内联元素的大值特性:无论内联元素的line-height如何设置,最终父级元素的高度都是由数值最大的那个line-height所决定的. line-height的好朋友vertical-align为什么说他们是好朋友呢,因为凡是line-height起作用的地方vertical-align也一定起作用.因为vertical-align的默认值是baseline基线对齐,而基线的定义是x的下边缘.它等同于vertical-align:0; ####基于vertical-align属性的水平垂直居中大小不固定弹框 12345<div class=\"container\"> <div class=\"dialog\"> </div></div> 12345678910111213141516171819202122.container{ position: fixed; top:0;right:0;bottom:0;left:0; background-color: rgba(0,0,0,.5); text-align: center; font-size:0; white-space: nowarp; overflow: auto;}.container:after{ content: ''; display: inline-block; height: 100%; vertical-align:middle;}.dialog{ display: inline-block; vertical-align:middle; text-align:left; font-size:14px; white-space: nowarp;} 流的破坏与保护魔鬼属性float浮动的本质就是为了实现文字环绕效果,这种文字环绕主要指文字环绕图片的显示效果.理论上可以通过float把整个页面结构都弄出来,但这种方式太脆弱缺乏弹性.一旦某个元素宽高发生变化,就会发生布局错位. float的特性: 包裹性 假设浮动元素的父元素width为200px,浮动元素子元素是一个宽度为128px的图片,此时元素宽度表现为”包裹”,就是里面图片的宽度128px; 123456789.father{ width:200px; .float{ float: left;//该元素宽度为128px; img{ width:128px; } }} 如果浮动元素的子元素不止是一张图片,还有许多文字,就会出现”自适应性”,此时浮动元素的宽度为父元素的宽度200px; 块状化并格式化上下文 块状化即float的值只要不是none,其display就会成为block或table. 破坏文档流 没有任何margin合并 float的作用机制float的最著名的特性表现就是会让父元素的高度塌陷.但只要父元素设置了一个具体的值就不需要担心高度塌陷的问题了.但不建议这样做,比较稳妥的做法还是采用一些手段清除浮动带来的影响. float与流体布局我们可以利用float破坏css正常流的特性,实现两栏或多栏的自适应布局. 一侧定宽,一侧自适应布局. 123456789.left{ width:100px;//不定宽可以设置为百分比 height:100px; float:left;}.right{ height:100px; margin-left: 100px;//不定宽可以设置为百分比} float的天然克星clear其语法如下: clear : none | left | right | both; clear属性本质上并不是清除浮动,而是让自身不能和浮动元素相邻,但clear属性只对块级元素有效.这也就是我们在借助::after等伪元素(内联)清除浮动时需要设置display的原因. 12345.clear:after{ content: ''; display: block; clear: both;} CSS的结界-BFCBFC全称 block formatting context,中文为”块级格式化上下文”. 它的表现原则为: 如果一个元素具有BFC,内部子元素再怎么样也不会影响外部的元素,所以BFC不可能发生margin重叠的.BFC也可以用来清除浮动的影响,因为不清除浮动会影响后面的布局和定位. 什么时候会触发BFC呢? <html>根元素 float的值不为none; overflow的值为auto, scroll 或 hidden; display 的值为: inline-block,table-cell,table-caption中的任何一个; position的值不为static和relative; 只要元素符合上面的任何一个条件就无需使用clear:both;去清除浮动的影响了. 最佳结界overflow要想彻底清除浮动的影响,最适合的属性不是clear而是overflow.一般使用overflow:hidden; HTML中有两个标签是可以默认产生进度条的,一个是根元素<html>,另一个是文本域<textarea>,之所以出现滚动条是因为这两个标签的overflow的默认值不是visible. 滚动条是可以自定义的.支持-webkit-前缀的浏览器可以这样设置. 123456789101112::-webkit-scrollbar{ width: 8px;/*血槽宽度*/ height: 8px;}::-webkit-scrollbar-thumb{ background-color: rgba(0,0,0,.3);/*拖动条*/ border-radius: 6px;}::-webkit-scrollbar-track{ background-color: #ddd;/*背景槽*/ border-radius: 6px;} overflow与锚点定位:锚点就是可以让页面定位到某个位置的点,本质上是通过改变容器滚动高度或者宽度来实现的.设置了overflow属性为auto,scroll,hidden的元素是可以滚动的,overflow:hidden与scroll 和 auto的区别就在于有没有那个滚动条.高度溢出,滚动依旧存在,只是滚动条不存在,牢记这一点可以让我们更简单更原生的方式实现一些交互效果. CSS世界的层叠规则在css中,z-index属性只有和定位元素(position不为static的元素)在一起的时候才有用,可以是正数也可以是负数.但随着css3到来,flex盒子也可以使用z-index属性. CSS层叠顺序类型如下:(由低到高) 层叠上下文background/border -> 负z-index -> block盒子 -> float浮动盒子 -> inline水平盒子 -> z-index: auto或0 -> 正z-index. CSS3新时代的层叠上下文: 元素为flex布局的元素. 元素opacity不是1 元素的transform不是none 元素mix-blend-mode不是 normal 元素isolation是isolate 元素will-change属性为上面2-6的任意一个 元素filter不是none 元素的-webkit-overflow-scrolling为touch z-index”不犯二”准则:对于非浮层元素,避免设置z-index的值,z-index的值没有任何道理需要超过2. 强大的文本处理能力font-size的能力line-height的部分类别属性是相对于font-size计算的,而vertical-align百分比属性值又是相对于line-height计算的. 1234567p{ font-size: 16px; line-height: 1.5;}p > img{ vertical-align: -25%; /* (即16px * 1.5 * -25% = -6px) */} font-size与ex,em,和rem的关系: ex是字符x的高度,font-size越大,对应的ex越大.em是根据当前元素的font-size计算的,而rem( root-em )是相对于HTML根元素的font-size进行计算的. 桌面Chrome浏览器有一个12px的字号限制但并不是所有小于12px的字号都当做12px处理,有一个值例外那就是0. 如果font-size:0;那么文字就会被直接隐藏掉.不然哪怕是0.00001px也会被当做12px处理. font-weight表示文字的粗细程度,我们通常设置为bold 和 normal.也可以设置为100-900,该值必须是整百数.其中400等同于normal,700等同于bold. font属性联写: [font-style] ? font-size [/line-height].例如.font{ font: normal 700 14px/20px } 了解@font face@font face 本质上就是一个定义字体和字体集的变量.这个变量不仅仅是简单的自定义字体,还包括字体重命名,默认字体样式设置.它大多用于字体图标技术.所谓字体本质上就是字符集和图形的一种映射关系.字体图标技术通常把字符映射成另外的图标形状,我们看到的图标,本质上就是一个普通的字符. 文本的控制text-indent就是对文本进行缩进控制.项目中我们用的最多的就是给text-indent一个很大的负值来隐藏文本内容.比如很多网站的LOGO放在<h1>中,然后设置一个很大的负值,比如-9999em. letter-spacing用来控制与字符的间距.这里的字符包括英文字母,汉字以及空格.支持负值. word-spacing用来控制与单词间的间距.它仅作用于空格而不是字面上的单词. white-space 声明了如何处理元素内的空白字符,normal为合并,pre为不合并,只有在换行符的地方换行.nowrap合并空白但不允许文字环绕.当设置为nowrap时,元素的宽度此时表现为”最大可用宽度”,换行符和一些空格全部合并,文本在一行显示. 如何解决text-decoration下划线和字体重叠的问题?可以用兼容性并不好的text-decoration-skip属性,或用box-shadow或background-image模拟,然而最好的解决方式是看似普通却很有用的border属性. 1234a{ text-decoration: none; border-bottom: 1px solid;} text-transform是专门为英文字符设计的,要么全部大写text-transform: uppercase,要么全部小写text-transform: lowercase,身份证号的x输入以及验证码的字母大小写都可以使用这个属性实现需求. 元素的显示与隐藏 display:none; display:none;可以让元素以及所有后代元素都隐藏,占据的空间消失,是真正意义上的隐藏. 在火狐浏览器下display:none的元素的background-image是不加载的,但是chrome和safari视情况而定,父元素隐藏图片不加载,自身元素隐藏,图片依旧会加载. visibility:hidden; 与display:none一样可以隐藏元素,但visibility:hidden;保留元素的空间.但他和display:none不一样的是,它具有继承性,父元素设置visibility:hidden;子元素也会看不到,一旦子元素设置visibility:visible;子元素就会显示出来这种后代可见特性在开发中非常有用. visibility可以喝transition配合使用,而display不能和transition配合,因为transition的属性有visibility而没有display. 12345678.list{ position:absolute; visibility: hidden;}td:hover .list{ visibility: visible; transition: visibility 0s .2s;} 希望元素不可见,不能点击,不占空间,但键盘可访问可以使用clip: rect(0 0 0 0 )剪切隐藏. 希望元素不可见,不能点击,但占据空间且键盘可访问使用relative配合负z-index隐藏 希望元素不可见,但可以点击,不占空间可以使用opacity:0;","categories":[{"name":"前端","slug":"前端","permalink":"http://yoursite.com/categories/前端/"}],"tags":[{"name":"css 前端","slug":"css-前端","permalink":"http://yoursite.com/tags/css-前端/"}]},{"title":"初识 TypeScript","slug":"初识 TypeScript","date":"2018-03-19T11:12:13.000Z","updated":"2021-08-30T11:13:41.862Z","comments":true,"path":"2018/03/19/初识 TypeScript/","link":"","permalink":"http://yoursite.com/2018/03/19/初识 TypeScript/","excerpt":"","text":"基础类型 any 任何类型 number 数字类型 string 字符串 boolean 布尔值 Array 数组 各类型元素相同 例如 any[] 或 Array Tuple 元组 各类型元素不同 enum 枚举 void 函数无返回值 null 空 undefined 未定义 never 永远不存在值的类型 自定义类型 自定义类型 type xxx = number | string 字符串字面量类型 type xxx = ‘click’ | ‘scroll’ … 变量声明123var [变量名] : [类型] = 值;var uname: string = 'jack'var uname: string; 类型断言(type assertion)类型断言可以手动指定一个值得类型,即允许变量从一种类型更改为另一种类型. 12345678910// <类型>值// 或: 值 as 类型// JSX 中使用 <type> 的断言语法时,这会与 JSX 的语法存在歧义,建议使用 asinterface Foo { bar: number; bas: string;}const foo = {} as Foo;foo.bar = 123;foo.bas = 'hello'; 类型推断当类型没有指定时,TypeScript 编译器会利用类型推断来推断类型. 如果由于缺乏声明而不能推断出类型,那么它的类型被视作默认的动态 any 类型. 12var num = 2;//类型推断为 numbernum = '12';// 编译错误 变量作用域 全局作用域(全局变量) 类作用域(类变量,静态变量) 局部作用域(局部变量) 123456789101112var global_num = 12 // 全局变量class Numbers { num_val = 13; // 类变量 static sval = 10; // 静态变量 storeNum(): void{ var local_num = 14; // 局部变量 }}console.log('全局变量为'+global_num)console.log(Numbers.sval)//静态变量var obj = new Numbers()console.log('类变量'+obj.num_val) 函数函数返回值1234// 函数定义function greet():string{ return 'helloworld'} 函数参数123function add (x:number,y:number): number{ return x + y} 可选参数12345678910function buildName(firstName:string,lastName ?:string){ if(lastName){ return firstName + ' ' + lastName }else{ return firstName }}let result1 = buildName('bob') // 'bob'let result2 = buildName('bob','jack','adams') // 错误,参数过多let result2 = buildName('bob','jack') // bob jack 默认参数123function caculate(price:number,rate:number = 0.50){ return price * rate} 剩余参数123function buildName(firstName: string, ...restName:string[]){ return firstName + ' ' + restName.join(' ')} lambda 箭头函数1var foo = (x:number) => 10 + x 联合类型可以通过管道符|将变量设置多种类型.赋值时可以根据设置的类型来赋值,也可以将联合类型作为函数参数使用. 12345var val:string|numberval = 12val = 'jack'// 数组联合声明var arr:number[] | string[] 交叉类型你可以通过&交叉类型从两个对象中创建一个新对象,新对象会拥有着两个对象所有的功能。 12345678910111213141516171819function extend<T, U>(first: T, second: U): T & U { const result = <T & U>{}; for (let id in first) { (<T>result)[id] = first[id]; } for (let id in second) { if (!result.hasOwnProperty(id)) { (<U>result)[id] = second[id]; } } return result;}const x = extend({ a: 'hello' }, { b: 42 });// 现在 x 拥有了 a 属性与 b 属性const a = x.a;const b = x.b; TypeScript 接口1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859interface IPerson{ firstName: string, // 只读属性 readonly lastName:string, sayHi: ()=>string, // 可选属性 age?:number, //任意属性 其类型必须是其他属性类型的子集, any,null... [propName:string]: any}var customer: IPerson = { firstName: 'Tom', lastName: 'Hanks', sayHi: ():string => { return 'hi' }}// 函数也可以使用 interfaceinterface SearchFunc{ (source: string, subString:string):boolean;}let mySearch: SearchFunc;mySearch = function(source:string,subString:string){ return source.search(subString) !== -1;}// 可索引类型(定义索引类型)interface StringArray{ [index:number]: string}let MyArray : StringArray;MyArray = ['初春令月','气淑风和']console.log(MyArray[1])// 接口也可以继承接口interface Shape{ color: string}interface PenStroke{ penWidth:nubmer}interface Square extends Shape,PenStroke{ sideLength:number}let s = <Square>{};s.color=\"blue\"s.penWidth =100s.sideLength = 10// 接口还可以继承类class Point{ x:number; y:number;}interface Point3d extend Point{ z:number;}let point3d : Point3d{ x:1, y:2, z:3} 内联类型注解内联类型能为你快速的提供一个类型注解。它可以帮助你省去为类型起名的麻烦,如果多次使用相同的内联注解时,可以考虑把它重构为一个接口. 123456789let name: { first: string; second: string;};name = { first: 'John', second: 'Doe'}; TypeScript 类123456789101112131415161718192021222324252627282930313233343536373839404142434445class Car{ //字段 engine: string; _name: string; // 构造函数 constructor(engine:string){ this.engine = engine } // 储存器 get name():string{ return this._name } set name(value:string){ this._name = value } // 方法 disp():void{ console.log(\"发动机为:\" + this.engine) }}class Feature extends Car{ static name: string; // 静态变量或静态方法,不需要实例化,直接通过类调用 constructor(){ super() // 调用父类构造函数和方法 } // private 私有,只能在类中访问 // protected 受保护,只能在自身以及子类父类访问 // public(默认) 公有,可以再任何地方被访问 run():void{ console.log('品牌是:'+Feature.name) }}// 创建一个实例var obj = new Car('XXSY1')// 抽象类(不允许实例化,但可以被继承,内部方法也可以抽象化)abstrct class xxx{...}// 接口 interface 可以对类的一部分进行抽象.implements(实现)是一个重要概念.不同类之间共有的特性提取为接口用 implements 实现.一个类 可以实现多个接口.interface Light{ lightOn() // 开灯 lightOff() // 关灯}class Car implements Light{ lightOn() // 开灯 lightOff() // 关灯} 模块TypeScript 文件模块支持commonjs, amd, es modules, others,你可以根据不同的 module 选项来把 TypeScript 编译成不同的 JavaScript 模块类型. 123456// 文件名: SomeInterface.tsexport interface SomeInterface{ // ...}// 引用import {SomeInterfaceRef} from './SomeInterface' 泛型泛型是在定义函数,接口或类时,不预先指定具体类型,而是在使用时再指定的一种特性. 使用泛型来创建可重用的组件,一个组件可以支持多种数据类型.这样用户就可以以自己的数据类型来使用组件. 12345function Hello<T>(arg:T):T{ return arg}let output = Hello<string>('helloworld')//调用时指定类型let output2 = Hello('helloworld') // 或不指定,让类型推论自动推算 泛型约束是在函数内部使用泛型变量时,由于事先不知道它是哪种类型,所以不能随意的操作它的属性和方法,比如 length 属性. 12345678interface Lengthwise { length: number}function loggin<T extends Lengthwise>(arg:T):T{ console.log(arg.length) return arg}// 泛型通过接口约束了必须有 length 方法,如果没有编译阶段会报错 接口可以定义函数形状,那么有泛型的接口也可以定义函数形状 123interface CreateArrayFunc<T>{ (length:number,value:T): Array<T>} 泛型类(当实际参数也无法推测出类型时,我们还可以给泛型指定默认参数) 1234class GenericNumber<T=string> { zeroValue: T; add: (x:T,y:T) => T;} 声明合并如果声明了两个以上的 相同名字的函数,接口或类,那么它们会合并成一个类型. 函数合并我们可以使用函数重载定义多个函数类型 123456789function reverse(x:number):number;function reverse(x:string):string;function reverse(x:number | string):number | string{ if(typeof x === 'number'){ return Number(x.toString().split('').reverse().join('')) }else if(typeof x === 'string'){ return x.split('').reverse().join('') }} 接口合并接口合并的类型要一致,否则会报错,接口中方法合并和函数合并一样.类合并和接口合并规则一样 1234567891011interface Alarm { price: number;}interface Alarm { weight: number;}// 合并后相当于interface Alarm { price: number; weight: number;} 声明文件123456789101112declare var jQuery: (selector: string) => anyjQuery('#foo')// 我们通常把声明放到一个文件中,比如 jquery.d.ts// 推荐使用@types 统一管理第三方库的声明文件// 如果第三方没有提供,需要我们自己书写声明文件declare var // 全局变量declare function // 全局函数declare class // 全局类declare enum // 全局枚举declare namespace // 全局对象(含子属性)interface // 全局接口type // 全局类型 命名空间TypeScript 提供了 namespace 关键字用来在确保创建的变量不会泄漏至全局变量中. 123456789101112namespace Utility { export function log(msg) { console.log(msg); } export function error(msg) { console.log(msg); }}// usageUtility.log('Call me');Utility.error('maybe'); namespace 关键字通过 TypeScript 编译后,与我们看到的 JavaScript 代码一样: 123(function (Utility) { // 添加属性至 Utility})(Utility || Utility = {});","categories":[{"name":"TypeScript","slug":"TypeScript","permalink":"http://yoursite.com/categories/TypeScript/"}],"tags":[{"name":"TypeScript","slug":"TypeScript","permalink":"http://yoursite.com/tags/TypeScript/"}]},{"title":"2017前端技术发展回顾","slug":"2017前端技术发展回顾","date":"2018-03-12T08:39:32.000Z","updated":"2018-03-12T08:53:53.000Z","comments":true,"path":"2018/03/12/2017前端技术发展回顾/","link":"","permalink":"http://yoursite.com/2018/03/12/2017前端技术发展回顾/","excerpt":"","text":"HTML 5.2 发布 毫无疑问,这是Vue.js在流行中飞速发展的一年. 前端HTML & CSS 开发者和前端应用程序开发者之间的巨大差别终于被认知,并重新定义. 作为使用web技术构建应用程序的前端 JavaScript开发者变得越来越好,也越来越糟. 今年似乎比往年更多的涌现出一批试图与主流 JavaScript 应用工具(React,Angular,Vue 等)相抗衡的应用程序/框架解决方案。我来列举一部分,Moon,Marko,Hyperapp,Quasar Framework,POI,frint,BunnyJS,jsblocks,Sapper,Stimulus,Choo… jsbin 和 jsfiddle 进化成了 StackBliz 和 codeSandbox 的样子,它们让分享一个应用程序变得如此简单 React 继续被 preact,inferno,nerv,dva 和 rax 之类的所追捧。 devhints.io 很好地将 cheatsheets 组织了起来。 我们发现应用程序的样板或者命令行工具是带有偏见的,例如 React Create App,必要的时候我们要从中逃离。 大多数开发人员发现,一个好的代码编辑器,eslint 以及 prettier 的组合使写代码这件事更快,更轻松愉快. CSS Flexbox 和 Grid 获得浏览器支持,因此越来越多开发者开始关注这两者。 我们终于有无头的 Chrome 了。 你不再需要用 Less 或者 Sass 来使用 CSS 来完成令人惊叹的事情了。 CSS 革命正在进行中。 JavaScript 对象浏览工具已经到来,JavaScript Array Explorer和 JavaScript Object Explorer,它们对于学习 JavaScript 数据类似(例如对象)是非常方便有用的。 Chrome 浏览器在市场上占据主导地位,人们开始担心历史可能会重演。 Brave 成为浏览互联网最愉快且安全的方式。 PhantomJS 不再维护,Headless Chrome 和 Puppeteer 进入。 Prettier 从一个意想不到的地方开始,却成为了一个主角。 很多开发者开始采用静态检查,主要是出于主观原因和赶时髦。有些人完全遵从了 Typescript 和微软的做事方式,也有些人采取的较慢的 Flow。有一点可以肯定,大多数开发者不需要类型,他们只是把已经复杂的问题和解决方法更加复杂化。像大多数事情一样,这种趋势大部分是主观教条而非客观价值。 Web 组件仍然潜伏着,等待着开发人员的大力推动,这可能永远不会发生。 JavaScript 安定了,CSS 爆发了,明年的这个时候所有人都会疲惫不堪。 许多人在使用组件树构建应用程序的时候,开始将 CSS 转移到 JS 中的 CSS 里。 Yarn 似乎满足了需求,因为很多人从 npm 换成 Yarn。然而,Yarn 最大的价值在于它为 npm 带来竞争,从而使得 npm 变得更好。 Scrimba 将用于交互式的编码屏幕录像(录制现场的编辑器是可编辑的)的新的视频格式成为现实。 大多数人开始意识到组件架构和原子化设计之间的关联。 ES 模块将成为浏览器的一部分,并且如果使用 ES 模块,备用计划将是必需的(即来自 webpack 之类的打包文件)。 MVC 框架正在淡出。 使用Bluekit,Storybook,React Styleguidist 和 bit 之类的工具,在你的应用程序之外开发和展示 React 组件开始变得流行。 在 2017 年获得前端工作有关经验,这是从个人项目以及 Github 账号上展示出来的。 从 HTML 文档中预加载资源(CSS,JavaScript,多媒体文件等)到来了。 Cypress 作为一个完整的测试解决方案出现了,测试会越来越好,因为对于应用程序的代码,端到端测试才是重点。 WebAssembly 现在可以总所有主流浏览器中使用了。 Webpack 占主导地位,然后竞争对手 parcel 出现了。 React 16 代号 fiber 发布。 在某些情况下,React 开始与 jQuery 一较高下了. React 的确成为了最多人使用的构建 UI 的工具. Facebook 放弃了 React 的 BSD 许可证,而转成了 MIT 许可证(同样包括 Jest,Flow,Immutable.js 和 GraphQL)。 2017 年 GraphQL 开始流行起来。 Facebook 继续领头负责开发即将到来的如 prepack.io 之类的工具。 正如期待,ECMA-262 第 8 版也就是 ES2017 发布。 React Router 终于稳定下来了。 所有的现代浏览器现在都支持 ECMAScript 2015(也就是 ES6)。 Async JavaScript 函数 开始获得一些认真的关注及使用,主要是因为所有现在浏览器现在都支持 Async 函数了。 移动开发,仍然很难。今年,强烈地倡导 web 平台作为解决这一痛苦的方案获得了大量的支持。 原文地址:Recap of Front-end Development in 2017原文作者:FrontendMasters译文出自:掘金翻译计划本文永久链接:点击译者:bambooom校对者:realYukiko","categories":[{"name":"前端","slug":"前端","permalink":"http://yoursite.com/categories/前端/"}],"tags":[{"name":"前端 技术 回顾","slug":"前端-技术-回顾","permalink":"http://yoursite.com/tags/前端-技术-回顾/"}]},{"title":"JavaScript 执行机制与EventLoop","slug":"JavaScript 执行机制与EventLoop","date":"2018-01-05T11:08:37.000Z","updated":"2021-08-30T11:11:41.316Z","comments":true,"path":"2018/01/05/JavaScript 执行机制与EventLoop/","link":"","permalink":"http://yoursite.com/2018/01/05/JavaScript 执行机制与EventLoop/","excerpt":"","text":"JavaScript 执行机制与EventLoopJavaScript的执行机制 同步和异步任务进入不同的’执行场所’. 同步进入主线程,形成一个执行栈(后进先出) 异步进入 EventTable 并注册函数.当异步任务有了运行结果,将函数移入 EventQueue.(先进先出) 主线程任务执行完毕为空,将 EventQueue 读取相应函数进入主线程执行. 上述过程不断循环,即 EventLoop(事件循环) 简单概括就是,js 只有一个主线程,同步代码会依次入栈,执行完出栈,待栈为空时去检查异步的任务队列, 如果异步事件触发,则将其加入到主线程的执行栈,不断循环. 宏任务和微任务宏任务(macrotask)和微任务(microtask)是异步任务的两种分类,首先取出宏任务执行然后取出微任务执行,不断循环,直到EventQueue为空.需要注意的是,在当前的微任务没有执行完成时,是不会执行下一个宏任务的。宏任务: 整体代码script,setTimeOut,setInterval,setImmediate,requestAnimationFrame微任务: promise,then,catch,finally,process.nexttick,MutationObserver 代码分析12345678910111213141516171819202122232425262728293031323334console.log('1')setTimeout(function(){ console.log('2') process.nextTick(function(){ console.log('3') }) new Promise(function(resolve){ console.log('4') resolve() }).then(function(){ console.log('5') })})process.nextTick(function(){ console.log('6')})new Promise(function(resolve){ console.log('7') resolve()}).then(function(){ console.log('8')})setTimeout(function(){ console.log('9') process.nextTick(function(){ console.log('10') }) new Promise(function(resolve){ console.log('11') resolve() }).then(function(){ console.log('12') })}) 代码解析: 1234567891011121314151617181920第一轮事件循环: 整体 script 作为第一个宏任务进入主线程,然后输出1. 然后遇到 setTimeout,其回调分发到宏任务 EveneQueue 中,暂记为 setTimeout1 process.nextTick分发到微任务 EveneQueue 中暂记为 process1 promise 和 new Promise 直接执行输入7,then 分发到微任务,记为 then1 然后是setTimeout 记为setTimeout2,分发到宏任务EveneQueue 第一轮宏任务执行完毕,执行微任务队列,把微任务process1,then1加入执行栈,分别输出6,8 第一轮执行完毕,分别输出 1 => 7 => 6 => 8第二轮事件循环: 开启第二轮宏任务setTimeout1,输出2 process和 then 分发到微任务,记为 process2,then2. 然后输出4,宏任务执行完毕. 微任务process2,then2加入执行栈,输出3,5 第二轮执行完毕,分别输出 2 => 4 => 3 => 5第三轮时间循环: 开启第三轮宏任务setTimeout2,输出 9 process和 then 分发到微任务,记为 process3,then3. 然后输出11,宏任务执行完毕. 任务process3,then3加入执行栈,输出10,12 第三轮执行完毕,分别输出 9 => 11 => 10 => 12","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yoursite.com/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yoursite.com/tags/JavaScript/"}]},{"title":"学习CSS Grid布局","slug":"学习CSS-Grid布局","date":"2017-12-13T07:39:20.000Z","updated":"2017-12-13T07:42:01.000Z","comments":true,"path":"2017/12/13/学习CSS-Grid布局/","link":"","permalink":"http://yoursite.com/2017/12/13/学习CSS-Grid布局/","excerpt":"","text":"CSS Grid 布局是CSS中最强大的布局系统,与flexbox的一维布局系统不同,CSS Grid是一个二维布局系统,它可以同时处理行和列. 第一个Grid布局css Grid布局由两个核心组成部分,warpper(父元素)和items(子元素).warpper是实际的grid(网格),items是网格的内容. 下面是一个warpper元素,内部包含了6个items: 12345678<div class=\"warpper\"> <div>1</div> <div>2</div> <div>3</div> <div>4</div> <div>5</div> <div>6</div></div> 要把warpper元素变成一个grid(网格),只需要把它的display属性设置为grid即可. 123.warpper{ display:grid;} 这时我们还没有写任何样式,他会简单的将6个div堆叠在一起. Columns(列)和rows(行)为了使其成为二维的网格容器,我们需要定义行和列.让我们创建两行三列,使用grid-template-cloumns和grid-template-rows属性. 12345.warpper{ display:grid; grid-template-rows: 100px 50px; grid-template-cloumns: 100px 80px 60px;} 我们为grid-template-cloumns写入了3个值,这样我们就得到了3列,每列的值代表列的宽度.而grid-template-rows代表行数及行的高度. 放置items(子元素)为了帮助理解,我们在每个items(子元素)加上单独的class. 12345678<div class=\"warpper\"> <div class=\"item1\">1</div> <div class=\"item2\">2</div> <div class=\"item3\">3</div> <div class=\"item4\">4</div> <div class=\"item5\">5</div> <div class=\"item6\">6</div></div> 现在我们来创建一个 3*3的grid: 12345.warpper{ display:grid; grid-template-rows: 100px 100px 100px; grid-template-cloumns:100px 100px 100px;} 我们只在页面上看到3-2的grid,而我们定义的是3-3的grid.这是因为我们只有6个items来填充这个网格.我们我们再追加3个items,name最后一行也会被填满. 要定位和调整items大小,可以使用grid-cloumn和grid-row属性来设置. 1234.item1{ grid-cloumn-start: 1; grid-cloumn-end: 4;} 上面的样式表示,我们希望item1占据从第一个网格线开始,到第四条网格线结束.换句话说它将独占一行.而剩下的items都推到了下一行. 这种形式也可以缩写为: 123456.item1{ grid-cloumn: 1/4;}.item3{ grid-row: 2/4;} 相关术语 网格容器(Grid):应用display:grid的元素,items的直接父级元素. 网格项(items): 网格容器的直接子元素,后代元素不是. 网格线(Grid-line):构成网格结构的分界线. 网格轨道(Grid-Track):两条相邻网格线之间的空间. 网格单元格(Grid-cell):两个相邻行与相邻列之间的空间. 网格区域(Grid-Area):四条网格线包围的总空间. 父元素 网格容器属性display将元素定义为网格容器,并为其建立新的网格式上下文. grid : 生成一个块级网格 inline-grid: 生成一个内联网格 subgrid: 嵌套的子网格. 在网格容器中使用float,clear,column,vertical-align不会产生任何效果. grid-template-cloumns/rows使用空格分割值列表,用来定义网格的行和列.可以是长度值和百分比,和自动分配(auto)或网格线名称. grid-template-areas指定Grid Area名称来定义网格模板.一个.号代表一个空的单元.你可以使用任意数量的.只要这些.之间没有空隙隔开就表示一个个的单元格. :由items的grid-area指定的区域名称 .(点号):代表一个空网格单元 none:不定义网格区域 12345678910111213141516.container{ display:grid; grid-template-rows:auto; grid-template-cloumns:50px 50px 50px 50px; grid-template-areas: \"header header header\" \"main main . sidebar\" \"footer footer footer footer\"}.item-a{ grid-area:header;}.item-b{ grid-area:main;}... 当你命名网格区域后,该区域两端的网格线实际上是自动命名的,如果你的网格区域名字是foo,则起始行网格线和列网格线是foo-statrt,最后的网格线是foo-end.则意味着一个网格线可能有很多名称. grid-template用于定义grid-template-rows,grid-template-cloumns,和grid-template-areas的缩写. none:将所有的属性设置为初始值. subgrid:将rows和cloumns设置为subgrid,areas设置为初始值. /:将rows和cloumns设置为特定值,areas为none. grid-column-gap/grid-row-gap指定网格线的大小,可以把它想象为设置列/行之间间距的宽度.值为长度值. grid-gapgrid-column-gap/grid-row-gap的缩写语法 justify-items网格容器的水平对其方式,这些行为也可以通过items的justify-self属性设置: start 左侧对其 end 右侧对其 center 居中对齐 stretch 填满区域宽度 align-items网格容器的垂直对齐方式: start 顶部对齐 end 底部对齐 center 垂直居中 stretch 填满区域高度 justify-content/align-content有时,网格容器的内容小于整体容器的大小,可以设置内容在容器中的对齐方式. start 左对齐 end 右对齐 center 居中 stretch 填充 space-around 左右两边都有空间,空格之间距离相等 space-between 左右两边没有空间,空格之间距离相等 space-evenly 左右和每个空间距离相等 grid-auto-columns/grid-auto-rows指定自动生成隐式网格轨道,但定义行和列自动超出网格范围时,隐式网格轨道创建.值为长度,百分比等等. grid-auto-flow如果你有一些未明确放置的网格,自动放置算法会自动放置这些项. row: 依次填充每行 column:依次填充每列 dense: 出现较小的网格项时,尝试填充网格中较早的空缺.但它可能导致你的网格项出现混乱. 子元素 网格项items属性grid-column-start/end通过指定网格线来确定网格在容器中的列的起始位置 grid-row-start/end通过指定网格线来确定网格在容器中的行的起始位置 grid-column/grid-row上面两者的缩写 grid-area为网格项提供一个名词,一遍容器grid-template-areas属性创建模板进行引用.也可以作为grid-row和grid-cloumn的缩写. justify-self/align-selfitems的水平/垂直对其方式","categories":[{"name":"前端","slug":"前端","permalink":"http://yoursite.com/categories/前端/"}],"tags":[{"name":"前端 css","slug":"前端-css","permalink":"http://yoursite.com/tags/前端-css/"}]},{"title":"浏览器数据持久化存储技术解析","slug":"浏览器数据持久化存储技术解析","date":"2017-11-04T07:09:36.000Z","updated":"2017-12-22T07:13:22.000Z","comments":true,"path":"2017/11/04/浏览器数据持久化存储技术解析/","link":"","permalink":"http://yoursite.com/2017/11/04/浏览器数据持久化存储技术解析/","excerpt":"","text":"打开Chrome浏览器的调试模式,Application就列举了现代浏览器的8种缓存机制:HTTP文件缓存,LocalStorage,SessionStorage,indexDB,webSQL,Cookie,CacheStorage,ApplicationCache. 从网址到网页展示 我们先看一个问题,从我们打开浏览器地址栏输入一个网址,到浏览器展示网页内容的这段时间,浏览器和服务端都发生了什么事情? 在接受到用户输入的网址后,浏览器会开启一个线程来处理这个请求,对用户输入的 URL 地址进行分析判断,如果是HTTP协议就按照HTTP方式来处理. 调用浏览器引擎中的对应方法,比如 WebView 中的 loadUrl 方法,分析并加载这个 URL 地址. 通过 DNS 解析获取该网站对应的 IP 地址,查询完成后连同浏览器的 Cookies,userAgent 等信息向网站目的地 IP 发送 Get 请求. 进行 HTTP 协议会话,浏览器客户端向 web 服务器发送报文. 进入网站后台上的 web 服务器处理请求, 如 Apache,Tomcat, Node.js服务器. 进入部署好的后端应用,如 PHP, java, Javascript, Python等后端程序,找到对应的请求处理逻辑,这期间可能会读取服务器缓存或查询数据库等. 服务器处理请求并返回响应报文,此时如果浏览器访问过该页面,缓存上有对应资源,会与服务器最后修改记录对比,一致则返回304,否则返回200与对应的内容. 浏览器开始下载 HTML 文档(响应报文状态码为200时) 或者从本地缓存读取文件内容(浏览器缓存有效或响应报文状态码为304时). 浏览器根据下载接收到的 HTML 文件解析结构建立 DOM文档树,并根据 HTML 中的标记请求下载指定的MIME文件(如CSS,JAvaScript脚本等),同时设置缓存等内容. 页面开始解析渲染DOM,CSS根据规则解析并结合DOM文档树进行网页内容布局和绘制渲染,JavaScript根据DOM API 操作DOM,并读取浏览器缓存,执行事件绑定等,页面整个展示过程完成. HTTP文件缓存HTTP文件缓存是基于HTTP协议的浏览器端文件级缓存机制,在文件重复请求的情况下,浏览器可以根据HTTP响应的协议头信息判断是从服务器端请求文件还是本地读取文件.以下是 文件缓存的过程. 1. 浏览器会先查询Cache-Control来判断内容是否过期,如果未过期,直接读取浏览器端缓存文件不发送HTTP请求,否则进入下一步. 2. 在浏览器端判断上次文件返回头中是否含有Etag信息,有则连同If-None-Match一起向服务器发生请求,服务端判断Etag未修改则返回状态304,修改则返回200,否则进入下一步. 3. 在浏览器端判断上次文件是否含有Last-Modified信息,有则一起向服务器发送请求,服务器判断是否失效,失效返回200,未失效返回304. 4. 如果Etag和Last-Modified都不存在,则向服务器请求内容. 在HTML中我们添加的meta标签中的Expires和Cache-Control,且一般Cache-Control设置的是秒,如果以上两个同时设置,只要Cache-Control的设置生效. 12<meta http-equiv=\"Expires\" content=\"Mon, 20 Jul 2016 23:00:00 GMT\"/><meta http-equiv=\"Cache-Control\" content=\"max-age=7200\"> 同时服务端也要设置静态资源的缓存时间.我们可以结合Koa-static中间件设置实现. 12345const static = require('koa-static')const app = koa()app.use(static('./pages',{ maxage: 7200})) localStoragelocalStorage是HTML5的一种本地缓存方案,目前主要用于浏览器端保存体积较大的数据(如AJAX返回结果等).但它在各版本浏览器的长度限制不一.它的核心API只有4个. 1234localStorange.setItem(key,value)//设置存储记录localStorage.getItem(key)//获取储存记录localStorage.removeItem(key)//删除记录localStorage.clear()//清空 LocalStorage只支持简单数据类型的读取,为方便读取对象等格式内容,通常需要进行一层安全封装再引入使用. sessionStoragesessionStorage和LocalStorage功能类似,但sessionStorange在浏览器关闭时会自动清空.它的API和LocalStorage的API完全相同,但由于不能持久化数据存储,因此使用场景较少. cookiecookie是网站为了辨别用户身份或session追踪而存储在用户浏览器的数据,cookie一般会通过HTTP请求到服务器端.一条cookie主要由键,值,域,过期时间和大小组成,一般用于保存用户的网站认证信息.通常最大限制为4KB. cookie分为两种,sessionCookie和持久型Cookie.前者一般不设置过期时间,表示与浏览器会话期间保存在内存中,持久性Cookie会设置过期时间保存在本地硬盘中,知道过期或清空才失效. Cookie设置中有个HttpOnly参数,浏览器端通过doucument.cookie是读取不到HttpOnly类型的Cookie的,只能通过HTTP请求头发送到服务器进行读写操作.这样可以避免服务器端的Cookie记录被js修改,保证了服务端验证cookie的安全性. WebSQLwebSQL是浏览器端用于存储大量数据的缓存机制,以一个独立浏览器端数据存储规范的形式出现.它在HTML5前就已经出现,是单独的规范,它将数据以数据库二维表的形式存储在客户端,并且允许SQL语句的查询. webSQL的API主要包含上个核心方法:openDatabase(),transaction()和executeAql(). 1234567//openDatabase()打开已经存在的数据库,不存在就创建.他的五个参数是数据库名,版本号,描述,数据库大小,创建回调.let db = openDatabase('mydatabase','1.0','test',2*1024*1024)db.transaction(function(table){ table.executeSql('INSERT INTO t1 (id,msg) VALUES (1,\"hello\")')})//transaction方法允许我们根据情况控制事物提交或回滚.//executeSql用于执行真实的SQL查询语句. IndexDBIndexDB也是客户端存储大量结构化数据并且能在这些数据上使用索引进行高性能检索的一套API.由于webSQL不是HTML5规范,一般推荐使用IndexDB进行大量数据存储,其基本实现和webSQL类似. 12345if(database){ database.transaction(function(tx){ tx.executeSql('INSERT INTO t1 (id,msg) VALUES (1,\"hello\")') })} Application CacheApplication Cache是一种允许浏览器通过manifest配置文件在本地有选择性的存储js,css,图片等静态资源的缓存机制.当页面不是首次打开时,通过一个特定的manifest文件配置描述选择性新读取本地ApplicationCache的文件.所以它具有离线浏览,快速加载,服务器载荷小的优势.它的文件访问及更新机制如下: 1. 判断是否是第二次加载页面. 2. 是的话访问AppCache. 3. 检查manifest文件是否更新 4. 无更新从AppCache读取,有更新则重新拉取并更新AppCache. 使用方式 12345678<html manifest=\"app.manifest\">//对应的描述文件如下CACHE MANIFEST#VERSION 1.0CACHE:xxx.cssXXX.jsXXX.png 浏览器也可以根据window.applicationCache来对其进行控制. 尽管ApplicationCache的实现很方便,但是已经开始被标准弃用,渐渐将会由ServiceWorkers来代替.总之,ApplicationCache仍是一个不成熟的本地缓存解决方案. cacheStoragecacheStorage是ServiceWorkers规范中定义的,用于保存每个ServiceWorker声明的Cache对象,是未来可代替ApplicationCache的离线方案. CacheStorage在浏览器端为window.caches,有open,match,has,delete,keys五个API. 12345caches.has();//检查如果包含cache对象,返回一个promisecaches.open();//打开一个cache对象,返回一个promsiecaches.delete();//删除一个cache对象,返回一个promisecaches.keys();//含有keys中字符串的任意一个,返回一个promisecaches.match();//匹配key中含有该字符的cache,返回一个promise Flash缓存Flash缓存主要基于网页端Flash,具有读写浏览器本地目录的功能,同时也可以向js提供调用的API,这样页面就可以通过js调用Flash读写本地指定的磁盘目录,达到本地数据缓存的目的. 本文摘自张成文编著<<现代前端技术解析>>,详情请点击张成文的Github","categories":[{"name":"前端","slug":"前端","permalink":"http://yoursite.com/categories/前端/"}],"tags":[{"name":"前端 数据储存","slug":"前端-数据储存","permalink":"http://yoursite.com/tags/前端-数据储存/"}]},{"title":"JavaScript 作用域和变量提升","slug":"JavaScript 的传值与传址","date":"2017-10-26T11:03:01.000Z","updated":"2021-08-30T11:05:30.471Z","comments":true,"path":"2017/10/26/JavaScript 的传值与传址/","link":"","permalink":"http://yoursite.com/2017/10/26/JavaScript 的传值与传址/","excerpt":"","text":"JavaScript 的传值与传址复制类型与引用类型复制: 数字,布尔值,字符串(字符串无法改变,无法确定,行为类似于复制类型)引用: 数组,对象,函数等 12345678910// 复制类型var a = 1;var b = a;b++;a == 1 // true,因为 b 的修改不影响 a,b 是 a 的复制// 引用类型var c = [1];var d = c;d[0]++;c; // [2] 因为 d = c 时赋值的是 a 地址的引用 复制类型和引用类型在函数参数中的应用传值的传递,传给函数的是数值的一个复制,函数中对其的修改对外部不可见. 123456var a = 1;function change(a){ return a = 2;}change(a);a; // 1 传址的传递,传递给函数的是数值的引用.函数对其属性的内部修改外部可见,但函数内部新引用覆盖旧引用时,外部不可见,这也就是闭包的私有变量特性 12345678var a = [1,2,3],b=[4,5]function change(a,b){ a[0] = 9; // 属性修改 b = [6,6,6]; // 覆盖引用}change(a,b)a; // [9,2,3]b; // [4,5] 但如果函数并未传递参数,根据作用域链向上查找,可以修改函数外部变量. 12345678var a = [1,2,3],b=[4,5]function change(){ a[0] = 9; // 向函数外部查找变量 a b = [6,6,6]; // 向函数外部查找变量 b}change(a,b)a; // [9,2,3]b; // [6,6,6]","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yoursite.com/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yoursite.com/tags/JavaScript/"}]},{"title":"读<<美国种族简史>>","slug":"读<<美国种族简史>>","date":"2017-09-06T08:58:07.000Z","updated":"2021-08-30T11:47:10.279Z","comments":true,"path":"2017/09/06/读<<美国种族简史>>/","link":"","permalink":"http://yoursite.com/2017/09/06/读<<美国种族简史>>/","excerpt":"","text":"爱尔兰人爱尔兰人是美国城市里第一批重要的”少数”种族之一.古代的爱尔兰曾经是个技艺和学术上有过某些建树的国家.在15世纪初期,英国国王对爱尔兰的有效控制仅仅局限在东海岸都柏林附近30英里长10英里宽的一条狭长地带.千百年来,爱尔兰的历史就是一部充满偶发的流血起义和遭到血腥镇压的历史.英国在战胜爱尔兰后,颁布了所谓的惩罚性法律,剥夺了爱尔兰人的许多基本权利.为了满足本民族的宗教,教育和政治需要,各种各样的爱尔兰人的秘密和底层的组织纷纷发展起来.由于不得不为自己提供是由政府提供的机构设施,爱尔兰人不但培养了高水平的组织本领,也锻炼了出去规避他们视为非法压迫他们的政府机构的本领. 爱尔兰的产权制度规定,佃农在土地上的任何额外收获,都属于土地所有者的,这就摧毁了佃农的积极性.这种制度在道德和经济上的影响,远远超过了这些法律的实施时间和地点造成的影响.爱尔兰人不但由于这些法律而蒙受了眼前的损失,而且由于积极性下降的社会环境而蒙受了更长远的损失.在后来的美国,学术界和民众都异口同声的说爱尔兰人懒惰和无能,当他们不会想到这是由他们长期受到的压迫中慢慢影响的. 19世纪30年代起,谷物歉收和灾荒加剧了爱尔兰的贫困情况.由于爱尔兰1/4的可耕地用于种植马铃薯,国际马铃薯的歉收对爱尔兰的影响是巨大的.但英国人并没有因为这个原因而放松对爱尔兰的压迫剥削.更多的爱尔兰人选择逃离,这时美国就成了爱尔兰人的第一选择.来美的第一批爱尔兰移民通过签订卖身契约,答应到美充当若干年苦工,以此抵偿未来雇主或轮船公司预先垫支的差旅费,做船抵达宾夕法尼亚中部,之后才然后慢慢涌进美国东北部. 德国人具有德国血统的美国人,仅次于英裔人.德国的军事传统为美国造就了若干名名垂青史的将帅,其中包括在一战和二战指挥美军打败德军的两名虎将,既潘兴和艾森豪威尔. 在还没有德国这个概念的时候,就有相当多的德意志人移居美国了.直到1871年,普鲁士,巴伐利亚等日耳曼小国才被俾斯麦统一起来.早起移民来美的德意志人大都掺杂在荷兰人的队伍中,于1620年定居在新阿姆斯特丹(后来改称纽约)的.”宾夕法尼亚荷兰人”(Pennsylvania Dutch)由此发端,而此处的”荷兰人”(Dutch)这个词,实际上由美国人的发音错误造成的,把”德意志人”(Deutsch)读成了荷兰人(Dutch). 1776年,美国人分裂为英国托利党的支持者和拥护独立的革命派,美籍德意志人则分裂为和平主义者和革命分子. 冯.施图本将军就是从他的祖国赶来,目的就是参加美国独立战争,华盛顿困守瓦力福基时,施图本将军曾为之出谋划策,作为美国军队的教官,他成功训练出一支足以打败英国职业化军队的铁骑. 不管在农村还是城市,德国人集居的方式使他们世代保留着自己的语言和文化.这一点时常表现在民居和文化上的孤立.”德国人不大于美国老百姓交往”,他们彼此从远处观察对方,”都带着一种真诚的好奇心,时常夹杂着对彼此的蔑视”.除了语言,其他德意志文化的许多特点也被带到了美国.和圣诞树一样,牛肉香肠,汉堡包和啤酒已成为美国生活方式的必备之物.在19世纪的辛辛那提,沿街叫卖的德裔食品摊贩,用把牛肉熏制的香肠往长条面包中一夹,就成了后来的热狗.圣路易斯及其周围地区聚集的大批德国人,为另一家德国酿酒厂的创办提供了市场,他的老板是安海斯-步希,生产的是百威啤酒.德国移民推动的最重大的社会变化之一,就是在美国推广了各式各样的天真活泼而且适合整个家庭在公开场合开展的娱乐活动.音乐演奏,野餐,跳舞,打牌,游泳等美国人闲暇时爱从事的文艺活动都是德国移民在19世纪带过来并推广出来的.德国人还组织了军乐团,交响乐团和各式各样的合唱团. 德国人越来越被美国人接受,但第一次世界大战爆发时,席卷全美的反德情绪却粗暴的使这些发生了变化.美国人的反德情绪不仅仅局限在德国本身,而且殃及日耳曼文化和美籍德国人.20世纪早期,美籍德国人慢慢被美国社会同化,其速度后来更有所加快.随着希特勒和纳粹与20世纪30年代在德国的崛起,德国掀起了向美国移民的新高潮,包括世界上数一数二的艺术家,科学家,其中最杰出的就是爱因斯坦.二战虽然也掀起了反德情绪,但其程度远不及一战期间,这次指挥美军在欧洲登陆并打败德国的就是一位具有德意志血统的将军”艾森豪威尔”. 犹太人犹太人不是来自某一国家或属于某一文化,而是来自许多国家并分属不同的文化.尽管犹太人因寄居的国家不同而产生了文化上的割裂,甚至在宗教理论和实践上也存在分歧.但是,犹太人不仅供奉古代以色列的统一始祖,分享宗教信仰和历史传统的共同内核,而且作为少数民族,不管寄居何处,都曾有过不同程度的长期遭到异族敌视的悲惨经历. 公元70年,当罗马帝国的军队占领巴勒斯坦时,古犹太人就从自己的故土被驱逐了出来,从此开始流落异国他乡.基督教在整个欧洲取代了多神信仰之后,犹太人成为了全欧洲大陆唯一不信仰基督教的民族,所到之处皆为”局外人”.这使犹太人在基督教狂热时期(比如十字军东侵)或宗教大恐怖时期出于自身难保的地位.他们在宗教上持有不同见解,而且不管他们到哪都是外乡人,他们有自己的文化,讲不同的语言,穿不同的衣服,而且一般都在单独的村庄居住,他们是被贴了标签的人,周围那些无知且迷信的居民无论陷入什么样的激情或恐惧,一遇到风吹草动,便自然而然的把犹太人当成攻击的靶子. 犹太人通常居住在一起,集聚区的生活方式排除了欧洲犹太人从事农业的可能性.他们在到达纽约及美国其他城市之前的好几百年,就已经都市化了.犹太人在他们最受孤立的欧洲地区,基本上没有受到文艺复兴后现代思想潮流的熏陶,所以他们的文化依然是古老的乡土文化.欧洲犹太人的典型特征之一就是对知识的尊重和景仰.这里指的主要是宗教知识,有关犹太教法典的知识,以及对其意义和内涵的周密而细致的分析. 第一批抵达美洲殖民地的犹太人是所谓的赛法迪犹太人,他们有在西班牙及葡萄牙生活的经历,哥伦布发现新大陆所引发了一系列时间,这也包括赛法迪犹太人到达美洲.之后,德国犹太人也来了,但德国犹太人不聚积在某一处,而是散落在美国各地,有些人在宾夕法尼亚的农场落户,有的则在中西部定居,有的则跟着大篷车向西部进发,这批德国犹太人中有个货郎,名叫李维.斯特劳斯,李维斯牛仔裤就是以他的名字命名的. 13世纪和14世纪,波兰王室鼓励犹太人在其国土上定居,并颁发保护他们的特许状.犹太人成了欧洲地区先进技术和知识传入波兰的载体.几个世纪之后,犹太工匠和商人的家业逐渐壮大起来,这招致周围贫穷而不识字的波兰农民及其教会头目的憎恶和仇恨.犹太人充当政府的税务官或给地主收租,这更使他们成为当众民众的眼中钉.随着政治气候的变化,犹太人时而受到当局的保护,时而遭受当局的迫害.于是,但俄国人从波兰手里夺取的地方,把犹太人禁锢在他们的居住区.叶卡捷琳娜二世之后的40年间,离俄赴美的人中有75%是犹太人,而到美的犹太人中也正好有75%的人来自俄国. 东欧犹太人涌入美国后,与当地的犹太人格格不入.德国犹太人对他们的生活之寒酸感到不可思议,不仅如此,受教育程度也很低,举止也显得很粗俗.德国犹太人还造出了一个词”老K”来称呼东欧的犹太人.但这些都是有原因的,德国犹太人一直在美国的安全环境下飞黄腾达,从来也没有体验过东欧犹太人遭受的那些令人发指的欺凌,不知个中滋味,而东欧犹太人对那些欺凌却记忆犹新,导致他们缺乏自信心和气度,不敢于陌生人搭讪,再加上那副胆小怕事,逆来顺受的模样,自然受到外人的欺凌和捉弄的对象.而且外部有东欧犹太人母亲保护子女太过分的说法,实际上这些犹太妇女当初在东欧时,家里的小孩一旦离开家门,父母就可能再也见不到了.如果人们一开始就知道这些背景就不会对他们有这么多的偏见. 辛苦的工作和重视教育的犹太人很快就有了积蓄,随着经济地位的上升,犹太人也向其他居住地区扩散开来.犹太裔电影业在美国很有地位,米高梅(MGM)电影制片厂的名字缩写,三个字母有两个都是取自犹太人Goldwyn和Mayer,其他的电影业巨子还有华纳兄弟和威廉福克斯.纽约历史上最出名的两家报纸也是由犹太人创办的,<<纽约时报>>和<<犹太前进报>>,有一半犹太血统的约瑟夫.普林策创办了<<纽约世界报>>并设立了普利策奖.在科学和医学界知名的犹太人当属爱因斯坦,罗伯特奥本海默和约纳斯沙克这样的巨擘了.从某种意义上来说,犹太人是美国成功故事的典型代表人物,在逆境中从一无所有到腰缠万贯. 意大利人今天的美籍意大利人,多数是从意大利南部移居美国的那部分人的后裔.意大利半岛的悠久历史可以追溯到基督之前的罗马共和国和罗马帝国,然而作为一个国家,意大利又是年轻的,它仅始于1861年.这一年意大利各省在历经多个世纪的分裂后首次统一在一个政府管辖之下.在文化上,意大利各省甚至于每个小镇,都觉得自身拥有自成一体的独特文化,即便距离很接近,语言和文化也相距甚远.在地理上,该国被亚平宁山脉一分为二,形成了许许多多的山谷,西西里岛和撒丁岛是其两大岛屿. 在意大利南部,可耕地少且零散,致使许多村落相互阻隔,反过来又加剧了他们在语言和其他文化的差距.另外,意大利南部的气候和地形造成了该地区的贫困,气温虽然适中,但降雨量偏低且只集中在少数几个月份,作物成长季节却逢干旱,雨季到来时又是倾盆而下,造成水土流失.致使沟壑,池塘积满死水,孳生疟疾.因为土质最肥沃的低地同时又是疟疾最容易传播的地方.除了农业,工业也先天不足,高山和丘陵占据该国国土面积的75%,只有一半的国土属可耕地,可耕地又大多集中在北部.历史加重了大自然造成的问题,意大利南部长期是历代帝国和王朝的战场,罗马帝国以来,战争在南部反复上演.此外,意大利文艺复兴的硕果是意大利北部的产物,对南部影响很小. 尽管就历史的创伤和极度的贫困而言,意大利人和爱尔兰人是难兄难弟,但在某些方面又不一样.爱尔兰人所受到的压迫,来自于其持有不同宗教信仰的异族,所以在回应时具有强烈的全民族意识,而意大利农民则长期受到宗教信仰相同的本种族人的压迫,这使他们不能团结在一起,而是完全仰仗直系家庭的其他成员,意大利人最信得过的人很少超出近亲的范围,南部意大利人的反击方式既种族间的报复和黑手党也都是以对方的家庭为目的的.值得一提的,在南部意大利人眼中,教育是认为对生活方式构成威胁的.他们认为受教育不能提供向上的社会流动,在那种等级森严的社会里,恐怕这不无道理.教育被视为对家庭神圣性的一种侵犯,是把儿童作为一个孤立的个体抽离出来,教给他们一种相左的一套价值体系,对于穷困潦倒的平民百姓来说,失去一个出外挣钱的劳动力实在是一种不堪忍受的损失. 早期来美的意大利人,近90%都是成年男性,小孩所占的比例远远低于其他国家的移民,这表明,他们来美一开始就是短暂的或是试探性的,返回故土既非失败亦非失望,不少回流的人都在美国挣了一笔可观的钱财.美籍意大利人的居住模式反映出他们在意大利时的地区主义格局,他们往往来自同一个地区的人一起集居在一处,社会关系也局限在某一个范围,这阻碍了全体意大利移民相互间缺乏强烈的认同感,又使他们不敢于其他种族发生摩擦.他们能和他们和谐共处,但并不代表他们能被别人同化,比如他们的择偶对象几乎从不超出本族的范围. 从社会关系来讲,在一个崭新的经济和社会环境中,意大利移民易遭不幸,出于自卫的目的,他们创建了互助会,这算是现代意义上的组织,即一种由陌生人组成了社会组织结构,该结构自身所确定的宗旨使这些陌生人聚集到了一起,通用汽车公司,红十字会,政治运动,公会或是体协,都是这类组织.职业犯罪活动在意大利南部是一门高度发达的艺术,特别是黑手党的老巢西西里,大多数意大利移民并不参与这类活动,因为他们自身就是黑手党的主要受害者.意大利和其他移民一样,较高的犯罪率都发生在第二代移民中. 今天的美籍意大利人在收入,教育智商得分上都与其他美国人大体相仿.值得一提的是,意大利人的崛起基本上并未借助人们通常视为必经之路的教育或是教育相关的职业.美籍意大利人不仅苦干,而且强调自立.他们拒绝接受政府的法定救济,甚至在收入低微时也保持良好的信誉,不热衷于政治和投机.但也是这样,造成了他们与黑人之间关系的恶化.早期意大利人对黑人表现出的敌意较少.黑人领袖所强调的种族进步之路,恰是意大利人所排斥的道路,他们认为靠政府救济和特殊照顾违背他们的价值观念.这两个种族的生活作风也相互冲突,双方都认为对方的言语和肢体语言是故意的侮辱,实际上在两种不同的文化里都有自己的内涵.这使他们与黑人的摩擦越来越多,而与华人则不一样,与华人虽然肤色不同,但价值观念和生活作风与意大利人并无相悖之处.意大利人和中国人相处的很好,远胜于他们和爱尔兰人的关系.总而言之,笼统的用”种族主义”这个字眼来解释,无法找到种族间敌对关系的缘由. 事实证明,美国确实是从意大利来到这里的那些人的机会之邦.但是,把机会变成现实也要付出艰苦的劳动和努力,并具备持之以恒的毅力.那些早起从贫困的意大利南部来的移民用他们的双手获得了新生. 华人中国人常被称为亚洲的犹太人,他们在许多不同的国度里过着自己独立的文化和社会生活.中国人的技能和组织本领使他们称为许多贫穷国家的有价值的外来户,而家境富足又使他们称为政治上受打击的对象.所以这些国家的政府对和华人的态度一直是爱憎兼具的.时至1966年,所罗门群岛政府还在是否要驱逐所有华人的问题进行过辩论.在印度尼西亚和墨西哥都曾发生过对华人的大屠杀. 中国曾在很长的一段时间内一直都是世界上经济,技术和社会组织最先进的国家,时至16世纪,中国人仍拥有世界上最高的生活水平.海华华人的出现和这个伟大文明的衰落是相关的,两者都是自明朝开始.”二战”前移居美国的华人,绝大部分都来自中国南方的一个省份,即广东省,且集中在广东省的台山,他们说台山的方言,但与中国通用的语言相距甚远.”二战”后移居美国的人大都说普通话,这和原有的在美华人产生了隔阂.中国人在极为动荡的悠久历史中所留下的遗产,就是忠于本国本土的家族,将其视为个人唯一可以依靠的归宿.随着华人来到美国的中国文化的另一特点就是对知识的尊重,毕竟中国在隋唐就开创了科举制. 第一批华人移民是作为合同工来美的,他们在农业,铁路修建和其他繁重的体力劳动方面很能吃苦.但他们来美只是尝试性的,只是想赚一笔钱就回国.但美国人对中国移民的态度是苛刻的,甚至是粗暴的.华人既不是白人,又不是基督徒,无论是文化上还是生理上都只能看做不能被同化的种族,美国人视他们为竞争对手,因为他们吃苦耐劳且工资很低.所以华人在美常常会受到其他种族的迫害.1882年的<<排华法案>>打打削减了华人移民的数量,其他新的法律先是禁止在美华人成为美国公民,接着又把公民身份当作从事多项职业的先决条件.华人赴美的大门几乎被彻底关闭.由于就业无门,只能自谋出路,在几十年间,他们的主要职业就是洗衣店.中国餐馆是另一个就业渠道,这些餐馆大都坐落在华人社区或唐人街,竭力让人感到他们不是在于白人竞争.面对无处不在的歧视,中国人的反应是退避三舍,尽量不惹人注目.这和身处隔离区的欧洲犹太人很相似.唐人街发展了自己的社会组织,并推举出首领来处理内部事务,除非万不得已不会求助于当地的美国机构. 早期中国移民的最大悲剧就是如此重视家庭的民族被剥夺了在美组建家庭的可能性.1882年美国单方面压低中国人移民的数量,结果造成华人两性比例严重失调.1860年,在美华人的男女比例约为20 : 1,到1890年上升为27 : 1,很明显许多早期抵美的华人无法组建家庭及繁衍后代,由于贫困又不能返回中国,数以千计的华人孤独的度过了他们的一生,同时也造成了同时期华人的自杀率是全美平均数的三倍. 华人领袖主持唐人街的工作,他们用现今唐人街典型的宝塔形建筑来装饰自己的地带,以至于唐人街旅游业兴旺发达.华人的节庆和游行活动也受到警察的保护,并成为招揽大批观光客的市政项目.由于华人集中居住在一个不发生问题的地区,或至少其问题很难引起外界注意的社区,以至于华人被视为一个安静而有有秩序的种族. 日本人日裔美国人的历史,大体上受到美国人对华人的所有态度,偏见及歧视的影响,华人到达美国比日本人要早30年左右。日本向美国移民始于19世纪后期,德川家族的军阀统治于1868年被推翻,取而代之的是一整套新式的价值观念和民族雄心。但日本的孤立于1854年被强行结束,美国的佩利将军率领美国海军进驻东京湾,诱使日本签订了条约,使日本有机会接触西方的思想和技术,为日本100年后的政治发展确定了发展方向。日本对西方感情很复杂,既憎恶其傲慢,又欣赏其赖以称霸的成就。 日本明治维新给了农民以行动和择业的自由,但也使地主在有利可图的情况下可以随时把佃农从土地上赶走。旧的武士阶层被打破,随着他们在经济和社会上的地位日益没落,那些为武士阶层服务的商人和手艺人也走了下坡路。因此许多人去海外寻找机会,但日本对海外日侨十分关心,国家有能力和威望为其撑腰,这使日本有别于当时的中国,中国当时太弱,自身也难免不被瓜分,根本谈不上为海外华侨讲话。日本政府事实上将其移民看做暂时性的,而美国因反对华人而制定的《排华法案》也使得其他亚洲人无法申请美国国籍。许多在美的日本人把钱汇到家乡,对日本很多城市的发展起到了很重要的作用。比如广岛这样的新兴工业中心才成为“二战”中的一个首要军事目标。 由于日本在19世纪和20世纪之交成为世界强国之一,它在1895年甲午海战战胜了中国,1905年又战胜了俄国,所以美国不能向以前对中国人那样以突然,单方面的终止向美国的移民,美日两国于1908年达成一个保全颜面协议就,史称“君子协议”,该协议规定,日本严格限制赴美的人数,而美方允许让在美的日裔家属与亲人团聚。还有一个很特别的地方,与其他国家不同,日本没有把它的那些精疲力尽,穷困潦倒的劳苦大众送到美国,在移民这一点,日本是严格挑选优秀公民,虽然他们并不是来自富裕家庭。 1941年12月7日,日本向停泊在夏威夷珍珠港的美国舰队发动了一次大规模袭击。这使美国遭受到有史以来最大的惨败。更惊人的是偷袭发生时,日本外交官正在华盛顿扮演和平使者的角色,还有就是日本同时在西太平洋发起攻势。美国人对日本的气愤和恐惧一股脑全发泄到了美籍日本人身上,辱骂和施暴行为时有发生。1942年罗斯福总统签署一项行政命令,授权军方可以把“有关人等”运送到“拘留营”,这个政策得到了广泛的支持。大规模的拘留使得在美日本人匆忙的把房子和其他产业在短时间卖掉,经济收到了很大的损失。但总体来讲,日裔美国人接受了拘留的严酷事实,并不得已退而求其次。尽力改善自己的境遇。 1943年美国陆军开始征召被划为“敌侨”而无资格参军的在美第二代日本人去服役。共有30多万日裔美国人参加了“二战”,日裔兵组成了442团成了“二战”获得荣誉最多的一支劲旅。战后随着日裔美国人的经济地位上升,再之美国种族主义普遍退潮,美籍日本人正在变得美国化。 黑人美国黑人是在违反其自身意志的情况下被强行带到美国的唯一种族。非洲的面积比欧洲大很多,但其海岸线却没有欧洲的长,且天然港口并不多。非洲的河流受到地形和四季变化的制约,只能断断续续的通航,茂密的原始森林和无边的沙漠使得大陆内部的交通和交流很是困难。造成了该大陆的人们各居一隅,操着800多种语言分成无数的部落,他们无法在收到攻击后有效和团结起来,为外人将他们大批虏获提供了机会。且奴隶制早就存在于非洲各部落之间,其历史可以追溯到古希腊和古罗马时期。把黑人当作商品出售,是公元8世纪阿拉伯人入侵北非之后开始的。当非洲人被阿拉伯的奴隶贩子带到西班牙时,欧洲的奴隶制已经开始销声匿迹了。于是西班牙和葡萄牙冒险来到撒哈拉以南的非洲地区,抓捕黑人当作自己的奴隶。当西班牙开始在西半球开辟殖民地时,有大批奴隶被运送到那里当苦力。哥伦布发现美洲大陆后,这种情况更严重了。任何一种奴隶制面临的关键问题就是如何防止奴隶逃跑,在美国南北战争爆发的南方,防止奴隶逃跑的办法不是筑起篱笆或设置岗哨,而是通过使奴隶处于无知,依附和恐惧的状态来实现的。1793年轧花机的发明,使60%的美国奴隶从事棉花种植工作,奴隶日趋集中到美国南部那些土质和气候适宜种植棉花的地带。而美国北方奴隶就很少,因为北方的气候并不适合种植大田作物。美国南北战争不仅是美国黑人历史上的一个转折点,而且堪称奴隶制度的最后一次大流血,对黑人来说,自由既解救了他们,又是他们陷入了一种很被动的境地。因为这些获得自由的奴隶文化水平并不高,只能从事和以前一样的工作,而且他们并不会安顿下来开始干活,只是继续依靠政府发放的救济生活,某些州的地方政府不得已颁布法令,强制黑人进行文化教育,但他们并不能和其他人种一起学习,只是创办专收黑人儿童的学校。虽然黑人在正规教育方面的进步及艰辛有缓慢,但他们发展了新型的文化风格。尤其是音乐和体育方面表现非常出色。","categories":[{"name":"读书","slug":"读书","permalink":"http://yoursite.com/categories/读书/"}],"tags":[{"name":"历史 美国","slug":"历史-美国","permalink":"http://yoursite.com/tags/历史-美国/"}]},{"title":"JavaScript 作用域和变量提升","slug":"JavaScript-作用域和变量提升","date":"2017-06-27T11:06:05.000Z","updated":"2021-08-30T11:08:12.514Z","comments":true,"path":"2017/06/27/JavaScript-作用域和变量提升/","link":"","permalink":"http://yoursite.com/2017/06/27/JavaScript-作用域和变量提升/","excerpt":"","text":"JavaScript 作用域与变量提升JavaScript 中的作用域在 JavaScript 中作用域分为以下三类: 全局作用域: 能够被任意作用域访问. 函数作用域: 可以在该函数内部访问. 块级作用域: let,const 形成的块级作用域. 词法作用域与动态作用域作用域确定当前执行代码对变量的访问权限.词法作用域(静态作用域) wirte-time 即编程时的上下文,是指函数的作用域在函数定义时就决定了.(JavaScript等绝大数编程语言)而动态作用域即 run-time 运行时上下文,是根据函数的调用位置.(Bash等语言) 123456789var value = 1function foo(){ console.log(value)}function bar(){ var value = 2 foo()}bar() // 1 代码解析:执行函数 foo,先从 foo 函数内部查找是否有局部变量 value,如果没有就从书写位置查找上一层作用域,输出1.这样一层一层向上查找就形成了作用域链. 变量的生命周期与提升变量的生命周期含: 变量声明,变量初始化,以及变量赋值三个步骤.其中声明步骤会在作用域中注册变量,初始化步骤负责为变量分配内存并创建作用域绑定,此时变量的值是 undefined,最后变量赋值步骤分配指定值给该变量. 123456console.log(a) // undefinedvar a = 1;// 相当于以下代码var a; // 变量声明及初始化 undefinedconsole.log(a) // undefineda = 1; // 变量赋值 函数的生命周期与提升不同于变量提升,在内存创建步骤,JS 解释器会通过 function 识别出函数声明,将声明,初始化,赋值三个步骤一起提升到作用域头部.需要注意的是函数表达式不属于函数声明,属于变量声明. 123456789hello() // helloWorld!function hello(){ console.log('helloWorld!')}sayHello() // sayHello is not a function // 函数表达式属于变量声明,变量声明只提升变量,不提升变量的值.var sayHello = function(){ console.log('hello!')}","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yoursite.com/categories/JavaScript/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yoursite.com/tags/JavaScript/"}]},{"title":"你不知道的 JS-读后总结","slug":"你不知道的 JS-读后总结","date":"2017-06-27T09:44:04.000Z","updated":"2021-08-30T09:46:58.925Z","comments":true,"path":"2017/06/27/你不知道的 JS-读后总结/","link":"","permalink":"http://yoursite.com/2017/06/27/你不知道的 JS-读后总结/","excerpt":"","text":"你不知道的 JavaScript作用域是什么1.1 编译原理 在传统编译语言的流程中,程序中的一段源代码在执行之前会经历三个步骤,统称为“编译”。 - 分词/词法分析(Tokenizing/Lexing) var a = 2;通常会被分解成 为下面这些词法单元:var、a、=、2 、; - 解析/语法分析(Parsing) 将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法 结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST)。 - 代码生成 将 AST 转换为可执行代码的过程称被称为代码生成。将 var a = 2; 的 AST 转化为一组机器指 令,用来创建一个叫作 a 的变量(包括分配内存等),并将一个值储存在 a 中。 1.2 理解作用域 编译器在编译过程的第二步中生成了代码,引擎执行它时,会通过查找变量 a 来判断它是 否已声明过。查找的过程由作用域进行协助,但是引擎执行怎样的查找,会影响最终的查 找结果。当变量出现在赋值操作的左侧时进行 LHS 查询,出现在右侧时进行 RHS 查询。表示是一个赋值操作 = 的左侧和右侧。 RHS 查询与简单地查找某个变量的值别无二致,而 LHS 查询则是试图 找到变量的容器本身,从而可以对其赋值。你可以将 RHS 理解成 retrieve his source value(取到它的源值),这意味着“得到某某的 值”。 1.3 作用域嵌套引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没找到,查找过程都会停止。 1.4 异常为什么区分 LHS 和 RHS 是一件重要的事情?因为在变量还没有声明(在任何作用域中都无法找到该变量)的情况下,这两种查询的行为是不一样的。12345function foo(a) { console.log( a + b ); b = a;}` 第一次对 b 进行 RHS 查询时是无法找到该变量的。如果 RHS 查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError 异常。相较之下,当引擎执行 LHS 查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎,前提是程序运行在非 “严格模式”下。 1.5 总结作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对变量进行赋值,那么就会使用 LHS 查询;如果目的是获取变量的值,就会使用 RHS 查询。不成功的 RHS 引用会导致抛出 ReferenceError 异常。不成功的 LHS 引用会导致自动隐式 地创建一个全局变量(非严格模式下) 词法作用域作用域共有两种主要的工作模型。第一种是最为普遍的,被大多数编程语言所采用的词法作用域,我们会对这种作用域进行深入讨论。另外一种叫作动态作用域,仍有一些编程语言在使用(比如 Bash 脚本、Perl 中的一些模式等)。 2.1 词法阶段大部分标准语言编译器的第一个工作阶段叫作词法化,词法作用域是由你在写 代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下是这样的).作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的 标识符,这叫作“遮蔽效应”.作用域查找始终从运行时所处的最内部作用域开始,逐级向外或者说向上进行,直到遇见第一个匹配的标识符为止。全局变量会自动成为全局对象(比如浏览器中的 window 对象)的属性,window.a 2.2 欺骗词法JavaScript 中的 eval(..) 函数可以在运行期修改书写期的词法作用域。123456function foo(str, a) { eval( str ); // 欺骗! console.log( a, b );}var b = 2;foo( \"var b = 3;\", 1 ); // 1, 3 JavaScript 中另一个难以掌握(并且现在也不推荐使用)的用来欺骗词法作用域的功能是 with 关键字。with 通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。12345678910111213141516function foo(obj) { with (obj) { a = 2; }}var o1 = { a: 3};var o2 = { b: 3};foo( o1 );console.log( o1.a ); // 2foo( o2 );console.log( o2.a ); // undefinedconsole.log( a ); // 2 ——a 被泄漏到全局作用域上了!` with 声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域。当我们将 o2 作为作用域时,其中并没有 a 标识符, 因此进行了正常的 LHS 标识符查找,一直向上没有找到标识符 a,因此当 a=2 执行时,自动创建了一个全局变量. 2.2.3 性能JavaScript 引擎会在编译阶段进行数项的性能优化。其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到 标识符。如果代码中大量使用 eval(..) 或 with,那么运行起来一定会变得非常慢。 函数作用域和块作用域3.1 函数中的作用域 函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)。这种设计方案是非常有用的,能充分利用 JavaScript 变量可以根据需要改变值类型的“动态”特性。 3.2 隐藏内部实现我们可以把变量和函数包裹在一个函数的作用域中,然后用这个作用域来“隐藏”它们。为什么“隐藏”变量和函数是一个有用的技术?如果所有变量和函数都在全局作 用域中,当然可以在所有的内部嵌套作用域中访问到它们。但这样会破坏前面提到的最小 特权原则,因为可能会暴漏过多的变量或函数,而这些变量或函数本应该是私有的,正确 的代码应该是可以阻止对这些变量或函数进行访问的。在任意代码片段外部添加包装函数,可以将内部的变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何内容。“隐藏”作用域中的变量和函数所带来的另一个好处,是可以避免同名标识符之间的冲突, 两个标识符可能具有相同的名字但用途却不一样,无意间可能造成命名冲突。冲突会导致 变量的值被意外覆盖。 3.3 函数作用域12345678var a = 2;(function foo(){ // <-- 添加这一行 var a = 3;console.log( a ); // 3 })(); // <-- 以及这一行 console.log( a ); // 2 (function foo(){ .. })作为函数表达式意味着foo只能在..所代表的位置中被访问,外部作用域则不行。foo 变量名被隐藏在自身中意味着不会非必要地污染外部作用域。 3.3.1 匿名和具名12setTimeout( function() { console.log(\"I waited 1 second!\");}, 1000 ); 匿名函数表达式,因为 function().. 没有名称标识符。函数表达式可以是匿名的,而函数声明则不可以省略函数名——在 JavaScript 的语法中这是非法的。行内函数表达式非常强大且有用——匿名和具名之间的区别并不会对这点有任何影响。给函数表达式指定一个函数名可以有效解决以上问题。始终给函数表达式命名是一个最佳实践:1234setTimeout( function timeoutHandler() { // <-- 快看,我有名字了! console.log( \"I waited 1 second!\" );}, 1000 ); 3.3.2 立即执行函数表达式(function foo(){ .. })();立即执行函数表达式IIFE.IIFE 的另一个非常普遍的进阶用法是把它们当作函数调用并传递参数进去。1234567var a = 2;(function IIFE( global ) {var a = 3;console.log( a ); // 3 console.log( global.a ); // 2})( window ); console.log( a ); // 2 IIFE 还有一种变化的用途是倒置代码的运行顺序,将需要运行的函数放在第二位,在 IIFE 执行之后当作参数传递进去。这种模式在 UMD(Universal Module Definition)项目中被广 泛使用。1234567(function IIFE( def ) { def( window );})(function def( global ) {var a = 3;console.log( a ); // 3 console.log( global.a ); // 2}); 函数表达式 def 定义在片段的第二部分,然后当作参数(这个参数也叫作 def)被传递进 IIFE 函数定义的第一部分中。最后,参数 def(也就是传递进去的函数)被调用,并将 window 传入当作 global 参数的值. 3.4 块作用域with 关键字。它不仅是一个难于理解的结构,同时也是块作用域的一个例子(块作用域的一种形式),用 with 从对象中创建出的作用域仅在 with 声明中而非外部作用域中有效。try/catch 的 catch 分句会创建一个块作用域,其中声明的变量仅在 catch 内部有效。let/const为其声明的变量隐式地了所在的块作用域。但是使用 let 进行的声明不会在块作用域中进行提升。 提升无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理。这个过程被称为提升。函数声明和变量声明都会被提升。函数会首先被提升,然后才是变量。 作用域闭包123456789function foo() { var a = 2; function bar() { console.log( a ); } return bar;}var baz = foo();baz(); // 2 —— 朋友,这就是闭包的效果。 函数 bar() 的词法作用域能够访问 foo() 的内部作用域。然后我们将 bar() 函数本身当作 一个值类型进行传递。在这个例子中,我们将 bar 所引用的函数对象本身当作返回值。在 foo() 执行后,其返回值(也就是内部的 bar() 函数)赋值给变量 baz 并调用 baz(),实 际上只是通过不同的标识符引用调用了内部的函数 bar()。在 foo() 执行后,通常会期待 foo() 的整个内部作用域都被销毁,因为我们知道引擎有垃 圾回收器用来释放不再使用的内存空间。闭包的“神奇”之处正是可以阻止这件事情的发生。事实上内部作用域依然存在,因此 没有被回收。谁在使用这个内部作用域?原来是 bar() 本身在使用。拜 bar() 所声明的位置所赐,它拥有涵盖 foo() 内部作用域的闭包,使得该作用域能够一 直存活,以供 bar() 在之后任何时间进行引用。bar() 依然持有对该作用域的引用,而这个引用就叫作闭包。 5.4 循环和闭包12345for (var i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 );} 这段代码在运行时会以每秒一次的频率输出五次 6。尽管循环中的五个函数是在各个迭代中分别定义的,但是它们都被封闭在一个共享的全局作用域中,因此实际上只有一个 i。 1234567for (var i=1; i<=5; i++) { (function(j) { setTimeout( function timer() { console.log( j ); }, j*1000 ); })(i);} 试试 IIEF 函数.在迭代内使用 IIFE 会为每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部,每个迭代中都会含有一个具有正确值的变量供我们访问。 12345for (let i=1; i<=5; i++) { setTimeout( function timer() { console.log( i ); }, i*1000 );} let 声明,可以用来劫持块作用域,并且在这个块作用域中声明一个变量。本质上这是将一个块转换成一个可以被关闭的作用域。 5.5 模块123456789101112131415function foo() { var something = \"cool\"; var another = [1, 2, 3]; function doSomething() { console.log( something ); } function doAnother() { console.log( another.join( \" ! \" ) ); } return { doSomething: doSomething, doAnother: doAnother };} 这个模式在 JavaScript 中被称为模块。最常见的实现模块模式的方法通常被称为模块暴露.foo只是一个函数,必须要通过调用它来创建一个模块实例。如果不执行 外部函数,内部作用域和闭包都无法被创建。这 个返回的对象中含有对内部函数而不是内部数据变量的引用。我们保持内部数据变量是隐 藏且私有的状态。可以将这个对象类型的返回值看作本质上是模块的公共 API。我们可以将模块函数转换成了 IIFE,立即调用这个函数并将返回值直接赋值给 单例的模块实例标识符 foo。var foo = (function xxx(){...})(); this和对象原型this到底是什么?this 是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调 用时的各种条件。当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包 含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的 其中一个属性,会在函数执行的过程中用到。 2.1 调用位置寻找调用位置最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。我们关心的 调用位置就在当前正在执行的函数的前一个调用中。2.2 绑定规则 2.2.1 默认绑定独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则。12345function foo() { console.log( this.a );}var a = 2; foo(); // 2 声明在全局作用域中的变量(比如 var a = 2)就是全局对 象的一个同名属性。foo() 是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,因此 this 指向全局对象。严格模式下全局对象将无法使用默认绑定,因此 this 会绑定到 undefined. 2.2.2 隐式绑定 12345678910111213function foo() { console.log( this.a );}var obj2 = { a: 42, foo: foo };var obj1 = { a: 2, obj2: obj2 };obj1.obj2.foo(); // 42 当函数引用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。对象属性引用链中只有最顶层或者说最后一层会影响调用位置。 12345678910111213141516function foo() { console.log( this.a );}var obj = { a: 2, foo: foo };var bar = obj.foo; // 函数别名!var a = \"oops, global\"; // a 是全局对象的属性 bar(); // \"oops, global\"function doFoo(fn) {// fn 其实引用的是 foo fn(); // <-- 调用位置!}//参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值doFoo( obj.foo ); // \"oops, global\" 一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象(严格模式下是undefined)。虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。 2.2.3 显式绑定 bind,call(..) 和 apply(..) 方法 12345function foo() { console.log(this.a)}var obj = { a:2};foo.call(obj); //2 2.2.4 new绑定12345function foo(a) { this.a = a;}var bar = new foo(2); console.log( bar.a ); // 2 使用 new 来调用 foo(..) 时,我们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上。new 是最后一种可以影响函数调用时 this 绑定行为的方法,我们称之为 new 绑定。 2.2.5 优先级 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。var bar = new foo() 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是 指定的对象。var bar = foo.call(obj2) 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this 绑定的是那个上下文对象。var bar = obj1.foo() 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。var bar = foo() 2.2.6 箭头函数箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决定 this。用更常见的词法作用域取代了传统的 this 机制。 对象无论返回值是什么类型,每次访问对象的属性就是属性访问。如果属性访问返回的是一个函数,那它也并不是一个“方法”。属性访问返回的函数和其他函数没有任何区别(除了 可能发生的隐式绑定 this)。1234567891011function foo() { console.log( \"foo\" );}var someFoo = foo; // 对 foo 的变量引用var myObject = { someFoo: foo};foo; // function foo(){..}someFoo; // function foo(){..} myObject.someFoo; // function foo(){..} someFoo 和 myObject.someFoo 只是对于同一个函数的不同引用,并不能说明这个函数是特 别的或者“属于”某个对象。如果 foo() 定义时在内部有一个 this 引用,那这两个函数引用的唯一区别就是 myObject.someFoo 中的 this 会被隐式绑定到一个对象。无论哪种引用形式都不能称之为“方法”。 3.3.3 数组数组也是对象,所以虽然每个下标都是整数,你仍然可以给数组添加属性:如果你试图向数组添加一个属性,但是属性名“看起来”像一个数字,那它会变成 一个数值下标. 3.3.4 复制对象对于 JSON 安全(也就是说可以被序列化为一个 JSON 字符串并且可以根据这个字符串解析出一个结构和值完全一样的对象)的对象来说,有一种巧妙的复制方法:var newObj = JSON.parse( JSON.stringify( someObj ) );这种方法需要保证对象是 JSON 安全的,所以只适用于部分情况。ES6 定义了 Object.assign(..) 方法来实现浅复制。 3.3.5 属性描述符从 ES5 开始,所有的属性都具备了属性描述符。1234567891011var myObject = { a:2};Object.getOwnPropertyDescriptor( myObject, \"a\" ); // {// value: 2,// writable: true,可写// enumerable: true,可枚举// configurable: true 可配置// }` 在创建普通属性时属性描述符会使用默认值,我们可以使用 Object.defineProperty(..) 来添加一个新属性或者修改一个已有属性(如果它是 configurable)并对特性进行设置。 混合对象“类”4.1.2 JavaScript中的“类”JavaScript 只有一些近似类的语法元素,虽然有近似类的语法,但是 JavaScript 的机制似乎一直在阻止你使用类设计模式。在 近似类的表象之下,JavaScript 的机制其实和类完全不同。在软件设计中类是一种可选的模式,你需要自己决定是否在 JavaScript 中使用它。 4.2.2 构造函数类实例是由一个特殊的类方法构造的,这个方法名通常和类名相同,被称为构造函数。这个方法的任务就是初始化实例需要的所有信息(状态)。构造函数会返回一个对象(也就是类的一个实例). 4.3 类的继承子类会包含父类行为的原始副本,但是也可以重写所有继承的行为甚至定义新行为。JavaScript 本身并不提供“多重继承”功能。 4.3.1 多态 4.4 混入在继承或者实例化时,JavaScript 的对象机制并不会自动执行复制行为。由于在其他语言中类表现出来的都是复制行为,因此 JavaScript 开发者也想出了一个方法来模拟类的复制行为,这个方法就是混入。接下来我们会看到两种类型的混入:显式和隐式。4.4.1 显式混入由于 JavaScript 不会自动实现复制行为,所以我们需要手动实现复制功能。这个功能在许多库和框架中被称为 extend(..),但是为了方便理解我们称之为 mixin(..)。现在我们来分析一下 mixin(..) 的工作原理。它会遍历 sourceObj(本例中是 Vehicle)的 属性,如果在 targetObj(本例中是 Car)没有这个属性就会进行复制。JavaScript 中的函数无法(用标准、可靠的方法)真正地复制,所以你只能复制对共享函数对象的引用. 原型5.1 [[Prototype]]JavaScript 中的对象有一个特殊的 [[Prototype]] 内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时 [[Prototype]] 属性都会被赋予一个非空的值。使用 for..in 遍历对象时原理和查找 [[Prototype]] 链类似,任何可以通过原型链访问到 (并且是 enumerable)的属性都会被枚举。使用 in 操作符来检查属性在对象中是否存在时,同样会查找对象的整条原型链(无论属性是否可枚举) 5.2.2 “构造函数”123456function Foo() { // ...}Foo.prototype.constructor === Foo; // truevar a = new Foo();a.constructor === Foo; // true Foo.prototype 默认有一个公有并且不可枚举的属性 .constructor,这个属性引用的是对象关联的函数.在 JavaScript 中对于“构造函数”最准确的解释是,所有带 new 的函数调用。.constructor 并不是一个不可变属性。它是不可枚举(参见上面的代码)的,但是它的值是可写的(可以被修改)。constructor 是一个非常不可靠并且不安全的引用。 5.3 (原型)继承Bar.prototype = Object.create(Foo.prototype)这条语句的意思是:“创建一个新的 Bar.prototype 对象并把它关联到 Foo. prototype”。如果能有一个标准并且可靠的方法来修改对象的 [[Prototype]] 关联就好了。在 ES6 之前, 我们只能通过设置 .proto 属性来实现,但是这个方法并不是标准并且无法兼容所有浏 览器。ES6 添加了辅助函数 Object.setPrototypeOf(..),可以用标准并且可靠的方法来修改关联。 1234// ES6 之前需要抛弃默认的 Bar.prototypeBar.ptototype = Object.create( Foo.prototype );// ES6 开始可以直接修改现有的 Bar.prototype Object.setPrototypeOf( Bar.prototype, Foo.prototype ); 5.4 instanceofa instanceof Foo; // true instanceof 回答的问题是:在 a 的整条 [[Prototype]] 链中是否有指向 Foo.prototype 的对象?可惜,这个方法只能处理对象(a)和函数(带 .prototype 引用的 Foo)之间的关系。 判断两个对象(比如 a 和 b)之间是否通过 [[Prototype]] 链关联,b.isPrototypeOf( c ); 直接获取一个对象的 [[Prototype]] 链。在 ES5 中,标准的方法是: Object.getPrototypeOf( a );大多浏览器也支持一种非标准的方法来访问内部 [[Prototype]] 属性a.__proto__ === Foo.prototype; // true如果你想直接查找(甚至可以通过 .proto.ptoto… 来遍历) 原型链的话,这个方法非常有用。 5.5 Object.create()的polyfill代码12345Object.create = function(o) { function F(){} F.prototype = o; return new F();};","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yoursite.com/categories/JavaScript/"}],"tags":[{"name":"前端 JavaScript","slug":"前端-JavaScript","permalink":"http://yoursite.com/tags/前端-JavaScript/"}]},{"title":"CSS常见布局实现","slug":"CSS常见布局实现","date":"2017-04-03T07:26:03.000Z","updated":"2018-04-04T07:33:56.000Z","comments":true,"path":"2017/04/03/CSS常见布局实现/","link":"","permalink":"http://yoursite.com/2017/04/03/CSS常见布局实现/","excerpt":"","text":"水平居中 文本/行内/行内块 123parent{ text-align:center;} 单个块级元素 1234.son{ margin: 0 auto; }/*左右设置margin为auto将会均分剩余空间.上下设置margin设置了auto,其计算值为0.*/ 多个块级元素 123456#parent{ text-align:center }.son{ display: inline-block } 绝对定位实现居中 123456789#parent{ position: relative }.son{ position: absolute; left: 50%;/*父元素的50%;*/ transform: translateX(-50%);/*自身的-50%;如果兼容性不好用margin: -XX;*/ /*子绝父相,通过left或right结合margin或translate达到居中.*/} 任意个元素(flex) 12345678#parent{ display: flex; justify-content: center;}.son{ flex:1;} 垂直居中 单行文本/行内/行内块 1234#parent{ height: 100px; line-height: 100px;} 多行文本/行内/行内块 1234#parent{ height: 100px; line-height: 20px;//高度除以文本的行数} 图片 12345678#parent{ height: 100px; line-height: 100px; font-size:0;//清除幽灵空白节点的BUG}.son{ vertical-align: middle;} 单个块级元素 123456789101112131415161718192021222324252627282930313233343536373839//方案一: table-cell#parent{ display: table-cell; vertical-align: middle; //缺点是设置table-cell的元素,宽高设置百分比无效,需要给他的父元素为table才生效. //设置table-cell不感知margin,设置float或position会对布局造成破坏.}//方案二: 绝对定位配合margin或translate#parent{ position: relative;}.son{ position: absolute; top: 50%; transform: translateY(-50%); //或margin-top: -XXX;}//方案三: margin: auto 0;#parent{ position: retive; height:100px;}.son{ height: 50px; position:absolute; top: 0; bottom: 0; margin: auto 0; //原理是当top和bottom为0时,margin-top&bottom会无限延伸沾满空间且平分.}//方案四: flex#parent{ display: flex; align-items: center; //或为son设置align-self: center;} 任意个元素(flex) 12345#parent{ display: flex; align-items: center; //或为son设置align-self: center;} 水平垂直居中 行内/行内块/图片 12345#parent{ display: flex; justify-content: center; align-items: center;} Table-cell 12345678#parent{ display: table-cell; vertical-align: middle; /*text-align: center;*/ /*如果子元素是行内元素则添加*/}.son{ margin: 0 auto;/*如果是块级元素则添加*/} 绝对定位 12345678910#parent{ position: retive;}.son{ position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); /*如果考虑兼容性问题的话可以使用margin*/} 绝对居中 1234567891011121314#parent{ position: retive;}.son{ position: absolute; top: 0; bottom: 0; left: 0; right:0; margin: auto; /* 当top&bottom为0,margin-top&bottom为无限延伸沾满空间并平分.left与right同上. */} 两列布局左定宽右自适应 float+margin 123456789.left{ width:100px; height:100px; float:left;}.right{ height:100px; margin-left:100px;} float+overflow 123456789.left{ width:100px; height:100px; float:left;}.right{ height:100px; overflow: hidden;} table的单元格自动分配 123456789101112#parent{ display:table;}.left{ width:100px; height:100px; display: table-cell;}.right{ height:100px; display:table-cell;} 绝对定位 1234567891011121314151617#parent{ position: relative;}.left{ width:100px; height:100px; position:absolute; top:0; left:0;}.right{ height:100px; position:absolute; top:0; left:100px; right:0;} 使用flex实现自适应. 1234567891011#parent{ display:flex;}.left{ width:100px; height:100px;}.right{ height:100px; flex:1;/*均分父元素剩余空间*/} 使用grid 1234#parent{ display: grid; grid-template-columns: 100px auto;/*auto换成1fr也可以*/} 一列不定,一列自适应 使用float+overflow实现 1234567891011#parent{ height:100px;}.left{ float: left;/*只设置浮动,不设宽度*/ height:100px;}.right{ overflow: hidden; height:100px;} flex实现 1234567891011#parent{ display: flex; height: 100px;}.left{ height:100px;}.right{ height:100px; flex:1;} 使用Grid实现 12345678910#parent{ display: grid; grid-template-columns: auto 1fr;}.left{ height: 100px;}.right{ height: 100px;} 三列布局两列定宽,一列自适应1234567891011<div id=\"parent\"> <div class=\"left\"> 左列定宽 </div> <div class=\"center\"> 中间定宽 </div> <div class=\"right\"> 右侧自适应 </div></div> 用float+margin实现 1234567891011121314151617#parent{ min-width:310px;/*防止宽度过小,子元素换行*/}.left{ width:100px; height:100px; float:left;}.center{ width:200px; height:100px; float:left;}.right{ margin-left:300px; height:100px;} float+overflow实现 12345/*其余样式与以上相同*/.right{ overflow: hidden; height:100px;} table-cell的单元格自动分配 1234567891011121314151617#parent{ height:100px; width:100%; display:table; border-spacing:10px;/*关键,设置间距*/}.left{ display: table-cell; width:100px;}.center{ display: table-cell; width:100px;}.right{ display: table-cell;} 使用flex实现 12345678910111213#parent{ height:100px; display:flex;}.left{ width:100px;}.center{ width:200px;}.right{ flex:1;/*均分父元素剩余空间.*/} 使用Grid实现 12345#parent{ height: 100px; display: grid; grid-template-columns: 100px 200px 1fr;/*1fr或auto都可以*/} ####双飞翼布局(两侧定宽,中间自适应) 1234567891011<div id=\"parent\"> <div id=\"center\"> </div> <div id=\"left\"> </div> <div id=\"right\"> </div> </div> 12345678910111213141516171819#parent{ height:100px;}#left{ float:left; width:100px; height:100px; margin-left:-100%;/*上移一行*/}#center{ height:100px; float:left; width:100%;}#right{ height:100px; float:left; margin-left: -100px;/*向上移动自身的距离*/} 圣杯布局方法 定位方式 12345678910111213141516171819#parent{ position:relative;}#left{ width:100px; position:absolute; top:0; left:0;}#center{ margin-left:100px; margin-right:100px;}#right{ width:100px; position: absolute; right:0; top:0;} flex方式 123456789101112#parent{ display: flex;}#left{ width:100px;}#center{ flex:1;}#right{ width:100px;} Grid方式 1234#parent{ display: grid; grid-template-columns: 100px 1fr 100px;}","categories":[{"name":"前端","slug":"前端","permalink":"http://yoursite.com/categories/前端/"}],"tags":[{"name":"布局 css","slug":"布局-css","permalink":"http://yoursite.com/tags/布局-css/"}]},{"title":"学习React","slug":"学习React","date":"2017-01-14T03:12:17.000Z","updated":"2017-12-14T03:35:54.000Z","comments":true,"path":"2017/01/14/学习React/","link":"","permalink":"http://yoursite.com/2017/01/14/学习React/","excerpt":"","text":"安装 creat-react-app 脚手架安装 npm install -g creat-react-app 初始化安装 creat-react-app hello-react 启动 cd hello-react npm start 使用jsx12345678910111213141516import React,{Component} from 'react'import ReactDOM from 'react-dom'import './index.css'class Header extends Component{ render(){ return( <div> <h1>hello React</h1> </div> ) }}ReactDOM.render( <header/>, document.getElementById(\"root\")) jsx在编译时会变成相应的js对象描述. react-dom负责把这个js对象描述变成dom元素并渲染. Reader方法我们在编写组件时,需要继承react的Component,一个组件类必须实现一个render方法,这个方法返回一个jsx对象,需要注意的是,必须用一个外层的元素把所有内容包裹起来,而不能是几个元素. 表达式插入在jsx中可以插入js表达式,表达式返回的结果会渲染在页面上,表达式用{}包裹,如果包裹的是一个对象,在对象的外面也要加上{}. {}内可以放任何js的代码.不仅仅可以放在标签内部,也可以放在标签属性上. <a className={className}/> 因为class,和for是js关键字,所以在react中用className和htmlFor代替. 条件返回我们可以根据不同的条件返回不同的jsx. 1234567891011121314render(){ const isGood = true return ( <div> <h1> { isGood ?<strong>isGood</strong> :<span>is bad</span> } </h1> </div> )} 如果你想要隐藏一个元素,返回一个null即可. jsx元素变量jsx元素就是js对象,那么jsx元素其实可以像js对象一样赋值给变量,作为函数参数传递或作为函数返回值. 12345678910111213141516171819202122232425//作为变量render(){ const isGood = true const good = <strong>isGood</strong> const bad = <span>isBad</span> return( <div> <h1> {isGood?good:bad} </h1> </div> )}//作为函数参数传递renderGood(good,bad){ const isGood = true return isGood?good:bad}render(){ return( <div> {this.renderGood(<strong>isGood</strong>,<span>isBad</span>)} </div> )} 组件的组合,嵌套和组件树自定义的组件必须用大写字母开头,普通html标签用小写字母开头. 组件之间可以组合,嵌套.就像普通的html标签一样使用就可以,这样组合嵌套最后构成一个组件树,来表示它们之间的关系. 事件监听在react中监听事件甚至需要给监听的元素加上类似于onClick和onKeyDown这样的属性,紧跟的是一个表达式插入,这个表达式返回一个实例方法. 在react中不需要调用浏览器原生的addEventListener进行事件监听,react帮我们封装好了一系列的on*的属性,而且不用考虑不同浏览器之间的兼容问题.如果需要用到事件对象event,在函数中传入e参数即可,react把event对象也做了封装. 一般在某个类的实例方法中,this指的就是这个实例本身,但在react中,调用你传给它方法的时候,并不是通过对象方法的方式调用(this.handleclick),而是通过函数调用(handleClick),所以在事件监听函数中的this是null或undefined.当你想在函数中使用当前实例的时候,需要手动将实例方法bind到当前实例再传给react,这种方式在react中非常常见. 这些on*事件只能用在普通的html元素上,不能用在组件标签上 组件的state和setState一个组件的显示形态是由它的数据状态和配置参数决定的.一个组件可以拥有自己的状态,就像一个点赞按钮,有点赞状态和未点赞状态,并可以再这两种状态之间来回切换.state就是存储这种可变化的状态的.改变状态时不能直接赋值,可以使用setState方法来改变状态.当我们调用setState时,react会更新组件状态,重新调用render方法,然后再把render方法所渲染的最新内容显示到页面上.state方法接受一个对象或函数作为参数如果我们用this.state=XXX,React就没办法知道你修改了组件的状态. state接收对象参数12345678910constructor(props){ super(props) this.state = { name: 'tom', isLicked: false }}handleClick(){ this.setState({isLicked: !this.state.isLicked})} state接收函数作为参数再调用setState时,react不会马上修改state,而是把这个对象放到一个更新队列中,稍后才回从多个队列中把新状态计算合并提取出来合并到state,再触发更新 12345678910111213141516171819//对象作为参数handClick(){ this.setState({count:0})//this.state.count还是undefinedthis.setState({count: this.state.count+1})//undefined+1=NaNthis.setState({count:this.state.count+2})//NaN+2=NaN}//函数作为参数可以接受一个参数作为上次setState的返回值handClick(){ this.setState((prevState)=>{ return {count:0}//0 }) this.setState((prevState)=>{ return {count: prevState.count +1}//0+1=1 }) this.setState((prevState)=>{ return {count: prevState.count+2}//1+2=3 })}//进行3次setState,但组件只会渲染一次.因为react会把所有事件循环中的消息队列中的state合并再渲染. 配置组件的props一个组件可能在不同的地方用到,所以组件要有一定的可配置性.每个组件都可以接收一个props参数,他是一个对象,包含你对这个组件的配置. 组件内部是通过this.props的方式来获取组件的参数,如果this.props有需要的属性就采用,没有的话就默认. 再使用一个组件的时候,可以把参数放在标签中的属性中,所以属性都会作为props对象的键值. 默认配置defaultProps我们可以通过||操作符来实现默认配置,const word = this.props.like || '已赞'React也提供了一种方式defaultProps来配置默认配置. 12345678910class LikeBtn extends Component{ static defaultProps = { like: '取消', unlike: '点赞' } constructor(){ super() this.state = {isLike: false} }} props不可变props一旦传入进来就不能改变.如果我们使用this.props.like='取消'控制台会直接报错. 你不能改变一个组件被渲染时传进来的props,因为如果渲染过程中可以改变会导致组件的显示形态和行为变得不可预测. 但这并不意味这props永远不能修改,组件的使用者可以主动的通过重新渲染的方式把新的props传入到组件中. 1234<div> <LikeBtn like={this.state.like}> <button onClick={this.handleClick.bind(this)}><button> </div> 在这里,我们把state中的数据传给props,但我们点击按钮时,我们使用setState改变state的值,并导致页面重新渲染,改变后的state会传给新的props. state VS propsstate的主要作用是用于组件保存,控制,修改自己的状态.state在组件内部初始化,可以被自身修改,但不能被外界访问和修改.可以把state当做一个局部的只能被自身控制的数据源.通过this.setState进行更新,该方法会导致组件重新渲染. props主要作用是可以传入参数来配置该组件,组件内部无法控制和修改,除非外部主动传入新的props,否则组件的props永远保持不变. 一个组件的state中的数据可以传给子组件的props,一个组件也可以使用外部传入的props来初始化自己的state.但他们职责非常清晰state是让组件控制自己的状态,props是让外部对组件自己进行配置,尽量少的用state,尽量多的使用props 无状态组件12345678const HelloWorld = (props)=>{ const sayHi = (event)=>{ alert(\"helloWorld\") } return( <div onClick={sayHi}>helloWorld</div> )} 以前的一个组件时通过继承Component来构建,一个子类就是一个组件,而用函数式编写方式是一个函数就是一个组件,你可以和之前使用使用该组件.不同的是,函数式组件只能接受props而无法和类组件一样在constructor里面初始化state.函数式组件就是一种只接受props和提供render方法的类组件. 渲染列表数据渲染存放jsx元素的数组1234567891011render(){ return( <div> {[ <span>1</span>, <span>2</span>, <span>3</span> ]} </div> )} 如果你往{}里放一个数组,react会把数组中的元素依次渲染出来. 使用map渲染列表数据123456789101112131415161718192021222324252627282930313233343536373839const users = [ {userName:'tom',age:21,gender:'male'}, {userName:'jerry',age:23,gender:'male'}, {userName:'lily',age:41,gender:'male'}, {userName:'lucy',age:31,gender:'male'},]render(){ const userEle = []//保存渲染后的jsx数组 for(let user of users){ userEle.push( <div> <span>{user.userName}</span> <span>{user.age}</span> <span>{user.gender}</span> </div> ) } return( <div>{userEle}</div> )}//但我们一般不会手动写循环来构建jsx结构,而是用es6 的map方法render(){ return( <div> { users.map((user,index,arr)=>{ return( <div> <span>{{user.userName}}</span> <span>{{user.age}}</span> <span>{{user.gender}}</span> </div> ) }) } </div> )} 然后你会发现,react报错了,因为对于用表达式套数组罗列到页面上的元素,都要为每个元素加上key属性,这个key必须是每个元素的标识 状态提升在编写组件时,当有别的组件依赖或影响某个组件的某个状态state时,我们通常将这种组件之间共享的状态交给组件最近的公共父节点保管,然后通过props把状态传递给子组件,这样就可以在组件之间共享数据了.这种方式在React中被称为状态提升. 如果这个公共的分组件只是组件树下很小的一个子树,我们需要一直把状态提升上去,一旦发生提升,就需要修改原来保存状态以及传递数据的所有代码,这种无限制的提升并不是一个好的方案. 如何更好的管理被多喝组件依赖的状态?React并没有提供更好的解决方案,我们可以引入Redux状态管理工具来帮助我们解决这种共享状态.对于不会被外界依赖和影响的状态,一般只保存在组件内部即可,不需要做提升. 挂载阶段组件的生命周期我们来看看下面这段代码发生了什么 123456789101112ReactDOM.render( <Header/>, document.getElementById('root'))//1.实例化一个Headerconst header = new Header(props,children)//2.调用header.renderconst headerJsx = header.render()//3.构建真正的DOM元素const headerDom = createDOM(...)//4. 把DOM元素追加到页面上document.getElementById('root').appendChild(headerDOm) 上面这个过程称为组件的挂载,这是一个从无到有的过程 React为了更好的掌握组件的挂载过程,提供了一系列等生命周期函数.包括了两个挂载函数. componentWillMount和componentDIdMount.当我们在页面渲染后删除了某个元素后,也有对应的函数componentWillUnmount. 他们之间的顺序为 1. constructor (指向prototype对象所在的构造函数,关于组件自身状态的初始化) 2. component will mount (组件将要挂载,一般组件启动的动作,包括ajax数据的拉取,设置定时器等等在此进行) 3. render (返回jsx元素) 4. component did mount (组件已经挂载,当组件的启动工作依赖dom时,例如动画,就可以放在这里.) 5. component will unmount (组件将要移除,在组件销毁时清除该组件定时器和其他数据清理工作) 更新阶段的组件生命周期除了挂载阶段,还有一种更新阶段.setState导致react重新渲染组件就是一个组件的变化过程. shouldComponentUpdate(nextProps,nextState): 你可以通过这个方法控制组件是否重新渲染,如果返回false就不重新渲染,该生命周期在性能优化上非常有用. componentWillReceiveProps(nextProps):组件从父组件接收到新的props之前调用. componentWillUpdate():组件重新渲染之前调用. componentDIdUpdate():重新渲染后调用. ref 和 React 中的 DOM 操作React中我们很少和打交道,有一系列的on*方法帮我们进行事件监听,我们不再需要调用addEventListener的DOM API,我们通过setState重新渲染组件,渲染时把新的props传给子组件达到页面更新效果,而不再借用jQuery进行页面更新. 但React并不能满足所有的DOM操作,比如进入页面自动focus到某个输入框,.比如你想获取某个元素的尺寸在做后续动画等等.所以它提供了ref属性帮助我们获取已经挂在的dom节点,你可以给某个JSX元素加上ref属性. <input ref={(input)=>{this.input = input}}> 我们给input加了一个ref属性,该属性是一个函数,该元素在页面上挂载完毕后调用这个函数,并把这个挂载后的dom节点传给这个函数.我们把元素赋值给组件实例的一个属性,这样就可以通过this.input获取这个DOM元素. 如果给组件挂载ref,那么我们获取的是这个组件在react内部初始化的实例,这并不常用,不建议这样做. props.children 和容器类组件1234567891011121314151617ReactDOM.render( <Card> <h1>I'm H1</h1> <div>I'm Div</div> </Card>, document.getElementById('root'))class Card extends Component{ render(){ return{ <div> {this.props.children} {this.props.children[0]} </div> } }} 在使用自定义组件时,可以再组件内部嵌套jsx结构.嵌套的结构可以再组件内部通过props.children获取到,这种组件编写方式在编写容器类型的组件当中非常有用,而在实际React项目中,我们几乎每天都需要用这种方式编写组件. dangerouslySetHTML 和 style 属性#####dangerouslySetHTML 出于安全因素(XSS攻击),React会把所有表达式插入的内容都自动转义.类似于jQuery的text(). 12345const header = '<h1>helloWorld</h1>'<div> {header} </div>//因为react的自动转义,并不会渲染<h1>元素,而是显示文本形式 如何做到动态设置HTML效果呢?我们可以给元素设置一个dangerouslySetHTML属性传入一个对象,这个对象的__html属性值就相当于innerHTML,就可以动态渲染元素结构了. 1234<div dangerouslySetHTML={{__html:'<h1>helloworld</h1>'}} className=\"container\"><div> 之所以搞这么复杂是因为设置这个属性可能会导致跨站脚本攻击,不必要的情况就不要使用. style普通DOM元素中的style 1<div style=\"font-size:14px;color:red;\"><div> React中需要把css属性变为对象再传给元素 1<h1 style={{fontSize:'14px',color:'red'}}></h1> style接收一个对象,里面是css属性键值对,原来css带’-‘的属性都需要换成驼峰命名法.我们可以用props或者state中的数据生成样式对象再传给元素,再用setState修改样式,非常灵活. 12<h1 style={{fontSize:'14px',color:this.state.color}}></h1>this.setState({color:'blue'}) PropTypes和组件参数验证React提供一种机制,可以给组件的配置参数加上类型验证.我们需要安装React提供的第三方库prop-types npminstall --save prop-types 12345678910import React,{ Component } from 'react'import PropTypes from 'prop-types'class Card extends Component{ static propTypes = { text: PropTypes.string.isRequired }static defaultProps = { ...}} PropTypes提供的参数有:array,bool,func,number,object,string,node,element… react规范组件和方法命名1. static开头的类属性,如`defaultProps`,`propTypes` 2. 构造函数,constructor 3. getter/setter 4. 组件生命周期 5. _开头的私有方法 6. 事件监听方法,handle** 7. render*()表示不同render()内容的函数 高阶组件高阶组件就是一个函数,传给它一个组件作为函数的参数,它返回一个新的组件. 1234567891011121314151617import React,{Component} from 'react'export default (OldComponent,name)=>{ class NewComponent extends Component{ constructor(){ super() this.state = {data:null} } componentWillMount(){ let data = localStorage.getItem(name) this.setState({data}) } render(){ return <OldComponent data={this.state.data}></OldComponent> } } return NewComponent} 怎么使用这个高阶组件呢? 12345678import NewComponent from './NewComponent'class InputName extends Component{ render(){ return <input value={this.props.data}> }}InputName = NewComponent(InputName,'username')export default InputName 其实高阶组件就是为了组件之间的代码复用.组件可能有着相同的逻辑,把这些逻辑抽取出来,放在高阶组件里进行复用.高阶组件内部包装的组件和被包装的组件通过props传递数据. contextcontext(上下文)是React中一个比较特殊的东西.某个组件只要往自己的context里面放一些状态,这个组件下的所有子组件都可以直接访问而不用通过中间组件一层层传递,它的父组件则不能访问到. context打破了组件之间通过props传递数据的规范,增强了组件间的耦合性.就像全局变量一样,每个组件都能随意访问和修改,这会让程序运行不可预料. 一些第三方状态管理的库就是充分利用了这种机制给我们提供了极大地便利,所以我们一般不手写context,也不要用它,需要时用这些第三方的应用状态管理库即可. 本文参考胡子大哈的React小书,详情请点击","categories":[{"name":"前端","slug":"前端","permalink":"http://yoursite.com/categories/前端/"}],"tags":[{"name":"前端 React","slug":"前端-React","permalink":"http://yoursite.com/tags/前端-React/"}]},{"title":"用NodeJS写一个爬虫","slug":"用NodeJS写一个爬虫","date":"2016-12-05T07:45:03.000Z","updated":"2017-12-05T07:51:45.000Z","comments":true,"path":"2016/12/05/用NodeJS写一个爬虫/","link":"","permalink":"http://yoursite.com/2016/12/05/用NodeJS写一个爬虫/","excerpt":"","text":"简介用NodeJS下一个爬虫,用来获取简书首页的文章,然后保存以txt文件的形式保存在本地,数量为20篇. 初始化项目 npm init || yarn init 安装依赖 npm install superagent cheerio —save || yarn add superagent cheerio 页面数据下载123456789101112131415161718192021const fs = require(\"fs\")const request = require(\"superagent\")const cheerio = require(\"cheerio\")let reptileUrl = \"http://www.jianshu.com/\"request .get(reptileUrl) .end(function(err,res){ if(err){ } else{ let $ = cheerio.load(res.text,{decodeEntities:false}) //每页有20篇文章,找到标题及其href的值 $(\"#list-container .note-list li\").each(function(index,value){ let url = $(v).find(\".title\").attr(\"href\") //url即文章的链接,需要和reptileUrl拼接起来 getContent(url) }) } }) 获取文章内容,保存在本地1234567891011121314151617181920212223242526272829303132function getContent(url){ let adress = reptileUrl + url request .get(adress) .end(function(err,res){ if(err){ }else{ let $ = cherrio.load(res.text,{decodeEntities:false}) //文章标题 let title = $(\".article .title\").text() //文章内容 let content = '' $(\".article .show-content p\").each(function(i, v, a) { content += $(v).text(); }) //要写入的数据 let data = { title: title, content: content } //本地保存 fs.writeFile('../data/' + title + '.txt', JSON.stringify(data), 'utf-8', function(err) { if (err) { } else { console.log(\"It's OK !\") } }) } })}","categories":[{"name":"后端","slug":"后端","permalink":"http://yoursite.com/categories/后端/"}],"tags":[{"name":"node 爬虫","slug":"node-爬虫","permalink":"http://yoursite.com/tags/node-爬虫/"}]},{"title":"深入理解Git 工作流","slug":"深入理解Git-工作流","date":"2016-06-08T01:46:52.000Z","updated":"2018-06-08T01:52:43.000Z","comments":true,"path":"2016/06/08/深入理解Git-工作流/","link":"","permalink":"http://yoursite.com/2016/06/08/深入理解Git-工作流/","excerpt":"","text":"git 工作流集中式工作流集中式工作流以中央仓库作为项目所有修改的单点实体。相比SVN缺省的开发分支trunk,Git叫做master,所有修改提交到这个分支上。本工作流只用到master这一个分支。要发布修改到正式项目中,开发者要把本地master分支的修改『推』到中央仓库中。这相当于svn commit操作,但push操作会把所有还不在中央仓库的本地提交都推上去。 示例 初始化一个空仓库 git init --bare /XX.git 员工A和B克隆中央仓库 git clone /xxx.git A开发功能 Git status, Git add , Git commit这些命令都是在本地提交,可以 反复操作多次,不用担心中央仓库。 B开发功能 Git status, Git add , Git commit A 发布功能 git push origin master origin是 A 在科隆仓库时git创建的远程中央仓库别名。master参数告诉git推送的分支。 B 发布功能 git push origin master时 B的本地历史和中央仓库有分歧,提交失败。如果要避免这种情况,B要先 pull A的更新到他的本地仓库合并上他的本地修改,再push。 B在 A的提交之上 rebase。 B用 git pull 合并上游的修改到自己仓库,类似于SVN的 update。命令如下:git pull -rebase origin master。 –rebase告诉git 把B的提交移到中央仓库的master分支顶部。如果你忘记这个选项,pull仍然可以完成,但是每次pull操作要同步中央仓库中别人修改时,提交历史会以一个多余的’合并提交‘结尾。对于集中式工作流最好还是使用 –rebase 选项。 B解决合并冲突。执行git rebase —abort可以回到执行git pull —rebase之前的样子。 B成功发布功能。 git push origin master 功能分支工作流功能分支工作流以集中式工作流为基础,不同的是为各个新功能分配一个专门的分支去开发。这样可以把新功能集成到正式项目前,用pull Request 的方式讨论变更。功能分支应该有一个描述性的名字,比如animated-menu-items或issue-#1061,可以让分支有个清楚的用途。 示例 A在开发新功能之前,建立一个独立的分支。git checkout -b xxx master 这个命令检出一个基于 master 的名为xxx分支,git -b 选项表示如果不存在就新建分支。然后老规矩 git status/add/commit 去吃午饭前,push功能分支到中央仓库是很好的做法,方便备份和开发协作。git push -u origin xxx -u选项表示设置本地分支去跟踪远程对于的分支,设置以后,A就可以使用git push 省去指定推送分支的参数。 A回来之后,完成了整个功能的开发,在合并master之前,A发起了一个pull Request 让团队其他人知道功能已经完成。然后请求合并到master。 B收到pull Request 会查看XXX分支的修改,决定在合并到正式项目前是否还要修改,且通过pull Request和A进行讨论。 A 再次修改,编辑,暂存,提交并push到中央仓库,A的活动都会显示在pull Request上。B如果有需要也可以吧XXX分支拉到本地,自己修改,他的提交也会在pull Request上。 Git flow 工作流Gitflow工作流定义了一个围绕项目发布的严格分支模型。虽然比功能分支工作流复杂几分,但提供了用于一个健壮的用于管理大型项目的框架。Gitflow工作流没有用超出功能分支工作流的概念和命令,而是为不同的分支分配一个很明确的角色,并定义分支之间如何和什么时候进行交互。除了使用功能分支,在做准备、维护和记录发布也使用各自的分支。 历史分支Gitflow工作流使用2个分支来记录项目的历史。master分支存储了正式发布的历史,而develop分支作为功能的集成分支。 功能分支功能分支不是从master分支上拉出新分支,而是使用develop分支作为父分支。当新功能完成时,合并回develop分支。新功能提交应该从不直接与master分支交互。 发布分支一旦develop分支上有了做一次发布的,就从develop分支上fork一个发布分支。新建的分支用于开始发布循环,所以从这个时间点开始之后新的功能不能再加到这个分支上。这个分支只应该做Bug修复、文档生成和其它面向发布任务。一旦对外发布的工作都完成了,发布分支合并到master分支并分配一个版本号打好Tag。另外,这些从新建发布分支以来的做的修改要合并回develop分支。常用的分支约定:用于新建发布分支的分支(develop),用于合并的分支(master),分支命名(relese-X或 relese/X) 维护分支维护分支或是热修复(hotfix)分支用于生成快速给产品发布版本打补丁,这是唯一可以从master分支fork出来的分支。修复完成,修改应该马上合并回master分支和develop分支(当前的发布分支),master分支应该用新的版本号打好Tag。 示例 创建开发分支。为master分支配套一个develop分支。简单来做可以本地创建一个空的develop分支,push到服务器上。git branch develop,git push -u origin develop,以后这个分支会包含项目的全部历史,而master分支只包含部分历史,其他开发者应该克隆中央仓库,建好develop分支跟踪分支。git clone XXX.git,git checkout -b develop origin/develop A 和 B 开始开发新功能。新分支应基于develop。git checkout -b xxx develop.然后 git status/add/commit… A完成功能开发,合并到他本地的develop分支后push到中央仓库。git pull origin develop,git checkout develop,git merge xxx,git push,git branch -d xxx A 开始准备发布,他用一个新的分支做发布准备,这一步也确定了发布的版本号。git checkout -b release-0.1 develop。这个分支是清理发布,执行测试,更新文档等用于改善发布的分支。 A 完成发布,一旦准备好对外发布,A合并并修改master分支和develop分支,删除发布分支。合并回develop分支很重要。发布分支是作为功能开发(develop)和对外发布(master)之间的缓冲。只要合并到master,就应该打好tag方便跟踪。 用户发现bug。 为了处理bug,A从master分支拉出一个维护分支,提交修改解决问题。然后直接合并到master分支,还有,这些信息需要包含到develop分支中。然后安全的删除这个维护分支。 forking工作流forking工作流是分布式工作流,充分利用 git 在分支上的优势,可以安全的管理可靠的开发者,并且可以接受不信任的贡献者的提交.这种工作流不是使用单个服务器的中央仓库代码基线,而是让每个开发者都有一个仓库.每个代码贡献者由两个Git 仓库,一个是本地私有的,另一个服务端公开的.Forking 的优势还有,不需要所有人都能 push代码到中央仓库中,开发者 push 到自己的服务端仓库,而只有项目维护者才能 push 到正式仓库.这也成为了开源项目的理想工作流. 工作方式和其他的 Git 工作流一样, Forking 工作流要先有一个公开的正式仓库存储在服务器上.但一个新的开发者想在项目上工作时,不是直接从正式仓库克隆,而是 fork 正式项目在服务器上创建一个拷贝.这个拷贝作为他个人公开仓库,其他开发者不允许 push 到这个仓库,但可以 pull 到修改.创建自己的服务器拷贝后就可以和往常一样执行 git clone 了.提交本地修改时, 提交到自己的公开仓库中,然后给正式仓库发起一个 pull request, 让项目维护者知道有新的集成了.维护者同意变更后会合并变更到自己本地的 master 中,然后 push master 分支到服务器的正式仓库中. 示例 开发者 fork 正式仓库 开发者克隆自己 fork 出来的仓库 相比用 origin 远程别名指向中央仓库, forking 需要2个远程别名,一个指向正式仓库,一个指向自己的服务端仓库.常见约定使用 origin 作为远程克隆的仓库别名, upstream 作为正式仓库的别名. 开发者开发自己的功能 开发者发布自己的功能 push 代码到自己的公开仓库,发起 pullRwquest 指定要合并的分支.一般是上游( upstream)的 master 分支 项目维护者集成开发者的功能 项目维护者收到 pull request, 他有两种方式,一是直接在 pull request 中查看代码,二是 pull 代码到自己的本地仓库,再手动合并.如果出现合并冲突,需要用第二种方式解决. 开发者和正式仓库做同步 由于正式仓库更新,其他开发需要和正式仓库同步,git pull upstream master. pull RequestPull Request 可以让开发者更方便的进行协作,可以再代码合并之前对修改进行讨论.如果变更有任何问题,团队成员反馈在 PR 中,所有的这些活动都直接跟踪在 PR 中. PR 需要提供4个信息以发起 pull Request: 源仓库,源分支,目的仓库,目的分支. PR 可以和以上的 git 工作流一起使用,基本过程是这样的: 开发者在本地仓库新建一个分支开发功能. 开发者 push 分支修改到公开的仓库中. 开发者通过公开的仓库发起一个 pull request 项目的其他成员 review code ,讨论并修改 项目维护者合并功能到官方仓库中并关闭 pull requset GIt Flow 插件的使用git flow 的简单介绍git flow 是构建在 git 之上的一个组织软件开发活动的模型,是在 Git 之上构建的一项软件开发最佳实践,也是一套使用 Git 进行源代码管理的一套行为规范和简化部分 git 操作的工具.总之, git flow 就是通过在一个项目里划分不同的分支,来实现功能开发, bug 修复,版本发布,以及代码冲突处理等. git flow 把分支划分了几个类别 Master (稳定无 BUG 发布版) Develop (功能开发最前线) Feature (为每一个新功能从 Develop 创建出来的分支) Hotfix (紧急修复 BUG) Release (版本发布,项目上线前的一些全面测试以及准备,同时也肩负版本归档,回滚支持) git flow 插件的常用命令(feature 版本) 切换到相应目录 cd .. 查看分支 git branch -a 切换到相应分支 git checkout develop 初始化 git flow init (-f) 新建分支 git flow feature start xxx develop(基于创建的分支) 发布分支 git flow feature publish 操作 git add . / git status / git commit -m “feat: XXX”/git push 结束分支 git flow feature finish XXX 取得一个发布的新特性分支 git flow feature pull origin XXX 跟踪在 origin 上的特性分支 git flow feature track XXX (release版本) 开始准备 release 版本 git flow release start RELESE [BASE 参数] 创建后发布 git flow release publish RELESE 签出远程变更 git flow release track RELESE 完成release 版本 git flow release finish RELEASE (hotfix 版本) 开始紧急修复 git flow hotfix start version [BASENAME 版本号] 完成紧急修复 git flow hotfix finish VERSION","categories":[{"name":"计算机","slug":"计算机","permalink":"http://yoursite.com/categories/计算机/"}],"tags":[{"name":"git","slug":"git","permalink":"http://yoursite.com/tags/git/"}]},{"title":"[阮一峰ES6标准]学习笔记","slug":"阮一峰ES6标准-学习笔记","date":"2016-03-21T02:43:25.000Z","updated":"2017-12-21T02:47:11.000Z","comments":true,"path":"2016/03/21/阮一峰ES6标准-学习笔记/","link":"","permalink":"http://yoursite.com/2016/03/21/阮一峰ES6标准-学习笔记/","excerpt":"","text":"let let声明的变量只在代码块内有效 不存在变量提升 let声明变量之前,该变量都是不可用的,称为暂时性死区 相同作用域内不允许重复声明同一个变量 const const声明一个只读的常量,一旦声明,常量的值不能改变. const一旦声明变量,必须初始化,不赋值就会报错. const保证的其实是保存变量的内存地址不得改动.对于简单数据类型,值就保存在变量指向的内存地址,等同于常量.对于复合类型的数据,变量指向的地址保存的是一个指针. var和function声明的全局变量,是顶层对象的属性.而let,const,class声明的全局变量不再属于顶层对象. 变量的解构赋值从数组和对象中提取值,对变量进行赋值,这就称为解构. 12let [a,b,c] = [1,2,3];//a=1,b=2,c=3let [a,b,c] = [1,2] //解构不成功变量值为undefined 如果等号右边不是数组(或不是可遍历的解构),会报错. 1let [a] = false;//false不是可遍历解构,报错 解构赋值允许指定默认值 1let [a,b=666] = [1]//a=1,b=666 对象也可以用于解构赋值 1234567let {foo,bar} = {foo:'a',bar:'b'};//foo='a',bar='b'//属性名相同才能取到值let {foo,bar} = {foo:'a',baszz:'b'};//bar为undefined//{foo}其实就是{foo:foo}的简写let {foo:bar} = {foo:'a'}//bar='a',foo未定义//对象解构赋值也可以指定默认值let {x,y=3}={x:1}//x=1,y=3 字符串也可用于解构赋值,因此此时,字符串被转换为一个类似数组的对象. 1let [a,b,c,d,e] = 'hello';//a='h',b='e' 函数的参数也可用于解构赋值. 1234function add([x,y=3]){ return x + y;}add([1,2]); 字符串的扩展ES6为字符串添加了便利器接口,使得字符串可以被for…of循环遍历. ES5有indexOf用来确定一个字符串是否在另一个字符串中. ES6又提供了三种方法: includes():返回布尔值,表示是否找到了参数字符串 startsWith():返回布尔值,表示是否在原字符串的头部 endsWith():返回布尔值,表示是否在原字符串的尾部 这三个方法都提供了第二个参数,表示搜索的位置. 1234let s =\"hello World\"s.includes(\"hello\")//trues.startsWith(\"hel\")//trues.endsWith(\"rld\")//true repeat()方法返回一个新字符串,表示将元字符串重复n次. 1'hello'.repeat(2);//'hellohello' 模板字符串 12let s = 'world'let a = `hello ${s} !`//a='hello world !' ${}里面不仅可以嵌入变量,还可以嵌入函数. 数值的扩展 Number.isFinite()用来检查一个数值是否为有限的(finite) Number.isNaN()用来检查一个数值是否为NaN. 上面两个方法与传统的全局方法isFinite()和isNaN()的区别在于,传统方法会先调用Number()将非数值转换为数值在判断,新的两个方法只对数值有效,非数组一律返回false. Number.parseInt(); Number.parseFloat();将全局方法移植到Number对象上,使语言逐步模块化. Number.isInteger();用来判断一个值是否为整数. 指数运算符,例如2**3===8; 函数的扩展函数参数的默认值 123456789function log(x,y='world'){ console.log(x,y)}log('hello')//hello world//参数是默认声明的,所以不能用let和const再次声明function foo(x=5){ let x = 1;//error const x = 2;//error} rest参数 ES6引入rest参数,用于获取函数的多余参数,rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中.rest参数只能是最后一个参数,否则会报错. 12345678function(...values){ let sum = 0; for(var val of values){ sum += val; } return sum;}add(2,3,5)//10 函数的name属性返回该函数的函数名. 箭头函数 12var f = v=> v;var f = ()=>5;//没有参数或多个参数用()包括,多个函数语句用{} 箭头函数内的this是定义时所在的对象,不是使用时的对象 不可以当做构造函数 不可以使用arguments对象 不可以使用yield命令,不能作为Generator函数 箭头函数中的this的指向是固定的,不可变的. 尾调用就是在函数的最后一步调用另外一个函数. 尾递归函数在最后一步调用自身就是尾递归.尾递归不会发生栈溢出,相对节省内存. 1234function factorrial(n,total){ if(n === 1) return total; return factorial(n-1,n*total)} 函数式编程有一个概念,叫做柯里化,意思就是将多参数的函数转换成单参数的函数形式. 数组的扩展扩展运算符是三个点....它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列. 1console.log(...[1,2,3])//1 2 3 由于扩展运算符可以展开数组,所以不再需要apply方法将数组转为函数的函数了. 12345678//ES5function f(x,y,z){...}var args = [1,2,3]f.apply(null,args)//ES6function f(x,y,z){...}let args = [1,2,3]f(...args) 扩展运算符的应用 复制数组 12345//ES5const a1 = [1,2,3]const a2 = a1.concat();//ES6cionst a2 = [...a1] 合并数组 1234//ES5arr1.concat(arr2,arr3)//ES6[...arr1,...arr2,...arr3] 字符串转为数组 1[...\"hello\"]//['h','e','l','l','o'] 只要是有Iterator接口的对象都可以用扩展运算符转为数组. Array.from()将类似数组的对象和可遍历对象转换为真正的数组. 扩展运算符转换为数组调用的是遍历器接口Iterator,Array.from()不仅可以支持可遍历对象还支持类似数组的对象,既任何拥有length属性的对象都可以通过Array.from转换为数组,而扩展运算符不行. 12345let arrayLike = {'0':'a','1':'b',length:2}//ES5var arr1 = [].slice.call(arrayLike)//ES6let arr2 = Array.from(arrayLike) Array.of()用于将一组值转换为数组.因为Array()和new Array(),由于参数不同导致行为不统一.一个参数指定数组的长度,不少于两个才能组成新数组,这样会导致行为有差异.Array.of()基本可以替代Array()和new Array(). 1Array.of(1,2,3);//[1,2,3] find()用于找出第一个符合条件的数组成员.他的第一个参数是一个回调函数,所有成员依次执行该函数,直到找到第一个为true的成员,没有找到返回undefined.该回调函数一个接受3个参数,分别是value,index,arr. 1234[1,4,-5,10].find((n)=>{n<0})//-5[1,4,-5,10].find(function(v,i,a){ return value > 9})//10 findIndex()与find()用法类似,用于找出符合条件成员的索引,都不符合返回-1.由于数组的IndexOf方法无法识别数组的NaN成员,而find和findIndex弥补了数组indexOf的不足. 123[1,4,-5,10].findIndex(function(v,i,a){ return value === 4;//1}) fill()填充一个数组.接受第二和第三个参数分别为起始位置和结束位置之前. 1['a','b','c'].fill(7,1,2)//['a',7,'c'] entries(),keys(),values()都返回一个遍历器对象,可以用for…of进行遍历.entries()是对键值对的遍历,keys()是对键名的遍历,values()是对值得遍历. 123for(let index of ['a','b'].keys()){ console.log(index);//0 //1} 数组的includes()返回一个布尔值表示数组是否包含给定的值,与字符串的includes()方法类似.另外Map和Set数据结构有一个has方法,需要注意与includes区分.Map的has用来查找key,Set的has用来查找value. 1[1,2,3].includes(3)//true 对象的扩展属性的简写形式 123var foo = {x:x,y:y};//等同于let foo = {x,y} 属性名表达式 ES6允许字面量定义对象时,用表达式作为对象的属性名. 123456let name = 'foo'let obj = { [name]: 'foo', ['a'+'bc']: 123}//{foo:'foo','abc':123} Object.is()用来比较两个值是否严格相等,与===行为基本一致.不同之处在于+0不等于-0,NaN等于NaN 1234+0 === -0;//trueObject.is(+0,-0)//falseNaN === NaN;//falseObject.is(NaN,NaN);//true Object.assign()用于对象合并,将源对象所有可枚举属性复制到目标对象. 如果只有一个参数,直接返回该对象. 同名属性,后者会覆盖前者 Object.assign方法实行的是浅拷贝.如果某个属性的值是对象,那么目标对象拷贝的是这个对象的引用. Object.assign的用途. 为对象添加属性 为对象添加方法(将函数放在空对象中) 克隆对象(和一个空对象合并) Object.setPrototypeOf()设置一个对象的prototype对象,返回参数对象本身,它是ES6整数推荐的设置原型对象的方法. 1234let a = {a:1}let proto = {b: 2}Object.setPrototypeOf(a,proto);//a.b === 2//上面代码将proto对象设置为a对象的原型 Object.getPrototypeOf()用于读取一个对象的原型对象. 1Object.getPrototypeOf(a);//{b:2} super关键字指向当前对象的原型对象. Object.keys()返回一个数组,成员是对象所有可遍历属性的键名. Object.values()返回一个数组,成员是对象所有可遍历属性的值. Object.entries()返回一个数组,成员是对象所有可遍历属性的键值对数组. 对象也可用于解构赋值以及扩展运算符 1234let {x,...y} = {x:1,y:2,z:3}x // 1y// {y:2,z:3}let z = {...y}//{y:2,z:3} Null传导运算符?. 123const first = (msg && msg.body&&msg.body.user || 'default')//使用null传导运算符const first = (msg?.body?.user || 'default') SymbolES6引入了一种原始数据类型Symbol,表示独一无二的值.它是js第七种数据类型,分别是undefined,null,Boolean,String,Number,Object,Symbol 由于symbol不是对象,而是数据类型,所以不能使用new,他是一种类似于字符串的的数据类型. 12345let s = Symbol();let s1 = Symbol();s === s1;//false,Symbol不能参与运算.let obj = {};obj[s] = 'hello'//Symbol作为属性名不能用.运算形式,且不能被遍历到 Set和MapES6提供了新的数据结构Set,它类似于数组,但是成员都是唯一的,没有重复的值. 12//去除数组的重复成员[...new Set(2,3,2,1,2,3)] Set,prototype.constructor: 构造函数,默认就是Set函数 Set.prototype.size: 返回set实例的成员总数 Set的4个操作方法 add(value): 添加某个值,返回set结构本身 delete(value):删除某个值,返回一个布尔值,表示是否成功 has(value): 返回一个布尔值,表示是否为Set成员. clear():清除所有成员. Set的4个遍历方法 keys():返回键名的遍历器 values():返回值得遍历器(默认遍历生成函数) entries():返回键值对的遍历器 forEach():使用回调函数遍历每个成员 ES6提供了Map数据类型,类似于对象,但他的”键”的范围不限于字符串,各种类型的值都可以作为键. Map的实例属性与操作方法: size: Map结构的成员总数 set(key,value):设置键值,返回整个结构 get(key): 读取某个键的值,找不到key返回undefined has(key):返回布尔值,表示某个键是否在当前Map对象中 delete(key):删除某个键,返回布尔值表示是否成功. clear():清除所有. Map结构提供的遍历方法与Set相同. promise对象promise是异步编程的一种解决方案.它接受一个函数作为参数,函数有两个参数resolve和reject有js引擎提供,resolve函数将promise有未完成变为成功,reject由未完成变为失败. promise实例生成后可以用then指定resolved和rejected状态的回调函数,并且then返回一个新的promise实例,所以可以链式调用. 12345678910111213const promise = new Promise(function(resolve,reject){ if(/*成功*/){ resolve(value) }else{ reject(error) }})promise.then(function(value){ //success},function(error){ //error})//一般来说,不要再then中定义rejected状态的回调函数,既then的第二个参数,而应该总是使用catch方法. promise.catch是.then(null,rejection)的别名,指定发生错误是的回调函数. promise.all将多个promise实例包装成一个promise实例.全部成功才成功,有一个失败就是失败. promise.race将多个promise包装为一个,只要其中有一个先改变状态,整个状态就会改变. promise.finally方法用于指定不管promise的最后状态无论怎样都会执行,它接受一个普通的回调函数作为参数,不管怎样都必须执行. Iterator和for…of循环当使用for…of循环某种数据结构时,该循环会自动寻找Iterator接口. 一个对象只要具备Symbol.iterator属性就代表该对象可遍历. 默认调用Iterator接口的场合: 解构赋值let [x,y] = [1,2] 扩展运算符[...arr] yield后面如果跟的是可遍历结构就会调用遍历器接口`yield\\ [2,3,4]` for…of,Array.from()… 遍历器对象除了具有next()函数,还要return()和throw() for..of与其他遍历语法比较. for循环 数组的forEach(无法跳出循环,break,return都不行) for …in(主要是循环对象而设计,不适用于遍历数组) for…of,与for..in一样简洁,可以跳出循环… Generator函数Generator是ES6题提供的异步编程解决方案.可以把它理解为一个状态机,封装了多个状态.还是一个遍历器对象生成函数. Generator函数的特征:function与函数名之间有一个*号,函数体内部使用yield表达式定义不同的内部状态. 12345678910function* helloWorld(){ yield 'hello'; yield 'world'; return 'ending';}var hw = helloWorld();//generator函数调用后该函数并不执行,返回的也不是函数运行结果,而是遍历器对象Iterator.然后调用遍历器对象的next方法使得指针移动到下一个状态.hw.next();//{value:'hello',done:fasle}hw.next();//{value:'world',done:false}hw.next();//{value:'ending',done:true} 由于Generator函数返回一个遍历器对象,调用next才会遍历下一个内部状态,所以其实他是一个可以暂停执行的函数,yield就是暂停标志.next()遇到yield就会暂停后面的操作,并把yield后面表达式的值作为返回的value值,下一次调用next,再继续执行,知道遇到yield或return为止.另外yield表达式在其他地方使用都会报错. next()可以带一个参数,作为上一次yield表达式的返回值. 12345678910111213function* foo(x){ var y = 2*(yield (x+1)); var z = yield (y/3); return (x+y+z);}var a = foo(5);a.next();//{value:6,done:fasle}a.next();//{value:NaN,done:fasle}a.next();//{value:NaN,done:fasle}var b = foo(5);b.next();//{value:6,done:fasle}b.next(12);//{value:8,done:fasle}b.next(13);//{value:42,done:fasle} for…of可以自动遍历Generator函数生成的遍历器对象,并且不再需要调用next方法.但函数return 的值不会再循环中. 在Generator函数中调用Generator函数是没有效果的,这就需要用到yield*表达式,用来达成以上目的.并且任何数据结构只要有Iterator接口,就可以使用yield*遍历 123456function* bar(){ yield 'a'; yield* foo(); yield 'b'; yield* [1,2,3]} async函数ES6引入了async函数,使异步操作更加方便.async函数就是Generator函数的语法糖.它将*替换成async,将yield替换成await,仅此而已. 12345678910111213const gen = function* (){ cosnt f1 = yield readFile('/etc/a.txt') const f2 = yield readFile('/etc/b.txt') console.log(f1.toString()); console.log(f2.toString());}const gen = async function(){ const f1 = await readFile('/etc/a.txt') const f2 = await readFile('/etc/b.txt') console.log(f1.toString()); console.log(f2.toString());} async对generator的改进 内置执行器.async函数的执行与普通函数一样gen() 更好的语义.比起*与yield,语义更清楚. 更广的适用性.async函数的await命令后面可以是promise对象和原始类型的值(但此时等同于同步操作). 返回值是promise对象,而generator返回的是一个Iterator遍历器.而async可以看做多个异步操作包装的promise对象,而await命令就是内部then的语法糖. async函数内部return语句返回的值会成为then方法回调函数的参数 1234async function f(){ return 'helloWorld'}f().then(v=>console,log(v))//'helloWorld' async函数内部抛出错误会导致promise对象变为reject状态.错误对象会被catch方法回调函数接收. 正常情况下await后面是一个peomise对象,如果不是,会转成一个立即resolve的promise for await of 用来遍历异步的iterator接口. classES6引入class的概念,作为对象的模板,通过class关键字来定义类. class其实只是一个语法糖,他的大部分功能ES5都可以做到,只是让对象原型的写法更像面向对象编程语法而已. 123456789101112131415161718192021//ES5function Point (x,y){ this.x = x; this.y = y;}Point.prototype.toString = function(){ return this.x+','+this.y}var p = new Point(1,2)//ES6class Point{ constructor(x,y){ this.x = x; this.y = y; } toString(){ return this.x+','+this.y }}Point === Point.prototype.constructor//truevar p = new Point(); toString是Point类内部定义的方法,它是不可枚举的,这与ES5不一致. 类不存在变量提升 class的静态方法,类相当于实例的原型,所有类中定义的方法都会被实例继承,如果在一个方法前加上static关键字就表示该方法不会被实例继承,而是直接通过类来调用,这就成为静态方法.静态方法中的this指的是类而不是实例. 12345678class Foo(){ static hello(){ return 'helloWorld' }}Foo.hello()//'helloWorld'var h = new Foo();h.hello()//报错typeError... 父的静态方法可以被子类继承 123456789101112131415161718192021class Foo(){ constructor(x,y){ } static hello(){ return 'helloWorld' } toString(){ ... }}class Bar extends Foo(){ constructor(x,y,z){ super(x,y)//调用父类的constructor(x,y) this.z = z; } toString(){ return this.z +','+super.toString }}Bar.hello()//'helloWorld' super关键字表示父类的构造函数,用来新建父类的this对象. 子类必须在constructor方法中调用super方法,否则新建实例会报错,这是因为子类没有自己的this对象而是继承父类的this对象,然后对其加工,不调用super()子类就得不到this对象. 如果子类没有定义constructor,这个方法会被默认添加,super也会默认添加.只要调用super才能使用this关键字. 修饰器修饰器函数用来修改类的行为,是对一个类进行处理的函数,修饰器函数的第一个参数就是说要修身的目标类.如果觉得一个参数不够用,可以再修饰器外再封装一层函数. 123456789@testableclass MyTest(){ ...} function testable(target){ target.isTest = true;//静态属性 target.prototype.isOk = false;//实例属性 } MyTest.isTest // true 修饰器实在代码编译时发生的,这意味着修饰器本质就是编译时执行的函数. Moduleexport 用于规定模块的对外接口. 1234567export var a = 'a'export var b = 'b'export {a,b}export default{ a, b} import 用于输入其他模块提供的功能. 123import {a,b} from './xxx'import a as A from './xxx'import * as num from './xxx' 如果import要取代Node的require方法就形成了障碍,因为require是运行时加载模块,而import无法取代require的动态加载功能.CommonJS输出的是一个值得拷贝,而ES6模块输出的是值得引用. 浏览器加载ES6模块,也使用<script>,但要加入type=’module’属性告诉浏览器这是一个ES6模块. 1<script type=\"module\" src=\"./foo.js\"></script>","categories":[],"tags":[]}]}