Code Monkey home page Code Monkey logo

blog's People

Contributors

pramper avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

blog's Issues

JavaScript单线程和异步机制

随着对JavaScript学习的深入和实践经验的积累,一些原理和底层的东西也开始逐渐了解。早先也看过一些关于js单线程和事件循环的文章,不过当时看的似懂非懂,只留了一个大概的印象:浏览器中的js程序z执行是单线程的。嗯,就这么点印象。当时也有些疑问:既然是单线程的,那异步调用是怎么实现的?计时器是靠谁来计时的,这单线程总不能一边执行程序一边计时吧?那些耗时的I/O操作为啥没把线程阻塞,不是说好的单线程么?相信很多不了解JavaScript单线程的同学也有过类似的疑问。

今天看了不少相关的资料,就详细地总结一下,希望能帮到大家,同时自己也梳理一下知识点,理解是一回事,能写下来是另一回事。好了,开始正文。

一、JavaScript单线程

在浏览器的一个页面中,该页面的JS程序只有一个线程,故曰单线程。因为是单线程,所以程序的执行顺序就是从上到下依次执行,同一时间内只能有一段代码被执行。那为什么不用多线程,这样不是更能充分利用CPU,提高效率么?

早期的网页内容非常简单,单线程足以应付,所以在设计之初,估计设计者就没考虑使用多线程。另外,JavaScript主要用来处理用户与页面产生的交互,以及操作DOM;如果以多线程的方式来操作DOM,一个线程要求删除该DOM,另一个要求修改DOM样式,那么浏览器该听谁的?这大大增加了程序设计的复杂度,本来前端都不好学,不是么~~~简简单单多好。

虽然JavaScript是单线程的,可是浏览器内部不是单线程的。你的一些I/O操作、定时器的计时和事件监听(click, keydown...)等都是由浏览器提供的其他线程来完成的。

如果想利用多线程处理一些耗时较长的任务,可以使用HTML5提出的Web Worker。

二、任务队列和事件循环

提到异步机制,不得不说到任务队列和事件循环,这是理解JS异步机制的要点。

首先理解浏览器的并发模型,来看下面这个图:

左边的栈存储的是同步任务,所谓同步的任务就是那些能立即执行、不耗时的任务,如变量和函数的初始化、事件的绑定等等那些不需要回调函数的操作都可归为这一类。右边的堆用来存储声明的变量、对象。下面的队列就是任务队列,一旦某个异步任务有了响应就会被推入队列中。如用户的点击事件、浏览器收到服务的响应和后面提到的setTimeout插入的事件。每个异步任务都和一个回调函数相关联。

一个js程序的单线程用来执行栈中的同步任务,当所有同步任务执行完毕后,栈被清空,然后读取任务队列中的一个待处理任务,并把相关回调函数压入栈中,单线程开始执行新的同步任务,执行完毕。

单线程从任务队列中读取任务是不断循环的,每次栈被清空后,都会在任务队列中读取新的任务,如果没有新的任务,就会等待,直到有新的任务,这就叫任务循环。因为每个任务都由一个事件所触发,所以也叫事件循环

三、异步机制

有了上面两节做铺垫,理解异步机制就容易多了。拿ajax来说,当页面的单线程执行xhr.send()之后,对于页面来说发送任务已经完成了。怎么发送,那是浏览器的事,和单线程无关;什么时候响应,这事说不准。为了及时地得到响应的内容,在单线程中注册相应的事件就好xhr.onreadystatechange = fn() {...}。注册之后,浏览器会在内部的其他线程中自动地帮我们监听该事件。直到该事件被触发,浏览器会在任务队列中添加一个任务等待该单线程执行。

四、定时器

setTimeout的作用是在间隔一定的时间后,将回调函数插入任务队列中,等栈中的同步任务都执行完毕后,再执行。因为栈中的同步任务也会耗时,所以间隔的时间一般会大于等于指定的时间。

setTimeout(fn, 0)的意思是,将回调函数fn立刻插入任务队列,等待执行,而不是立即执行。看一个例子:

setTimeout(function() {
    console.log("a")
}, 0)

for(let i=0; i<10000; i++) {}
console.log("b")

// 结果:b  a

打印结果表明回调函数并没有立刻执行,而是等待栈中的任务执行完毕后才执行的。栈中的任务执行多久,它就得等多久。

五、总结

JavaScript单线程和其异步机制就如上所述。所谓的单线程并不孤单,它的背后有浏览器的其他线程为其服务,其异步也得靠其他线程来监听事件的响应,并将回调函数推入到任务队列等待执行。单线程所做的就是执行栈中的同步任务,执行完毕后,再从任务队列中取出一个事件(没有事件的话,就等待事件),然后开始执行栈中相关的同步任务,不断的这样循环。

参考资料

HTML的语义化

HTML的语义化现在很流行,简单介绍一下

什么是HTML语义化?

首先来回答第一个问题,什么是HTML语义化?简单来说就是,让人和机器更容易读懂内容。比如标签<p>我们一看见它就知道里面肯定会有一段文字,而不是一个标题。虽然<div>里也可以直接放置文章内容,并且解析也完全没有问题,但这样能使人一目了然么,能使人快速的理解HTML结构么。我们看见<header>就知道它是web的头部,<footer>是web的底部,这些标签是多么的干净纯粹。当然HTML标签并不能完全表示web页面的所有部分或功能,所以就会有id和class进一步的对元素进行语义描述。还有,HTML5进一步增加了一些语义丰富的标签,如:<nav>等。

HTML语义化有什么意义?

  1. 有助于开发人员对HTML文档的理解,完善的语义化能够使阅读HTML文档的人员高效率地理解文档。
  2. 不单单有助于人,也可以让用户代理、爬虫和搜索引擎更好地分析web页面,从而可以对页面内容做更好地挖掘和处理。

所以,HTML语义化一定是未来的一个发展方向,我们在开发中也尽量遵守语义化。

【转载】如何练习一万小时

转自 同人于野

随着畅销书《异类》的流行,“练习一万小时成天才”这个口号现在是尽人皆知。也许仍然有不少人相信那些不世出的天才必有天生的神秘能力,但科学家通过大量的调查研究已经达成共识,那就是所有顶级高手都是练出来的。不但如此,最近几年的科学进展,人们可能第一次拥有了一个关于怎样炼成天才的统一理论。

好消息是除了某些体育项目对天生的身高和体型有特殊要求之外,神秘的天生素质并不存在,也就是说人人都有可能成为顶级高手。早在20多年以前,芝加哥大学的教育学家 Benjamin Bloom 就曾经深入考察过120名从音乐到数学多个领域内的精英人物,发现他们幼年时代没有任何特别之处。后人的研究更证明,在多个领域内,就连智商都跟一个人能不能达到专家水平没关系。

有个匈牙利心理学家很早就相信只要方法得当,任何一个人都可以被训练成任何一个领域内的高手。为了证明这一点,他选择了一个传统上女性不擅长的项目,也就是国际象棋。结果他和妻子把自己的三个女儿都训练成了国际象棋世界大师,这就是著名的波尔加三姐妹。这个实验甚至证明哪怕你不爱好这个领域,也能被训练成这个领域的大师,因为三姐妹中的一个并不怎么喜欢国际象棋。

而坏消息是成为大师需要长时间的苦练。每天练三小时,完成一万小时需要十年时间,但这只是达到世界水平的最低要求。统计表明对音乐家而言,世界级水平要求的训练时间是十五到二十五年。但最关键的并不是练习的时间,而是练习的方法。

天才是怎样炼成的?**传统思维比较强调一个“苦”字,冬练三九夏练三伏,甚至是头悬梁锥刺股。而近代生活条件越来越好,人们则开始强调一个“爱”字,说兴趣是最好的老师,强调寓教于乐,“哈佛女孩”的家长们纷纷写书,介绍自己的孩子如何一路玩进名校。

很多励志故事和流行的成功学书籍最爱强调的似乎是“顿悟”,认为一个人之所以不成功是因为他没想通,他没有认识到真正的自己!好像一旦一个人顿悟到了真正的自己,他就会非常简单地在本来应该属于自己的领域成为天才人物。一个销售员可能认为真正的自己其实是个小说家,一个医生可能认为真正的自己其实是个画家 — 唯一的问题是他们从来没有写过小说或者画过画 — 但他们认为他们距离“真正的自己”只有一步之遥,一旦尝试了就会爆发天才。

所有这些关于成功学的个人经验和励志故事都不科学。假设一个成功人士做过一百件事,包括参加演讲比赛,衣着有个性,听英文歌曲,最喜欢的颜色是绿色等等,他会非常自得地把这一百件事都写进自传,没准还要加上女朋友的影响。然而其中真正起到决定性作用的可能只有四件事,问题是他和读者都不知道是哪四件。

科学家不信励志故事,他们只相信调查研究。在过去二三十年内,心理学家们系统地调研了各行各业内的从新手,一般专家到世界级大师们的训练方法,包括运动员,音乐家,国际象棋棋手,医生,数学家,有超强记忆力者等等,试图发现其中的共性。他们的研究甚至细致到精确记录一所音乐学院的所有学生每天干的每一件小事,用多少时间做每件事,父母和家庭环境,来比较到底是什么使得那些音乐天才脱颖而出。

现在这项工作已经成熟了。2006年,一本900多页的书,The Cambridge Handbook of Expertise and Expert Performance, 出版。这是“怎样炼成天才”研究的一本里程碑式的学术著作,此书直接引领了后来一系列畅销书的出现,包括格拉德威尔的《异类》,Geoff Colvin 的 Talent is Overrated,和 Daniel Coyle 的 The Talent Code 等等。科学家们不但证明了高手是练出来的,而且通过考察各个领域最好的训练方法的共性,总结了一套统一的练习方法,这就是所谓“刻意练习”(deliberate practice)。

过去多年来,训练方法是不断进步的。比如说作曲,假设一名普通学生使用普通训练方法六年时间能达到的水平,另一个学生使用新的训练方法三年就能达到,那么我们可以说这个新训练方法的“有效指数”是200%。统计表明,莫扎特当时的训练,他的有效指数是130%。而二十世纪的天才也许没有莫扎特有名,但其训练水平都能达到300%到500%!十三世纪的哲学家培根曾经认为任何人都不可能在少于30年之内掌握数学,而现在的学生十几岁的时候已经学到多得多的数学,教学方法进步了。事实上,我们今天在所有领域都比过去做得更好,体育世界纪录被不断打破,艺术家们的技巧也是过去根本无法想象的。

训练方法重要性的另一个体现是“天才”的扎堆出现,比如曾经有一个时期俄罗斯对女子网球,韩国对女子曲棍球,更不必说**对乒乓球的的绝对优势。更进一步,哪怕你这个国家传统上并不擅长这个项目,只要有一名教练摸索掌握了科学训练法,那么他就可以带出一代绝世高手,比如**花样滑冰教练姚滨。人们经常感慨**十多亿人居然找不到11个足球天才 — 如果天才是天生的,那么十多亿人必然足以产生很多天才,但天才是练出来的,而**缺乏有效的练习环境,人口再多也比不上欧洲小国。

刻意练习

首次提出“刻意练习”这个概念的是佛罗里达大学心理学家 K. Anders Ericsson。这套练习方法的核心假设是,专家级水平是逐渐地练出来的,而有效进步的关键在于找到一系列的小任务让受训者按顺序完成。这些小任务必须是受训者正好不会做,但是又正好可以学习掌握的。完成这种练习要求受训者**高度集中,这就与那些例行公事或者带娱乐色彩的练习完全不同。“刻意练习”的理论目前已经被广泛接受,我们可以总结一下它的特点。

1、只在“学习区”练习

科学家们考察花样滑冰运动员的训练,发现在同样的练习时间内,普通的运动员更喜欢练自己早已掌握了的动作,而顶尖运动员则更多地练习各种高难度的跳。普通爱好者打高尔夫球纯粹是为了享受打球的过程,而职业运动员则集中练习在各种极端不舒服的位置打不好打的球。真正的练习不是为了完成运动量,练习的精髓是要持续地做自己做不好的事。

心理学家把人的知识和技能分为层层嵌套的三个圆形区域:最内一层是“舒适区”,是我们已经熟练掌握的各种技能;最外一层是“恐慌区”,是我们暂时无法学会的技能,二者中间则是“学习区”。只有在学习区里面练习,一个人才可能进步。有效的练习任务必须精确的在受训者的“学习区”内进行,具有高度的针对性。在很多情况下这要求必须要有一个好的老师或者教练,从旁观者的角度更能发现我们最需要改进的地方。

只在学习区练习,是一个非常强的要求。一般的学校课堂往往有几十人按照相同的进度学习知识,这种学习是没有针对性的。同样的内容,对某些同学来说是舒适区根本无需再练,而对某些学生则是恐慌区。科学教学必须因材施教,小班学习,甚至是一对一的传授。真正高手训练与其说是老师教学生,不如说是师傅带学徒。

一旦已经学会了某个东西,就不应该继续在上面花时间,应该立即转入下一个难度。长期使用这种方法训练必然事半功倍。2004年的一项研究表明,大学生的学习成绩和他们在学习上投入的总时间没有直接关系,关键是学习方法。

2、大量重复训练

从不会到会,秘诀是重复。美国加州有个“害羞诊所”(The Shyness Clinic),专门帮助那些比如说不敢和异性说话的人克服害羞心理。这个诊所的心理学家不相信什么心理暗示疗法,什么童年回忆之类,他们相信练习。他们认为使人害羞的并不是事情本身,而是我们对事情的观点。怎么治疗恐女症?做法是设计各种不同难度的场合进行对话训练。最初是在房间内集体对话,角色扮演。然后是到直接跑到大街上找陌生美女搭讪要求约会。最高难度是有意在公共场合做出使自己难堪的事情,比如去超市把一个西瓜掉在地上摔坏。

这种把不常见的高难度事件重复化的办法正是MBA课程的精髓。在商学院里一个学生每周可能要面对20个真实发生过的商业案例,学生们首先自己研究怎么决策,提出解决方案,最后老师给出实际的结果并作点评。学习商业决策的最好办法不是观察老板每个月做两次决策,而是自己每周做20次模拟的决策。军事学院的模拟战,飞行员在计算机上模拟各种罕见的空中险情,包括丘吉尔对着镜子练习演讲,都是重复训练。

在体育和音乐训练中,比较强调“分块”练习。首先你要把整个动作或者整首曲子过一遍,看专家是怎么做的。然后把它分解为很多小块,一块一块地学习掌握。在这种训练中一定要慢,只有慢下来才能感知技能的内部结构,注意到自己的错误。在美国一所最好的小提琴学校里,甚至有禁止学生把一支曲子连贯地演奏的要求,规定如果别人听出来你拉的是什么曲子,那就说明你没有正确练习。职业的体育训练往往是针对技术动作,而不是比赛本身。一个高水平的美式足球运动员只有1%的时间用于队内比赛,其他都是各种相关的基础训练。

反过来说如果没有这种事先的重复训练,一个人面对不常见的事件往往会不知所措。统计表明工作多年的医生通过读X光片诊断罕见病症的水**而不如刚毕业的医学院学生 — 因为很少遇到这种病例,而在医学院学到的东西早就忘了。最好的办法其实是定期地让医生们拿过去的旧X光片集中训练,而不是期待在工作中碰到。

3、持续获得有效的反馈

传道,授业,解惑,老师和教练最大的用处是什么?也许对一般人来说小学老师最大的作用是激发了他学习的兴趣,教会了他什么东西,曾经有过传道授业解惑。而真正的高手都有很强的自学能力,对他们而言,老师和教练的最重要作用是提供即时的反馈。

一个动作做得好与不好,最好有教练随时指出,本人必须能够随时了解练习结果。看不到结果的练习等于没有练习:如果只是应付了事,你不但不会变好,而且会对好坏不再关心。在某种程度上,刻意练习是以错误为中心的练习。练习者必须建立起对错误的极度敏感,一旦发现自己错了会感到非常不舒服,一直练习到改正为止。

从训练的角度,一个真正好教练是什么样的?John Wooden 是美国最具传奇色彩的大学篮球教练,他曾经率领 UCLA 队在12年内10次获得 NCAA 冠军。为了获得 Wooden 的执教秘诀,两位心理学家曾经全程观察他的训练课,甚至记录下了他给球员的每一条指令。结果统计表明,在记录的2326条指令之中, 6.9%是表扬,6.6%是表示不满,而有75% 是纯粹的信息,也就是做什么动作和怎么做。他最常见的办法是三段论:演示一遍正确动作,表现一遍错误动作,再演示一遍正确动作。

与外行想象的不同,最好的教练从不发表什么激情演说,甚至不讲课,说话从不超过20秒。他们只给学生非常具体的即时反馈。所有训练都事先进行无比详细的计划,甚至包括教运动员怎么系鞋带。他们仿佛有一种诡异的知道学员在想什么的能力,即使是第一次见面能指出学生在技术上最需要什么。他们是绝对的因材施教,源源不断地提供高度具有针对性的具体指导。

获得反馈的最高境界是自己给自己当教练。高手工作的时候会以一个旁观者的角度观察自己,每天都有非常具体的小目标,对自己的错误极其敏感,并不断寻求改进。

4、精神高度集中

刻意练习没有“寓教于乐”这个概念。曾经有个著名小提琴家说过,如果你是练习手指,你可以练一整天;可是如果你是练习脑子,你每天能练两个小时就不错了。高手的练习每次最多1到1.5小时,每天最多4到5小时。没人受得了更多。一般女球迷可能认为贝克汉姆那样的球星很可爱,她们可能不知道的是很少有球员能完成贝克汉姆的训练强度,因为太苦了。

科学家们曾经调查研究了一个音乐学院。他们把这里的所有小提琴学生分为好(将来主要是做音乐教师),更好,和最好(将来做演奏家)三个组。这三个组的学生 在很多方面都相同,比如都是从8岁左右开始练习,甚至现在每周的总的音乐相关活动(上课,学习, 练习)时间也相同,都是51个小时。

研究人员发现,所有学生都了解一个道理:真正决定你水平的不是全班一起上的音乐课,而是单独练习:

  • 最好的两个组学生平均每周有24小时的单独练习,而第三个组只有9小时。
  • 他们都认为单独练习是最困难也是最不好玩的活动。
  • 最好的两个组的学生利用上午的晚些时候和下午的早些时候单独练习,这时候他们还很清醒;而第三个组利用下午的晚些时候单独练习,这时候他们已经很困了。
  • 最好的两个组不仅仅练得多,而且睡眠也多。他们午睡也多。

那么是什么因素区分了前两个组呢?是学生的历史练习总时间。到18岁,最好的组中,学会平均总共练习了7410小时,而第二组是 5301小时,第三组 3420小时。第二组的人现在跟最好的组一样努力,可是已经晚了。可见要想成为世界级高手,一定要尽早投入训练,这就是为什么天才音乐家都是从很小的时候就开始苦练了。

人脑学习原理

现代神经科学和认知科学认为,几乎没有任何技能是人一出生就会的。哪怕是对简单物体的识别,把东西抓取过来这些简单的动作,也是婴儿后天学习的结果。一个人一出生的时候根本不可能预见到将来自己需要什么技能,基因不可能把一切技能都用遗传的方法事先编程,那样的话太浪费大脑的存储空间。最好的办法是不预设任何技能,只提供一个能够学习各种技能的能力,这就是人脑的巧妙之处。基因的做法是先预设一些对刺激的基本反应和感觉,比如看见好吃的东西我们会饿等等。这些基本的反应需要调动的神经较少。但对于更高级别的技能,比如演奏音乐,需要协调调动很多神经,就必须靠后天学习了。

人的任何一个技能,都是大脑内一系列神经纤维传递的电脉冲信号的组合。解剖表明拥有不同技能的人,其大脑的神经结构非常不同,比如出租车司机大脑内识别方向的区域就特别发达。也就是说与计算机不同,人对于技能的掌握是在大脑硬件层次实现的。

而最近有一派科学家认为,髓磷脂是技能训练的关键,它的作用是像胶皮把电线包起来一样,把这些神经纤维给包起来,通过防止电脉冲外泄而使得信号更强,更快,更准确。不管练习什么,我们都是在练习大脑中的髓磷脂,就好像把一堆杂乱无章的电线被排列整齐变成电缆。直到2000年新技术允许科学家直接观察活体大脑内的髓磷脂之后,髓磷脂的作用才被发现,而且一直到2006年才第一次被在学术期刊上说明。科学家认为髓磷脂是脑神经的高速公路,提高信号传递速度,并且可以把延迟时间减少30倍,总共提速3000倍,甚至可以控制速度,想慢就慢。

人脑之中分布着大量“自由的”髓磷脂,它们观测脑神经纤维的信号发射和组合,哪些神经纤维用的越多,它们就过去把这一段线路给包起来,使得线路中的信号传递更快,形成高速公路。这就是为什么练习是如此重要。

髓磷脂理论可以解释很多事情。比如为什么小孩常会犯错?他们的神经系统都在,也知道对错,只是需要时间去建立起来髓磷脂的高速网络。为什么习惯一旦养成不容易改变?因为所谓“习惯”,其实是以神经纤维电缆组合的形式“长”在大脑之中的,髓磷脂一旦把神经包起来,它不会自动散开 — 改变习惯的唯一办法是形成新习惯。为什么年轻人学东西快?因为尽管人的一生之中髓磷脂都在生长,但年轻人生长得最快。最激进的理论则认为人跟猴子的最显著区别不在于脑神经元的多少,而在于人的髓磷脂比猴子多20%!解剖表明,爱因斯坦的大脑中的神经元数量是平均水平,但他拥有更多能够产生髓磷脂的细胞。

谁愿意练习一万小时?

看了钢琴家朗朗的传记之后,可能很多人会怀疑是否真的应该让孩子接受这样的苦练。实际上,顶级运动员都是穷人家的孩子。不练这一万小时,一定成不了高手,但问题是考虑到机遇因素练了这一万小时也未必成功。

这就是兴趣的作用了。如果说有什么成功因素是目前科学家无法用后天训练解释的,那就是兴趣。有的孩子似乎天生就对某一领域感兴趣。感兴趣并不一定说明他能做好,就算不感兴趣只要愿意练,也能练成。兴趣最大的作用是让人愿意在这个领域内苦练。

不论如何,刻意练习是个科学方法,值得我们把它运用到日常工作中去。显然我们平时中做的绝大多数事情都不符合刻意练习的特点,这可能就是为什么大多数人都没能成为世界级高手。天才来自刻意练习。

斐波那契数列的程序优化

斐波那契数列数列:0 1 1 2 3 5 8 13...,用数学公式表示为:fn(n)=fn(n-1)+fn(n-2), n>1。下面我们用js程序来实现这个算,并一步步优化它。一下程序均在Node.js v6.2.0下实现、测试。

简单的JS程序

实现斐波那契数列的程序非常简单,如下:

var count = 0  // 记录函数被调用的次数
function fib(n) {
    count++
    if(n<2) {
        return n;
    }else{
        return fib(n-1) + fib(n-2);
    }
}
fib(30)  // count=2692537,耗时16.338ms

利用记忆进行优化

可以看到,上面的程序效率之差,简直不能忍。为什么会出现这种情况呢?看下面这张图:

可以看出,执行了许多相同的运算,例如fib(3)执行了两次,fib(2)执行了三次。如果我们对中间求得的变量值,进行存储的话,就会大大减少函数被调用的次数。程序如下:

var count = 0
var tmp = [0, 1] // 存储中间求得的变量值;下标即为n
function fib(n) {
    var result = tmp[n]
    count++
    if(typeof result !== 'number') {
        result = fib(n-1) + fib(n-2)
        tmp[n] = result
    }
    return result
}
fib(30) // count=59, 耗时2.531ms

这是典型的以空间换时间。很明显,函数被调用的次数大大减少,耗时明显缩减。

动态规划

其实,我觉得最好的方法还是动态规划的方法,避免了递归调用。动态规划简单来说,就是把一个问题分解成子问题,然后求子问题的解。在斐波那契数列数列中我们可以这样想,要求fib(30),我求出fib(29)和fib(28)就好;要求fib(29),我求出fib(28)和fib(27)就好;要求fib(28),我求出fib(27)和fib(26)就好...依次把问题逐渐分解最后就是求出fib(2)就好。我求出了fib(2),再求fib(3),再求fib(4)...直到求出fib(30)。程序如下:

var count = 0 // 技术循环次数
function fib(n) {
    var val = [0, 1]  // val[0]= fib[n-2], val[1]=fib[n-1]
    if(n<2) {
        return n
    }
    for(let i=2; i<n; i++) {
        count++
        let tmp = val[0] + val[1]
        val[0] = val[1]
        val[1] = tmp
    }
    return val[0] + val[1]
}
fib(30)  // count=28,耗时:2.552ms

总结

由变量count可以明显的看出使用动态规划性能最好。当n更大时,耗时也会比第二种方法少,可以自己试试。

使用hexo搭建github主页中间遇到的一些问题及总结

主页基于github,使用hexo搭建,主题为maupassant

一些问题

博客搭建过程中遇到了一些问题,现把问题和解决方案归纳如下:

  • hexo deploy出错,无法部署到github,报错信息如下:

    FATAL fatal: remote error: You can't push to git://github.com/pramper/pramper.github.io.git Use https://github.com/pramper/pramper.github.io.git

    意思是你不能使用SSH的方式部署到github,应该使用HTTPS的方式部署。由于使用HTTPS的方式进行部署,每次都得输入账号、密码,很麻烦,所以选择SSH方便些。步骤如下:

    1. 创建一个SHH Key:一般SSH Key在windows系统中存放在C:\Users\你的用户名\.ssh下,一般会有id_rsaid_rsa.pub两个文件,前者是私钥,后者公钥;如果存在着两个文件则不需要创建SHH Key了;如果不存在,在命令行输入ssh-keygen -t rsa -C "[email protected]"-t指定密钥类型,这里使用rsa,-C注释文字;
    2. 在github上添加deploy key,打开博客所在的repo,按下图操作即可:
  • 设置网站缩略图,favicon

    这个花了我快一个小时,其实很简单,是我自己给自己挖了个坑-_-!。方法如下:首先你得有一个大小建议为32*32的ico图标,记住一定要是ico格式的;简单的把png|jpg之类的图片改一个后缀名是行不通的,我就是随便找了张图,改了个后缀,然后浪费了快一小时找错~~~ 可以到比特虫等网站进行格式转换。有了ico图标后,把它放到hexo根目录的source文件夹下即可。

  • 更换网站语言

    在目录themes\maupassant\languages下可以查看当前主题支持哪些语言。在hexo根目录下_config.yml中,language控制着网站所用语言,使用简体中文为language: zh-CN。如果你在hexo server命令下修改的话,必须重启该命令再刷新页面才能看到效果。

  • 新建页面

    hexo搭建完成后默认没有about页面,我们添加about页面:

    1. 打开你主题中的_config.yml文件
    2. 找到menu,添加
    page: about
    directory: about/
    
    1. 输入命令hexo new page about,然后hexo根目录下地source文件夹中会出现一个about文件夹,里面有个index.md文件,编辑此文件即可。
  • 给github添加README

    由于hexo会把文件夹下的素有*.md文件解析为HTMl文件,而github只支持md文件。解决办法就是在blog/source下创建一个README.MDOWN文件,这样hexo无法解析它,而github却依然可以把它解析为md文件。

hexo常用指令,官网

  • hexo init [foldername]:以hexo新建一个网站
  • hexo new [layout] <title>:新建一篇文章
  • hexo s/server:启动本地服务器,访问网址:http://localhost:4000 ;启动本地服务器后,可以进行网站的修改或写文章,不需要重启hexo s命令,刷新页面后,所做修改会自动呈现出来
  • hexo d -g:生成静态文件并部署到github;一定要先生成静态文件才能部署。

参考资料

在Sublime3中使用ESLint

许多刚开始使用Sublime的同学可能都抱怨过为什么没有语法错误提示功能。像WebStorm,输入的语法错误在程序被编译之前就被编辑器提示出来,这样会在一定程度上提高开发的效率。而Sublime似乎不会提示语法错误或是简单的拼写错误,比如误用了中文符号,对象的属性之间忘记用逗号隔开等等一些低级错误;我们必须每次编译后才知晓这些错误,这样会将我们的部分精力和时间浪费在一次次地修改这些低级错误上,从而无法专注于程序本身。

甚至有时候像if(a=1)这样的错误,编译也是完全通过的。我就不只一次碰到过这样的情况,编译完全通过,逻辑也对,就是结果不对。结果debug半天,发现是这样的错误,顿时有种精力都喂了狗的感觉。好在现在有解决办法了(其实早有了,我刚发现而已~~),我们也能像在WebStorm中一样,一边写程序,一边系统自动帮我们检测语法正确性,就像下面这样:

我误用了=,在文件保存时就会被提示,直接顺手改掉就行了,方便的不行。

这些都是ESLint的功劳,它包含了一系列的Rules,通过配置这些Rules,你可以轻松地实时检测语法错误。在使用之前最好把官方教程学习一下。下面说说在Sublime中的配置方法:

  • Sublime集成ESLint需要两个插件SublimeLinterSublimeLinter-contrib-eslint;直接在Package Controll中安装就好
  • 安装ESLint:npm i -g eslint
  • 安装后修改SublimeLinter的配置文件,在Package Settings中打开其Setting-User,将下列代码复制进去:
{
    "user": {
        "debug": false,
        "delay": 0.25,
        "error_color": "D02000",
        "gutter_theme": "Packages/SublimeLinter/gutter-themes/Default/Default.gutter-theme",
        "gutter_theme_excludes": [],
        "lint_mode": "save only",
        "linters": {
            // 新增的
            "eslint": {
                "@disable": false,
                "args": [],
                "excludes": []
            }
        },
        "mark_style": "outline",
        "no_column_highlights_line": false,
        "passive_warnings": false,
        "paths": {
            "linux": [],
            "osx": [],
            "windows": [
                // 这个是你全局安装eslint后eslint.cmd的所在目录
                "C:/Users/Lin/AppData/Roaming/npm/eslint.cmd"
            ]
        },
        "python_paths": {
            "linux": [],
            "osx": [],
            "windows": []
        },
        "rc_search_limit": 3,
        "shell_timeout": 10,
        "show_errors_on_save": false,
        "show_marks_in_minimap": true,
        "syntax_map": {
            "html (django)": "html",
            "html (rails)": "html",
            "html 5": "html",
            "javascript (babel)": "javascript",
            "magicpython": "python",
            "php": "html",
            "python django": "python"
        },
        "warning_color": "DDB700",
        "wrap_find": true
    }
}
  • 关键的一步,配置eslint,最好把官方教程看看,配置出适合自己编程习惯的配置文件再好不过了,我的配置文件内容如下:
{
    "env": {
        "browser": true,
        "es6": true,
        "node": true
    },
    "parserOptions": {
        "sourceType": "module"
    },
    "rules": {
        "no-cond-assign": [2, "always"], //if, while等条件中不允许使用“=”
        "no-constant-condition": 2,
        "no-debugger": 2,   // 程序中不能出现debugger
        "no-dupe-args": 2,  // 函数的参数名称不能重复
        "no-dupe-keys": 2,   // 对象的属性名称不能重复
        "no-duplicate-case": 2,  // switch的case不能重复
        "no-func-assign": 2,
        "no-obj-calls": 2,
        "no-regex-spaces": 2,
        "no-sparse-arrays": 2,
        "no-unexpected-multiline": 2,
        "no-unreachable": 2,
        "use-isnan": 2,
        "valid-typeof": 2,
        "eqeqeq": [2, "always"],
        "no-caller": 2,
        "no-eval": 2,
        "no-redeclare": 2,
        "no-undef": 2,
        "no-unused-vars": 1,
        "no-use-before-define": 2,
        "comma-dangle": [1, "never"],
        "no-const-assign": 2,
        "no-dupe-class-members": 2
    }
}
  • 最后,把配置好的文件放在你项目的根目录中就可以了,项目中所有的子文件也会自动使用该配置文件。

这只是入门级的介绍,ESLint还有很多其他的功能,对JSX也支持,想了解的同学不妨自己去官网学习一下。

如何在github中做在线Demo演示

作为一个前端工程师,平常写一些小demo或项目时会遇到这样的情况:我想把这个html页面呈现给别人看看,又不想把整个工程项目发给他,这样太麻烦,要是有个能在线演示demo的平台就好了。OK,github可以帮我们,一切都不是问题。

把github上的index.html文件作为网页展示我目前知道两种方法,下面道来:

1、随便在github找一个html文件,例如这个,然后在其url地址前加上http://htmlpreview.github.com/?,变成了
http://htmlpreview.github.com/?https://github.com/pramper/Demos/blob/master/JS-Demos/ResponsiveTable/index.html
访问这个地址即可。

不过这种方法有个缺陷,打开浏览器调试工具,会发现莫名多了几个<script>标签。感觉不爽,下面重点说说第二种方法。

2、利用gh-pages,步骤很简单,如下:

  • 创建gh-pages分支:如果一开始是在master分支下工作,工作完成后,输入命令git checkout -b gh-pages,此命令会新建一个gh-pages分支并切换到该分支
  • 推送gh-pages分支到github:输入命令git push origin master:gh-pages,此命令会把
  • 如果你在本地是在master分支下工作,工作完成后,输入命令git push origin master:gh-pages,此命令的意思是把本地master分支的内容推送到远程的gh-pages。如果远程没有gh-pages分支的话,会自动创建该分支
  • 通过访问http://[用户名].github.io/[index.html所在目录],这样就会访问到项目目录下的index.html文件。还是方法一种那个例子,其访问地址为http://pramper.github.io/Demos/Vue-Demos/questionnaire/dist

ps:又知道的了一种更简单的方法,不需要gh-pages分支,是我目前所知最简单的方法:

打开仓库的setting,然后这个设置,简单粗暴!!

【翻译】Vue动态组件

翻译自 Dynamic Components in Vue.js

这篇文章将会教你如何使用Vue中的动态组件。当创建小型的单页应用时,这些小型应用可能并不需要vue-router来控制路由,我们只想简单地切换不同的组件来达到不同的展示效果。

Vue中的动态组件要求你指定一个挂载点,在这个挂载点下你可以动态地切换组件。在这篇教程中,我们将通过几个例子来展示如何使用动态组件。我们会创建一个动态组件,学习使用keep-alive指令参数,最后我们还会在组件的切换之间添加一些过渡效果。下面开始吧:

创建动态组件

假设我们正在为一个Blog做个简单的导航栏,我们想添加两个页面:

  • 一个用于管理已经存在的博客文章
  • 一个用于创建新的博客文章

稍后,我们会组件化这两个页面。现在,让我们从头一步步来吧,我们把这个简单的应用挂载到body下:

new Vue({
  el: 'body'
})

添加导航栏,在头部引入BootStrop 3.3.6

<div class="header clearfix">
  <nav>
    <ul class="nav nav-pills pull-right">
      <li role="presentation">
        <a href="javascript:void(0)">Manage Posts</a>
      </li>
      <li role="presentation">
        <a href="javascript:void(0)">Create Post</a>
      </li>
    </ul>
  </nav>
  <h3 class="text-muted">Admin Panel</h3>
</div>

<div class="container">
  <!-- render the currently active component/page here -->
</div>

运行程序,我们会得到如下图所示的效果:

现在,我们这个小小的应用已经有了一个大概的结构,下面我们开始创建两个组件:一个用于管理已经存在的博客文章,一个用于创建新的博客文章。如果你还不了解如何在Vue中创建组件,建议你看看这个教程,或者看官方的教程,那里已经说得够明白了。

下面创建一个manage-posts组件,并模拟一些文章标题数据:

Vue.component('manage-posts', {
  template: '#manage-template',
  data: function() {
    return {
      posts: [
        'Vue.js: The Basics',
        'Vue.js Components',
        'Server Side Rendering with Vue',
        'Vue + Firebase'
      ]
    }
  }
})

接着为组件manage-posts创建一个模板:

<template id="manage-template">
  <div>
    <h1>Manage Posts</h1>
    <div class="list-group">
      <a v-for="post in posts" href="#" class="list-group-item clearfix">
        {{ post }}
        <span class="pull-right">
          <button class="btn btn-xs btn-info">
            <span class="glyphicon glyphicon-edit"></span>
          </button>
          <button class="btn btn-xs btn-warning">
            <span class="glyphicon glyphicon-trash"></span>
          </button>
        </span>
      </a>
    </div>
  </div>
</template>

效果

目前为止,我们已经有了导航栏和mange-posts组件,下面我们添加create-post组件,并把它们结合起来:

Vue.component('create-post', {
  template: '#create-template'
})

模板:

<template id="create-template">
  <div>
    <h1>Create Post</h1>
    <form class="form-horizontal">
      <div class="form-group">
        <label class="col-sm-2 control-label">Post title</label>
        <div class="col-sm-10">
          <input type="text" class="form-control" placeholder="Post title">
        </div>
      </div>
      <div class="form-group">
        <label class="col-sm-2 control-label">Post body</label>
        <div class="col-sm-10">
          <textarea class="form-control" rows="5"></textarea>
        </div>
      </div>
      <div class="form-group">
        <div class="col-sm-offset-2 col-sm-10">
          <button type="submit" class="btn btn-primary">Create</button>
        </div>
      </div>
    </form>
  </div>
</template>

好了,组件都已准备完毕,下面我们进入本文重点:把这些组件制作成动态组件。

当一个用户点击Manage Posts按钮,我们想让Vue渲染manage-posts组件。同样的,当点击Create Post按钮,渲染create-post组件,

实现动态组件我们需要<component>元素,我们把它当做动态组件的挂载点,然后在该元素上通过is属性指定我们需要渲染的组件。如此,我们可以通过下面的方式渲染manage-post组件:

<div class="container">
  <!-- render the currently active component/page here -->
  <component is="manage-posts"></component>
</div>

效果图如下:

然而,这种硬编码的方式似乎不是所谓地动态,我们目前仍然无法通过点击不同的按钮来展示不同的组件。

聪明的你也许已经想到了Vue的数据绑定。我们只需在data中定义一个名为currentView的属性,然后再对is属性进行数据绑定就行了,很简单:

new Vue({
  el: 'body',
  data: {
    currentView: 'manage-posts'
  }
})
<div class="container">
  <!-- render the currently active component/page here -->
  <component :is="currentView"></component>
</div>

最后,我们再给导航栏的两个按钮添加点击事件,这样用户每次点击即不同的按钮都会改变currentView的值,从而显示不同的组件:

...

<li role="presentation">
  <a href="#" @click="currentView='manage-posts'">Manage Posts</a>
</li>

<li role="presentation">
  <a href="#" @click="currentView='create-post'">Create Post</a>
</li>

...

完整代码

keep-alive

目前我们已经知道了如何创建动态组件,下面我们说说更加重要的keep-alive。

现在当我们每次点击按钮切换组件时,旧的组件就会被销毁而新的组件会被渲染出来。

这样一来就存在一个问题,旧的组件因为被销毁从而丢失了所有的状态,当重新渲染这个组件时不得不重新调用它所需要的API来获取已经发表的文章(这里我们假设是从服务器获取文章)。为了避免这个问题,我们可以使用keep-alive指令参数来把切出去的组件保留在内存中,以保留它的状态或避免重新渲染:

<div class="container">
  <!-- render the currently active component/page here -->
  <component :is="currentView" keep-alive></component>
</div>

在下面,我们可以验证被切出去的组件是否被保存在了内存中。我们可以在create-post组件中输入一些内容,接着切换组件,然后再切换回来。你会发现,我们输入的内容还在。

完整代码

如果你的组件需要调用很多API或者渲染时需要大量的数据,那么这个指令参数会很有用。

组件间的过渡

下面,我们在组件切换间添加一些过度效果。

在组件的挂载点上,增加transition属性,我们使用简单的淡入淡出的过渡效果。

<div class="container">
  <component :is="currentView" transition="fade" transition-mode="out-in"></component>
</div>

添加CSS过渡控制:

.fade-transition {
  transition: opacity 0.2s ease;
}

.fade-enter, .fade-leave {
  opacity: 0;
}

OK了,完整代码

你可能已经注意到了transition-mode="out-in"。这个属性告诉Vue,我们希望旧的组件先淡出,然后新的组件再淡入。否则的话,就会同时出现两个组件,一个淡出一个淡入,看上去相当别扭。

结束语

希望这篇教程能使你学会如何使用动态组件。这篇教程并不是为了说明动态组件可以替代vue-router,vue-router具有更多更有用的特性。对于一些简单的组件切换,我想动态组件可能会更合适。为了构建更好的SPA我觉得你应该学习一下vue-router。

一张图彻底掌握scrollTop, offsetTop, scrollLeft, offsetLeft......

标记的很完善的一张图

补充说明

上面的图已经标记的已经很明显了,我再稍微补充几点:

  • offsetTop, offsetLeft:只读属性。要确定的这两个属性的值,首先得确定元素的offsetParentoffsetParent指的是距该元素最近的position不为static的祖先元素,如果没有则指向body元素。确定了offsetParentoffsetLeft指的是元素左侧偏移offsetParent的距离,同理offsetTop指的是上侧偏移的距离。
  • offsetHeight, offsetWidth:只读属性。这两个属性返回的是元素的高度或宽度,包括元素的边框、内边距和滚动条。返回值是一个经过四舍五入的整数。如下图:

  • scrollHeight, scrollWidth:只读属性。返回元素内容的整体尺寸,包括元素看不见的部分(需要滚动才能看见的)。返回值包括padding,但不包括margin和border。如下图:

  • scrollTop, scrollLeft:图中已经表示的很明白了。如果元素不能被滚动,则为0。
  • window.innerWidth, window.innerHeight:只读。视口(viewport)的尺寸,包含滚动条
  • clientHeight, clientWidth:包括padding,但不包括border, margin和滚动条。如下图:

  • Element.getBoundingClientRect():只读,返回浮点值。这个方法非常有用,常用于确定元素相对于视口的位置。该方法会返回一个DOMRect对象,包含left, top, width, height, bottom, right六个属性:
    • left, right, top, bottom:都是元素(不包括margin)相对于视口的原点(视口的上边界和左边界)的距离。
    • height, width:元素的整体尺寸,包括被滚动隐藏的部分;padding和border参与计算。另外,heigth=bottom-top, width=right-left

参考资料

HTML5中拖曳与拖放的学习

很多场景下都需要拖曳或拖放元素的功能,例如拖曳一个登陆框或提示框等。今天温习了一下传统的拖曳方式,并学习了一下HTML5提供的原生拖放功能,总结了一下。

传统拖放元素的方式

实现步骤如下:

  1. 鼠标选中元素,触发onmousedown事件
  2. 开始拖动,触发onmousemove事件,元素跟随鼠标移动
  3. 释放鼠标,触发onmouseup事件

其实关键就是在移动过程中将鼠标的坐标不断赋值给被拖动的元素,从而达到拖曳元素的效果。

核心实现代码如下,进行了简单的封装,使用时调用drag函数并传入点击的元素和被拖曳的元素即可:

var drag = (function() {
    var flag = false    // 表示是否拖曳
    /**
     * @param  {[element]} bar:点击的元素,如登录框的头部
     * @param  {[element]} target:被拖曳的元素
     */
    function startDrag(bar, target) {
        var innerX, innerY  // 鼠标距bar的左边距和上边距的距离

        bar.addEventListener('mousedown', mousedown, false)
        document.addEventListener('mouseup', mouseup, false)
        document.addEventListener('mousemove', mousemove, false)                    
        //获取target的CSS 
        var targetCSS = document.defaultView.getComputedStyle(target, null)

        function mousedown() {
            flag = true //鼠标点下时,表示才能被拖曳
            innerX = event.clientX - parseInt(targetCSS.left)
            innerY = event.clientY - parseInt(targetCSS.top)
        }

        function mousemove() {
            if(flag) {
                target.style.left = event.clientX - innerX + 'px'
                target.style.top = event.clientY - innerY + 'px'
            }

        }

        function mouseup() {
            flag = false //松开鼠标后,元素无法被拖曳
        }

    }
    return startDrag
})();

完整示例

使用HTML5的原生拖放功能

默认情况下元素是不允许被拖曳的,要想拖曳某元素,在元素添加属性draggable="false"。拖动某元素时,如果拖动的区域不允许放置元素,则光标变为禁止符号(圆环中一道反斜杠);在被拖动元素上依次触发以下事件:

  1. dragstart
  2. drag
  3. dragend

按下鼠标并开始拖动元素时,在被拖动元素上首先触发dragstart事件,然后在拖动期间会持续触发drag事件,松开鼠标触发dragend事件。

当被拖曳的元素被拖动到一个有效放置的目标元素时,目标元素会被依次触发以下事件:

  1. dragenter
  2. dragover
  3. drop或dragleave

被拖曳的元素进入该目标的区域时触发dragenter事件,随后持续触发dragover事件,直到元素被被放下触发dropdragleave事件。虽然所有元素都支持放置目标事件,但默认是不允许放置的,drop事件不会发生。把元素变为有效放置目标的方法是阻止dragenterdragover的默认行为。还有,在Firfox 3.5+中,drop事件的默认行为是打开被放到放置目标上的URL,意思就是如果把图像拖放到放置目标上,页面就会转向图像文件。因此,还要取消drop事件的默认行为。核心代码如下:

var drag = function() {

  var boxCSS = document.defaultView.getComputedStyle(box, null)
  var innerX, innerY  // 鼠标距别拖曳元素左边界和上边界的距离
  /**
   * @param  {[element]} el:被拖放的元素
   * @param  {[element]} target:拖放的目标元素 
   */
  function startDrag(el, target) {

    el.addEventListener("dragstart", function() {
      innerX = event.clientX - parseInt(boxCSS.left)
      innerY = event.clientY - parseInt(boxCSS.top)
    }, false)
    el.addEventListener("dragend", function() {
      this.style.left = event.clientX - innerX + 'px'
      this.style.top = event.clientY - innerY + 'px'
    }, false)
    target.addEventListener("dragenter", function() {
      event.preventDefault()
    }, false)
    target.addEventListener("dragover", function() {
      event.preventDefault()
    }, false)
    target.addEventListener("drop", function() {
      event.preventDefault();
    }, false)
  }

  return startDrag
}()

完整示例

总结

个人感觉还是HTML5提供的原生拖放功能好用,浏览器会自动创建一个被拖动元素的半透明副本跟随光标移动,所以不用一直跟踪鼠标的位置,以改变元素的位置。还有,利用HTML的拖放效果还可以在拖曳元素和目标之间交换数据,修改拖曳效果等等。

参考资料

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.