阅读或在工作使用RxJS的人一定会遇到RxJS中各式各样的操作符(operators)。包括但不限于:
上面这些是最常用的,你可以知道更多新的操作符。
我之前遇到过一种使用Observables中的Observable(嵌套Observable)的情景,我想要获取单流输出。为了解决这个问题,接下来介绍另外的有趣操作符。
flatMap - 它主要将observable序列中的observable序列合并成单个observable序列。
所以,让我们来看看一段代码,看其是如何工作的。我们有一个访问者数组,如下:
let visitors = [ |
现在,我想要这个数组被转换为一个Observable序列,所以它可以像下面一样解决:
let source = Rx.Observable.from(visitors) |
让我们来订阅这个序列:
source.subscribe(x => document.getElementById('flatMap').innerText += x + "\n"); |
视图将会如下所示:
但是我们想解决的是如何使observable序列中的observable能正确处理,所以我们给上述代码做点改变:
let source = Rx.Observable.from(visitors) |
然而我们的视图将会如下面图片一样(不正确地显示):
所以应该如何修复? 那我们就要使用flatMap操作符了:
let source = Rx.Observable.from(visitors) |
现在我们就可以像之前那样简单地订阅,即可得到我们期望的结果:
所以,map和flatMap的区别是什么:
map将方法应用于Observable发射(emit)出的items转换为单个元素的item,而flatmap是:
让我们来使用ASCII珠宝图梳理一下上述的区别,或许更加通熟易懂。
----Namita---Amit---Rohit---Neetika----- //Input Stream |
当需要处理observables中的observable(嵌套)的情况的时候,也可以使用另外的操作符mergeAll,和map搭配使用。这与直接使用flatMap的效果是一样的。 RxJS有着大量的操作符,希望这次学习之旅能帮到我们每一个人…继续快乐地学习。
]]>本文件与《pandoc-markdown测试文件》大同小异,再加上一些比较特殊的情况,比如嵌套列表, 测试在pandoc-markdown下翻译的处理情况是否准确。但为了行文容易阅读,不再包含代码块,可以自行到markdown
格式的文件查看。 【请忽略不准确的翻译,毕竟本文展示的是翻译的显示效果而不是内容~】
这篇博客的目的是为了帮助你确认你的HTML元素是否以合适的方式展示出来。如果你使用了CSS重置样式, 不要忘记重新自定义你的样式。
不知道生成的文本怎么翻译,只认识测试链接 强调文本后面也是一串生成文本。 斜体后面也是一串生成文本。 下划线文本后面也是一串生成文本。 删除效果的文字 后面亦是如此。 那我就念两句诗?gou……zhi
这么长一段文字“Interdum et malesuada fames ac ante ipsum primis…”就只有一种样式,那就是行内代码。比如Sed erat diam
引用文本应该不会有问题吧。短文本我也看不懂机器生成的。
好像“Maecenas ornare arcu at mi suscipit, non molestie tortor ultrices.”并没有什么需要注意的。我在Webstorm里面将其折叠, 而没有空一行或者空两个空格,应该还是在用一个块级元素里面,不会生成多个标签吧?就不知道Github的兼容如何了。
有序列表Item 1
有序列表Item 2
浅嵌套有序列表Item 2.1
无序列表Item 1
无序列表Item 2
浅嵌套无序列表Item 2.1
表格标题1 | 表格标题2 | 表格标题3 |
---|---|---|
表格体1.1 | 表格体1.2 | 表格体1.3 |
表格体2.1 | 表格体2.2 | 表格体2.3 |
表格体3.1 | 表格体3.2 | 表格体3.3 |
这里有一个上角标superscript,这里有一个下角标 subscript,当然还有你我看不懂的胡说八道。 这一句并没有什么特别之处,就有一个cite引用,不信你看~ cite 我真的好想把手放在键盘上胡乱地敲。因为并没有什么需要讲的,但我还是忍了,but I am angry。而下面的这一句NBA,是使用了“acronym”标签, 因为它已经有了title属性,那我们就直接翻译,不做处理。NBA,当然特殊的还有“abbr” 这个缩写标签,也不做处理,Let it go,谁让它们在p标签下面呢. AVE
这一章节是用来测试Hexo自带的标签(tag)插件。更多详情请查看文档
真心无f**k可说,别总是想借用生成文本搞大新闻
为什么只有body部分呢?哦,对哦,人名一般也不需要翻译,nice~这句谚语翻译过来就是:独乐乐不如众乐乐?
震惊!!!DevDocs支持语法高亮!!详情点击 http://devdocs.io
每次的互动都是宝贵的?(好low的翻译)
alert('Hello World!'); |
array.map(callback[, thisArg]) |
.compact([0, 1, false, 2, ‘’, 3]); |
第1,7,8,10行应该被不同于背景色的颜色标记。
const http = require('http'); |
注意:主题的样式应该支持.highlight.line.marked
(建议使用选中颜色或者当前行的颜色)
说好的左醒目引文,你就糊弄我?
一串很长又没有意义的文字,我也可以啊。 ash多哈so防患未然斯蒂芬拉黑色调为urodhfasdf 阿大区物业QWDGUWQWAHEWOEFH阿萨德阿误认为而已发撒旦气温偶点告破i热土未全额uiqwyefiasdfwqeiuiowa三大队立即投入诶阿怒气未tv了关于i二阿桑的期望哦谷热他俩三等奖权威。 就问你怕不怕!!!我先去洗一下脸,键盘好脏。
一个糊弄我就算了,右边的你也来?
一串很长又没有意义的文字,我也可以啊。 ash多哈so防患未然斯蒂芬拉黑色调为urodhfasdf 阿大区物业QWDGUWQWAHEWOEFH阿萨德阿误认为而已发撒旦气温偶点告破i热土未全额uiqwyefiasdfwqeiuiowa三大队立即投入诶阿怒气未tv了关于i二阿桑的期望哦谷热他俩三等奖权威。 就问你怕不怕!!!我先去洗一下脸,键盘好脏。 Ctrl+C,Ctrl+V就是那么简单~
]]>在刚开始学习前端的时候,我根本就没有注意到伪类和伪元素说的是两个不同的东西,毕竟都是:xxx
来表示。在MDN查询document.querySelectorAll()
的API时,跳转到了伪元素
就像 pseudo classes (伪类)一样…
然后就意识到两者的不同,而且document.querySelectorAll()
的中文翻译也搞混了,英文版本的是说querySelectorAll()
的选择器语法不支持伪元素
而不是伪类
。以伪类中的:target
为例子,可以正确返回结果,见下图所示,可见两者还是很容易混淆的。
(有一个小插曲,想吐槽一下MDN。在很久之前就发现了翻译页面的错误,那时也想着改正,但是MDN编辑语法什么的真是orz,后来就放弃了,因为学习成本还算挺高的。一直到写这篇文章的时候,又记起这件事,实在忍不住,改正了翻译,不到100个字,花了我半个多小时…)
那么问题来了,如何区分两者?
在一篇博客中的一张图已经为我们归纳了两者,
但是这并不是正确的打开方式,这只是知其然不知其所以然。
让我们回归W3C规范看看,规范是怎么对伪元素和伪类进行定义的。
The pseudo-class concept is introduced to permit selection based on information that lies outside of the document tree or that cannot be expressed using the other simple selectors. 简单翻译:伪类这个概念的引入,是为了允许(authors)对那些在文档树之外或无法使用简单css选择器表示的信息(ps:个人感觉“文档树之外的状态”和“css选择器表示的元素”会更贴切)进行选择获取
Pseudo-elements create abstractions about the document tree beyond those specified by the document language. For instance, document languages do not offer mechanisms to access the first letter or first line of an element’s content. 简单翻译:在文档语言规定之外,伪元素创建了一个关于文档树的抽象引用。比如,文档语言并未提供获取元素的第一个字或者第一行的机制。
orz,差别在哪?
如果你想看看别人对两者的解读,可以看看stackoverflow这个回答以及评论区的讨论
按照我个人的理解:伪类
侧重描述(descript,not filter)以不同于简单选择器选择存在的元素(比如:first-child
)或元素的特殊状态(比如:hover
)。而伪元素
侧重的是虚拟的元素,比如:first-letter
,就好像真实地存在文档树中一样,但实际上是不存在的,而:first-child
指向的元素是存在于文档树的,这是两者的不同。这大概也是为什么querySelectorAll()
适用于伪类
而不适用于伪元素
的原因吧–因为前者是真实存在于DOM树,但是后者并不是。
这方面的知识是在写这篇文章不久前才接触的。总的来说,伪元素的优先级和标签的优先级是一样的,而伪类的优先级和类以及属性选择器等等的优先级是相同的。
如果就这么简单地介绍完这个小节,那也就不必大费周章了。我们来解读W3C中关于选择器优先级计算的部分~
A selector’s specificity is calculated as follows:
- count 1 if the declaration is from is a ‘style’ attribute rather than a rule with a selector, 0 otherwise (= a) (In HTML, values of an element’s “style” attribute are style sheet rules. These rules have no selectors, so a=1, b=0, c=0, and d=0.)
- count the number of ID attributes in the selector (= b)
- count the number of other attributes and pseudo-classes in the selector (= c)
- count the number of element names and pseudo-elements in the selector (= d)
总的来说就是,每个行内样式(style=""
)以1000
计,每个id选择器以100
计,其他属性选择器和伪类选择器以10
计,标签和伪元素选择器以1
计,而通配符*
以0
计。注意,这里只是提供一个计算的方便,并不是11个类的选择器可以将一个id选择器的样式覆盖掉。
还有一点,HTML行内样式可以被JavaScript的style覆盖,而JavaScript的行内样式可以被!important
的样式覆盖。
官方还给出了一些例子:
* {} /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */ |
这只是选择器Selector的优先级,而样式的确定不仅仅是由选择器的优先级这一个因素来确定,还有层叠次序。
W3C规范的6.4.1 Cascading order小节中有说明最终决定一个元素的样式的步骤:
- Find all declarations that apply to the element and property in question, for the target media type.Declarations apply if the associated selector matches the element in question and the target medium matches the media list on all @media rules containing the declaration and on all links on the path through which the style sheet was reached.
- Sort according to importance (normal or important) and origin (author, user, or user agent). In ascending order of precedence: 2.1. user agent declarations 2.2. user normal declarations 2.3. author normal declarations 2.4. author important declarations 2.5. user important declarations
- Sort rules with the same importance and origin by specificity of selector: more specific selectors will override more general ones. Pseudo-elements and pseudo-classes are counted as normal elements and classes, respectively.
- Finally, sort by order specified: if two declarations have the same weight, origin and specificity, the latter specified wins. Declarations in imported style sheets are considered to be before any declarations in the style sheet itself.
用图形来概括就是:
Declarations in imported style sheets are considered to be before any declarations in the style sheet itself.
英文水平有限的我,看到这句话的时候,一直搞不懂讲的是什么,误以为“import和link导入的样式会被看成是在<style>
之前”。(不知道各位会翻译成什么样子呢哈哈)但是直到看Cascading Styles中讲到:
Note: If the link to the external style sheet is placed after the internal style sheet in HTML, the external style sheet will override the internal style sheet!
决定用例子来试试,看看那句话说的是什么。
从图片中可以看出,样式最终选择的是blue
,因为篇幅问题,大家可以在自己的电脑上试试,验证一下:优先级相同,以<style>
,<link>
的出现顺序来定义声明出现的顺序。
那最后的一句话真正的含义是什么?
我觉得应该是在讲,在同一个样式表中,@import
的规则总是在其他规则的前面。
]]>In CSS 2.1, any @import rules must precede all other rules (except the @charset rule, if present). – https://www.w3.org/TR/CSS2/cascade.html#at-import
在正式开始之前,让我们先看看一个简易版的模块Loader是如何实现的?
感谢@爝神的iWo.js,虽然不一定符合AMD规范,而且有着一些使用限制,比如必须有id,而且路径是相对于最后加载<script>
的baseUrl
而言等等缺陷,毕竟不足100行的代码。但麻雀虽小,五脏俱全,不妨碍我们解读一个加载器的大概思想。
/** |
先来看看最外一层,很熟悉是不是?对,就是IIFE(立即执行函数表达式),在很多插件的实现或者开源代码中很常见。
(function(win, doc, undef) { |
虽然很常见,但还是多说几句吧。
将window
,document
传入是为了提高效率,使得变量查找不需要再沿着作用域链向上查找,还有就是为了在压缩的时候,两者都可以被压缩,达到一定的优化。而undef
这里因为第三个参数没有定义,所以自然获得undefined
,而不传undefined
是因为在一些浏览器中undefined
的可以被修改。
接着,让我们来看看里面的成员都有哪些,有什么作用吧,从上到下分别是
Module
对象,key即为path,也为id,value为对应的Module
对象script
标签对象Module
构造函数factory
的参数,在此用于require(Module)
使用,返回reuqire
的Module
的返回值loadings
,调用loadMod()
加载声明的依赖,即loadings[id]=0loadings[id]=1
),调用loadScript()
,并传入callback
回调<script>
,异步加载模块文件*.js
,监听加载事件(做了兼容处理),加载成功(或失败)后移除标签,重置并调用回调函数callback
new Module()
Module
对象的公共方法compile
Module
对象的factory()
,只不过将_r
作为实参传入,作用自然是执行Module
的factory()
方法,而在_r()
中,核心代码是return mod.exports || (mod.exports = mod.compile());
,这行代码使得依赖模块只执行一次,而不是多次,从而达到优化目的run()
和define()
)挂载到全局对象window中。阅读源码的方法我个人的做法是使用调试的方法,所以自己瞎写了一些例子,让我们先来看看项目的结构。
. |
其中home.html
是我们的测试页面,其实页面什么都没有,只是用于加载script脚本而已,核心代码如下:
<!-- home.html --> |
而iWo.js
的内容就是Loader加载器了,代码已经粘贴在上面。而app.js
是我们业务逻辑代码的入口,为了搞清楚加载流程和执行的顺序,特地增加了好几个console.log()
。
//app.js |
而module2.js
为了能验证其路径的正确处理,特放在mods
目录下面。
//module2.js |
让我们来从app.js
入口梳理一下整个代码执行的流程。
打印loaded app.js file
,说明浏览器已经加载app.js
文件并执行该文件。
mods/module2
的module1
模块,执行iwo.define()
方法,实际上是new出来一个新的Module
对象Module
对象都要将其依赖加入loadings
对象中,并赋值loadings[id]
为0,说明是依赖声明状态。而且,将Module
对象缓存到cache
对象中,并设置loadings[id]
为2,说明是模块声明状态。iwo.run()
方法,执行module1
模块queue.push(path);
–先将当前执行模块压入执行队列,因为要先加载其依赖,方可执行当前模块。utils.addLoading([path]);
–标明当前模块的状态。(PS:但是我个人认为Module
对象的状态已经在new
的时候标注为2
,这里显得多此一举,暂时不知道该步骤为了考虑什么情况。若大家知道,可以在评论区指出,谢谢~)utils.loadDeps();
–加载当前模块的依赖。具体的工作原理是怎么样的?该方法遍历loadings
对象,如果loadings[id]
的值小于1,说明是依赖声明状态,并未加载依赖文件,这时候调用loadMod(id)
方法loadMod(id)
–先是改变loadings[id]
的状态为“依赖加载”,也就是“1”,然后调用loadScript(id,callback)
方法loadScript(id,callback)
–其实就是根据id
构建出src
,动态创建<script>
标签将依赖的模块所在文件异步加载,并监听新创建标签的onload
/onerror
/onreadystatechange
事件,当事件完成后移除创建的标签并且重置script
标签对象的各个变量,最后调用callback()
。这里需要注意的是,onreadystatechange
是为了处理ie下的兼容性,因为在IE9以下版本的IE中script
标签不支持onload
和onerror
,所以使用onreadystatechange
进行替代,但是还有一个问题,重置script
对象下的变量不仅是为了垃圾回收机制,更多的是为了避免某些浏览器(如IE9)触发两次事件。可以查看这篇久远的国外文章进行简单的了解mods/module2.js
加载进来的时候,又一次开始执行iwo.define()
,再一次进入模块定义阶段,初始化mods/module2
模块。mods/module2.js
执行结束后,意味着mods/module2
已经定义好了。这时候就会调用callback()
方法了。callback()
–这个方法的主要任务是调用checkLoading()
检测依赖是否都已经加载进来,如何检测?对,就是利用loadings[id]
的状态值,当所有的loadings[id]
都为2(也就是所有模块文件都加载进来,都定义好了)的时候,就会根据执行队列的模块长度,从第一个开始进行执行。这里有一个妙用,queue.shift()
这样就能保证从头到尾按照顺序执行模块。具体的执行是调用了挂载在Module
原型上的compile()
方法,实质上是其对应模块的factory()
的执行,唯一的差别在于其可以传递require
进行导出结果操作。上面的代码例子执行结果如下图所示:
由一二条打印结果可以看出,模块加载是异步的,而且模块内部的变量无法被外部全局访问,而第3,4条打印结果可以告诉我们,module1
的执行是先加载并定义mods/module2
,但是并没有运行mods/module2
的factory
函数,mods/module2
的factory()
函数真正执行的时机在于require('mods/module2')
,所以从这点上来看,iwo.js
这一个特点更像是CMD规范
的懒执行。
可以看出,一个简单的模块加载器,最起码需要有:
而如果你想了解JavaScript异步加载
的多种加载方式(其中属动态创建DOM节点
最为常用),以及JavaScript延迟执行
的方法,可以查看以下两个链接:
其中第二个文章的分析也不仅仅讲的是加载和执行,干货满满,非常值得一看。
而这里还是要再说明一下,JavaScript延迟执行
与factory函数懒执行
说的是两码事。以iWo.js
为例子,动态创建DOM的方式加载js脚本的时候,当一加载完成就会执行该外部脚本,否则哪里来的define
。而factory函数懒执行
只不过是调用该factory
函数是在require
相应模块的时候。
End.
]]>sea.js
已经不再维护)不敢说对整篇文章说讲的都全部掌握,只求能比之前懵懂的状态前进一点。
一个工具流行,必然是它解决了当前的某个痛点,满足了人们的需求。就好像jQuery,它封装了DOM的常用操作,并处理了浏览器的兼容性,使得人们操作DOM更加方便。而JavaScript模块化工具必然也是解决了某一类问题。
在学习C语言的时候,都能清楚地知道模块化的重要性–代码复用以及高内聚低耦合,不仅是为了可维护也是为了代码的健壮性。而在JavaScript中,解决的是什么?
–当一个项目逐渐复杂,多个人进行开发的时候,难免会出现命名冲突的问题,还有一个就是前端开发并不像后端一样,有专门的工具进行管理依赖,只以script标签的出现顺序来简略地规定文件的依赖关系。所以命名冲突和文件依赖是JavaScript模块化解决的主要问题,它的好处自然是可维护性和复用性。
在sea.js
等工具的出现之前,我们的JavaScript代码是如何组织的?或者说模块化开发的整个演变过程是如何的?
这种方式前期最常见的。
function add(){} |
试想一下,如果全局中也有一个变量和函数同名,会导致什么后果?变量提升的时候,函数声明的优先级是高于变量的,所以会导致函数声明被覆盖(不管这个变量在函数声明之前还是之后都是一样的)
缺点:全局命名空间被污染,很容易造成命名冲突。
我们知道不同的对象可以有不同的成员,包括变量和函数。这样我们可以将变量和函数都挂载在特定的自定义对象下,只能通过对象来引用其内部成员,达到命名空间的效果。
//objBase.js |
优点:减少了全局变量。 缺点:1. 对象的成员都被暴露,可以被外界修改,无私有成员可言。2. 当命名空间过长时,造成记忆负担。
//objBase.js |
优点:1. 减少了全局变量,一定程度上解决了变量命名冲突。2. 可以选择性地将内部成员暴露给外部接口。
让我们为上面的闭包再增强一点,给它注入依赖,并且让它更加易于拓展。
//objBase.js |
优点:高内聚低耦合,根据需要注入不同的依赖。
确实,只要在全局中存在$
变量就可以在匿名函数中使用,但是这样不利于后期代码的维护。因为在维护的后期你很有可能不记得obj
的依赖有哪些,而注入依赖会使得代码更加清晰。
(PS:Hexo-admin插件会莫名丢失draft
中的内容,再也不用该插件了。心累…)
看起来闭包已经足够使用,但是当基础模块objBase.js
需要扩展的时候,在源文件进行修改是不妥当的,因为可能会把新的bug引入原有已经测试通过的模块中,这会导致在原有模块中定位错误相当困难。这也是面向对象中的开闭原则 – 允许在改变它的源代码的前提下变更它的行为。 这就需要我们在扩展模块objExtend.js
等文件中进行拓展。
而这需要用到闭包的高级用法,而且不同需求对应不同的扩展模式。其实说是高级用法,其实还是用的基础知识,但是思想很值得我们去学习。高级用法有好多,还请根据自己的需求进行选择。
//objExtend.js |
需求:将一个模块分离到多个文件,同步加载模块文件。
缺点:需要保证obj
在objExtend.js
加载前已经存在,所以需要同步加载,先加载obj
基础模块。
var obj = (function(myObj,$){ |
需求:将一个模块分离到多个文件,异步加载模块文件(无论基础模块还是扩展模块)。
Tips:其实是应用了||
操作符的短路操作,JavaScript中比较常见。
缺点:松耦合扩展不能和闭包依赖注入
等返回新对象的模块模式进行混合使用,否则会覆盖。试想一下,当返回新对象的*.js
文件后加载,会是怎样的情况?
var obj = (function(myObj,$){ |
需求:将一个模块分离到多个文件,同步加载模块文件,覆盖原有属性或函数并且需要用到原有属性或函数。
缺点:同步加载。
var MODULE = (function (my) { |
以上代码可以在英文原文中找到,使用松耦合扩展
模式对内部私有属性进行维护,从而达到模块内的多个文件访问私有对象。
Any file can set properties on their local variable _private, and it will be immediately available to the others. Once this module has loaded completely, the application should call MODULE._seal(), which will prevent external access to the internal _private. If this module were to be augmented again, further in the application’s lifetime, one of the internal methods, in any file, can call _unseal() before loading the new file, and call _seal() again after it has been executed. This pattern occurred to me today while I was at work, I have not seen this elsewhere. I think this is a very useful pattern, and would have been worth writing about all on its own.
需求:将一个模块分离到多个文件,私有对象模块内跨文件共享。
Tips:可以看到_seal()
是使用了delete
删除属性的操作符,而_unseal()
使用了重新赋值的方法。
缺点:代码冗余。
根据英文原文中的意思,“一个模块的所有文件都可以访问私有属性,提倡模块加载结束后调用_seal()
,而如果需要加载新的模块文件,先调用_unseal()
,加载完成后,再调用_seal()
”,但是由于使用了松耦合扩展
,说明模块加载无法保证顺序,这就意味着_seal
和_unseal
方法都要在该模块的每一个文件中。还有一个问题,“Once this module has loaded completely, the application should call MODULE._seal()“。这没有问题,但是
“If this module were to be augmented again, further in the application’s lifetime, one of the internal methods, in any file, can call _unseal() before loading the new file, and call _seal() again after it has been executed."
这似乎就说不过去了。
因为调用Module._seal()
的时候delete my._unseal;
,会失去_unseal
的引用,在新增加模块文件之前,自然无法调用Module._unseal()
,导致出错。而且跨文件访问私有属性很难管理。我找到了作者在评论讨论的一番话,当时的内心Orz…
I’ve never actually used it, which is probably a good sign that it’s not useful. I was mostly just trying to prove that you COULD do something like that with the Module Pattern, and to show the flexibility of JavaScript.
文章挺久远的了,但是不妨碍我们学习其思想:)
后端开发中有专门的依赖管理软件或者包管理软件,比如Java
中的Maven
和Gradle
,nodejs
中的npm
,而在前端中的前期是如何管理依赖的?
<script>
的出现顺序或者代码出现的顺序。
就好像引入jQuery插件的时候,jQuery
文件一定需要在这插件引入之前引入。这似乎没什么毛病啊,但当一个项目越来越庞大的时候,或者依赖关系出现闭环、相互依赖等复杂关系的时候,就会出现难以维护,依赖难以管理,请求过多等弊端了。
而且要知道,前面的模块模式虽然可以注入依赖,但是模块导出都是存在单个全局变量,有时候我们只是基于一个存在的模块构建另外一个模块,而不是都将其挂载在全局变量中。
PPT的例子如下:
body |
所以我们需要一些工具,比模块模式更加好用,既可以避免全局变量冲突(模块模式的冲突避免得不够彻底),还可以明确代码直接的依赖关系,更重要的是模块声明的方式让编写代码更加舒适。
CMD
和AMD
这部分纯属科普向,RequireJS
和SeaJS
都是历史阶段性的产物,也权当了解即可。
JavaScript 模块化最早是出现在Nodejs
当中,推出了Module/1.0
规范并获得不错的成功后,想继而推广到浏览器端的时候,出现了分歧,形成了三大流派,大致如下:
如果你想了解这部分历史,请翻阅玉伯大大的前端模块化开发那点历史。
我们在这里简单地探讨一下AMD规范
和CMD规范
两者的实现,即RequireJS
和SeaJS
,有哪些不同。
AMD(Async Module Definition)是RequireJS对模块定义的规范化产出,而CMD(Common Module Definition)是SeaJS对模块定义的规范化产出。所以CMD和CommonJS不是说的同一个东西。这里不展开探讨,也没有意义,只简单地总结AMD和CMD的相同点和代码方式上的差别。
关于第3点,或许看代码例子更容易理解一些。
//AMD |
在一些同时需要AMD和CommonJS功能的项目中,你需要使用另一种规范:Universal Module Definition(通用模块定义规范)。
这种模式的使用方法因为其兼容性好,兼顾了AMD、CommonJS和客户端,在开源类库或者自己实现某个原生插件、功能时比较常见。基本的代码框架如下:
// returnExports.js |
原理就是一个IIFE,并且加上判断,判断是否存在AMD,存在CommonJS,否则将其挂载到全局对象。
如果你想看更多的例子:见官方Repo
前端模块化的重要性逐渐显现出来,ECMAScript在ES2015也就是ES6中引入了模块的功能。
ES6默认一个文件一个模块,也就是说,在一个模块文件里面声明的任何东西都是默认私有的,如果想变为public
,需要使用export
命令。还有很重要的一点,ES6的import是实时只读的。
对于export
命令来说,有两种导出方式,一种是命名导出,一种是默认导出。一般来说,导出多个值的时候使用命名导出,导出一个值比如一个函数,一个类或者一个对象等使用默认导出。
//multiple named export |
//Single default export |
基本的用法如上所示,如果想查看更加高级的用法,比如两种方式混合导出等可以查阅专门的文章或者期待我的ES6学习日记~
好处是它是一个标准,而且实时更新,然而残酷的现实是浏览器支持度全线飘红,不过可以通过Babel
等工具进行代码转换。
AMD和CMD是在前端引入RequireJS
或者SeaJS
的脚本,在线加载解释器并对代码进行编译,从而达到模块化管理的目的。ES6 Module是JavaScript原生支持,但目前浏览器的兼容性不理想,需要通过Bable
转换。
其实除了上述两者之外,JavaScript前端模块化还有一种解决方案,是通过Node
和相应的工具,本地预编译,不需要在前端加载解释器,将模块化的代码打包处理(合并和压缩等),而浏览器端的代码引入只需要引入打包处理后的一个文件,比如<script src="app.js"></script>
即可。而这种解决方案目前流行的有两款工具:Browserify
和Webpack
。
( 注意Babel
和Browserify
、Webpack
不是同一类工具,前者用于转换ES6代码,以达到更强的浏览器兼容性,而后者是构建工具,用于压缩合并等功能。 )
Browsers don’t have the
require
method defined, but Node.js does. With Browserify you can write code that usesrequire
in the same way that you would use it in Node.
在Node.js
中,CommonJS是同步载入的,对于浏览器来说并不理想。而Browserify
的作用就是将CommonJS模块(或者纯前端脚本)转换为浏览器可以调用的格式。然后Browserify
通过抽象语法树(AST)来解析代码中的每一个 require
语句,在分析完所有模块的依赖和结构之后,就会把所有的代码合并到一个文件中。它还有一个好处就是,可以让前端JavaScript
模块直接使用npm install
的方式进行安装,比如npm install backbone jquery
安装backbone和它所依赖的jQuery模块。
而如果你想在Browserify
中使用AMD
模块语法,可以看一下这款插件deamdify或者下面的解决方案。
而如果你想使用AMD的语法以及RequireJS
来异步加载模块,可以使用RequireJS optimizer
, r.js
一类的构建工具来合并和压缩AMD的模块。同理,SeaJS
可以使用spm
。
Browserify
的基本用法是:
browserify main.js -o bundle.js |
如果你想详细了解Browserify
的用法、例子,可以查看其官网或者Browserify 使用指南
Rollup is a module bundler for JavaScript which compiles small pieces of code into a something larger and more complex, such as a library or application. It uses the new standardized format for code modules included in the ES6 revision of JavaScript, instead of previous idiosyncratic solutions such as CommonJS and AMD.
从官网的介绍可以看到RollupJS
作为打包工具,有一些优点:
关于什么是Tree shaking
,官方的定义是:
Tree-shaking a.k.a. ‘live code inclusion’ is the process of only including the code that is used. It is similar to dead code elimination but can be more efficient
简单地来说,是一种减少代码冗余的技术,只加载需要调用的代码,而不是加载全部,这样就不怕载入的依赖存在冗余了。
但是很不幸,以上的优点在最新版本v2.2的Webpack
中都已经实现了。
目前主流的前端构建工具,估计属Webpack
是最热的了吧。
虽然Webpack和Browserify都是模块化打包工具,但是两者面向的方面可是大有不同。Browserify
是一个为浏览器环境提供Node模块的模块打包工具,而Webpack
不仅仅是将JavaScipt
纳入模块化的范畴,图片、样式文件等资源也可以进行模块化、打包等,在很多人(包括我)看来,Webpack
不仅仅是JavaScript模块化工具,依赖其强大的插件,也包含了gulp
的大部分功能,可以说是大而全的前端工程化工具,也就是说Webpack 约等于 gulp+Browerify
。当然,也有使用Webpack+gulp
的(将Webpack做打包工具,gulp做自动化工具),这些工具的使用暂时不是很熟悉,就不献丑了。
回到正题,Webpack提供的模块化思想似乎更加的先进,可以使用code splitting
这样的功能,将代码分割成一个个的chunk,然后实现按需加载进行性能优化。
Webpack在JavaScript模块化方面的简单使用和Browserify
很类似。
webpack main.js bundle.js |
如果你想对比Webpack
和Browserify
之间的区别,可以查看文章Browserify VS Webpack - JS Drama和Webpack官方的对比,或者看看Webpack
可以为你提供什么
所以,介绍了那么多的工具,目前(2017/3/10)的最优方案应该是什么?
个人意见是ES6+Webpack2.2(需要安装相关的Babel插件)
或者ES6+RollupJS+gulp等
,至于如何还请根据自己的项目要求~
这篇文章讲了什么?无外乎模块化历史的发展。学到了几种模块模式,懂得了UMD
(之前知道这种写法,但不清楚术语),还见识到了前端发展的迅速。
因为中间hexo-admin
插件抽风了,让我一个下午的写作没有保存。本来一天可以写完的硬是花了我两天的时间来写这篇文件,我能怎么办QAQ我也很绝望啊。讲了那么多,无外乎一些历史和简单的介绍,到底模块化AMD
等内部是如何实现的,内部是怎么样的原理,似乎没有讲到。想接着大致地分析内部的原理,虽然知道异步加载模块的原理是通过动态生成<script>
标签,却怕自己的能力不够分析那么多。
考虑到时间、篇章和能力的限制,暂且到此结束该篇,将其他部分的内容后移:)
JavaScript模块化加载器的大概原理
Webpack的使用和ES6 Module的详细介绍
** 参考资料:**
]]>Hexo 主题使用 YAML
语言作为配置文件,阮一峰老师的博文对这种语言进行了详细的介绍。
YAML 是专门用来写配置文件的语言,非常简洁和强大,远比 JSON 格式方便。
虽然简洁和方便,但是对于习惯了JSON的我来说,还是不大习惯。
这里以ylion
主题中的部分配置为例子,要想学习更多,还请翻阅阮一峰老师的博文,讲的清晰易懂。
|
转换为JSON格式就是:
{ |
再以菜单栏部分为例子:
|
转换为:
{ |
以上都是复合结构的应用。字符串数据类型的应用在ylion主题的应用也是广泛的。YAML
还支持在字符串中插入HTML标记。
cc: |- |
为了在模版引擎部分如EJS中更容易读取到自己所需要的数据,请选择合适的数据类型并组织好自己的配置文件结构!
为什么要在脑海中将 YAML
格式转换为 JSON
格式? 自然是为了方便已经习惯了 JSON
思维的我们,这也是为了能在EJS
的逻辑部分方便获取。
以sidebar部分为例子,代码如下:
<% if(theme.widgets&&theme.widgets.length>0){%> |
如果主题配置文件中的widgets
变量(theme.widgets
)存在且给变量的长度大于0,则对其进行遍历,并将_widget
目录下的对应名字的ejs文件加载进来。
如果获取到undefined或者运行hexo g
命令生成预览的时候出错,很大的可能是你的配置文件出错(比如不适当的缩进)或者你获取的方式不对。
Tips: 利用线上工具对自己的配置进行转换看看,是否获取正确。如果你不想使用该种方法,也可以使用笨方法console.log()
或者console.dir()
,将获取的变量打印在Terminal,看是否准确。
Github Pages 提供给我们的是静态的网页,也就是说,并没有后台管理系统让我们对我们的博客、留言、阅读量等进行线上管理。但是我们可以通过集成第三方服务的方式达到目的。ylion主题中的第三方服务大致上有:
其实集成第三方服务,最主要的也还是看文档(这不是废话吗…)。在实现ylion主题的过程中,属leancloud的集成最为困难,而且坑比较多。所以就以leancloud
的集成为例子作为该部分的例子进行讲解吧。
Leancloud的入门知识请查看官方链接以及JavaScript SDK安装指南,我们制作主题一般是选用CDN的方式。SDK和Leancloud数据存储的初始化大致代码请见ylion主题的相关代码,制作主题阶段可以打开调试日志
localStorage.setItem('debug', 'leancloud*'); |
但在应用发布的时候,删掉该行代码,即可关闭调试日志,以免暴露敏感信息。如果删掉之后,在本地还是会显示的,这时请再清空Local Storage对应的数据即可。
初始化leancloud之后,接下来就是逻辑部分的代码了。但在逻辑部分开始前,让我们先明确几个leancloud需要用到的概念,为了更好的理解,从数据库的角度进行类比,但是这是不严谨的。
应用:为了方便管理不同的应用或者网站或者程序等。不同的应用对应着不同的数据库。
Class: 在官方的说法中,Class是AV.Object的一个实例。个人认为Class对应着数据库中的表,用来存储信息,表与表之间也是可以进行“交互”的。在创建应用的时候,leancloud会为我们初始化一些表,比如_User
、_Role
等,在我们的主题中并不需要用到,就不展开讲了,如果你的应用需要用到这几个Class,还希望自己翻阅文档,leancloud的文档还算比较清晰的了。
属性:对应数据表中的列。
在创建新的应用
后,在存储
的菜单,选择创建Class
,Class名字自己取,但是要在主题中的配置文件中添加(ylion主题自动把其挂载到全局对象中,方便获取),以BlogCounter
为例子,并选择无限制
。(这种方式有风险,后面会讲到如何控制),点击创建Class
即可。
其实在后台初始化地建立列是可以的,但是会显得麻烦,我们把它移到我们的代码中。
var newcounter = new Counter(); |
在这里创建了title
、url
、times
3个属性列,并设置其中的值为未有阅读量文章的相关信息。
说回安全控制,可使用如图所示的方法,这也是官方推荐使用的方法,这样就确保在一定域名下才能访问你的敏感数据。
for (var i = 0; i < arrLength; i++) { |
这里使用遍历是因为在归档页面有多个文章需要查询阅读量。核心代码解释如下:
var query = new AV.Query(className);
新建一个Query对象,而className为你在leancloud后台新建应用的Class对象
。
query.equalTo("url", url);
为查询条件设置。在ylion主题中,以文章的url比如/2017/02/22/制作Hexo主题详细教程(1)/
作为查询条件。更多的查询方式和逻辑运算请看官方文档相关内容
var times = counter.get("times");
获取Class对象
中的times
列,并赋值。
var query = new AV.Query(className); |
如果理解了上面的查询过程,那么热门文章的获取也就很好理解了。先是获取Query对象,再对times
进行降序,再选取limits
篇文章,将其进行字符串拼接,最后更新innerHTML
。
在这里只是对部分代码进行讲解,如果你想看完整代码,Here,之后的地址和代码可能会进行更改,而且我相信你会写出更加优美的代码而不是复制粘贴。
leancloud的集成还特别感谢“使用LeanCloud平台为Hexo博客添加文章浏览量统计组件”该篇文章。
在上一篇文章中,讲到有好多目录,其中有一个scripts
目录,这个目录和source
目录下的js
目录是不同的,前者是在部署之前的脚本,后者是部署之后,前端使用的脚本文件。在官方的API中的拓展有很多,比如Console
、Filter
、Generator
和Helper
等。本人接触的不多,只能将自己的见解写下来,如果有错误,还望指出~
在我个人的理解看来,Helper是为了代码的模块化和复用代码块而存在,Generator为了生成所需文件而存在,Filter是为了改变原先的文件内容而存在。除了写过Helper以及改写过hexo-generator-search
外(因为该插件并不提供标签和分类搜索,已提交了pr),其余的尚未接触过,就不误人子弟了。
很多人可能会问了,在前端模版引擎中照样可以写逻辑处理,为什么不那样?
其实可以是可以,但是当逻辑过于复杂或者代码量过大,写在ejs文件里面不利于复用和维护,就像内嵌的JavaScript一样。在前面的文章讲到,Hexo为我们提供了很多的辅助器,而这辅助器和scripts
目录下的脚本文件一样的性质,只不过接口不同,代码的编写不同,但是大同小异,因为都是用的JavaScript。
是不是觉得Hexo提供给我们的辅助函数很好用?或者,你想根据自己的主题需要,想在原来的辅助函数上进行修改?
首先我们得找到文件所在,在博客目录下的/node_modules/hexo/lib/plugins/helper
目录中,可以找到与Hexo自带的辅助函数同名文件。比如<%- js(path, ...) %>
辅助函数对应js.js
文件,其他的一般也是如此(但是is_home
等都在is.js
文件中),js.js
的代码如下:
; |
这样就可以在原本的基础上,根据自己的需求进行修改了-
在ylion主题中,my_paginator.js文件就是在自己的需求上进行修改。原本的分页器在首页会隐藏“上一页”的按钮以及在末页会隐藏“下一页”的按钮,觉得不美观,就进行了更改,只让其不可点击,而不是隐藏。
至于自定义Helper的使用方法,也是和自带的Helper使用方法一样<%- my_paginator()%>
。
但是在原本基础进行修改并不能解决所有的情况,比如提取post文件中的图片地址,比如让文章支持top置顶功能。在这里以文章支持top置顶功能为例
。置顶功能查看过很多博文,都是提倡使用修改Hexo的排序算法。我在写ylion主题的时候也查看过Next主题的相应的源代码,但是看不懂…所以就决定根据该博文的排序算法,自己写一个支持置顶功能的Helper。(Update: 该方法有个bug,文章页的上一篇和下一篇还是会是以时间为序,待解决)
先说大概的思路:
slice
对应部分赋值给curPosts
,并对其进行遍历,而不仅仅遍历page.posts
。先是实现了排序功能的Helper
function sortPostsFunc(posts) { |
hexo.extend.helper.register('sortPosts', sortPostsFunc);
是核心部分,就像nodejs
暴露接口一样,sortPosts
为提供ejs文件使用的Helper名字,而sortPostFunc
为Helper的逻辑处理函数,而参数posts
则是在使用的使用所用到的,也就是实参是site.posts
而curPosts
变量的获取使用了getCurPosts
这个Helper,代码比较简短,相信大家可以看得懂,就不详细说明了。
function getCurPosts(sortedPosts, config, page) { |
可以发觉,实现一个Helper并没有什么难度,也是考察JavaScript的编写能力。两个Helper的使用方法可以查看这部分代码
制作Hexo主题详细教程系列基本上算是完结了。而第一篇说会写ylion主题的小功能及其实现思路
。但想想,好像与主题制作无关,只能算是彩蛋部分,所以暂时移除。后面再用一篇文章专门讲讲这个方面吧~
利用寒假接近半个月来写了一个主题。为什么要造轮子?其实做这个Hexo主题的目的就是想学习更高级的用法,比如less
,比如ejs
,比如CSS3
以及原生JavaScript
,也为了更好地沉淀。因为大半年的时间学习jQuery,处理IE的兼容,感觉越来越与前端的发展脱节…
在写ylion主题的时候,我也没想到会是如此艰辛。艰辛的不是技术使用方面,艰辛的是Hexo官方文档。当你写过主题就知道官方的API写的多么的含糊不清(摔!),而要命的是,这方面的介绍少之又少(也可能是我搜索的姿势不对…)。所以,为了方便后来造轮子者有个参考,也为了温习制作主题时利用到的知识,写下本教程,本教程以ylion主题为例,水平有限,如有纰漏,还望指出~
制作一款Hexo主题的要求并不高,要求掌握ejs
、less
、以及基本的HTML
、CSS
、JavaScript
,还有熟悉Hexo提供的插件和功能(参见API文档)。至于设计方面的知识,可以参考现有的主题,包括但不限于Hexo主题。
ejs
和less
可以使用Swig
和Sass/Scss
等,要看自己熟悉哪一个,自己用的适应就行。
ejs
和less
并不难学,两者都是为了模块化,减少代码量。前者基本上是js+html,后者基本是css。而JavaScript
部分,如果想减少上手难度,可以使用jQuery
代替。
按照教程配置好自己的开发环境,然后初始化,Hexo则会在指定目录新建需要的文件。 该目录下的文件说明,可以在官方文档中看到,不再赘述。
在themes
建立新的文件夹,命名为ylion
,为了减少工作量,这里使用generator-hexo-theme
和yo
来帮我们自动生成需要的目录和文件,再根据我们的需求进行删减和添加。当然,手动建立也是可以的。
首先是需要工具的安装:
npm install yo -g |
然后在新建的ylion
目录,使用命令yo hexo-theme
,进行技术栈的选择。结束后大概是以下的画面:
而现在的ylion
目录下的结构为:
. |
经过删减和增加后的文件结构一般如下,至于该新建什么文件,是根据自己的主题进行的。
├── _config.yml ### 主题的配置文件 |
至于每个目录和文件有什么作用,为什么这么分,以及如何自己更强地自定义,会在下面的内容或者以后的内容讲到。
这一部分是对Hexo官方文档的烂的一比的部分(主要表现在自定义
部分)进行的补充说明,以及编写主题时的一些心得。我会尽量对照着官方文档的顺序进行补充。
在官方的模板说明文档页面,有这么一个表格。让我们回头看ylion
目录下的部分文件。
│ ├── archive.ejs ### 归档布局文件 |
再对照表格:
其中layout.ejs
是最主要的文件,它是所有布局的入口,而其他的布局(layout)比如index.ejs
只是layout.ejs
的<%- body %>
部分。
当你访问博客的首页时,通过表格知道首页对照的模板为index.ejs
,这时Hexo将index.ejs
的内容替换掉layout.ejs
中的<%- body %>
。而如果是归档界面呢?对的,是archive.ejs
的内容替换掉<%- body %>
。而如果archive.ejs
不存在呢?这时就该表格中的回调
部分出场了,archive.ejs
的回调是index.ejs
,则说明当archive.ejs
不存在的时候,用index.ejs
的内容。
那问题来了,如果我想添加新的页面(page)怎么办?其实有好多种方法。
Github Pages有一个功能,是每个项目都可以算是一个page。比如你的repo名字是demo
,而你的Github名字是Geek
,也就是说你的个人域名是geek.github.io
,那你可以通过geek.github.io/demo/index.html
访问demo
repo下的index.html
,是不是很方便,这样就可以建立很多个pages了。
但是,我们不想那么麻烦怎么办?根据一定的网站知识,我们可以知道,在你博客目录下的source
目录下建立一个demo
目录,然后添加index.html
照样可以实现上述的功能。
但是又有一个问题,这个index.html
没有样式,脚本等等,你想让Hexo帮你渲染,怎么弄? hexo new page 'demo'
则会帮你完成在source
目录创立demo
目录,并初始化一个index.md
文件,使用的渲染布局自然就是page.ejs
了,为什么?官方命令说明如下: hexo new [layout] <title>
但这还不够,因为比如会添加许多许多pages。page.ejs
如何加以区分,然后进行渲染? 这时候front-matter出场了。给它加上一个字段type: 'demo'
,然后在page.ejs
进行逻辑判断,获取的变量为page.type
,然后实现你的业务逻辑即可。目前(2017-2-22)ylion主题尚未实现page.ejs
渲染分类等。
page.ejs
进行渲染呢,毕竟在一个文件里写过多的page.type
判断会显得乱,可不可以单独一个文件,比如demo.ejs
进行渲染。当然可以!更改demo
目录下的index.md
文件的front-matter
中的layout
字段,为你要渲染的layout名字,此处为demo
,然后在layout
文件夹下新建一个文件demo.ejs
即可~这相当于自定义layout。至于Partial
局部模块,还是要根据自己的需求进行处理。比如ylion
主题中的article-meta
是文章的信息显示。因为在ylion主题的每个页面都有出现,这时候就有必要抽取为一个局部模块,减少代码的代码量。
而优化是不能够缺少的,如果你的主题是提交至官方Hexo的话。在这里有一个坑!!!一般来说,局部缓存的代码如下:
<%- partial('_partial/xxxxx', null, {cache: !config.relative_link}) %> |
但是,如果你xxxx
局部模块中的变量是根据不同页面被赋不同的值,则不能够使用,不然所有页面中的此变量都是同一个值,切记。
全局变量,官方已经讲的很清楚了,不再赘述,主要讲页面变量。
不过,还是举一个例子。以ylion主题中的leancloud
统计功能为例子,如果在ejs文件中想获取主题下的_config.yml
的设置,可以使用theme.leancloud.enable
进行判断。至于怎么正确获取配置中的值,还请熟悉配置文件的格式。之后会有教程专门讲解,不是这部分的内容。
注意:全局变量中的page
指的是页面变量
,与页面变量中的page
是不同的。页面变量到底有哪些内容,是根据当前不同的布局(layout)而决定的。
比如页面变量中的page
是指由page.ejs
渲染的页面,post
是post.ejs
渲染的页面。就这么简单?Too Young!!
让我们来看看文档又给我们挖了什么样的坑。页面变量中的page
变量说的就是你。
page
变量有一部分是front-matter 所设定的变量,而不是它本身拥有的。比如page.photos
,其他的暂时没有验证,目测 page.link
也是。想当时,一直郁闷为什么无法获取文章的照片,原来是需要在文章中指定(摔!)
似乎理清了变量怎么使用,但有些人会对page.layout
变量感到郁闷,我也是在写这篇文章的时候才搞懂。算是临时加上的吧。page.layout
对应page.ejs
来说肯定是page
,对于post.ejs
来说肯定是post
,这毋庸置疑。但问题来了。对于hexo new page 'demo'
的demo page
来说,layout也是page
,其他的新建页面同理,但是有没有办法改变?
办法是有的,但是改变的话’意思’就完全变了。
front-matter变量中有一个layout
字段,官方文档在该页并没有详细讲解。layout
字段指定的是该page使用哪一个layout进行渲染。由前面的模板部分讲解的4种方法创建一个page中的’自定义布局一样’。所以,页面变量中的page变量指的是page布局和自定义布局的对象变量更加准确。
这一部分倒不是有什么问题,而是我有话想说。关于制作Hexo主题的一点小经验。
ylion主题的制作过程中,我是先花了将近两天完成了基本的原型,期间参考了很多博客的设计,完成了HTML和基本的CSS部分。在写原型的时候,有意地进行模块划分,这样有利于后期转换为ejs和less更加方便。这样好处是有的,不用在多个文件来来回回地跳,这样很容易混乱。但坏处是什么?
坏处就是,你写的结构和类名与Hexo官方提供的辅助函数生成的结构和类名不同,这就会导致你要改类名,严重的需要修改整个结构。当然,修改Hexo官方的辅助函数是可以的,以后的教程会讲到如何做,但是这样又增加了工作量,得不偿失。
所以,折中或者理智的做法应该是,看官方的辅助函数为你生成的结构和类名是什么,这类辅助函数集中在列表和分页器,所以在写主题前最好了解一下。举个栗子:
result += '<li class="' + className + '-list-item' + additionalClassName + '">'; |
这是list_categories
辅助函数的一部分内容,生成的是文章的分类列表,如果你的结构不是这样,不就需要进行修改了吗?
官方的补充说明暂且就这么多吧。
因为文章过长,所以分割为两个部分进行,前一个部分也就是本文,主要讲如何开始和补充那坑爹的Hexo文档。对应着ylion
目录的文件,已经算是讲解了
languages
多语言支持(官方文档)。layout
布局,讲了如何新建page,如何自定义layout,讲了如何看到官方文档的模板表格以及优化的注意事项。还剩下实现的一些细节:
_config.yml
文件以及如何读取其中的变量To be continued…
]]>例子参考来自wikipedia [请自备梯子]
说明: 主题是否要支持Mathjax,其实思考了挺久的。因为在Hexo下的默认markdown渲染会与 Mathjax的渲染冲突,而其他大部分的渲染并不如此。为了和其他的Markdown渲染引擎兼容, 需要做一些工作。
如果是其他主题迁移到本主题,或者将以前在其他地方的文章放在本主题,为了更大的兼容性和减少工作量,还请使用上述链接中的修改Hexo渲染源码。
如果是第一次在本主题编写的Latex,还请做出一些妥协。为了通用性和减少工作量,你可以使用修改Hexo渲染源码的方法,但这本人并不推荐。 本主题参考该网址的做法,使用<div></div>
包裹Displayed Equation
,使用 <code>
标签的点符号包含 Inline Equation
,比如 ‘\(inline Equation\)’(此处为了演示,使用了单引号)
因为各种原因,还请各位自己参考上述链接,自行寻找适合自己的解决方案。
本测试文件使用hexo-renderer-pandoc
插件进行Markdown文件渲染。
\[\acute{a} \grave{a} \hat{a} \tilde{a} \breve{a}\\\check{a} \bar{a} \ddot{a} \dot{a}\]
\[\sin a \cos b \tan c\\\sec d \csc e \cot f\\\arcsin h \arccos i \arctan j\\\sinh k \cosh l \tanh m \coth n\!\\\operatorname{arsinh}r\,\operatorname{arcosh}s\,\operatorname{artanh}t\\\lim u \limsup v \liminf w \min x \max y\!\\\inf z \sup a \exp b \ln c \lg d \log e \log_{10} f \ker g\!\\\deg h \gcd i \Pr j \det k \hom l \arg m \dim n\]
\(s_k \equiv 0 \pmod{m}\)` `$ a,,b $
\(\nabla \, \partial x \, \mathrm{d}x \, \dot x \, \ddot y\, \mathrm{d}y/\mathrm{d}x\, \frac{\mathrm{d}y}{\mathrm{d}x}\, \frac{\partial^2 y}{\partial x_1\,\partial x_2}\)
\[\frac{2}{4}=0.5\] \[\tfrac{2}{4} = 0.5\] \[\cfrac{2}{c + \cfrac{2}{d + \cfrac{2}{4}}} = a\] \[\dfrac{2}{4} = 0.5 \qquad \dfrac{2}{c + \dfrac{2}{d + \dfrac{2}{4}}} = a\] \[\dbinom{n}{r}=\binom{n}{n-r}=\mathrm{C}_n^r=\mathrm{C}_n^{n-r}\] \[\tbinom{n}{r}=\tbinom{n}{n-r}=\mathrm{C}_n^r=\mathrm{C}_n^{n-r}\] \[\binom{n}{r}=\dbinom{n}{n-r}=\mathrm{C}_n^r=\mathrm{C}_n^{n-r}\] \[\begin{Bmatrix}x & y \\z & v\end{Bmatrix}\] \[f(n) =\begin{cases}n/2, & \mbox{if }n\mbox{ is even} \\3n+1, & \mbox{if }n\mbox{ is odd}\end{cases}\] \[\begin{alignat}{3}f(x) & = (m-n)^2 \\f(x) & = (-m+n)^2 \\& = m^2-2mn+n^2 \\\end{alignat}\] \[\begin{cases}3x + 5y + z \\7x - 2y + 4z \\-6x + 3y + 2z\end{cases}\]
]]>hexo-renderer-pandoc
插件进行基于Hexo官方Markdown测试文件渲染,以验证pandoc-markdown语法是否兼容hexo-markdown语法。The purpose of this post is to help you make sure all of HTML elements can display properly. If you use CSS reset, don’t forget to redefine the style by yourself.
# Heading 1 |
Lorem ipsum dolor sit amet, [test link]() consectetur adipiscing elit. **Strong text** pellentesque ligula commodo viverra vehicula. *Italic text* at ullamcorper enim. Morbi a euismod nibh. <u>Underline text</u> non elit nisl. ~~Deleted text~~ tristique, sem id condimentum tempus, metus lectus venenatis mauris, sit amet semper lorem felis a eros. Fusce egestas nibh at sagittis auctor. Sed ultricies ac arcu quis molestie. Donec dapibus nunc in nibh egestas, vitae volutpat sem iaculis. Curabitur sem tellus, elementum nec quam id, fermentum laoreet mi. Ut mollis ullamcorper turpis, vitae facilisis velit ultricies sit amet. Etiam laoreet dui odio, id tempus justo tincidunt id. Phasellus scelerisque nunc sed nunc ultricies accumsan. |
Lorem ipsum dolor sit amet, test link consectetur adipiscing elit. Strong text pellentesque ligula commodo viverra vehicula. Italic text at ullamcorper enim. Morbi a euismod nibh. Underline text non elit nisl. Deleted text tristique, sem id condimentum tempus, metus lectus venenatis mauris, sit amet semper lorem felis a eros. Fusce egestas nibh at sagittis auctor. Sed ultricies ac arcu quis molestie. Donec dapibus nunc in nibh egestas, vitae volutpat sem iaculis. Curabitur sem tellus, elementum nec quam id, fermentum laoreet mi. Ut mollis ullamcorper turpis, vitae facilisis velit ultricies sit amet. Etiam laoreet dui odio, id tempus justo tincidunt id. Phasellus scelerisque nunc sed nunc ultricies accumsan.
Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed erat diam
, blandit eget felis aliquam, rhoncus varius urna. Donec tellus sapien, sodales eget ante vitae, feugiat ullamcorper urna. Praesent auctor dui vitae dapibus eleifend. Proin viverra mollis neque, ut ullamcorper elit posuere eget.
Praesent diam elit, interdum ut pulvinar placerat, imperdiet at magna.
Maecenas ornare arcu at mi suscipit, non molestie tortor ultrices. Aenean convallis, diam et congue ultricies, erat magna tincidunt orci, pulvinar posuere mi sapien ac magna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Praesent vitae placerat mauris. Nullam laoreet ante posuere tortor blandit auctor. Sed id ligula volutpat leo consequat placerat. Mauris fermentum dolor sed augue malesuada sollicitudin. Vivamus ultrices nunc felis, quis viverra orci eleifend ut. Donec et quam id urna cursus posuere. Donec elementum scelerisque laoreet.
<dl><dt>Definition List Title</dt><dd>This is a definition list division.</dd></dl> |
1. List Item 1 |
- List Item 1 |
| Table Header 1 | Table Header 2 | Table Header 3 | |
Table Header 1 | Table Header 2 | Table Header 3 |
---|---|---|
Division 1 | Division 2 | Division 3 |
Division 1 | Division 2 | Division 3 |
Division 1 | Division 2 | Division 3 |
Lorem <sup>superscript</sup> dolor <sub>subscript</sub> amet, consectetuer adipiscing elit. Nullam dignissim convallis est. Quisque aliquam. <cite>cite</cite>. Nunc iaculis suscipit dui. Nam sit amet sem. Aliquam libero nisi, imperdiet at, tincidunt nec, gravida vehicula, nisl. Praesent mattis, massa quis luctus fermentum, turpis mi volutpat justo, eu volutpat enim diam eget metus. Maecenas ornare tortor. Donec sed tellus eget sapien fringilla nonummy. <acronym title="National Basketball Association">NBA</acronym> Mauris a ante. Suspendisse quam sem, consequat at, commodo vitae, feugiat in, nunc. Morbi imperdiet augue quis tellus. <abbr title="Avenue">AVE</abbr> |
Lorem superscript dolor subscript amet, consectetuer adipiscing elit. Nullam dignissim convallis est. Quisque aliquam. cite. Nunc iaculis suscipit dui. Nam sit amet sem. Aliquam libero nisi, imperdiet at, tincidunt nec, gravida vehicula, nisl. Praesent mattis, massa quis luctus fermentum, turpis mi volutpat justo, eu volutpat enim diam eget metus. Maecenas ornare tortor. Donec sed tellus eget sapien fringilla nonummy. NBA Mauris a ante. Suspendisse quam sem, consequat at, commodo vitae, feugiat in, nunc. Morbi imperdiet augue quis tellus. AVE
This post is used for testing tag plugins. See docs for more info.
> Praesent diam elit, interdum ut pulvinar placerat, imperdiet at magna. |
Praesent diam elit, interdum ut pulvinar placerat, imperdiet at magna.
{% blockquote David Levithan, Wide Awake %} |
Do not just seek happiness for yourself. Seek happiness for all. Through kindness. Through mercy.
{% blockquote @DevDocs https://twitter.com/devdocs/status/356095192085962752 %} |
NEW: DevDocs now comes with syntax highlighting. http://devdocs.io
{% blockquote Seth Godin http://sethgodin.typepad.com/seths_blog/2009/07/welcome-to-island-marketing.html Welcome to Island Marketing %} |
Every interaction is both precious and an opportunity to delight.
alert('Hello World!'); |
{% codeblock Array.map %} |
array.map(callback[, thisArg]) |
{% codeblock .compact http://underscorejs.org/#compact Underscore.js %} |
.compact([0, 1, false, 2, ‘’, 3]); |
Line 1,7-8,10 should be marked with different color. |
Line 1,7-8,10 should be marked with different color.
const http = require('http'); |
Note: Theme’s style should support .highlight.line.marked
(recommend to use the selection or current line color).
{% gist 996818 %} |
{% jsfiddle ccWP7 %} |
{% pullquote left %} |
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas tempus molestie arcu, et fringilla mauris placerat ac. Nullam luctus bibendum risus. Ut cursus sed ipsum feugiat egestas. Suspendisse elementum, velit eu consequat consequat, augue lorem dapibus libero, eget pulvinar dolor est sit amet nulla. Suspendisse a porta tortor, et posuere mi. Pellentesque ultricies, mi quis volutpat malesuada, erat felis vulputate nisl, ac congue ante tortor ut ante. Proin aliquam sem vel mauris tincidunt, eget scelerisque tortor euismod. Nulla tincidunt enim nec commodo dictum. Mauris id sapien et orci gravida luctus id ut dui. In vel vulputate odio. Duis vel turpis molestie, scelerisque enim eu, lobortis eros. Cras at ipsum gravida, sagittis ante vel, viverra tellus. Nunc mauris turpis, elementum ullamcorper nisl pretium, ultrices cursus justo. Mauris porttitor commodo eros, ac ornare orci interdum in. Cras fermentum cursus leo sed mattis. In dignissim lorem sem, sit amet elementum mauris venenatis ac.
{% pullquote right %} |
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ligula justo, lobortis sit amet semper vel, dignissim sit amet libero. Praesent ac tempus ligula. Maecenas at gravida odio. Etiam tristique volutpat lacus eu faucibus. Donec non tempus arcu. Phasellus adipiscing, mauris nec mollis egestas, ipsum nunc auctor velit, et rhoncus lorem ipsum at ante. Praesent et sem in velit volutpat auctor. Duis vel mauris nulla. Maecenas mattis interdum ante, quis sagittis nibh cursus et. Nulla facilisi. Morbi convallis gravida tortor, ut fermentum enim gravida et. Nunc vel dictum nisl, non ultrices libero. Proin vestibulum felis eget orci consectetur lobortis. Vestibulum augue nulla, iaculis vitae augue vehicula, dignissim ultrices libero. Sed imperdiet urna et quam ultrices tincidunt nec ac magna. Etiam vel pharetra elit.
有几点是需要注意的:
---
前后都要有空行不同的错误有不同的解决方法。不保证可以解决相同类型的错误,因为同种类型错误可以由多种不同的原因造成。但是如果遇到无法启动时,以下方法值得一试~
大概情况是,在启动Genymotion模拟器的时候,会发生错误。错误如下图所示:
解决无法打开GM的办法之一如下。来自stackoverflow
,原链接没有保存到(T-T)只保留住其中一个答案,以便笔记
I have a same problem and I solve it with this :
Open Windows Network ConnectionsRight click on VirtualBox Host only adapter that createdChoose propertiesCheck "VirtualBox NDIS6 Bridged Networking driver"disable and Enable the adapter
在网络和共享中心
中找到VisualBox的适配器。
右键,然后属性。
勾上图中的选项,并且点击确定按钮退出。
再次启动Genymotion模拟器即可,完美解决。
如果你看到这部分内容竟然和《css设计指南》的如此相似,不要惊讶,因为这个是我在看书时的笔记,权当是查漏补缺和加强自己的理论知识。当然,不是全书地照搬,还会增加个人的理解,还有一些很好的文章。比如
@font-face
的使用,找了很多不错的参考文章,作为对书中的补充。 如果你和我是从初级阶段,当过几次切图仔(本人目前还是),想进阶到更高一个级别,那这部分内容你不容错过。因为在你使用的时候并不注意,一些坑坑洼洼会让你烦恼很久。如果你是大神级别,还不吝你能指出错误,感激不尽~
此部分为CSS设计指南(第3版)读书笔记的理论部分(个人认为),除开第5章部分,第6章全章,第7章全章。
不设置宽度的块级元素盒子会扩展到与父元素同宽
有一点很重要,那就是 CSS 样式是通过<style>
标签嵌入到页面里的。当浏览器遇到 开标签<style>
时,就会由解释 HTML 代码切换为解释 CSS 代码。等遇到闭标签</style>
时,它会再切换回解释 HTML 代码
紧邻同胞选择符+,一般同胞选择符~
一个冒号(:)表示伪类,两个冒号(::)表示 CSS3 新增的伪元素。尽管浏览器目前都支持对 CSS 1 和 CSS 2的伪元素使用一个冒号,但希望你能习惯于用双冒号代替单冒号,因为这些单冒号的伪元素最终可能都会被淘汰掉。更多相关信息,可以参见这里:传送门
通过在上下文选择符中使用链接伪类,可以轻易地为 nav、 footer、 aside 和article元素中的链接应用不同的外观和行为。[技巧一]
link伪类四个同时使用的时候 :为了好记,我建议大家可以这 么想:“ LoVe? HA!”大写字母就是每个伪类的头一个字母。【顺序问题困扰很久】
新学习的伪类,:target
如果用户点击一个指向页面中其他元素的链接,则那个元素就是目标target),可以用:target 伪类选中它。 对于下面这个链接
<a href="#more_info">More Information</a>
位于页面其他地方、 ID 为more_info 的那个元素就是目标。该元素可能是这样的:<h2 id="more_info">This is the information you are looking for.</h2>
那么,如下CSS规则#more_info:target{background:#eee;}
会在用户单击链接转向ID为more_info的元素时,为该元素添加浅灰色背景。
:nth-child
伪类最常用于提高表格的可读性,比如对表格的所有行交替应用不同颜色。
e:nth-child(n)
,括号里的n为第n个元素e元素
font-variant
: 属性设置小型大写字母的字体显示文本
::first-line
为伪元素。伪元素就是你的文档中若有实无的元素。类似的还有::first-letter
,可以设置第一个字的样式.::before
和::after
伪元素。
“first-line”,“first-letter” 伪元素只能用于块级元素。
搜索引擎不会取得伪元素的信息(因为它在标记中并不存在)。因此,不要通过伪元素添加你想让搜索引擎索引的重要内容。
只给 background-position 设定一个关键字值,则另一个也会取相同的值。 > /center center 的简化写法/ p#center {background-position:center;}
background-position:center center
设定图片中心点与元素中心点重合,然后再向各个方向重复。由14可知,通过把background-position
设定为 50% 50%
,把 background-repeat
设定为no-repeat
,实现了图片在背景区域内居中的效果。
background-size
为我们控制背景图片提供了更多可能性。
17. 。(update:尚未熟悉) 18. 使用 Modernizr来检测浏览器对它们的支持情况,并为不支持它们的浏览器提供替代 CSS。font-size
,height
,line-height
三者的关系是:height
将盒子撑开,line-height
是从文本的基线处在文本行上下平均分配,不影响标准文档流,font-size
是在从文本的基线处开始变大,会撑开盒子模型,但可以显示在其他元素上方。丝毫不影响标准文档流。不信可以在Chrome中将font-size
调大进行观察
Modernizr 是一个 JavaScript 库,用于检测用户浏览器支持哪些 HTML5 和 CSS3 功能。 更多信息,请参考这个网址: http://modernizr.com
默认情况下,背景绘制区域是扩展到边框外边界的。 > 如果想使用更多的选择,使用CSS3中的background-clip
属性。提供3个可选值。 background-clip: border-box;/背景包括border/ background-clip: padding-box;/背景包括padding,不包括border/ background-clip: content-box;/背景包括content,不包括padding和border/
font-family
用于设定元素中的文本使用什么字体。一般来说,应该给整个页面设定一种主字体,然后只对那些需要使用不同字体的元素再应用 font-family
。要为整个页面指定字体,可以设定 body 元素的 font-family
属性。font-family
是可以继承的属性,因此它的值会遗传给所有后代元素。
小技巧:我们永远也不敢保证一定能用某种字体来显示网页。为此,在指定文本的字体时,需要多列出几种后备字体,以防第一种字体无效。这个字体的列表也叫字体栈。
西方国家字母体系分为两类:serif
以及sans serif
。serif
是有衬线字体,在每个字符笔画的末端会有一些装饰线。 sans-serif
,也就是无衬线字体,字符笔画的末端没有装饰线。具体看下图。
默认情况下, 1em等于 16 像素,这也是 font-size 的基准大小。如果需要重新设定了基准大小,则修改body 的字体大小。body的字体大小即为1em(比如重新设定为20px,则1em = 20px)
绝对字体大小。 优点:它们是绝对单位,因此设定多大就多大,与祖先元素的字体大小无关。 缺点:使用绝对单位的缺点很明显,那就是在需要调整页面所有元素的字体大小时,必须一个一个地修改样式表中的 font-size。
相对字体大小。如果你给某个元素设定了相对字体大小,则该元素的字体大小要相对于最近的“被设定过字体大小的”祖先元素来确定。 优点:使用相对大小后,通过调整body元素的字体大小,可以成比例地改变所有元素的字体大小。或者,至少能通过改变某个祖先元素,只影响它的所有子元素。 缺点:“牵一发而动全身”的事,出现连锁反应,所以使用相对字体大小时,必须事先规划好。 【请看下面例子】
<body> |
默认值并不是无用,它可以用来有选择地覆盖某个默认或你设定的全局属性。
由于浏览器对数字值的实现各不相同,所以从常规字体到粗体的切换可能发生在不同的数值上——通常是 400左右。总之,对于 font-weight 属性来说,最好只用 bold
和 normal
这两个值。
26. 粗体和斜体:两个都有两种方法,一种是标签来控制显示,一种是用css来控制。
font-style:italic;
或font-style:oblique;
或<em>
(emphasized)标签或<i>
(italic)标签。font-weight:bold;
或<strong>
(strong)标签或<b>
(bold)标签。那应该如何选择?
其实,个人认为,如果不是特别强调的,不要轻易使用
<em>
,<strong>
两种分别显示斜体和粗体。因为他们在搜索引擎中更受重视,将会影响SEO。也就是说,在一般情况下,使用css来控制字体样式(推荐)或者<b>
和<i>
。
text-indent
是可以被子元素继承的。如果你在一个 div
上设定了 text-indent
属性,那么 div
中的所有段落都会继承该缩进值。然而,与 所有继承的 CSS 值一样,这个缩进值并不是祖先元素中设定的值,而是计算的值。(注意)【请看下面例子】假设有一个 400 像素宽的 div,包含的文本缩进 5%,则缩进的距离是 20 像素(400 的 5%)。 在这个 div 中有一个 200 像素宽的段落。作为子元素,它继承父元素的
text-indent
值,所以 它包含的文本也缩进。但继承的缩进值是多少呢?不是 5%,而是 20 像素。也就是说,子元素 继承的是根据父元素宽度计算得到的缩进值。结果,虽然段落只有父元素一半宽,但其中的文 本也会缩进 20 像素。这样可以确保无论段落多宽,它们的缩进距离都一样。当然,在子元素 上重新设定 text-indent 属性,可以覆盖继承的值
【坑2】
一个容易疏忽的点,就是,
text-indent
对于块级文本元素有效,比如<p>
,<li>
。如果一个段落很长,则只会对首行缩进,要实现每行都缩进,可以使用多个<p>
,或者margin-left
属性(看需求)。
【坑3】
在设定缩进和外边距时最好同时使用em,以便在改变字体大小时, 它们的长度能够按比例变化。
- blink 是为文本添加闪烁效果的,实际上很讨厌,应该少用,最好不用。
- 上网的人都习惯了把带下划线的文本当成链接。如果你给本来不是链接的文本加上下划线,很容易导致困惑和无效点击。
- 利用下划线的出现和隐藏,呈现有效的视觉反馈。
letter-spacing
对英文字母、汉字及其他字符都起作用。etter-spacing
的值是在浏览器默认值基础上增加或减少的值。
- 纯汉字文本一段之中没有空格,因此
word-spacing
对中文网页几乎没有用,但对中英混排段落可能有用。【见下图】
使用@font-face 规则在网页中嵌入可下载字体的 CSS 功能,为设计师提供了系统自带字体以外的广泛选择,不必再依赖用户机器中的字体。有3种使用方法。
小缺陷: 使用@font-face的一个问题是不同浏览器要求的字体格式不一样。比如, Firefox、Webkit 核心的浏览器(Safari 和Chrome),以及 iOS 4.1 版之后的移动Safari使用OTF(OpenType)或TTF(TrueType)字体。 Internet Explorer 使用 EOT(Embedded OpenType)。另外, iOS 4.1 之前版本中的移动 Safari 以及其他浏览器使用 SVG(Scalable Vector Graphics)格式。
讲那么多很难描述清楚,书中也是忽略而过。看下面的链接,已经讲得够详细。但需要提醒的是,在定义好自己的font-family
之后,在css中的选择器上使用才可以显示效果。
CSS 中有很多属性是可以继承的,其中相当一部分都跟文本有关,比如颜色、字体、字号。然而,也有很多CSS属性不能继承,因为继承这些属性没有意义。这些不能继承的属性主要涉及元素盒子的定位和显示方式,比如边框、外边距、内边距。【继承对自身有意义的属性】
举个例子吧,假设我们想创建一个边栏,在其中放一组链接。为此,我们用 nav 元素嵌套该组链接,并给nav应用了一种字号和一个边框效果,比如 2 像素宽的红色边框。不难想象,nav中的所有链接都继承它的字号正常,可要是也继承它的边框就不合适了。当然,这些链接不会继承边框效果,因为 border 属性不能继承。
层叠,就是层叠样式表中的层叠,是一种样式在文档层次中逐层叠加的过程,目的是让浏览器面对某个标签特定属性值的多个来源,确定最终使用哪个值。这就涉及到了优先级。 优先级的大概计算如下通俗易懂 如果优先级的值一样,则以后者为先。
如果哪个值没有写,那就使用对边的值。 E.G. {margin:12px 10px 6px;}
对这个例子来说,由于没有写最后一个值(左边的值),所以左边就会使用右边的值。
叠加的只是垂直外边距,水平外边距不叠加。对于水平相邻的元素,它们的水平间距是相邻外边距之和。
盒模型结论一:没有(就是没有设置width的)宽度的元素始终会扩展到填满其父元素的宽度为止。添加水平边框、内边距和外边距,会导致内容宽度减少,减少量等于水平边框、内边距和外边距的和。[其实也可以看成是结论二的特殊情况,因为最大宽度被父级宽度所限制,不会再增加]
盒模型结论二:为设定了宽度的盒子添加边框、内边距和外边距,会导致盒子扩展得更宽。实际上,盒子的width属性设定的只是盒子内容区的宽度,而非盒子要占据的水平宽度。 (补充:宽度高度都适用。) 这两种盒子所表现出来的完全不同的行为,对将来构建多栏布局具有重要的启示。因为在多栏布局中,每一栏都必须时刻维护自己的宽度. CSS3 新增了一个
box-sizing
属性,通过它可以将有宽度的盒子也设定成具有默认的auto状态下的行为。【IE8+支持】 具体是怎么一回事?box-sizing
box-sizing
的默认值是content-box
,也就是上面的两个结论。 box-sizing
的另外一个值,是CSS3的内容,border-box
。就是将结论1的特性应用到结论2,无论是否有设定具体的高度和宽度,为元素指定的任何内边距和边框都将在已设定的宽度和高度内进行绘制,不会再增加元素的宽度和高度。
这两个结论有什么用?
在三栏布局的时候,最外层固定宽度,要在不改变盒子的宽度的前提下,需要增加外层和内容间的间距,如果使用内边距
padding
则会增加盒子的宽度。(见盒子模型结论2)。一个好的办法是使用box-sizing:border-box;
,还可以增加一个div
,不设置宽度。但考虑到那两个神一般存在的浏览器(IE6,IE7),还是推荐使用方法2–增加一个div
,不设置宽度【虽然方法1可以用JS的方式来实现,使用borderBoxModel.js】
<body> |
但使用JS的方式有所局限,比如,宽高必须使用px作为单位。不支持onresize, min/max-width等不固定属性
,如果布局复杂,还需要更多的测试来保证布局没有被破坏。具体可以看这个github项目
盒子模型中还有一个巨坑。坑在这~
简单一句话(引自:《css权威指南》)
非替换元素的内边距,边框和外边距对行内元素及其生成框没有垂直效果;也就是说,他们不会影响行内框的高度。
什么是非替换元素和可替换元素?用一句话来区别~
可替换元素是浏览器根据元素的标签和属性,来决定元素的具体显示内容。
比如图像,浏览器根本不知道显示的是什么图片,只知道图片的地址,并且将其显示出来。
典型的可替换元素有 <img>
、 <object>
、 <video>
以及 <textarea>
、 <input>
。还有,使用了 CSS content 属性插入的对象被称作匿名的可替换元素,比如:before
和:after
伪类。
需要注意的是,非替换元素和可替换元素,与 块级元素和行内元素,没有关系,两者是对元素的不同划分方式。
这一部分来自《css设计指南》第五章。 Tip 1 > 关于通配符*
,它会导致浏览器遍历整个DOM结构去查找所有匹配的元素。但也发现这一点性能影响几乎可以忽略不计。除非页面有成千上万个元素。
这一部分还是不大同意作者所说,毕竟大家都已经习惯使用自己的重置样式表reset.css
让自己的页面在各大浏览器尽量在间距等可控方面表现一致。况且对于大型网站而言,优化还是十分重要的。如果有时间,当然提倡根据自己的网站进行自定义。比如网站中没有使用到表格,就没有必要对表格进行消除间距。如果没有时间,使用通配符达到目的也未尝不可。
Tip 2
* {box-sizing:border-box}
使用通配符和CSS特性,使得页面中的盒模型就全都符合逻辑。
Tip 3 保护布局
- img{max-width:100%;} –限制图片的宽度不超过其父元素
- 给内容包装
div
添加overflow:hidden
声明,任何超出容器边界的部分剪切掉,保护布局。- 给所有栏的外包装元素应用
word-wrap:break-word
声明,保护布局不会被 长 URL或长文本顶得支离破碎。
浮动元素脱离了常规文档流之后,原来紧跟其后的元素就会在空间允许的情况下,向上提升到与浮动元素平起平坐。
CSS 设计 float 属性的主要目的,是为了实现文本绕排图片的效果。后来,也成了创建多栏布局的方式。
对于float:right;
的元素,它脱离文档流向右移动,直到它的右边框碰到包含框。如果包含框太窄,则自动向下浮动,直到有足够的空间(此时可能导致’卡住’的情况)。
清除浮动是清除浮动对被影响元素本身的影响,而不是浮动本身。对影响的元素使用clear:left;
是清除浮动在左边的元素对该元素的影响,不去围绕着它浮动的元素。
<div>
标签。(例子1)<p>
),如果浮动元素下面没有兄弟元素,则不可使用该方法。:after伪元素
。:after
会在元素内容后面而不是元素后面插入一个伪元素,在例子2中是the footer element…后面添加一个点,而不是在<section>
父级元素后面添加。但设置height:0;
后因为没有高度,父级元素不把它包含住,所以显示是在父级元素后面添加,其实不是的。可以自己将visibility:hidden
去掉,调整高度进行观察。 /*例子1--html*/ |
在方法2和方法3中,由于包含元素一定会包围非浮动的子元素,而且清除会让这个子元素位于(清除一侧)浮动元素的下方,因此包含元素一定会包含这个子元素——以及前面的浮动元素。【特别注意这部分的描述】
且方法3.1和方法3.2不同,3.1的p元素还是会受浮动的影响,围绕在图片周围,而3.2的p元素不会受到浮动的影响,因为是p元素是块级元素,所以它在图片下面独自一行。
这三种方法的使用要因地制宜。比如,不能在下拉菜单的顶级元素上应用 overflow:hidden,否则作为其子元素的下拉菜单就不会显示了。因为下拉菜单会显示在其父元素区域的外部,而这恰恰是 overflow:hidden
所要阻止的。再比如,不能对已经靠自动外边距居中(margin:0 auto;
)的元素使用“浮动父元素”技术,否则它就不会再居中。而是根据浮动值浮动到左边或右边了。
因为文章篇幅的原因,布局就移到另外一篇文章(实战部分)进行总结。这里大概给出几种常见,并且重要的布局方式。
使用一项叫媒体查询的CSS功能,很容易检测出用户设备的屏幕大小。然后,据以提供替代或额外的CSS,可针对相应屏幕实现更加优化的体验。使用这种方式创建对设备有感知力的网站,被称为响应式设计。实际上真正的设计原则是“移动先行”,屏幕从小到大。
一种布局不能适应多种屏幕尺寸。我们需要一种能够检测屏幕大小的方法,然后相应地修改布局。简言之,就是需要让页面能够自己响应屏幕变化。
移动设备的媒体查询:http://pugetworks.com/blog/2011/04/css-media-queries-for-targetingdifferent-mobile-devices/
常用的媒体类型如下。
all:匹配所有设备; handled:匹配手持设备(小屏幕、单色、带宽有限); print:匹配分页媒体或打印预览模式下的屏幕; screen:匹配彩色计算机屏幕; 其他媒体类型还有 braille(盲文点字触觉反馈设备)、 embossed(盲文分页打印机)、projection(投影仪)、 speech(语音合成器)、 tty(电话机屏幕等固定宽度字符栅格设备)和 tv(电视机)。
任意时刻浏览器窗口中只能使用一种媒体类型。
媒体特性也就是媒体某一方面的特征,一般带有 min-或 max-前缀。 常用的媒体特性如下:
min-device-width 和 max-device-width:匹配设备屏幕的尺寸; min-width 和 max-width:匹配视口的宽度,例如浏览器窗口宽度; orientation(值为portrait(竖屏)和landscape(横屏)):匹配设备是横屏还是竖屏。 如果想通过媒体查询来根据用户对浏览器窗口的缩放重新调整布局,应该使用 min-width 和max-width。
还可以使用逻辑运算符 and、 not、 or 及关键字 all、 only 组合媒体类型和媒体特性。
media="only screen and (max-width: 480px)"
把 CSS 规则嵌套在了一个@media 规则中。
screen and (max-width:568px) { |
注意该@media规则是放在css文件中或者<style>
标签中使用,如图:
如果要通过媒体查询应用的CSS规则非常多,那么就可以考虑使用<link标签的media 属性设定条件,有选择地加载独立的样式表。
<link type="text/css" media="print" href="css/print_styles.css" /> |
断点是指区分不同宽度设备的临界点。 比如:@media screen and (max-width:640px) { /*CSS 规则*/ }
,断点是640px。
一个匹配的技巧是,不要去用断点一下子去匹配特定设备的屏幕宽度。而是慢慢地缩小窗口,在发现当前布局不合适的时候再确定断点,编写新的样式。可以确保一个样式在特定宽度范围内都适用。减少工作量。
用标签设定视口。比如:<meta name="viewport" content="width=device-width; maximumscale=1.0" />
。这个标签告诉浏览器按照屏幕宽度来显示网页,不要缩小网页。
流动布局的大小会随用户调整浏览器窗口大小而变化。这种布局能够更好地适应大屏幕,但同时也意味着放弃对页面某些方面的控制,比如随着页面宽度变化,文本行的长度和页面元素之间的位置关系都可能变化。Amazon.com 的页面采用的就是流动中栏布局,在各栏宽度加大时通过为内容元素周围添加空白来保持内容居中,而且现在的导航条会在布局变窄到某个宽度时收缩进一个下拉菜单中,从而为内容腾出空间。
弹性布局与流动布局类似,在浏览器窗口变宽时,不仅布局变宽,而且所有内容元素的大小也会变化,让人产生一种所有东西都变大了的感觉。
已经知道的兼容属性放在前面,而部分浏览器不兼容的新属性放在后面。浏览器不支持新属性时,会自动忽略后面的新属性。而如果浏览器支持新属性,由于[css的层叠][13]
会自动覆盖。如下面的书中举例。
IE9 之前的浏览器都不支持多背景,因此后备代码就是在多背景 声明之前简单地再加一条单背景声明,比如: .someElement {background-image:url(images/basic_image.jpg);}
.someElement {background-image:
url(images/cool_image1.jpg),
url(images/cool_image2.jpg),
url(images/cool_image3.jpg);
}
如果你真想单独为 IE浏览器做点什么,可以使用如下所示的条件注释来添加后备代码。
<!-- 在为 IE8 及更低版本的 IE 加载额外的样式 --> |
lte(less than or equal to,小于等于)、 lt(less than,小于)、gte(greater than or equal to,大于等于)、 gt(greater than,大于),甚至只写一个浏览器版本号,如 IE 6。以此为不同版本的 IE 提供后备代码。
注意:这种条件注释只对指定版本的IE浏览器有效,而不满足版本的IE浏览器和其他非IE浏览器都会自动忽略该条件注释。
不算是条件注释的一部分,但个人感觉有一定的逻辑在里面。故把这个十分有用的标签放在这一部分。 noscript 元素用来定义在脚本未被执行时的替代内容(文本)。 这个标签可被用于可识别<script>
标签但无法支持其中的脚本的浏览器。用法和普通的html标签一样。
腻子脚本(polyfill)指的是一段JavaScript代码,能够赋予浏览器未曾有过的功能。
一份完整的腻子脚本列表,配合modernizr使用效果更佳。前者负责修复,后者负责通过检查支持情况添加相应的类,便于前者操作。
实现’古老浏览器’对HTML5和CSS3的支持的大概过程如下。
- Modernizr脚本能够帮你检测用户浏览器对 HTML5 和 CSS3功能的支持情况。
- 为顶级的标签添加一组类(为 CSS 提供便利),标明浏览器支持什么功能。
- 设定一个 JavaScript对象modernizr的属性,以便通过JavaScript 来测试这些功能。
更详细的用法见使用Modernizr 检测HTML5和CSS3浏览器支持功能
以下是CSS设计指南(第3版)列出的常见polyfill(腻子脚本)
参考文章与书籍:
[Airen的博客--CSS3 @font-face]26
]]>此部分为记录艰辛的历程+吐槽,不喜勿喷,可跳过。
比较严格意义上的浏览器兼容性测试,需要这么多浏览器(天啊噜!)。 > 传送门–浏览器兼容测试环境
下面的每个环境上下文都是互斥的: * winxp + IE6 / 搜狗 / 360 * win7 + ie9(可以使用其 ie7 / ie8 兼容模式来测试 IE7 和IE8 )/ 搜狗 / 360 / opera / firefox / chrome * win7 + ie10 * win7 或 xp + ie8 或 ie7(可忽略) * mac + safari / firefox / chrome (可忽略) * win8.1 + ie11
每一个选项都应该是一台远程或本地的测试机器,或者是一个vmware虚拟机,装有原生对应版本浏览器的操作系统。 在学习前端的时候,浏览器的兼容器总是让人头疼。因为硬性规定,要用原生的浏览器进行测试,不可以是IE11下对IE7-9进行仿真,而且测试的时候不可以使用在线工具(测试地址要求权限,使得在线工具无权限访问)。
有人会认为没有必要,使用IETester就可以了。引用原回答的一句话,深有体会。
最后,不要相信IE Tester,不要相信Microsoft Expression 4 Superview的交互模式的结果,我吃了N遍“IE Tester自测通过但QA用原生低版本IE测出bug”的亏。
只好乖乖地把浏览器装上。众所周知,IE浏览器不可多装,会覆盖,只能利用虚拟机进行安装。[微软官方镜像下载地址]好家伙~虽然IE6明文不再支持,但还有IE7这个反人类的存在。windows XP
系统只能使用虚拟机进行安装啦。不知道为什么本人的VMware虚拟机不支持有IE浏览器的WinXP镜像,会出错。所以换成了Visual Box
浏览器安装。安装完成(过程很简单,只需将下载的镜像解压,进行双击即可)之后,原本以为就完事了,但问题来了。
为了测试中文网页,使得页面不乱码,只能上网找教程。利用中文关键字在网上找遍了,无非两种方法。
以上两种方法不行,国内提供的软件包和教程都无法解决(如果有,还望指出~)。但心中有了大概的答案,只能利用提取镜像里面的软件进行安装。中文不可以,利用蹩脚的英文,打上关键字,在Google上搜索,OK,没想到真的有。‘皇天不负有心人’(此时搜索答案已过去3小时..T_T)
但国外网站再加上时间久远,这下载有时候真的很坑爹啊。利用BT种子在迅雷里下载,MD,体验的时候下载可以达到100K/S,高速体验后,为0!!!!下次要找下载工具替换迅雷才行了。没办法,作者提供的链接都无法下载。哭…只能动用万能的国内网盘搜索了,用该文件名进行搜索。在百度云里找到了。
因为该虚拟系统只能使用30天,在设置好支持中文字体后,记得别忘记保存快照,这样,到时候体验到期后可以利用快照的备份进行恢复。当然,还有其他的“续期”方法,请另行百度,或者参照桌面中的英文方法。说了这么多,我会告诉你,利用官方提供的系统镜像可以通过某些方法“变相”地免费体验正版的Windows系统~?
]]>安装并且设置后不可用中文键盘进行中文输入,也算一个bug。目前本人尚未解决,如果有网友解决了,还请分享~
线程是一个程序内部的顺序控制流。
每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,一个进程的内存空间是共享的,每个线程都可以使用这些共享内存。每个线程都有独立的运行栈和程序计时器,线程切换的开销小。
多进程: 在操作系统中能同时运行多个任务(程序) 多线程: 在同一应用程序中有多个顺序流同时执行。
进程和线程的关系初步可以理解成:进程是加载到内存里的程序,而一个进程至少拥有一个线程(Java中的Main()方法),当然可以使用代码进行开启多线程。
JAVA的线程是通过java,lang.Thread类来实现的。 在Java中可以通过创建Thread的实例来创建新的线程。 每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。 通过调用Thread类的start()方法来启动一个线程。
run()方法是执行的代码,而start()方法是开启线程,注意两者的区别。
在Java中可以有两种方式创建新的线程。
- 第一种
- 定义线程类A实现Runnable接口
- 实例化Runnable线程类A的对象B
- 实例化Thread对象: Thread mThread = new Thread(B);
- 实现Runnable接口中的线程运行体,也就是run()方法
- 使用Runnable接口的类可以为多个线程提供共享的数据。
- 在实现Runnable接口的类的run方法定义中可以使用Thread的静态方法。比如currntThread()方法,该方法可以获取当前线程的引用。
- 第二种
- 可以定义一个Thread的子类并重写run()方法
- 实例化Thread对象。
方法1
public class MyThread{ |
运行结果 //不贴出来了,两者是同时两个线程一起执行,而不是先等run()方法执行完成之后
//再执行main()中其他语句。因为线程的开启时间有快有慢,以及两个线程分得的
//时间片不一样,所以每次运行的结果有可能有区别。
//但当把 mThread.start(); 替换成 mThread.run(); 时,
//此时实际上是main()方法对run()方法进行调用,而不是开启线程执行run()方法
方法二
与上面方法一类似,只需要适当修改一下。
public class MyThread{
public static void main(String[] args) {
MyRunnableClass mThread = new MyRunnableClass();
mThread.start();//开启线程
for (int i = 0; i < 50; i++) {
System.out.println("这是主线程中第"+(i+1)+"行");
}
}
}
class MyRunnableClass extends Thread {
public void run() {
System.out.println("=====开启子线程======");
for (int i = 0; i < 50; i++) {
System.out.println("这是子线程中第"+(i+1)+"行");
}
}
}
在两个方法中,推荐使用实现接口的方法,即方法一。原因是可以实现多个接口但继承只能是单继承,不利于该类的扩展。
从图中可以看出,Java中线程大致有5中状态,在Java的API中也提供了操作Java线程的方法。让我们先睹为快。
isAlive() 判断线程是否还未被终止。 getPriority() 获得线程的优先级数值 setPriority() 设置线程的优先级 Thread.sleep() 静态方法,将当前的线程指定毫秒数进行睡眠。会抛出interruptedException异常。 join() 调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行。 yield() 让出cpu,当前线程进入就绪队列等待调度。 wait() 当前线程进入对象的wait pool notify()/notifyAll() 唤醒对象的wait pool中的一个/所有等待线程
让我们使用sleep()方法实现子线程每隔1s打印出当前的时间。偷一下懒,还是使用该类,不新建其他类了。sleep()方法会抛出InterruptedException
异常,我们需要进行捕获,关于异常的知识点在之前的博客中已经详细地讲解过了,这里不再叙述。
public class MyThread { |
运行的结果为:
当前时间Tue Feb 09 16:31:03 CST 2016 |
注意此处打印了两次!哎哎哎,为什么呀?不是应该只打印一次吗?那究竟interrupt()是何时作用的呢?InterruptedException是什么时候抛出的?让我们先看下面的修改版代码。
public class MyThread { |
对原来的代码进行微小的修改,运行结果如下:
当前时间Tue Feb 09 17:38:34 CST 2016 |
从结果可以看到,当我们开启线程的时候,先执行打印当前时间,在之后便进入第一次睡眠(sleep),此时的线程状态并未被打断(此时尚未执行this.interrupt();
)(isInterrupted()
是用来判断线程的中断状态),当执行完this.interrupt();
之后,线程的状态已经变成中断标志,但并非退出运行,它只是一种标识,程序还会继续执行。sleep完成之后由于是while(true)死循环,所以继续执行run()方法体的代码,再次打印时间,但当sleep()方法再次调用的时候,它抛出异常了InterruptedException
异常,并且中断状态清除。WHY?
我在遇到这个问题的时候,也Google和百度过,但可能关键字的问题,愣是没找出来。
还是回归官网的文档吧~在IDE中双击sleep()方法,找到它的文档(已经在NetBeans中配置好文档的显示)
InterruptedException - if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown
这下全都明白了吧~~
如果该线程被任何的线程所阻塞(在文档中还说明线程可被自身阻塞),则该线程的阻塞状态会清除并抛出InterruptedException异常。
小结一下:interrupt()可以阻塞线程自己本身,但它不是真正意义上中断该线程,只是打上中断标志,线程还会执行。当sleep()遇到interrupted为true的线程时,会清除状态为false,并抛出interruptedException
异常。
当run()方法执行结束之后,该线程也为终止状态。上面的程序修改,可以使用一个布尔型值变量来控制while循环,而不是简单粗暴的中断加异常处理。
join()方法用来合并线程,相当于方法调用,等到子线程中的run()执行结束之后,main()方法中的其余部分才会继续执行。
,yiled是“退让”的意思。yield()方法让出CPU,给其他线程执行的机会。 #### join()代码示例 public class MyThread {
public static void main(String[] args) {
MyRunnableClass mThread1 = new MyRunnableClass("张三");
MyRunnableClass mThread2 = new MyRunnableClass("李四");
mThread1.start();
mThread2.start();//开启线程
}
}
class MyRunnableClass extends Thread {
MyRunnableClass(){
}
//通过super(String)为线程起名,可以使用getName()获取名字
MyRunnableClass(String name){
super(name);
}
public void run() {
for (int i = 1; i <= 50; i++) {
try {
sleep(1000);
System.out.println(getName()+"拍"+i);
if(getName().equals("张三")&&i%5==0){
System.out.println(getName()+"说:每拍5次我让你一下");
yield();
}
} catch (InterruptedException ex) {
System.out.println(ex);
}
}
}
}
运行结果过长,就不贴了,该代码是两人每隔1s就拍一次,每5s,张三调用了yield()方法。
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照线程的优先级决定应调度哪个线程来执行。
线程的优先级用数字来表示,范围从1~10,一个线程的缺省优先级是5.
三个优先级常量。
/** |
优先级越高,得到CPU调度的时间片越多。
]]>ListView的学习主要是适配器(Adapter)的使用以及对MVC的理解。代码尽量做到简单,为的是演示它的核心功能,不顾及界面。所以界面过丑,勿喷~~ ### 1.ArrayAdapter
只需要我们提供数据,即可以显示。显示的方式使用Android提供的布局–e.g:android.R.layout.simple_expandable_list_item_1
。还有其他的官方提供的布局,可以修改一下试一试。
simple_list_item_1 : 单独一行的文本框 simple_list_item_2 : 两个文本框组成,要调整数据。 simple_list_item_checked : 每项都是有一个勾选框的列表项 simple_list_item_multiple_choice : 都带有一个复选框 simple_list_item_single_choice : 都带有一个单选钮
<?xml version="1.0" encoding="utf-8"?> |
<?xml version="1.0" encoding="utf-8"?> |
protected void onCreate(Bundle savedInstanceState) { |
1.除了通过数组外,我们还可以写到一个数组资源文件中:
比如:在res:arrays.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="myarray">
<item>语文</item>
<item>数学</item>
<item>英语</item>
</string-array>
</resources>
接着布局的listview属性设置下这个列表项:
<ListView
android:id="@id/list_test"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:entries="@array/myarray"/>
就可以了~
当然我们也可以在Java代码中这样写: ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.myarray,android.R.layout.simple_list_item_multiple_choice );
SimpleAdapter也是Android自己提供的一个Adapter适配器,它与ArrayAdapter不同的是ArrayAdapter需要使用Android自己定义的view布局文件,而SimpleAdapter则可以使用我们自己定义的布局文件。
首先,我们先来看SimpleAdapter的构造方法:来自【博客】 public SimpleAdapter(Context context, List<? extends Map<String, ?>> data, @LayoutRes int resource, String[] from, @IdRes int[]to)
第一个参数Context context是指当前的Activity,我们传入this即可。 第二个参数List<? extends Map
>是指传入的数据类型必须是List集合,集合存放的数据类型必须是Map。 第三个参数int resource是指View的布局文件。传入我们的布局文件 第四个参数 String[]from数据是以Map类型存放在List集合中的,from参数是指存放在List中每条Map数据的键值集合。 第五个参数int[] to是指将每条Map类型的数据中的不同键值对应到不同的得布局控件中。
与ArrayAdapter的一样,只有一个ListView控件:
<?xml version="1.0" encoding="utf-8"?> |
由于使用了第三方开源控件,需要在build.gradle中引入依赖compile 'de.hdodenhof:circleimageview:2.0.0'
:
dependencies { |
<?xml version="1.0" encoding="utf-8"?> |
public class MainActivity extends AppCompatActivity { |
根据MVC的思想,先构造Model–数据:(新建数据类–People)
public class People { |
布局文件与上述一样。
自定义一个Adapter,让它继承自BaseAdapter,并且实现其抽象方法。 来自【博客】 >BaseAdapter中有四个抽象的方法:public int getCount(), public Object getItem(int position), public long getItemId(int position), public View getView(int position, View convertview,ViewGroup viewGroup),因此在继承BaseAdapter类后必须实现这四个方法。 >public int getCount(): 是用来返回数据的数量的。 >public Object getItem(int position): 该方法使用来获得每一条ListView中的Item的,这里我们返回position即可,position是指每条Item在ListView中的位置(0, 1, 2……)。 >public long getItemId(int position): 该方法是来获得ListView中每条Item的Id的,这里我们依然返回position即可。 >public View getView(int position, View convertview, ViewGroup viewGroup): 该方法是自定义Adapter最重要的方法,在这个方法中我们需要将数据一一对应的映射或者添加到我们自己定义的View中。然后返回view。
不需要强行记住这四个方法,使用AS的时候,当继承了BaseAdapter抽象类时,自动提醒你实现,所以,应该记住他们的功能是什么。
下面为ListViewAdapter的代码 public class ListViewAdapter extends BaseAdapter {
//定义数据
private List<People> peoples;
//定义Inflater,用来加载我们自定义的布局。
//inflater是泵的意思,生动形象
private LayoutInflater inflater;
private Context mContext;
//构造函数
public ListViewAdapter(List<People> peoples, LayoutInflater inflater, Context mContext) {
this.peoples = peoples;
this.inflater = inflater;
this.mContext = mContext;
}
public int getCount() {
return peoples.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
//1.创建viewHolder对象
ViewHolder viewHolder;
//2.获取viewHolder
//2.1 convertview为空时,先使用inflater加载布局,
// ViewHolder将显示在ListView中的数据通过findViewById获取到
if (convertView == null) {
viewHolder = new ViewHolder();
//从inflater中获取视图
convertView = inflater.inflate(R.layout.listview_item, parent, false);
//将视图的控件'赋值'给viewHolder来操作
viewHolder.circleImageView =
(CircleImageView) convertView.findViewById(R.id.tv_item_img);
viewHolder.tv = (TextView) convertView.findViewById(R.id.tv_item_text);
convertView.setTag(viewHolder);
} else
viewHolder = (ViewHolder) convertView.getTag();//2.2 不为空时通过Tag获取viewHolder
//3.获取到viewHolder,对值进行设置
viewHolder.circleImageView.setImageResource(peoples.get(position).getImg());
viewHolder.tv.setText(peoples.get(position).getPeoName());
return convertView;//返回视图
}
//ViewHolder内部类
class ViewHolder {
CircleImageView circleImageView;
TextView tv;
}
}
完成了数据类和适配器Adapter之后,就可以编写代码了,相对于适配器的代码来说,Activity的代码就很简单了。无非就是初始化数据,获取ListView的实例,并且设置它的适配器。
public class MainActivity extends AppCompatActivity { |
OK~大功告成。偷懒了不少,很粗略地完成了ListView的学习。难点在于自定义Adapter。而自定义Adapter的难点在于getView的重写和优化【废话!】[优化之处在于使用内部类ViewHolder以及convertView]
看完是不是觉得很难以理解代码?为什么这样?为什么不是那样?刚开始看郭神的《第一行代码》时,也有这种感觉,不理解getView()方法是有什么用,不理解ListView的工作原理,云里雾里。
关于convertView可以参考以下的文章,这些是个人在理解原理时收集的文章,还请仔细阅读。这里结合自己的理解,理解总结getView()方法整个过程的使用,纰漏之处,还望指出~
特别重要的一点:Recycler中’回收’的是出现过又不可见的Item类型(ItemType)
- 第一次加载ListView时会先根据ListView的高度和宽度,将
layoutInflater
读取的布局创建视图,加载在ListView可见区域,对于不可见的Item暂时不加载进内存中(记得是内存)。- 比如下图中,有7个Item,会根据高度,加载每个Item的布局并创建视图。 3.使用convertView的原因在于从’布局读取’的内存方面进行优化。当Item1滑出可见区域,则被Recycler回收,该布局类型为Type1,并存在convertView当中,因为Item8的ItemType相同,均为Type1,这时候就不需要从layoutInflater中加载布局文件,而是利用convertView的View视图类型,重用布局,只更新Item8的数据,而不更新布局。
//也就是说如果布局文件中有一个多选框checkbox,该checkbox与数
//据无关,只和布局相关,在Item1中为勾选状态,Item1滑出保存在
//convertView中时,在加载Item8的时候,调用getView()返回的视
//图Viwe是Item8的数据以及Item1的勾选状态。因为它们的布局文件
//一样,会使得ItemType相同,重用布局。
- 所以convertView是从加载布局文件方面进行优化。
- 使用ViewHolder的原因是findViewById方法耗时较大,如果控件个数过多,会严重影响性能,而使用ViewHolder主要是为了可以省去这个时间。通过setTag,getTag直接获取View。至于里面的是否要用static修饰,关于是否定义成静态,跟里面的对象数目是没有关系的,加静态是为了在多个地方使用这个Holder的时候,类只需加载一次,如果只是使用了一次,加不加也没所谓!
- 根据1中所述,在使用convertView的时候需要注意:
- 不可以设置listView的layout_height为wrap_content。原因是: ListView根据高度来初始化加载Item的时候,不知道本身到底要定义成多高 所以需要调用多次getView方法来计算item的高度 来填充自己,所以要match_parent或者写成固定高度。
ListView经常需要展示图片,如果在滑动时对滑动过的每张图片都要加载,会比较占内存。推荐的优化方法是设置OnScrollListener,在滑动完成后再下载当前页面的图片。
listView.setOnScrollListener(new AbsListView.OnScrollListener() { |
当ListView的item中有比如button这些子view时,需要对其设置onclickListener
,通常的写法是在getView方法中一个个设置,但是这种写法每次调用getView时都设置了一个新的onClick事件,效率很低。高效的写法可以直接在ViewHolder中设置一个position,然后viewHolder implements OnClickListenr
。
class ViewHolder implements OnClickListener{ |
如果ListView中的Item中有按钮等控件抢占了焦点的获取,我们是无法使用onItem(Long)Click两个方法的,解决的办法如下:
- 将Item的控件焦点获取设置为false
android:focusable="false"
当然,在代码中使用方法setFocusable(false)也可以达到同样的效果。- 对item的根节点进行设置:
android:descendantFocusability="blocksDescendants"
viewgroup会覆盖子类控件而直接获得焦点 该属性有3个可选值,
- beforeDescendants:viewgroup会优先其子类控件而获取到焦点
- afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
- blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点
虽然内容很多,但是ListView已经逐渐被新出的RecyclerView替代。学习它的原因是想了解Adapter的自定义,为RecyclerView的使用做准备,谁叫咱基础太薄弱呢~!!哈,为了我的第一个APP,下一个内容即将会是RecyclerView
控件的使用。
今天要为下学期的课程准备,打打酱油吧,感觉好多事要做啊啊啊啊啊。APP没有完成,JS也放下没有继续学习,就急急忙忙为下学期的《数据结构和算法》做准备,还有部门的官网任务,感觉这个假期是所有事情都不可能做好,哭。战线拉得太长了T-T
在看《算法导论》的时候,几个常见的算法分析符号把我难住了。(PS:讲的够快的,而且一堆定义,看到定义就头疼),经过百度后,略略能get到那个point,所以赶紧做笔记,以便以后温习,怕的是没几天就忘记了,白白浪费时间。
大O,Ω,Θ,主要用到的是这个符号,当然还有小o,小Ω(类似w)。下面的描述是摘自pake007的博客。
f (n) = O (g (n))代表g(n)是f(n)的一个上界,即f(n)的增长率小于等于(≤)g(n)的增长率,如n^2 = O(n^3),若f(n) = n^2, g(n) = 2n^2, 从而f(n) = O(g(n))也是正确的;
f (n) =Ω (g (n)) 代表g(n)是f(n)的一个下界,和O符号的意义正好相反,代表f(n)的增长率大于等于(≥)g(n)的增长率;
f (n) = Θ(g (n)) 代表两个函数以相同的速率增长,若f(n) = 2n^2,则写成f(n) = Θ(n^2)是最好的答案;
f (n) = o (g (n)) 和大O符号的意义基本相同,除了相等的情况外,即表示f(n)的增长率小于(<)g(n)的增长率.
由理可得,小Ω代表f(n)的增长率大于(<)g(n)的增长率。
OK,看完之后有点眉目。但主要还是大O,Θ这两个经常搞混。再经过看视频和百度,Google,F(n) = Θ(g(n)) 等价于 F(n) = O(g(n)) AND F(n) = Ω(g(n))
,并且在知乎发现了一张图,一图胜千言…所谓“增长率”和图中的“order”讲的是阶这个概念。
由图可以看出,大O是同阶或低阶,Θ是同阶。下面举几个例子。
n^2 = O(n3)—对的 n2 = Θ(n3)—错的 n2 = O(n2)—对的 n2 = Θ(n^2)—对的
为什么不讲Ω这个符号,其实在算法分析中很少会涉及到最优,很多时候考虑的是最坏的情况,而且,对算法的时间复杂度T(n),我们用符号大O来进行分析。
T(n):定义:如果一个问题的规模是n,解这一问题的某一算法所需要的时间为T(n),它是n的某一函数 T(n)称为这一算法的“时间复杂性”。
时间复杂度按数量级递增排列依次为:常数阶O(1)、对数阶O(log2n)、线性阶O(n)、线性对数阶O(nlog2n)、平方阶O(n2)、立方阶O(n3)、……k次方阶O(n^k)。
有了以上的概念,让我们看一些实例,一探究竟:来自学步园
O(n^2) |
2.4. |
Scanner input = new Scanner(System.in);
。不知道还记不记得上一篇文章讲到的“标准流”?传送门
当然可以从文件中读取。`Scanner input = new Scanner(new File(filename))
Scanner中常见的方法:
close() : void—关闭该Scanner hasNext() : boolean —判断是否还有更多数据读取 next(): String — 从该Scanner中读取下一个标记作为字符串返回 nextLine() :String —从该Scanner中读取一行,以换行结束 nextFloat() : float—从该Scanner中读取下一个标记作为float值返回(其他基本类型同理) useDelimiliter(String) :Scanner —设置Scanner的分割符,并且返回该Scanner
当Scanner创建的时候,从指定的文件中(包括字符串,控制台)中扫描标记。Scanner中很多方法都和标记
打交道,默认的标记是空格,可以使用上述的useDelimiliter()设置新的分隔符模式。这也可以在一定程度上解释了为什么当我们用空格分隔开输入的数据时,一般后面的数据不会被读取。
那怎么样才算是一个标记呢?无论前面有多少个分割符,都看成一个,且该数值以分隔符结尾。简言之,被分隔符包围的都算一个标记。而且,要注意next()和nextLine()的不要。虽然都是读取字符串,但是next()方法读取一个由分隔符分分隔的字符串,nextLine()读取一个以换行符结束的行。
import java.util.Scanner; |
运行结果为: > 该Scanner对象的分隔符为 —->+ 结果如下 这 是 一个 测试 字符串 该Scanner对象的分隔符为—->个 新的结果如下 这 是 一 测试 字符串
细心的应该会发现上述的方法中,有说到,“读取下一个标记作为float值返回”。对,是作为,不是读取为float值的标记,那如果该标记读取后无法转化为float类型怎么办?也就是如果标记和期望的类型不匹配。那就会抛出一个运行异常java.util.InputMismatchException
。
Scanner在使用之后一般要使用close()方法关闭,或者使用try-with-resources方法关闭,因为Scanner有实现AutoClosable接口。
// 举例:Scanner 类(来自 Java API 文档) |
这是本博客中第二篇关于Java IO
的文章,主要补充上一篇文章没有讲完的知识点–具体的’流’。上一篇链接:
先来召唤上次文章所用的图吧。
FileInputStream
继承自InputStream 和 FileOutputStream
继承自OutputStream.
这两个类类用于从/向文件读取/写入字节。它们的方法都是从InputStream和OutStream类继承的,没有引入新的方法。使用文件(File
)对象或文件路径字符串来作为参数进行实例构造。
// 文件输入流构造方法 |
如果试图为一个
不存在的文件
创建FileInputStream对象,将会发生java.io.FileNotFoundException
异常。
而对于FileOutputStream而言,如果要写入数据的File对象不存在,则会创建一个新的文件,如果文件存在,可选文件是否以追加形式写入。
注意小标题,它是单向的。有没有反向的? > 计算机都是以二进制来存储文件的。
文本文件在计算机以二进制存着,当然可以直接读,也就是说,不需要也没必要字符流转换为字节流。
它们的用处是什么? 为了记忆方便,在上一篇文章中我们说有’-er’的是处理文本文件数据,’Stream’是处理二进制文件数据。用来将字节数据以某种编码(如UTF-8)转换到字符数据。 转换流有InputStreamReader
(继承自Reader抽象类)和OutputStreamWriter
(继承自 Writer抽象类)。这两个也是常见的处理流
。 >- InputStreamReader 需要和 InputStream 抽象类的子类 “套接”(PS:废话!它处理的对象是“字节数据”,当然套接在它上面啦,而且input与input相对应。“套接”就意味着要以InputStream的子类实例为构造参数)。 >- 同理可得,OutputStreamWriter 需要和 OutputStream抽象类的子类 “套接”. >- 转换流在构造时可以指定其编码集合
,若不指定,默认为Unicode编码。
其中带有指定编码参数的InputStreamReader
对象的构造方法。 InputStreamReader(InputStream in, CharsetDecoder dec)
Creates an InputStreamReader that uses the given charset decoder.
FilterInputStream
,OutputStream
是过滤数据流的基类,它以及其子类都是处理流
。在上一篇博客中已经介绍过,处理流是在节点流或其他处理流的基础上建立的,相当于一个大水管(处理流)“套接”在了其他小水管上面。需要“套接”在InputStream和OutputStream类型的节点流上。字节流中不是已经有了吗,怎么还需要一个过滤流。存在即有理
。Filter Stream 是为某种目的过滤字节的数据流。
Data Stream 是数据过滤流(Filter Stream
)的其中一个子类。基本字节注入流提供的读取方法read()只能用来读取字节,而没有Java基本类型以及字符串–这就需要数据流(DataInputStream
/DataOutputStream
)了,它们提供的方法很方便。 试想,如果在读取文件数据的时候,你是不是读取出来之后还要进行基本类型转换?
但Filter Stream的一些子类有提供便捷的方法。存取的时候直接以该类型进行操作。比如: DataInputStream
和DataOutputStream
提供了存取与机器无关的Java原始类型数据(如:int,double等)的方法(readFloat()/writeFloat()等等),无需转换就可以从内存复制到输出数据流中。
DataOutputStream 将一个输入流的数据过滤成合适的基本类型值或者字符串。而DataOutputStream 将基本类型值或者字符串转换成字节并且输出字节到输出流中。这么看来,Data Stream 就像在程序和文件间的一个“转换器”。
Data Stream中还有几组有意思的方法。在这里总结一下,也可以提一下醒。
//读取 |
UTF-8是一种常用的字符编码,它是变长的,由统一码Unicode改进的编码。它根据字符的大小来使用1个字节,2个字节或者3个字节来存储字符。如果一个字符的编码值小于0X7F(即0111,1111 [逗号只是为了观看直观]),也就是ASCII码,则使用一个字节来存储。如果大于0X7F且小于等于0X7FF,则使用两个字节来存储。如果大于0X7FF则使用3个字节。UTF-8编码字符的起始几位标明这个字符是存储在一个字节两个字节还是三个字节中。如果是前4位是1110,则表明它是由3个字节序列组成的字符中的第1个字节。如果是0.则表明它是由一个字节组成的字符(ASCII),更多具体的请查阅资料~~
writeUTF(“ABCDEF”)写入文件的是8个字节(00 06 41 42 43 44 45 46 )[16进制表示],其中前两个字节用来存储字符串中的字符个数。
注意,在Java中,当用UTF-8来存储中文汉字的时候,每个汉字占3个字节,而GBK是两个字节 可以改动下面的代码看看,将“ABCDEF”改成“ABCDEF测试”,再观察结果,就明了了。
import java.io.DataInputStream; |
BufferedInputStream 和 BufferedOutputStream 类可以减少磁盘读写次数来提升输入和输出的速度。使用BufferedInputStream时,磁盘上的整块数据一次性地读入到内存中的缓冲区。然后从缓冲区中将个别数据传递到程序中。使用BufferedOutputStream时,个别的数据首先写入到内存中的缓冲区中,当缓冲区已满时,缓冲区中的所有数据一次性写入到磁盘中。
构造方法 >- 1.BufferedInputStream(InputStream in) Creates a BufferedInputStream and saves its argument, the input stream in, for later use. >- 2.BufferedInputStream(InputStream in, int size) Creates a BufferedInputStream with the specified buffer size, and saves its argument, the input stream in, for later use.
如果不指定缓冲区大小,默认大小为512个字节。不管对于多大的文件,都应该使用缓冲区I/O来加速输入输出。 ### 对象流 – Object Stream
对象类可以用来读写可序列化的对象,所谓可序列化对象是指该类实现了可序列化Serializable接口,这样可以将对象直接转换为字节流。
serializable “可序列化的”。它是一个接口,一种标记接口,因为没有方法。实现这个接口可以启动Java的序列化机制,自动完成存储对象和数组的过程。在ObjectOutStream中完成将可序列化的对象写入文件,这个过程称为序列化
。在ObjectInputStream中完成将文件中的可序列化对象读取出来,这个过程称为反序列化
。 当储存一个可序列化对象时,会对该对象的类进行编码。编码包括类名、类的签名、对象实例变量的值以及该对象引用的任何其他对象的闭包
,但是不存储对象静态变量的值。
externailzable 接口 Serializable的子接口,它的作用是让程序员自定义序列化过程。已经超出本人目前能力之外,不予讨论。
已经知道,对象的静态变量的值不会存储。如果一个对象是Serializable的实例,但它包含了非序列化的实例数据域,那么就不可以序列化这个对象。一个对象保证能够被序列化,就要必须保证其数据域都可被序列化。还有一种方法是transient
关键字,它的作用是告诉Java虚拟机将对象写入流的时候,忽略这些数据域。
来自《Java语言程序设计(基础篇)》 如果一个对象不止一次写入对象流,会存储对象的多个副本吗?答案是不会。第一次写入一个对象的时候,就会为它创建一个序列号。Java虚拟机将对象的所有内容和序列号一起写入对象流中。以后每次存储时,如果再写入相同的对象,就只存储序列号。读出这些对象时,它们的引用相同,因为在内存中实际上存储的只是一个对象。
System.in 是“standard” InputStream(标准输入流),这里的InputStream是父类指向子类引用对象(多态性
),其实System.in是BufferedStream的一个实例。也就是InputStream的一个子类。可以把它看成获取键盘的输入。 public class JavaIO {
public static void main(String[] args) {
System.out.println(System.in.getClass().getName());
}
}
运行结果: java.io.BufferedInputStream
为什么是BufferedInputStream?在我迷惑的时候,去翻过Java API文档,但找不到答案。最终还是在StackOverFlow
上找到了答案。其实“答案”在Java源码中可以看得到。
用NetBeans
或者其它IDE,ctrl+点击“in”进入’System.java’,当然其他方式也可以。
- public final static InputStream in = null;
- private static native void setIn0(InputStream in);
- FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
- setIn0(new BufferedInputStream(fdIn));//就是在这里
PrintWriter 和 PrintStream都属于输出流,分别针对字符和字节。 两者都提供了重装的print PrintWriter和PrintStream的输出操作不会抛出异常,用户通过检测错误状态获取错误信息。(方便)?什么来的 PrintWriter和PrintStream有自动flush的功能。
PrintfWriter的构造方法有点特殊。它可以有Writer的子类或者OutputStreamd的子类作为构造参数。所以它也是一种处理流。
注意:System.out是PrintfStream类型。System.in是InputStream类型。System.out.print()默认的输出是DOS窗口(标准输出),但如果在方法中使用了System.setOut(PrintfStream),会将默认的输出改变成设置的PrintStream目标,而不再是DOS窗口(屏幕)。屏幕也是一种’文件’
例子 //用print流实现将test.txt文件中的字符串打印到new.txt中
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.util.Scanner;
public class JavaIO {
public static void main(String[] args) {
File mFile = new File("D:\\test.txt");
File mNewFile = new File("D:\\new.txt");
Scanner in;
try {
PrintStream mOut = new PrintStream(mNewFile);
in = new Scanner(mFile,"UTF-8");
while (in.hasNext()) {
System.out.print(in.nextLine());
}
in.close();
in = new Scanner(mFile,"UTF-8");
System.setOut(mOut);
while (in.hasNext()) {
System.out.println(in.nextLine());
System.out.println("这句话也会在new.txt文件中");
}
in.close();
}
catch (FileNotFoundException ex) {
System.out.println("找不到文件");
}
}
}
这个是什么东西?其实,使用它来声明和创建输入输出流,从而在使用后可以自动关闭。 我们都知道,Java I/O操作都会抛出异常,需要我们进行关闭。而我们也知道,在操作完流之后,需要使用close()方法将它关闭。不关闭流
可能会在输出文件中造成数据受损,或者导致其他的程序出错。但除了这种方法之外,还有一种简洁的方法。就是try-with-resources
。
// 方法一 |
//方法2 try-with-resources |
来自《Java语言程序设计(基础篇)》 程序使用了
try-with-resources
来声明和创建输入输出流,从而在使用后可以自动关闭。java.io.InputStream和java.io.OutputStream实现了AutoClosable接口
。该接口定义了close()方法,用来关闭资源,任何AutoClousable类型的对象都可以用于try-with-resources语法中,实现其自动关闭。
其实实现该接口的还有Scanner,Reader,Writer。目前接触涉及I/O的类都有close()方法,都有实现AutoClosable接口。
花了3天的时间来写关于I/O的,那感觉,满脑子都是’流’啊,’Scanner’等等的概念。估计要疯了。。。因为实在是太多了,整理的过程中总要想着如何排版更加合理,怎么说才能尽可能地述说完整。。。加上自己是菜鸟,总怕说错,不断地查文档和搜索,结果下来,发现还有很多琐碎的关于I/O的知识点等着T^T,还是加油吧~~!其实有很多类我们只需要了解就好,常见的I/O操作也就Scanner,Object Stream,Data Stream,File Stream,Buffered Stream.那为什么要将其他的像print流。 > 因为它在那里。
(PS:这篇文章那么长,难免会出错。如果哪里有错还望指出,记得轻拍。)
]]>声明:由于I/O操作的Scanner
类不在java.io包内,不属于文中的主角–‘流类型
’,故留着最后,防止解说过于混乱
java.io
、java.util
(Scanner这个特殊的存在)
文件
文件就是字节序列。每一个I/O设备,包括磁盘,键盘,显示器,甚至网络都可以视为文件。–摘自《CSAPP》
至于在文件中,文本文件和二进制文件的区别,可以看上一篇文章。
流
在Java的I/O操作
中,流是一个很重要的概念。在计算机的基础课程中,有学习过数据流
,控制流
等,这是一种类比的思想,将Java的I/O操作更加形象地描述出来。在视频或者书籍中,都喜欢用’流’来描述文件读取
的过程。为了便于理解和记忆,姑且可以把’流’看成’水流’,而涉及’I/O操作的类’看成’管道’,‘文件和程序’看出’水池’。
java.io
包中定义了多个流类型
(类或抽象类)来实现输入/输出功能。在刚开始学习Java
的I/O操作
时,着实给那么多的类和方法吓到了。也有时搞不清楚,为什么一些类的构造方法是其他的I/O类为参数,而一些类的构造方法是File类
或者文件路径字符串
为参数如果不细细地梳理,可能在使用的时候就会很混乱。(虽然可以查找API 文档
)。
那我们开门见山吧~其实很简单。[请紧记于心] 就两个"大类"--文本I/O和二进制I/O。
最后的单词是'-er'的(包括Scanner)是处理文本文件数据,'-Stream'可以处理二进制文件数据**以及文本数据**。
为什么这样分?!
SDK 所提供的所有流类型位于包java.io内都分别继承自以下4种抽象流类型。
流的类型 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
[四种抽象流类型]
抽象类
InputStream
是读取二进制数据的根类,抽象类OutputStream
是写入二进制数据的根类。
字节流一般处理的是二进制文件(包括文本),而字符流处理的是文本文件。 再来看一副图。这幅图替我们总结了IO操作中的IO’流’类。这只是常见的,并不是所有。请注意
仔细看图中的类,可以看到I/O处理的类大部分是对称的。也就是大部分4个一起出现。
当然,也可以从不同的角度对其进行分类,关键是怎么记忆怎么来。比如: 按照数据流的方向不同可以分为输入流和输出流
按照处理数据单位不同可以分为字节流和字符流
按照功能不同可以分为节点流和处理流
先来解释这3种不同分法的’流’。
输入流
和输出流
(Input/Output Stream)输入流和输出流 在程序和文件当中,输入流和输出流是相对的。当读取文件的时候,从程序的角度看,文件数据是输入流,从文件的角度看则是输出流。输入输出是相对于参考体而已的,远离则为输出。
字节流
和字符流
(byte Stream/character Stream)为什么存在字节流还需要字符流?因为存在二进制文件和文本文件。 ### 节点流
和处理流
看到节点流
和处理流
,应该知道为什么一些类的构造方法是其他的I/O类为参数,而一些类的构造方法是File类
或者文件路径字符串
为参数了吧?
以“程序以FileReader
类读取文件”的过程为例。按照前面所说的思维方法,把’流’看成’水流’,而涉及’I/O操作的类’看成’管道’,‘文件和程序’看出’水池’。
1.文件读取流,首先是利用文件的地址字符串或者文件(File)对象,进行建立,成为了节点流。
这个过程可以看作是:
要从一个水池A(文件)中取水到另外水池B(程序运行时分配的内存空间),首先把小的管道(‘节点流’类型的类,FileReader)插在水池的出水口。这个插的过程相当于构造一个’节点流’。所以,FileReader 对象的构造方法中要有File 对象 或者 “文件路径的字符串”。这时就是
节点流
。
FileReader的3个构造方法: |
2.而为了更加方便地读写数据和操作文件(因为处理流提供了很多好用的方法,如BufferedReader的readLine()可以读取一行数据),引入了处理流。处理流的构造方法参数为节点流或者处理流的子类。
这个过程可以看作是: > 在取水的时候,发现水中有杂质。想要它先缓冲在一个地方进行过滤、沉淀,等到一定的水量之后再一次输入到水池B中。这时我需要一个带有过滤头并且具有缓冲功能的大水管(‘处理流’类型的类,BufferReader)。所以,BufferedReader中的构造参数要有’节点流’对象。这时候就是’处理流’。
BufferedReader的2个构造方法: |
public class JavaIO { |
运行结果 |
总结一下,因为某一些流是处理流,是建立在其他节点流或处理流的基础之上,(相当于在节点流的“管道”基础上再套接一个管道),所以构造方法需要某个’节点流’或’处理流’类型的对象为构造参数。从程序中可以看到,更加方便地读写数据和操作文件。
处理流
字节流-输入流 ObjectInputStream(对象流),SequenceInputStream(合并流),FilterStream和它的子类(BufferedInputStream,DataInputStream等)。
字节流-输出流 ObjectOutputStream(对象流),FilterStream和它的子类(BufferedOutputStream,DataOutputStream等)。
字符流-输入流 BufferedReader,InputStreamReader(转换流),FilterReader和它的子类。
字符流-输出流 BufferedWriterer,InputStreamWriter(转换流),FilterWriter和它的子类。
它们还是有一定的规律性的,比较对称。要查更多,请参照Java官方的API文档
。要记住全部也不现实,但要记得一点。处理流是在节点流或处理流的基础上构造的,它可以(不是必须)用“节点流”对象做构造参数。
从两大类–文本I/O
和二进制I/O
来梳理’流’。并从大的方向区分了各式各样的’流类型’,分享了记忆方法。 > 二进制I/O类中的所有方法都声明为抛出java.io.IOException或其子类。
【还有…】 因为IO操作的内容太庞大了,所以只能将它们分隔开来。这一篇主要从大的分类方向梳理I/O处理的“流”,第二篇会继续更新常见的具体’流’类,第三篇将更新Scanner的知识,比如工作原理。
]]>在《理清Java异常(1)》中,我们简单地说了Java必检异常的处理方式。主要有两种处理(但解决异常的方法主要有一种,就是try-catch)。怎么知道是必检异常?查看Java API文档中类的继承,除了Error和RunTimeException及它们子类,其他Exception类就是必检异常。
- 声明和抛出异常
- try-catch块捕获异常
注意,还要区别“处理”这个词的上下文或者说意境。其实说两种处理方式并不怎么准确,它可能会混淆视听。(PS:好吧,当我咬文嚼字吧-.-!!)
当方法B调用会抛出异常的方法C,C出错抛出异常时,方法B使用try-catch捕获到异常,它有两种处理手段,可以选择在catch语句块中继续抛出(throw)异常(补充:’继续抛出’也可以不使用try-catch,直接声明异常即可),让B的调用方法A用try-catch解决异常,也可以在方法B的catch语句块中解决异常。
简而言之,异常只需要“解决”一次,当一个异常对象被捕获并被解决时,它会“消失”。但一个异常对象可以有两种“处理手段”,1.继续抛出,2.解决异常。 以上“咬文嚼字”的过程涉及三个操作–1.声明异常,2.抛出异常,3.捕获异常。 #### 代码实例 import java.io.IOException;//引入需要的包
import java.io.File;
import java.io.FileInputStream;
public void A() {
try{
B();
}catch(IOException e){
// B选择的处理方法是抛出给A方法处理,A
System.out.println(e);
}
}
public void B() throws IOException/*声明IO异常,关键字throws*/{
try{
File mFile = new File("myFile.dat");
FileInputStream input = new FileInputStream(mFile);
//方法C
//IO其他操作
input.close();
}
catch(IOException e){
//1.继续抛出,关键字throw
//如果该方法无法解决该异常,或者只是简单地希望它的
//用者(方法A)注意到该异常,可以抛出异常让A解决
throw e;
//2.解决异常,这里选择直接打印
//System.out.println("出错+"e);
}
}
每个方法都必须声明它可能抛出的必检异常的类型。这样,方法的调用者会被告知有异常抛出,必须处理(注意是’处理’哦,不是’解决’)。这也是为什么在使用某些对象的方法时,IDE会报错,一定要你处理抛出的异常。
检测到错误的程序可以创建一个合适的异常类型对象,并把它抛出,这就称为抛出异常。(一般使用if语句判断,对于无法处理的就抛出异常。问题1?在文末讲)
注意是抛出对象,所以使用 throw new IllegalArgumentException("*")
抛出异常对象。Java API 中有一些方法已经throw 异常,则不需要我们在自己的调用方法中手动抛出,但是,如果在一个声明了throws 异常的方法中,没有调用到有throw异常的方法,必须要手动抛出异常,即实例化一个异常对象并抛出。否则,在方法头声明异常的方法会报错。
一般来说,Java API中的每个异常类至少有两个构造方法 : 一个无参构造方法和一个带可描述这个异常的String参数的构造方法,该参数也称为 异常信息 。
异常信息可以使用异常对象的
getMessage()
获取。【见代码部分】
使用try语句块包含可能出错的方法,使用catch语句块捕获可能出现的异常。只有catch语句块中的异常对象相匹配,才会执行catch语句块中的语句。
注意:catch异常对象只需声明引用,如Exception e;这样我们就可以直接使用了。那对象的初始化呢?异常在Java运行时系统抛出异常对象时已帮我们自动初始化对象,即’new Exception()’。
finally 关键字
无论异常是否产生或者是否被捕获,finally子句总是会执行。finally 语句为异常处理提供了一个统一的出口,使得在控制流程转到程序的其他部分之前,能对程序的状态作统一的管理
通常在finally语句中可以进行资源的清除工作,如: - 关闭打开的文件 - 删除临时文件 - ……
关于try-catch-finally 捕获异常的执行过程详见 捕获异常的执行过程
public class Exc { |
运行结果如下: > 正常 构造函数的参数为:出错!半径小于0 java.lang.IllegalArgumentException: 出错!半径小于0
上面在讲抛出异常的时候遗留了一个问题。相信有一部分人在看到使用if语句判断然后抛出异常,都会认为,既然可以判断了,为什么不直接将异常解决,还要抛出给调用方法呢?(答案在第三点)这其实也是我在第一次在Java课堂上接触了异常处理时候的一个疑惑。但在书中找到了答案(所以说,书还是要看,不能卷起袖管就开撸代码~),书中讲得很清楚,而且还包括了很多要点。在理解的基础上整理的要点如下。
- 异常出现在方法中,如果想让该方法的调用者解决异常,应该创建一个异常对象并将其抛出。如果能在发生异常的方法解决异常,那么就不需要抛出或使用异常了。(这已经在上面的“咬文嚼字”详细地说明)
- 一般来说,一个项目中多个类都会发生的共同异常应该考虑作为一种异常。
- 什么时候应该抛出异常? 当错误需要被方法的调用者处理(不一定是’解决’)的时候,方法应该抛出异常。为什么?以上面设置圆的半径为例,在用户输入圆的半径的时候,构造方法调用setRaidus()方法。假设某个调皮捣蛋的用户,或者说是闲着蛋疼没事干的用户,输入了小于0的数字,setRadius()方法会出现错误,因为半径不可以小于0。当然可以在setRadius()方法中处理,处理的方法有很多种,比如说1.if语句判断,然后打印出错提示信息。2.try-catch语句解决异常。 当然可以这么解决。但如果构造方法中,紧紧跟着调用依赖于setRadius()的其它方法,那构造方法会接二连三地出错。为什么?因为被调用方法setRadius()在构造方法不知情的情况下中断了程序的正常运行。所以,setRadius()要做的事是,判断传入的参数是否符合要求,如果不符合,应该使用抛出异常告知调用方法,即构造方法,让构造方法去处理–继续抛出或者解决。当然,如果构造方法Exc()无法解决(同上所说),就要继续抛出异常让main方法继续处理
- 什么时候使用try-catch?当处理不可意料的错误状况时应该使用。(尤其在网络环境的文件读写中,会出现断网,网络拒绝请求,请求文件不存在等不可预料的事件,这是程序员无法控制的)。不要用try-catch块处理简单的,可预料的情况。
如何理解要点的第四点呢? //伪代码
if(setRadius() throws IllegalArgumentException)
return null;
假设第3点中,setRadius()抛出的异常可以在Exc()内部自己处理,不需要抛出异常给main方法,那这一段代码不需要try-catch,因为太简单了,可以预料。那执意要使用try-catch呢?虽然说try-catch使得错误处理的代码和正常的代码分离,更易懂。但是 > 由于异常处理需要初始化新的异常对象,需要从调用栈返回,而且还需要沿着方法调用链来传播异常以便找到它的异常处理器,这个过程需要更多的内存和时间。
重写方法需要声明的异常和原方法声明的异常类型一致或不抛出。当一个类内的某方法没有声明异常时,不能在其子类的该方法重写声明异常。
和其他异常一起抛出一个异常,构成链式异常
有时候我们需要在新异常的基础上,对原始异常进行信息附加,这样就可以产生一个 链式异常
public class Exc { |
运行结果(行数和包名不一定一样): > java.lang.Exception: info from method at exc.Exc.method1(Exc.java:21) at exc.Exc.main(Exc.java:11) Caused by: java.lang.Exception: info from method2 at exc.Exc.method2(Exc.java:25) at exc.Exc.method1(Exc.java:18) … 1 more
从程序代码中可以看到,链式的异常主要是利用Excepion的构造方法,将导致的原因“Caused by…”作为参数传入,然后附加成新的异常再抛出。
ee.printStackTrace(); |
这段代码用来打印堆栈信息。可以查看错误的抛出时的“路径”,经过哪些方法,抛出错误的源头在哪里。这个方法对于编写程序员调试出现异常的程序十分有用。与Java运行时系统自身抛出的异常信息十分相似。
一个方法内部调用有抛出异常的方法时,或手动抛出异常时,必须做出处理。处理的方式有两种:1.继续向调用方法抛出异常 2.捕获异常并解决
main方法也可以不捕获并解决异常,直接抛出。这时是抛给Java运行时系统,异常信息会被打印并终止程序的运行。也就是我们一般见到的情况。
为什么要写这篇博文?在完成Java面向对象程序设计的综合性实验过程中,使用到了文本IO和二进制IO。写过这部分代码的人都知道,IO难免会跟异常
打交道。但在完成之后,却迷迷糊糊的,在需要处理异常的时候,总是按照IDE(使用的是NetBeans
)的提示来处理异常,却不知道为什么要这样,为什么不要这样。很不喜欢这感觉,所以,就逮住这个机会来“更新”脑中的知识。哈哈,不知道我能将异常学到几成,又能清楚地说出几成。说得不对的地方,还希望看到这篇文章的人能够好心指出,谢谢~
参考资料 > 1.Java语言程序设计(基础篇)
2.尚学堂马士兵的Java视频教程
异常是程序在运行时的错误。
设计良好的程序应该在异常发生的时候提供处理这些错误的方法,使得程序不会因为异常的发生而阻断或者产生不可遇见的结果。
还有一个好处在于,程序员应该对可能抛出的异常进行处理,而不是让异常直接抛出给使用的用户,专业的异常术语可能导致用户不好的体验,所以应该进行处理,可以进行更加易懂或者说是友好的提示。
打开Java API的异常类,即Throwable
类,可以看到有两个直接的子类(为什么是直接的子类?因为用户可以自定义自己的异常类)
Error
(系统错误) 不需要处理,也处理不了。描述的是内部系统的错误,由Java虚拟机抛出,很少会发生,一旦发生,只能通知用户以及尽量稳妥地终止程序。Exception
(异常) 错误或异常能被程序捕获和处理。描述的是程序和外部环境所引起的错误。
在Exception
中,为什么要特意说明RunTimeException
?让我们来理清一下吧。 > Error和Exception的子类–RunTimeException 以及它们的子类都称为免检异常
,在程序中的任何一个地方都可能会出现,为避免过多使用try-catch块,不要求在代码中强制处理(有两种方式,try-catch块或者在方法头throws声明并在方法中抛出)。
前者Error是因为“系统出错,无法处理”的原因免检,而后者RunTimeException是因为在程序中的任何地方都可能出现,如果都处理,会增加许多代码(Error免检也有这个原因),试想,如果每次数组访问或者算术运算都必须处理,那还不累死人?!所以,Java语言不强制要求编写代码捕获
或声明异常
。但其他异常则需要处理。
总结 >- 异常是程序在运行时的错误 >- Throwable是所有异常的父类。其有两个直接的子类–Error和Exception,其中Exception中有一个特殊的子类–RunTimeException,它和Error是免检异常
,Java不强制要求处理免检异常。