-
Notifications
You must be signed in to change notification settings - Fork 0
/
search.xml
194 lines (194 loc) · 168 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[【Node教程】- Node的readline (逐行读取)]]></title>
<url>%2Fposts%2Fdb1ef6b9%2F</url>
<content type="text"><![CDATA[【Node教程】- Node的readline (逐行读取).note-content{font-family:微软雅黑,'Helvetica Neue',Arial,'Hiragino Sans GB',STHeiti,'Microsoft YaHei','WenQuanYi Micro Hei',SimSun,Song,sans-serif}.note-content code,.note-content h1,.note-content h2,.note-content h3,.note-content h4,.note-content h5.div,.note-content p,.note-content pre{line-height:2}【Node教程】- Node的readline (逐行读取)readline 模块提供了一个接口,用于从可读流(如 process.stdin)读取数据,每次读取一行先来看这个基本示例:1.const readline = require('readline'); 2. 3.const rl = readline.createInterface({ 4. input: process.stdin, 5. output: process.stdout 6.}); 7. 8.rl.question('你叫什么名字?', (answer) => { 9. // 对答案进行处理 10. console.log(`你好:${answer}`); 11. 12. rl.close(); 13.}); 在命令行中执行node hello.js将出现如下结果:1.$ node hello.js 2.你叫什么名字?liu 3.早上好,liu 通过这个示例,我们可以发现readline模块可以方便的实现命令行的交互功能。实现如Node命令行工具开发【看段子】治疗的小应用下面我们先来了解readline的基本用法,然后再实现一个有趣的小功能。readline基本用法readline.Interface 类的实例是使用 readline.createInterface() 方法构造的。 每个实例都关联一个 input 可读流和一个 output 可写流。output 流用于为到达的用户输入打印提示,且从 input 流读取创建Readline实例1.readline.createInterface(options) 创建一个readline的接口实例. 接受一个Object类型参数,可传递以下几个值:input - 要监听的可读流 (必需)output - 要写入 readline 的可写流 (必须).completer - 用于 Tab 自动补全的可选函数。(不常用)terminal - 如果希望 input 和 output 流像 TTY 一样对待,那么传递参数 true ,并且经由 ANSI/VT100 转码。 默认情况下检查 isTTY 是否在 output 流上实例化。(不常用)方法rl.close() 关闭接口实例 (Interface instance), 放弃控制输入输出流。”close” 事件会被触发rl.pause()暂停 readline 的输入流 (input stream), 如果有需要稍后还可以恢复。rl.prompt([preserveCursor]) 为用户输入准备好readline,将现有的setPrompt选项放到新的一行,让用户有一个新的地方开始输入。将preserveCursor设为true来防止光标位置被重新设定成0。rl.question(query, callback) 预先提示指定的query,然后用户应答后触发指定的callback。 显示指定的query给用户后,当用户的应答被输入后,就触发了指定的callbackrl.resume()恢复 readline 的输入流 (input stream).rl.setPrompt(prompt) 用于设置每当 rl.prompt() 被调用时要被写入到 output 的提示。rl.write(data[, key]) 把 data 或一个由 key 指定的按键序列写入到 output事件line事件 :在 input 流接受了一个\n 时触发,通常在用户敲击回车或者返回时接收。 这是一个监听用户输入的利器。pause事件: 输入流被暂停就会触发。 同样当输入流未被暂停,但收到 SIGCONT 也会触发。 (详见 SIGTSTP 和 SIGCONT 事件)resume事件:只要输入流重新启用就会触发close事件:当 close() 被调用时触发。 当 input流接收到end事件时也会被触发. 流接收到表示结束传输的 <ctrl>-D,收到表示 SIGINT 的 <ctrl>-C,且 readline.Interface 实例上没有注册 SIGINT 事件监听器。更多请参考node中文网readline 实列现在我们来实现一个命令行可交互的百度搜索1.使用readline实现一个可交互的命令行1.// 先来实现一个可交互命令行 2.const rl = readline.createInterface({ 3. input: process.stdin, 4. output: process.stdout, 5. prompt: 'search>>> ' 6.}) 7. 8.rl.prompt() 9. 10.rl.on('line', (line) => { 11. console.log(line) 12. rl.prompt() 13.}).on('close', () => { 14. console.log('再见!') 15. process.exit(0) 16.}) node index.js运行这段代码出现可交互的命令行,等待我们输入,输入完成回车后会打印出输入的值2.使用http模块发起请求这里为了加深对原生http模块的理解我们没用第三方模块,有很多好用的第三方模块如:request、superagent1.... 2.function search(words, callback) { // es6默认参数 3. let options = { 4. hostname: 'www.baidu.com', 5. port: 80, 6. path: `/s?wd=${encodeURI(words)}`, 7. method: 'GET' 8. } 9. 10. const req = http.request(options, (res) => { 11. // console.log(`STATUS: ${res.statusCode}`) //返回状态码 12. // console.log(`HEADERS: ${JSON.stringify(res.headers, null, 4)}`) // 返回头部 13. res.setEncoding('utf8') // 设置编码 14. let body = '' 15. res.on('data', (chunk) => { //监听 'data' 事件 16. body+=chunk 17. }) 18. res.on('end', ()=>{ 19. let $ = cheerio.load(body) 20. $('.t a').each(function(i, el){ 21. console.log($(this).text(), $(this).attr('href'),'\n') 22. }) 23. callback() 24. }) 25. }) 26. req.end() // end方法结束请求 27.} 28. 29.... 这里我们用http模块发起客户端请求,并打印出来搜索结果的标题与链接,如果你忘记了http模块的使用可以回头查看了解并使用Http模块,http模块是node相当重要的模块,后续我们还将继续学习。这里我们用到了cheerio具体使用可参考其官网3. 实现命令行搜索到此,我们只需将命令行的代码与http请求的代码整合起来就可以完成这个小项目了1.... 2.rl.on('line', (line) => { 3. search(line.trim(), ()=>{ 4. rl.prompt() 5. }) 6.}).on('close', () => { 7. console.log('再见!') 8. process.exit(0) 9.}) 10.... 现在在快命令行中试试我们一起完成的这个小案例吧!!!相关链接Node中文网]]></content>
<categories>
<category>Node</category>
</categories>
<tags>
<tag>Node</tag>
<tag>后端,服务端</tag>
</tags>
</entry>
<entry>
<title><![CDATA[【Node教程】- Node中的stream (流)]]></title>
<url>%2Fposts%2Ff00fe664%2F</url>
<content type="text"><![CDATA[【Node教程】- Node中的stream (流).note-content{font-family:微软雅黑,'Helvetica Neue',Arial,'Hiragino Sans GB',STHeiti,'Microsoft YaHei','WenQuanYi Micro Hei',SimSun,Song,sans-serif}.note-content code,.note-content h1,.note-content h2,.note-content h3,.note-content h4,.note-content h5.div,.note-content p,.note-content pre{line-height:2}【Node教程】- Node中的stream (流)流(stream)在 Node.js 中是处理流数据的抽象接口(abstract interface)。 stream 模块提供了基础的 API 。使用这些 API 可以很容易地来构建实现流接口的对象。Node.js 提供了多种流对象。 例如, HTTP 请求 和 process.stdout 就都是流的实例。流可以是可读的、可写的,或是可读写的。所有的流都是 EventEmitter (Node事件机制将在后续讲解,可以先自行了解)的实例。尽管所有的 Node.js 用户都应该理解流的工作方式,这点很重要, 但是 stream 模块本身只对于那些需要创建新的流的实例的开发者最有用处。 对于主要是消费流的开发者来说,他们很少(如果有的话)需要直接使用 stream 模块。1.了解Node Stream(流)数据流(stream)是处理系统缓存的一种方式。操作系统采用数据块(chunk)的方式读取数据,每收到一次数据,就存入缓存。Node应用程序有两种缓存的处理方式,第一种是等到所有数据接收完毕,一次性从缓存读取,这就是传统的读取文件的方式(遇上大文件很容易使内存爆仓);第二种是采用“数据流”的方式,收到一块数据,就读取一块,即在数据还没有接收完成时,就开始处理它(像流水一样)Node.js 中有四种基本的流类型:Readable - 可读的流 (例如 fs.createReadStream()).Writable - 可写的流 (例如 fs.createWriteStream()).Duplex - 可读写的流 (例如 net.Socket).Transform - 在读写过程中可以修改和变换数据的 Duplex 流 (例如 zlib.createDeflate()).所有的 Stream 对象都是 EventEmitter 的实例常用的事件有:data - 当有数据可读时触发。end - 没有更多的数据可读时触发。error - 在接收和写入过程中发生错误时触发。finish - 所有数据已被写入到底层系统时触发。可读流(Readable streams)可读流(Readable streams)是对提供数据的 源头 (source)的抽象可读数据流有两种状态:流动状态和暂停状态。处于流动状态时,数据会尽快地从数据源导向用户的程序(就像流水一样);处于暂停态时,必须显式调用stream.read()等指令,“可读数据流”才会释放数据,(就像流水的闸门,打开它水才继续流下去)可读流在创建时都是暂停模式,暂停模式和流动模式可以互相转换。要从暂停模式切换到流动模式,有下面三种办法:给“data”事件关联了一个处理器显式调用resume()调用pipe()方法将数据送往一个可写数据流要从流动模式切换到暂停模式,有两种途径:如果这个可读的流没有桥接可写流组成管道,直接调用pause()如果这个可读的流与若干可写流组成了管道,需要移除与“data”事件关联的所有处理器,并且调用unpipe() 方法断开所有管道可读流常用事件:readable:在数据块可以从流中读取的时候发出。它对应的处理器没有参数,可以在处理器里调用read([size])方法读取数据。data:有数据可读时发出。它对应的处理器有一个参数,代表数据。如果你只想快快地读取一个流的数据,给data关联一个处理器是最方便的办法。处理器的参数是Buffer对象,如果你调用了Readable的setEncoding(encoding)方法,处理器的参数就是String对象。end:当数据被读完时发出。对应的处理器没有参数。close:当底层的资源,如文件,已关闭时发出。不是所有的Readable流都会发出这个事件。对应的处理器没有参数。error:当在接收数据中出现错误时发出。对应的处理器参数是Error的实例,它的message属性描述了错误原因,stack属性保存了发生错误时的堆栈信息。可读流还提供了一些方法,我们可以用它们读取或操作流:read([size]):该方法可以接受一个整数作为参数,表示所要读取数据的数量,然后会返回该数量的数据。如果读不到足够数量的数据,返回null。如果不提供这个参数,默认返回系统缓存之中的所有数据。setEncoding(encoding):给流设置一个编码格式,用于解码读到的数据。调用此方法后,read([size])方法返回String对象。pause():暂停可读流,不再发出data事件resume():恢复可读流,继续发出data事件pipe(destination,[options]):绑定一个 Writable 到 readable 上, 将可写流自动切换到 flowing 模式并将所有数据传给绑定的 Writable。数据流将被自动管理。这样,即使是可读流较快,目标可写流也不会超负荷(overwhelmed)unpipe([destination]):该方法移除pipe方法指定的数据流目的地。如果没有参数,则移除所有的pipe方法目的地。如果有参数,则移除该参数指定的目的地。如果没有匹配参数的目的地,则不会产生任何效果可写流(Writable streams)Writable streams 是 destination 的一种抽象,这种 destination 允许数据写入write方法用于向“可写数据流”写入数据。它接受两个参数,一个是写入的内容,可以是字符串,也可以是一个stream对象(比如可读数据流)或buffer对象(表示二进制数据),另一个是写入完成后的回调函数,它是可选的,write方法返回一个布尔值,表示本次数据是否处理完成可写流常用事件drain writable.write(chunk)返回false以后,当缓存数据全部写入完成,可以继续写入时,会触发drain事件finish 调用end方法时,所有缓存的数据释放,触发finish事件。该事件的回调函数没有参数pipe 可写数据流调用pipe方法,将数据流导向写入目的地时,触发该事件unpipe 可读数据流调用unpipe方法,将可写数据流移出写入目的地时,触发该事件error 如果写入数据或pipe数据时发生错误,就会触发该事件可写流常用方法write() 用于向“可写数据流”写入数据。它接受两个参数,一个是写入的内容,可以是字符串,也可以是一个stream对象(比如可读数据流)或buffer对象(表示二进制数据),另一个是写入完成后的回调函数,它是可选的。cork(),uncork() cork方法可以强制等待写入的数据进入缓存。当调用uncork方法或end方法时,缓存的数据就会吐出。setDefaultEncoding()用于将写入的数据编码成新的格式。它返回一个布尔值,表示编码是否成功,如果返回false就表示编码失败。end()用于终止“可写数据流”。该方法可以接受三个参数,全部都是可选参数。第一个参数是最后所要写入的数据,可以是字符串,也可以是stream对象或buffer对象;第二个参数是写入编码;第三个参数是一个回调函数,finish事件发生时,会触发这个回调函数。管道流管道提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中>(我们把文件比作装水的桶,而水就是文件里的内容,我们用一根管子(pipe)连接两个桶使得水从一个桶流入另一个桶,这样就慢慢的实现了大文件的复制过程)链式流链式是通过连接输出流到另外一个流并创建多个对个流操作链的机制。链式流一般用于管道操作。接下来我们就是用管道和链式来压缩文件创建 compress.js 文件, 代码如下:1.const fs = require("fs"); 2.const zlib = require('zlib') 3. 4.// 压缩 README.md 文件为 README.md.gz 5.fs.createReadStream('./README.md') 6. .pipe(zlib.createGzip()) 7. .pipe(fs.createWriteStream('README.md.gz')) 8. 9.console.log("文件压缩完成") 在fs中使用Stream在介绍完stream后我们用流来实现文件读取1.const fs = require("fs") 2. 3.//可读数据流 4.//========================================== 5.let data = '' 6.//创建可读流 7.let readerStream = fs.createReadStream('./README.md') 8. 9.// 设置编码为 utf8 10.readerStream.setEncoding('UTF8') 11. 12.// 处理流事件 --> data, end, and error 13.readerStream.on('data', function(chunk) { 14. data += chunk 15.}) 16. 17.readerStream.on('end',function(){ 18. console.log(data) 19.}) 20. 21.readerStream.on('error', function(err){ 22. console.log(err.stack) 23.}) 24. 25.console.log("程序执行完毕") 大致介绍了Node的Stream(流)很少需要直接使用 stream 模块,更多信息参考Node中文网下面我们就进入今天的实例小项目:实现文件的复制功能2.通过Stream实现文件复制功能本例参考 chshouyu:nodejs中流(stream)的理解Node的 fs 模块并没有提供一个 copy 的方法,但我们可以很容易的实现一个。我们先来看看在不知道流之前我们会怎么做:1.const fs = require('fs') 2.const file = fs.readFileSync('./README.md', {encoding: 'utf8'}) 3.fs.writeFileSync('./TEST.md', file) 这种方式是把文件内容全部读入内存,然后再写入文件,对于小型的文本文件,这没有多大问题。但对大文件来说要花很长时间,才能进入数据处理的步骤。甚至引起内存爆仓既然我们学了Stream,现在我们就用Stream来实现这个简单的功能:1. 简单实现文件的复制1.const fs = require('fs') 2. 3.let pathname = { 4. src: './copy.js', 5. dist: './test.js' 6.} 7. 8.let ReadStream = fs.createReadStream(pathname.src) 9.let WriteStream = fs.createWriteStream(pathname.dist) 10. 11.ReadStream.pipe(WriteStream) 12. 13.// 复制完成触发的事件 14.WriteStream.on('finish', ()=>{ 15. console.log('复制完成') 16.}) 我们可以通过管道流很方便的实现单个文件的复制。2.添加显示处理状态的功能这里很简单就直接贴代码了。1.const fs = require('fs') 2.const out = process.stdout 3. 4.let paths = { 5. src: '../test/test.mp4', 6. dist: '../test1.mp4' 7.} 8. 9.function copy(paths){ 10. let {src, dist} = paths 11. let readStream = fs.createReadStream(src) 12. let writeStream = fs.createWriteStream(dist) 13. 14. let stat = fs.statSync(src), 15. totalSize = stat.size, 16. progress = 0, 17. lastSize = 0, 18. startTime = Date.now() 19. 20. readStream.on('data', function(chunk) { 21. progress += chunk.length; 22. }) 23. 24. // 我们添加了一个递归的setTimeout来做一个旁观者 25. // 每500ms观察一次完成进度,并把已完成的大小、百分比和复制速度一并写到控制台上 26. // 当复制完成时,计算总的耗费时间 27. setTimeout(function show() { 28. let percent = Math.ceil((progress / totalSize) * 100) 29. let size = Math.ceil(progress / 1000000) 30. let diff = size - lastSize 31. lastSize = size 32. out.clearLine() 33. out.cursorTo(0) 34. out.write(`已完成${size}MB,${percent}%, 速度:${diff * 2}MB/s`) 35. if (progress < totalSize) { 36. setTimeout(show, 500) 37. } else { 38. let endTime = Date.now() 39. console.log(`共用时:${(endTime - startTime) / 1000}秒。`) 40. } 41. }, 500) 42.} 43. 44.copy(paths) 到此我们就用流来处理了文件复制。当然我们还可以用它来处理HTTP requests, on the client、HTTP responses, on the server、 fs write streams、zlib streams、crypto streams、TCP sockets、 child process stdin、 process.stdout, 、process.stderr,大家可以自己试试。对所有流来说,通常使用pipe方法更为简便直接本文介绍了Stream的一些基础,并用它实现了一个小的文件复制功能。这是只是入门小文档,关于Node的Stream更多的知识,需要大家自己去了解抛砖引玉相关连接Node中文网阮一峰nodejs中流(stream)的理解]]></content>
<categories>
<category>Node</category>
</categories>
<tags>
<tag>Node</tag>
<tag>后端,服务端</tag>
</tags>
</entry>
<entry>
<title><![CDATA[【Node教程】- 搭建静态文件服务器]]></title>
<url>%2Fposts%2F804c1ce4%2F</url>
<content type="text"><![CDATA[【Node教程】- 搭建静态文件服务器.note-content{font-family:微软雅黑,'Helvetica Neue',Arial,'Hiragino Sans GB',STHeiti,'Microsoft YaHei','WenQuanYi Micro Hei',SimSun,Song,sans-serif}.note-content code,.note-content h1,.note-content h2,.note-content h3,.note-content h4,.note-content h5.div,.note-content p,.note-content pre{line-height:2}【Node教程】- 搭建静态文件服务器通过前面的几篇介绍,我们这个教程算正式打开Node开发的大门,学习了环境搭建、然后为了提高各位看官的新趣粗略的介绍了Http模块、之后又了解了Node的模块。之前说过,我们将通过实例来学习Node,从这一篇开始,我们就将用实例来学习各个模块。这一节我们将学习File System (文件系统)以及Path(路径))并结合之前学习的知识打造 🔨一个Node静态文件服务器Node.js 文件系统Node.js提供本地文件的读写能力,基本上类似 UNIX(POSIX)标准的文件操作API。 所有的方法都有异步和同步的形式。例如读取文件内容的函数有异步的fs.readFile() 和同步的 fs.readFileSync()。异步的方法函数最后一个参数为回调函数,回调函数的第一个参数包含了错误信息(error),则第一个参数会是 null 或 undefined。1.const fs = require('fs') 2./*异步读取 3. *==================================*/ 4.fs.readFile('README.md', function (err, data) { 5. if (err) { 6. return console.error(err) 7. } 8. console.log("异步读取: " + data.toString()) 9.}) 10.console.log("程序执行完毕。") 结果:程序执行完毕。会被先打印出来1./*同步读取 2. *==================================*/ 3.const fs = require('fs') 4.const data = fs.readFileSync('README.md') 5.console.log("同步读取: " + data.toString()) 6. 7.console.log("程序执行完毕。") 结果:程序执行完毕。后打印出来强烈推荐大家是用异步方法,比起同步,异步方法性能更高,速度更快,而且没有阻塞接下来我们一起来看看fs模块的常用方法写入文件1.fs.writeFile(file, data[, options], callback) 参数说明:file - 文件名或文件描述符data - 要写入文件的数据,可以是 String(字符串) 或 Buffer(流) 对象options - 该参数是一个对象,包含 {encoding, mode, flag}。默认编码为 utf8, 模式为 0666 , flag 为 ‘w’,*如果是一个字符串,则它指定了字符编码callback - 回调函数以追加模式往README.me写入字符串Hello Node.js1.fs.writeFile('README.md', 'Hello Node.js', {flag: 'a+'}, (err) => { 2. if (err) throw err 3. console.log('It\'s saved!') 4.}) 这里我们介绍下flags :Flag描述r以读取模式打开文件。如果文件不存在抛出异常。r+以读写模式打开文件。如果文件不存在抛出异常。rs以同步的方式读取文件。rs+以同步的方式读取和写入文件。w以写入模式打开文件,如果文件不存在则创建。wx类似 ‘w’,但是如果文件路径存在,则文件写入失败。w+以读写模式打开文件,如果文件不存在则创建。wx+类似 ‘w+’, 但是如果文件路径存在,则文件读写失败。a以追加模式打开文件,如果文件不存在则创建。ax类似 ‘a’, 但是如果文件路径存在,则文件追加失败。a+以读取追加模式打开文件,如果文件不存在则创建。ax+类似 ‘a+’, 但是如果文件路径存在,则文件读取追加失败。打开文件同步 fs.open(path, flags[, mode], callback)异步fs.openSync(path, flags[, mode])参数说明:path - 文件的路径flags - 文件打开的行为。具体值详见下文mode - 设置文件模式(权限),文件创建默认权限为 0666(可读,可写)callback - 回调函数,带有两个参数如:callback(err, fd)1.const fs = require("fs"); 2. 3.fs.open('README.md', 'r+', function(err, fd) { 4. if (err) { 5. return console.error(err) 6. } 7. console.log("文件打开成功!") 8.}) 读取文件1.fs.read(fd, buffer, offset, length, position, callback) 参数说明:fd - 通过 fs.open() 方法返回的文件描述符buffer - 是数据将被写入到的 bufferoffset - 是 buffer 中开始写入的偏移量length - 是一个整数,指定要读取的字节数position - 是一个整数,指定从文件中开始读取的位置。 如果 position 为 null,则数据从当前文件位置开始读取callback - 回调函数,有三个参数err, bytesRead, buffer,err 为错误信息, bytesRead 表示读取的字节数,buffer 为缓冲区对象1.const fs = require("fs"); 2.let buf = new Buffer(1024) 3.fs.open('README.md', 'r+', function(err, fd) { 4. if (err) { 5. return console.error(err) 6. } 7. fs.read(fd, buf, 0, buf.length, 0, function(err, bytes){ 8. if (err){ 9. console.error(err); 10. } 11. console.log(bytes + " 字节被读取") 12. // 仅输出读取的字节 13. if(bytes > 0){ 14. console.log(buf.slice(0, bytes).toString()) 15. } 16. }) 17.}) 现在我们就可以从README.md中读取出1kb的数据读取目录readdir方法用于读取目录,返回一个所包含的文件和子目录的数组。1.fs.readdir(path[, options], callback) 同步版本:1.fs.readdirSync(path[, options]) 我们来写个遍历目录的方法吧!这是个同步的(同步版本简单点)遍历目录时一般使用递归算法,否则就难以编写出简洁的代码。递归算法与数学归纳法类似,通过不断缩小问题的规模来解决问题,目录是一个树状结构,在遍历时一般使用深度优先+先序遍历算法1.function travel(dir, callback) { 2. fs.readdirSync(dir).forEach(function (file) { 3. var pathname = path.join(dir, file) 4. 5. if (fs.statSync(pathname).isDirectory()) { 6. travel(pathname, callback) 7. } else { 8. callback(pathname) 9. } 10. }) 11.} 该函数以某个目录作为遍历的起点。遇到一个子目录时,就先接着遍历子目录。遇到一个文件时,就把文件的绝对路径传给回调函数。回调函数拿到文件路径后,就可以做各种判断和处理了:1.travel(__dirname, function (pathname) { 2. console.log(pathname) 3.}) 接下来,大家可以试着去实现异步遍历,原理都是一样的关于File System (文件系统)的更多API请自行查看Node中文网,Path模块path 模块提供了一些工具函数,用于处理文件与目录的路径,path 模块的默认操作会根据 Node.js 应用程序运行的操作系统的不同而变化。 比如,当运行在 Windows 操作系统上时,path 模块会认为使用的是 Windows 风格的路径常用方法介绍path.join([...paths])方法用于连接路径1.path.join('foo', "bar"); 2.// 返回: '/foo/bar' 3. 4.path.join('foo', {}, 'bar') 5.// 抛出 TypeError: path.join 的参数必须为字符串 path.resolve() 方法会把一个路径或路径片段的序列解析为一个绝对路径方法用于将相对路径转为绝对路径1.path.resolve('/foo/bar', './baz') 2.// 返回: '/foo/bar/baz' 3. 4.path.resolve('/foo/bar', '/tmp/file/') 5.// 返回: '/tmp/file' 6. 7.path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif') 8.// 如果当前工作目录为 /home/myself/node, 9.// 则返回 '/home/myself/node/wwwroot/static_files/gif/image.gif' path.extname() 方法返回 path 的扩展名,即从 path 的最后一部分中的最后一个 .(句号)字符到字符串结束1.path.extname('index.html') 2.// 返回: '.html' 3. 4.path.extname('index.coffee.md') 5.// 返回: '.md' 关于Path (路径)的更多API请自行查看Node中文网上面介绍了fs与path模块的几个常用API,在使用时我们应该经常查看API文档,上面所学习的方法已经足够打造一静态文件服务器了,下面我们就一起开完成这个小案例吧。Node静态文件服务器这个静态文件服务器大致是这样的:浏览器发送URL,服务端解析URL,对应到硬盘上的文件。如果文件存在,返回200状态码,并发送文件到浏览器端,我们要实现的功能如下:主页显示当前目录下的文件和文件夹点击链接可以打开文件或者文件夹有图有真相,话不多说看图:现在我们来一步一步实现这个小项目1.首先起一个http服务器我们先来初始化工作:新建文件夹file-servernpm init初识初识化生成package.json新建index.js文件现在我们在index.js文件中起一个服务器:1.const http = require('http') 2. 3.const hostname = '127.0.0.1' 4.const port = 3000 5. 6.const server = http.createServer((req, res) => { 7. res.statusCode = 200 8. res.setHeader('Content-Type', 'text/plain') 9. res.end('Hello World\n') 10.}) 11. 12.server.listen(port, hostname, () => { 13. console.log(`服务器运行在 http://${hostname}:${port}`) 14.}) 还记得我们第一节让大家自己如了解的supervisor工具吗?它可以实现监测文件修改并自动重启应用1.$ supervisor --harmony index.js 2.Running node-supervisor with 3. program '--harmony 7.js' 4. --watch '.' 5. --extensions 'node,js' 6. --exec 'node' 7. 8.Starting child process with 'node --harmony 7.js' 9.Watching directory '/Users/mac/development/node/src/lesson4' for changes. 10.Press rs for restarting the process. 11.服务器运行在 http://127.0.0.1:3000 🚩现在我们这个服务器跑了起来,而且每次更该文件后不需要手动重启服务。2.处理URL请求1.const http = require('http') 2.const url = require('url') //引入url模块 3. 4.const hostname = '127.0.0.1' 5.const port = 3000 6. 7.const server = http.createServer((req, res) => { 8. if(req.url == '/favicon.ico') return //不响应favicon请求 9. // 获取url->patnname 即文件名 10. let pathname = path.join(__dirname, url.parse(req.url).pathname) 11. pathname = decodeURIComponent(pathname) // url解码,防止中文路径出错 12. console.log(pathname) // .../node-abc/lesson4/file-server/ 请求的pathname 13.}) 14. 15.server.listen(port, hostname, () => { 16. console.log(`服务器运行在 http://${hostname}:${port}`) 17.}) 3. 读取文件发送给浏览器接下来我们就运用本节所讲的文件系统的知识来处理文件,记得上面引入fs和path模块1.先来处理文件夹:1..... 2. if(req.url == '/favicon.ico') return //不响应favicon请求 3. // 获取url->patnname 即文件名 4. let pathname = path.join(__dirname, url.parse(req.url).pathname) 5. pathname = decodeURIComponent(pathname) // url解码,防止中文路径出错 6. console.log(pathname) // .../node-abc/lesson4/file-server/ 请求的pathname 7. 8. /** 9. * 判断文件是否是文件夹 10. * 是:返回文件列表 11. * 否:读取文件内容 12. */ 13. // stat方法的参数是一个文件或目录,它产生一个对象,该对象包含了该文件或目录的具体信息。我们往往通过该方法,判断正在处理的到底是一个文件,还是一个目录,这儿使用的是它的同步版本 14. if(fs.statSync(pathname).isDirectory()){ 15. // 设置响应头 16. res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'}) 17. fs.readdir(pathname, (err, files)=>{ 18. res.write('<ul>') 19. files.forEach((item)=>{ 20. // 处理路径 21. let link = path.join(url.parse(req.url).pathname, item) 22. res.write(`<li><a href="${link}">${item}</a></li>`) 23. }) 24. res.end('</ul>') 25. }) 26. } 27.... 我们先用fs.statSync(pathname).isDirectory()来判断是否为文件夹,是则给浏览器返回当前文件夹下的文件列表,请求/直返回:此处我们新建了个test文件夹放了一些文件作为测试文件2.文件处理因为我们的服务器同时要存放html, css, js, png, gif, jpg等等文件。并非每一种文件的MIME类型都是text/html的,所以这里我们引入了mime模块,来处理mime支持1.const mime = require('mime'); 2. 3.// mime 2.x lookup 更名为 getType 4.mime.lookup('/path/to/file.txt'); // => 'text/plain' 5.mime.lookup('file.txt'); // => 'text/plain' 6.mime.lookup('.TXT'); // => 'text/plain' 7.mime.lookup('htm'); // => 'text/html' 8.//具体使用移步官网 这儿用到了前面讲的文件读取:1.else{ 2. // 以binary读取文件 3. fs.readFile(pathname, 'binary', (err, data)=>{ 4. if(err){ 5. res.writeHead(500, { 'Content-Type': 'text/plain'}) 6. res.end(JSON.stringify(err)) 7. return false 8. } 9. res.writeHead(200, { 10. 'Content-Type': `${mime.lookup(pathname)};charset:UTF-8` 11. }) 12. res.write(data, 'binary') 13. res.end() 14. }) 15.} 如果路径不是文件夹,就读取具体的文件,这儿我们以二进制(binary)编码读取,你也可以试试UTF-8比较他们的区别。到此我们这个Node静态文件服务器就算搭建完成👏👏👏,当然了还有许多优化的地方如:缓存、Gzip、页面美化等等…在此就不做介绍了,请自行搜索了解。总结:这一节我们了解了fs模块,path模块。并运用这些知识搭建一简单的文件服务器一切学问最重要的是融会贯通,要把它转变成自身的学问.]]></content>
<categories>
<category>Node</category>
</categories>
<tags>
<tag>Node</tag>
<tag>后端,服务端</tag>
</tags>
</entry>
<entry>
<title><![CDATA[【Node教程】- Node模块与npm]]></title>
<url>%2Fposts%2Fa552a3d3%2F</url>
<content type="text"><![CDATA[【Node教程】- Node模块与npm.note-content{font-family:微软雅黑,'Helvetica Neue',Arial,'Hiragino Sans GB',STHeiti,'Microsoft YaHei','WenQuanYi Micro Hei',SimSun,Song,sans-serif}.note-content code,.note-content h1,.note-content h2,.note-content h3,.note-content h4,.note-content h5.div,.note-content p,.note-content pre{line-height:2}【Node教程】- Node模块与npm【Node教程】- Node模块与npmNode模块小示例1.模块引用2.模块定义npm模块管理器npm包package.jsonnpm的使用npm 命令安装模块卸载模块更新模块创建模块模块发布推荐推荐为了让Node.js的文件可以相互调用,Node.js提供了一个基于CommonJS的模块系统。模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。我们都知道JavaScript先天就缺乏一种功能:模块。浏览器环境的js模块划分只能通过src引入使用。然而,我们是辛运的的,我们在身在这个前端高速发展的时代(当然了,这也是一种压力,一觉醒来又有新东西诞生了)。高速发展下社区总结出了CommonJS这算得上是最为重要的里程碑。CommonJS制定了解决这些问题的一些规范,而Node.js就是这些规范的一种实现。Node.js自身实现了require方法作为其引入模块的方法,同时NPM也是基于CommonJS定义的包规范Node模块每个文件就是一个模块,有自己的作用域。在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。commonJS这套规范的出现使得用户不必再考虑变量污染,命名空间这些问题了。小示例1.// add.js 2.const add = (a, b)=>{ 3. return a+b 4.} 5.============================= 6.// index.js 7.const add = require('./add') 8. 9.let result = add(1, 2) 10.console.log(result) 1.模块引用require()这个方法存在接受一个模块标识,以此引入模块1.const fs = require('fs') Node中引入模块要经历一下三步:路径分析文件定位编译执行Node优先从缓存中加载模块。Node的模块可分为两类:Node提供的核心模块用户编写的文件模块Node核心模块加载速度仅次于缓存中加载,然后路径形式的模块次之,最慢的是自定义模块。2.模块定义在模块中,上下文提供了exports来到处模块的方法或者变量。它是唯一出口1.exports.add = function(){ 2. // TODO 3.} 在模块中还存在一个module对象,它代表模块自身,exports是它的属性。为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令1.var exports = module.exports 不能直接将exports变量指向一个值,因为这样等于切断了exports与module.exports的联系exports和module.exports 区别exports仅仅是module.exports的一个地址引用。nodejs只会导出module.exports的指向,如果exports指向变了,那就仅仅是exports不在指向module.exports,于是不会再被导出module.exports才是真正的接口,exports只不过是它的一个辅助工具。最终返回给调用的是module.exports而不是exports。所有的exports收集到的属性和方法,都赋值给了module.exports。当然,这有个前提,就是module.exports本身不具备任何属性和方法。如果,module.exports已经具备一些属性和方法,那么exports收集来的信息将被忽略Node开发者建议导出对象用module.exports,导出多个方法和变量用exportsnpm模块管理器npm的出现则是为了在CommonJS规范的基础上,实现解决包的安装卸载,依赖管理,版本管理等问题,npm不需要单独安装。在安装Node的时候,会连带一起安装npm允许用户从NPM服务器下载别人编写的第三方包到本地使用。允许用户从NPM服务器下载并安装别人编写的命令行程序到本地使用。允许用户将自己编写的包或命令行程序上传到NPM服务器供别人使用。npm包一个符合CommonJS规范的包应该是如下这种结构:一个package.json文件应该存在于包顶级目录下二进制文件应该包含在bin目录下。JavaScript代码应该包含在lib目录下。文档应该在doc目录下。单元测试应该在test目录下package.jsonname:包名,需要在NPM上是唯一的,小写字母和数字组成可包含_ - .但不能有空格description:包简介。通常会显示在一些列表中version:版本号。一个语义化的版本号(http://semver.org/ ),通常为x.y.z。该版本号十分重要,常常用于一些版本控制的场合keywords:关键字数组。用于NPM中的分类搜索maintainers:包维护者的数组。数组元素是一个包含name、email、web三个属性的JSON对象contributors:包贡献者的数组。第一个就是包的作者本人。在开源社区,如果提交的patch被merge进master分支的话,就应当加上这个贡献patch的人。格式包含name和emailbugs:一个可以提交bug的URL地址。可以是邮件地址(mailto:mailxx@domain),也可以是网页地址licenses:包所使用的许可证repositories:托管源代码的地址数组dependencies:当前包需要的依赖。这个属性十分重要,NPM会通过这个属性,帮你自动加载依赖的包除了前面提到的几个必选字段外,还有一些额外的字段,如bin、scripts、engines、devDependencies、authornpm的使用行下面的命令,查看各种信息1.# 查看 npm 命令列表 2.$ npm help 3. 4.# 查看各个命令的简单用法 5.$ npm -l 6. 7.# 查看 npm 的版本 8.$ npm -v 9. 10.# 查看 npm 的配置 11.$ npm config list -l npm 命令安装模块Node模块采用npm install命令安装。每个模块可以“全局安装”,也可以“本地安装”。“全局安装”指的是将一个模块安装到系统目录中,各个项目都可以调用。一般来说,全局安装只适用于工具模块。“本地安装”指的是将一个模块下载到当前项目的node_modules子目录,然后只有在项目目录之中,才能调用这个模块。1.# 本地安装 2.$ npm install <package name> 3. 4.# 全局安装 5.$ sudo npm install -global <package name> 6.$ sudo npm install -g <package name> 指定所安装的模块属于哪一种性质的依赖关系–save:模块名将被添加到dependencies,可以简化为参数-S。–save-dev: 模块名将被添加到devDependencies,可以简化为参数-D。1.$ npm install <package name> --save 2.$ npm install <package name> --save-dev 卸载模块我们可以使用以下命令来卸载 Node.js 模块1.$ npm uninstall <package name> 更新模块我们可以使用以下命令来卸载 Node.js 模块1.$ npm update <package name> 创建模块我们可以使用以下命令来创建 Node.js 模块1.$ npm init npm init创建模块会在交互命令行帮我们生产package.json文件1.$ npm init 2.This utility will walk you through creating a package.json file. 3.It only covers the most common items, and tries to guess sensible defaults. 4. 5.See `npm help json` for definitive documentation on these fields 6.and exactly what they do. 7. 8.Use `npm install <pkg> --save` afterwards to install a package and 9.save it as a dependency in the package.json file. 10. 11.Press ^C at any time to quit. 12.name: (node_modules) test # 模块名 13.version: (1.0.0) 14.description: Node.js 测试模块 # 描述 15.entry point: (index.js) 16.test command: make test 17.git repository: https://github.com/test/test.git # Github 地址 18.keywords: 19.author: 20.license: (ISC) 21.About to write to ……/node_modules/package.json: # 生成地址 22. 23.{ 24. "name": "test", 25. "version": "1.0.0", 26. "description": "Node.js 测试模块", 27. …… 28.} 29. 30. 31.Is this ok? (yes) yes 以上的信息,你需要根据你自己的情况输入。默认回车即可。在最后输入 “yes” 后会生成 package.json 文件。模块发布发布模块前首先要在npm注册用户1.$ npm adduser 2.Username: zwq8299174 3.Password: 4.Email: (this IS public) zwq8299174@126.com 然后1.$ npm publish 现在我们的npm包就成功发布了。更多请查看npm帮助信息npm 文档推荐推荐npm 文档深入Node.js的模块机制]]></content>
<categories>
<category>Node</category>
</categories>
<tags>
<tag>Node</tag>
<tag>后端,服务端</tag>
</tags>
</entry>
<entry>
<title><![CDATA[【Node教程】-了解并使用Http模块]]></title>
<url>%2Fposts%2F9cf818f5%2F</url>
<content type="text"><![CDATA[【Node教程】-了解并使用Http模块.note-content{font-family:微软雅黑,'Helvetica Neue',Arial,'Hiragino Sans GB',STHeiti,'Microsoft YaHei','WenQuanYi Micro Hei',SimSun,Song,sans-serif}.note-content code,.note-content h1,.note-content h2,.note-content h3,.note-content h4,.note-content h5.div,.note-content p,.note-content pre{line-height:2}【Node教程】-了解并使用Http模块上一节,我们用Http模块搭建了一个hello world服务器。现在我们就来了解了解Http模块,学习它的常用API,并在最后实现两个小案例。http模块主要用于搭建HTTP服务。使用Node搭建HTTP服务器非常简单。按理说,我们应该先讲讲NPM,package.json。但我觉得先讲讲Http模块实现两个小案例,可以让各位看官更有兴趣,能够愉悦的学习使用Node.js我想你可能已经学会了看官网的文档,但脑子在想这是什么鬼,的确,Node.js的文档对初学者不太友好。很多用不上,例子太少。大体来说Http模块,主要的应用是两部分,一部分是http.createServer 担当web服务器,另一部分是http.createClient,担当客户端,实现爬虫之类的工作。下文将从这两方面着手介绍HTTP api。1.Http服务器看上一节例子1.const http = require('http') 2. 3.const hostname = '127.0.0.1' 4.const port = 3000 5. 6.const server = http.createServer((req, res) => { 7. res.statusCode = 200 8. res.setHeader('Content-Type', 'text/plain') //writeHead,200表示页面正常,text/plain表示是文字。 9. res.end('Hello World\n') // end 完成写入 10.}) 11. 12.server.listen(port, hostname, () => { 13. console.log(`服务器运行在 http://${hostname}:${port}`) 14.}) 首先 使用 HTTP 服务器和客户端必须 require('http')。然后使用http.createServer([requestListener])来创建一个web服务器,其中传入一个可选的回调函数,这回调函数有两个参数分别代表客户端请求与服务器端的响应对象使用 server.listen([port][, hostname][, backlog][, callback])开始在指定的 port 和 hostname 上接受连接简单的3步一个http服务器就建好,有打开就有关闭1.server.close([callback]) // 停止服务端接收新的连接 Node.jsHttp模块也提供了server.timeout用于查看或设置超时1.server.timeout = 1000 //设置超时为1秒 2.console.log(server.timeout) 获取客户端请求信息request对象request.url 客户端请求的url地址request.headers 客户端请求的http headerrequest.method 获取请求的方式,一般有几个选项,POST,GET和DELETE等,服务器可以根据客户端的不同请求方法进行不同的处理。request.httpVersion http的版本request.trailers 存放附加的一些http头信息request.socket 用于监听客户端请求的socket对象我们可以写一小段js讲客户端的请求信息保存在log.txt中1.const http = require('http') //引入http模块 2.const fs = require('fs') //引入fs模块 文件 I/O 3.const hostname = '127.0.0.1' 4.const port = 3000 5. 6.const server = http.createServer((req, res) => { //创建一个http服务器 7. res.statusCode = 200 8. res.setHeader('Content-Type', 'text/plain') 9. if(req.url !== '/favicon.ico'){ 10. let out = fs.createWriteStream('./log.txt') // 创建写入流 11. out.write(`请求方法:${req.method} \n`) 12. out.write(`请求url:${req.url} \n`) 13. out.write(`请求头对象:${JSON.stringify(req.headers, null, 4)} \n`) 14. out.write(`请求http版本:${req.httpVersion} \n`) 15. } 16. res.end('Hello World\n') 17.}) 18. 19.server.listen(port, hostname, () => { 20. console.log(`服务器运行在 http://${hostname}:${port}`) 21.}) 22. 23.log.txt 24.============================================ 25.请求方法:GET 26.请求url:/ 27.请求头对象:{ 28. "host": "127.0.0.1:3000", 29. "connection": "keep-alive", 30. "cache-control": "max-age=0", 31. "upgrade-insecure-requests": "1", 32. "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36", 33. "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", 34. "accept-encoding": "gzip, deflate, sdch, br", 35. "accept-language": "zh-CN,zh;q=0.8,en;q=0.6" 36.} 37.请求http版本:1.1 response对象response.writeHead(statusCode, [reasonPhrase], [headers])写服务端入响应头部信息response.header 返回的http header,可以是字符串,也可以是对象response.setTimeout(msecs, callback)设置http超时返回的时间,一旦超过了设定时间,连接就会被丢弃response.statusCode 设置返回的网页状态码response.setHeader(name, value)设置http协议头response.headersSent判断是否设置了http的头response.write(chunk, [encoding]) 返回的网页数据,[encoding] 默认是 utf-8response.end([data], [encoding])URL解析在Node.js中,提供了一个url模块与querystring (查询字符串)模块querystring模块 用于 URL 处理与解析1.querystring.parse(str, [sep], [eq], [options]) // 将字符串转成对象 欲转换的字符串str设置分隔符,默认为 ‘&’sep设置赋值符,默认为 ‘=’eq可接受字符串的最大长度,默认为1000[options] maxKeys1.// 例子: 2.querystring.parse('foo=bar&baz=qux&baz=quux&corge') 3.// returns 4.{ foo: 'bar', baz: ['qux', 'quux'], corge: '' } querystring.stringify(obj,separator,eq,options)这个方法是将一个对象序列化成一个字符串,与querystring.parse相对。1.querystring.stringify({name: 'whitemu', sex: [ 'man', 'women' ] }); 2. 3.// returns 4.'name=whitemu&sex=man&sex=women' url 模块提供了一些实用函数,用于 URL 处理与解析一个 URL 字符串是一个结构化的字符串,它包含多个有意义的组成部分。 当被解析时,会返回一个 URL 对象,它包含每个组成部分作为属性。以下详情描述了一个解析后的 URL 的每个组成部分1.┌─────────────────────────────────────────────────────────────────────────────┐ 2.│ href │ 3.├──────────┬┬───────────┬─────────────────┬───────────────────────────┬───────┤ 4.│ protocol ││ auth │ host │ path │ hash │ 5.│ ││ ├──────────┬──────┼──────────┬────────────────┤ │ 6.│ ││ │ hostname │ port │ pathname │ search │ │ 7.│ ││ │ │ │ ├─┬──────────────┤ │ 8.│ ││ │ │ │ │ │ query │ │ 9." http: // user:pass @ host.com : 8080 /p/a/t/h ? query=string #hash " 10.│ ││ │ │ │ │ │ │ │ 11.└──────────┴┴───────────┴──────────┴──────┴──────────┴─┴──────────────┴───────┘ 12.(请忽略字符串中的空格,它们只是为了格式化) url.format(urlObject) 返回一个从 urlObject 格式化后的 URL 字符串url.parse(urlString[, parseQueryString[, slashesDenoteHost]]) 解析一个 URL 字符串并返回一个 URL 对象我们可以使用url.parse()解析出的对象来获取URL中的各个值更多请参考node中文网实例:HTTP JSON API 服务器编写一个 HTTP 服务器,每当接收到一个路径为 ‘/api/parsetime’ 的 GET请求的时候,响应一些 JSON 数据。我们期望请求会包含一个查询参数(querystring),key 是 “iso”,值是 ISO 格式的时间。/api/parsetime?iso=2017-04-05T12:10:15.474Z所响应的 JSON 应该只包含三个属性:’hour’,’minute’ 和 ‘second’。例如:1.{ 2."hour":21, 3."minute":45, 4."second":30 5.} /api/unixtime?iso=2017-04-05T12:10:15.474Z,它的返回会包含一个属性:’unixtime’,相应值是一个 UNIX时间戳。例如:1.{ "unixtime": 1376136615474 } 具体代码如下1.const http = require('http') 2.const url = require('url') 3. 4.const hostname = '127.0.0.1' 5.const port = 3000 6. 7.// 解析时间的函数 8.function parsetime(time) { 9. return { 10. hour: time.getHours(), 11. minute: time.getMinutes(), 12. second: time.getSeconds() 13. } 14.} 15. 16.function unixtime(time) { 17. return { unixtime: time.getTime() } 18.} 19. 20.const server = http.createServer((req, res) => { 21. let parsedUrl = url.parse(req.url, true) 22. let time = new Date(parsedUrl.query.iso) 23. let result 24. // 主页 返回当前时间的json 25. if(req.url=='/'){ 26. result = parsetime(new Date()) 27. } 28. // 返回查询时间的json 29. else if (/^\/api\/parsetime/.test(req.url)) { 30. result = parsetime(time) 31. } 32. // 返回查询时间的unixtime 33. else if (/^\/api\/unixtime/.test(req.url)) { 34. result = unixtime(time) 35. } 36. 37. if (result) { 38. res.writeHead(200, { 'Content-Type': 'application/json' }) 39. res.end(JSON.stringify(result)) 40. } else { 41. res.writeHead(404) 42. res.end() 43. } 44.}) 45. 46.server.listen(port, hostname, () => { 47. console.log(`服务器运行在 http://${hostname}:${port}`) 48.}) 2.Http客户端在Node.js可以很容易的使用request方法想向其他网站求数据,也可以用http.get(options[, callback])1.http.request(options, callback) request方法的options参数,可以是一个对象,也可以是一个字符串。如果是字符串,就表示这是一个URL,Node内部就会自动调用url.parse(),处理这个参数。http.request()返回一个http.ClientRequest类的实例。它是一个可写数据流,如果你想通过POST方法发送一个文件,可以将文件写入这个ClientRequest对象现在我们拿www.example.com试试1.const http = require('http') 2.let options = { 3. hostname: 'www.example.com', 4. port: 80, 5. path: '/', 6. method: 'GET' 7.} 8. 9.const req = http.request(options, (res) => { 10. console.log(`STATUS: ${res.statusCode}`) //返回状态码 11. console.log(`HEADERS: ${JSON.stringify(res.headers, null, 4)}`) // 返回头部 12. res.setEncoding('utf8') // 设置编码 13. res.on('data', (chunk) => { //监听 'data' 事件 14. console.log(`主体: ${chunk}`) 15. }) 16. 17.}) 18. 19.req.end() // end方法结束请求 到此我们请求到了网站上的信息,基于这些我们可以开发出更有用的爬虫,提取到有用的信息,后续将介绍3.一些好用的包express Express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。request request模块让http请求变的更加简单4.总结本节我们学习使用Http模块建立服务端以及发起本地请求的客户端,并展示了两个超级简单的例子。想必各位看官都已经迫不及待的将Node.js用了起来,甚至觉得有点小激动。下一节,我们还是退回去学习Node的npm,package.json,以及模块机制。然后再学习其他的]]></content>
<categories>
<category>Node</category>
</categories>
<tags>
<tag>Node</tag>
<tag>后端,服务端</tag>
</tags>
</entry>
<entry>
<title><![CDATA[【Node教程】- 搭建Node.js开发环境]]></title>
<url>%2Fposts%2F8a95a813%2F</url>
<content type="text"><![CDATA[【Node教程】- 搭建Node.js开发环境.note-content{font-family:微软雅黑,'Helvetica Neue',Arial,'Hiragino Sans GB',STHeiti,'Microsoft YaHei','WenQuanYi Micro Hei',SimSun,Song,sans-serif}.note-content code,.note-content h1,.note-content h2,.note-content h3,.note-content h4,.note-content h5.div,.note-content p,.note-content pre{line-height:2}【Node教程】- 搭建Node.js开发环境HELLO WORLD本章节我们将向大家介绍在各个平台上(win,mac与ubuntu)安装Node.js的方法。本安装教程以Latest LTS Version: v6.10.2 (includes npm 3.10.10)版本为例安装配置要学习一门语言,我们首先应该去它的官网逛逛,Node.js的官网地址为https://nodejs.org(现在腾讯团队翻译的Node.js中文网貌似也进行的差不多了)进入到Node.js的官网,我们点击Download可以看到各个平台Node.js的安装包,现在我们就来看看如何在各个平台安装Node(当然官网也提供了详细的安装指引)Windows和mac平台其实windows和mac上安装Node.js没有什么好说的,和安装其他软件一样,同意协议然后一直点击下一步就好了。Duang的一下安装完后,我们就可以打开命令行查看是否安装成功1.$ node -v 2.v6.10.2 #如果出现如下结果,那么恭喜你安装成功了 Linux平台(Ubuntu)相比于Windows与mac,Linux平台的安装还是有些许繁琐,但无非也就是使用命令行Node.js官网提供的安装方式1.$ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - 2.$ sudo apt-get install -y nodejs 当然了可能会不成功,不要怕还可以试试我们其他的方式从源代码安装Node.js首先我们更新下系统,并下载编译需要的包1.$ apt-get update 2.$ apt-get install python gcc make g++ 然后使用wget下载源码包1.$ wget https://nodejs.org/dist/v6.10.2/node-v6.10.2-linux-x64.tar.gz 2. 3.# 移动目录 4. 5.$ cp node-v6.10.2-linux-x64.tar.gz /usr/local/src/ 现在我们解压源代码,并进入目录下开始编译1.$ tar zxvf node-v0.12.4.tar.gz 2.$ cd node-v0.12.4/ 3.$ ./configure 4.$ make install 最后我们node -v现在我们应该可以看见Node.js的版本号上述每一步操作注意权限问题*apt-get安装我们也有简单的选择apt-get1.$ sudo apt-get install nodejs 但是apt-get安装有一个问题就是版本有点老使用nvm这放在最后说就说明这玩意儿不简单,毕竟重量级选手都最后出场。nvm是Node版本管理器:nvm。简单的bash脚本来管理多个活跃的node.js版本,与nvm类似的还有n模块安装nvm我们可以使用curl或者wget安装1.curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.1/install.sh | bash 1.wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.1/install.sh | bash nvm使用使用nvm可以方便的下载安装删除各个版本的Node.js1.nvm install stable #安装最新稳定版 node,现在是 5.0.0 2.nvm install 4.2.2 #安装 4.2.2 版本 3.nvm install 0.12.7 #安装 0.12.7 版本 4. 5. 6.# 特别说明:以下模块安装仅供演示说明,并非必须安装模块 7. 8.nvm use 0 #切换至 0.12.7 版本 9.nvm use 4 #切换至 4.2.2 版本 具体使用请参考nvm官网hello world到此想必各位看官已经在自己的电脑上安装后了Node.js开发环境(我想大家没看这个就已经安好了,安装指引只是列行公事 ~.~)下面我们来一个hello world开启本次学习之路这是官网的一个例子,一个使用 Node.js 编写的 web 服务器,响应返回 ‘Hello World’1.const http = require('http'); 2. 3.const hostname = '127.0.0.1'; 4.const port = 3000; 5. 6.const server = http.createServer((req, res) => { 7. res.statusCode = 200; 8. res.setHeader('Content-Type', 'text/plain'); 9. res.end('Hello World\n'); 10.}); 11. 12.server.listen(port, hostname, () => { 13. console.log(`服务器运行在 http://${hostname}:${port}/`); 14.}); 一些有用的工具nrm 快速切换 NPM 源cnpm 淘宝 NPM 镜像supervisorSupervisor实现监测文件修改并自动重启应用总结在本机成功安装Node.js用http模块起一个http服务器,打开Node的大门了解一些有用的模块(详细使用请自行了解)既然这节用http模块说了hello world,下一节,我们就来一起看看http模块]]></content>
<categories>
<category>Node</category>
</categories>
<tags>
<tag>Node</tag>
<tag>后端,服务端</tag>
</tags>
</entry>
<entry>
<title><![CDATA[妙用 scale 与 transfrom-origin,精准控制动画方向]]></title>
<url>%2Fposts%2Fb33001d8%2F</url>
<content type="text"><![CDATA[妙用 scale 与 transfrom-origin,精准控制动画方向.note-content{font-family:微软雅黑,'Helvetica Neue',Arial,'Hiragino Sans GB',STHeiti,'Microsoft YaHei','WenQuanYi Micro Hei',SimSun,Song,sans-serif}.note-content code,.note-content h1,.note-content h2,.note-content h3,.note-content h4,.note-content h5.div,.note-content p,.note-content pre{line-height:2}妙用 scale 与 transfrom-origin,精准控制动画方向将下面这个动画的下划线效果,从左进入,右边离开修改为从上方进入,下方离开。描述很难理解,看看原本的效果:难点所在第一眼看到这个效果,我的内心毫无波澜。以为只是简单的一个下划线 hover 效果,经过友人提醒,才发现,这个动画效果中,下划线是从一端进入,从另外一端离开的。而且,这个 hover 动画是纯 CSS 实现的。先不考虑上面说的修改需求,先想一想,如果就是还原上述效果,仅仅使用 CSS,该如何做呢?还原效果嗯,正常而言,我们一个 hover 效果,可能就是从哪里来,回哪里去,大部分的应该是这样的:现在,难点就在于如何在 hover 离开的时候,改变动画行进的方向。下面我们将一个 hover 动画分解为 3 个部分:hover 进入状态hover 停留状态hover 离开状态但是,对于一个 hover 效果而言,正常来说,只有初始状态,和hover状态两种。可能我们的代码是这样:1.div { 2. 3. xxxx... 4. 5.} 6. 7.div:hover { 8. 9. xxxx... 10. 11.} 对于一个 hover transition 动画,它应该是从:正常状态 -> hover状态 -> 正常状态 (三个步骤,两种状态)所以,必须要有一种方法,能够使得 hover 动画的进入与离开产生两种不一样的效果,实现:状态1 -> hover状态 -> 状态2 (三个步骤,三种状态)实现控制动画方向的关键点所以,这里的关键点就在于(划重点):使得 hover 动画的进入与离开产生两种不一样的效果 。接下来,也就是本文的关键所在,使用 transform: scale() 以及 transform-origin 实现这个效果。transform: scale() 实现线条运动transform: scale 大家应该都很熟悉了,通俗来说是用于缩放,用官方的话说,就是:CSS 函数 scale() 用于修改元素的大小。可以通过向量形式定义的缩放值来放大或缩小元素,同时可以在不同的方向设置不同的缩放值。这里我们使用 transform: scaleX(0) 与 transform: scaleX(1) 来改变线条的显示与隐藏,它的 CSS 代码简单来看,可能是这样:1.div { 2. position: absolute; 3. width: 200px; 4. height: 60px; 5.} 6.div::before { 7. content: ""; 8. position: absolute; 9. left: 0; 10. bottom: 0; 11. width: 200px; 12. height: 2px; 13. background: deeppink; 14. transition: transform .5s; 15. transform: scaleX(0); 16.} 17. 18.div:hover::before { 19. transform: scaleX(1); 20.} 嗯?为什么是要用transform: scale() 来实现线条的动画?因为它可以配合 transform-origin 实现动画的不同运动方向:transform-origin 实现线条运动方向transform-origin 让我们可以更改一个元素变形(transform)的原点,transform-origin 属性可以使用一个,两个或三个值来指定,其中每个值都表示一个偏移量。 没有明确定义的偏移将重置为其对应的初始值。本效果最最最重要的地方就在于这里,我们使用 transform-origin 去改变 transform: scale() 的原点实现线条运动的方向。我们给线条设置一个默认的 transform-origin 记为状态1hover 的时候,设置另外一个不同的 transform-origin, 记为状态2所以,当然我们 hover 的时候,会读取状态2的transform-origin,从该原点开始放大至 scaleX(1),hover 离开的时候,会读取状态1的transform-origin,从scaleX(1)状态缩小至该原点。嗯,CSS代码大概是这样:1.div { 2. position: absolute; 3. width: 200px; 4. height: 60px; 5.} 6.div::before { 7. content: ""; 8. position: absolute; 9. left: 0; 10. bottom: 0; 11. width: 200px; 12. height: 2px; 13. background: deeppink; 14. transition: transform .5s; 15. transform: scaleX(0); 16. transform-origin: 100% 0; 17.} 18.div:hover::before { 19. transform: scaleX(1); 20. transform-origin: 0 0; 21.} 这里,我们巧妙的通过 hover 状态施加了一层新的 transform-origin ,让动画的进入与离开产生了两种不同的效果,两个不同的方向。如此一来,也就顺利实现了我们想要的效果,撒花:注意,这里使用了 transform-origin 去改变 transform: scale() 的原点实现线条运动的方向,而没有借助诸如 position 位移,transform: translate(),或者 margin 等位置属性去改变线条所在的位置。所以,有趣的是,线条其实没有产生过任何位移,这里其实也是障眼法,让它看上去,它好像在移动。拓展延伸嗯,有了上述方法,也就是 transform: scale() 配合 transform-origin ,我们可以开始随意改变动画的初始与结束状态了。把他们运用到其他效果之上,简单的几个示意效果:值得注意的点还有几个点是比较有意思的,大家可以尝试尝试,思考思考:尝试改变两种状态的 transition-timing-function 缓动函数,可以让动画更加流畅具有美感;注意一下,线条的 transition 设置的是 transition: transform .5s 而不是 transition: all .5s,体验一下两种写法所产生的不同效果。]]></content>
<categories>
<category>css</category>
</categories>
<tags>
<tag>css</tag>
<tag>css3</tag>
<tag>样式</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Vue组件之间通信方式总结]]></title>
<url>%2Fposts%2F251b637b%2F</url>
<content type="text"><![CDATA[Vue组件之间通信方式总结.note-content{font-family:微软雅黑,'Helvetica Neue',Arial,'Hiragino Sans GB',STHeiti,'Microsoft YaHei','WenQuanYi Micro Hei',SimSun,Song,sans-serif}.note-content code,.note-content h1,.note-content h2,.note-content h3,.note-content h4,.note-content h5.div,.note-content p,.note-content pre{line-height:2}Vue组件之间通信方式总结Vue组件之间通信方式总结一、props和$emit (v-model)二、parent和children三、attrs和listeners四、中央事件总线五、boradcast和dispatch六、provide和inject七、Vuex处理组件之间的数据交互八、sessionStorage或localStorage或Cookie九、URL参数一、props和$emit (v-model)父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过$emit触发事件来做到的。我们最早接触vue框架的双向绑定指令v-model其实是在子组件中vue框架自动帮你绑定了一个value的prop属性,同时在子组件内部值value改变的时候通过this.$emit(‘input’,value)帮你广播了一个input事件出去,同时还在外面的组件帮你监听了这个input事件,从而使你用v-model绑定的属性实现双向绑定的效果。props和$emit示例1.Vue.component('child', { 2. data() { 3. return { 4. mymessage: this.message 5. } 6. }, 7. template: ` 8. <div> 9. <input type="text" v-model="mymessage" @input="passData(mymessage)"> </div> 10. `, 11. props: ['message'], //得到父组件传递过来的数据 12. methods: { 13. passData(val) { 14. //触发父组件中的事件 15. this.$emit('getChildData', val) 16. } 17. } 18.}) 19.Vue.component('parent', { 20. template: ` 21. <div> 22. <p>this is parent compoent!</p> 23. <child :message="message" v-on:getChildData="getChildData"></child> 24. </div> 25. `, 26. data() { 27. return { 28. message: 'hello' 29. } 30. }, 31. methods: { 32. //执行子组件触发的事件 33. getChildData(val) { 34. console.log(val) 35. } 36. } 37.}) 38.var app = new Vue({ 39. el: '#app', 40. template: 41. `<div> 42. <parent></parent> 43. </div>` 44.}) 在上面的例子中,有父组件parent和子组件child。父组件传递了message数据给子组件,并且通过v-on绑定了一个getChildData事件来监听子组件的触发事件。子组件通过props得到相关的message数据,最后通过this.$emit触发了getChildData事件。v-model示例1.Vue.component('child', { 2. props: { 3. value: String, //v-model会自动传递一个字段为value的prop属性 4. }, 5. data() { 6. return { 7. mymessage: this.value 8. } 9. }, 10. methods: { 11. changeValue() { 12. this.$emit('input', this.mymessage); //通过如此调用可以改变父组件上v-model绑定的值 13. } 14. }, 15. template: 16. `<div> 17. <input type="text" v-model="mymessage" @change="changeValue"> 18. </div>` 19.}); 20.Vue.component('parent', { 21. template: 22. `<div> 23. <p> this is parent compoent! </p> 24. <p></p> 25. <child v-model="message"></child> 26. </div>`, 27. data() { 28. return { 29. message: 'hello' 30. } 31. } 32.}); 33.var app = new Vue({ 34. el: '#app', 35. template: ` 36. <div > 37. <parent > < /parent> 38. </div>` 39.}); 二、parent和children父组件可以通过$children来访问子组件实例内部的属性与方法,同样的道理,子组件可以用过$parent来访问父组件实例内部的属性与方法。示例代码1.Vue.component('child', { 2. props: { 3. value: String, //v-model会自动传递一个字段为value的prop属性 4. }, 5. data() { 6. return { 7. mymessage: this.value 8. } 9. }, 10. methods: { 11. changeValue() { 12. this.$parent.message = this.mymessage; //通过如此调用可以改变父组件的值 13. } 14. }, 15. template: 16. `<div> 17. <input type="text" v-model="mymessage" @change="changeValue"> 18. </div>` 19.}); 20.Vue.component('parent', { 21. template: 22. `<div> 23. <p> this is parent compoent! </p> 24. <button @click = "changeChildValue">test</button> 25. <child></child> 26. </div>`, 27. methods: { 28. changeChildValue() { 29. this.$children[0].mymessage = 'hello'; 30. } 31. }, 32. data() { 33. return { 34. message: 'hello' 35. } 36. } 37.}); 38.var app = new Vue({ 39. el: '#app', 40. template: 41. `<div> 42. <parent></parent> 43. </div>` 44.}); 三、attrs和listeners第一种方式处理父子组件之间的数据传输有一个问题:如果父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C怎么办呢?如果采用第一种方法,我们必须让组件A通过prop传递消息给组件B,组件B在通过prop传递消息给组件C;要是组件A和组件C之间有更多的组件,那采用这种方式就很复杂了。Vue 2.4开始提供了$attrs和$listeners来解决这个问题,能够让组件A之间传递消息给组件C。示例代码1.Vue.component('C', { 2. template: 3. `<div> 4. <input type="text" v-model="$attrs.messagec" @input="passCData($attrs.messagec)"> 5. </div>`, 6. methods: { 7. passCData(val) { 8. //触发父组件A中的事件 9. this.$emit('getCData', val) 10. } 11. } 12.}); 13. 14.Vue.component('B', { 15. data() { 16. return { 17. mymessage: this.message 18. } 19. }, 20. template: 21. `<div> 22. <input type="text" v-model="mymessage" @input="passData(mymessage)"> 23. <!-- C组件中能直接触发getCData的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性 --> 24. <!-- 通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的) --> 25. <C v-bind="$attrs" v-on="$listeners"></C> 26. </div>`, 27. props: ['message'],//得到父组件传递过来的数据 28. methods: { 29. passData(val) { 30. //触发父组件中的事件 31. this.$emit('getChildData', val) 32. } 33. } 34.}); 35.Vue.component('A', { 36. template: 37. `<div> 38. <p>this is parent compoent!</p> 39. <B :messagec="messagec" :message="message" v-on:getCData="getCData" v-on:getChildData="getChildData(message)"></B> 40. </div>`, 41. data() { 42. return { 43. message: 'hello', 44. messagec: 'hello c' //传递给c组件的数据 45. } 46. }, 47. methods: { 48. getChildData(val) { 49. console.log('这是来自B组件的数据') 50. }, 51. //执行C子组件触发的事件 52. getCData(val) { 53. console.log("这是来自C组件的数据:" + val) 54. } 55. } 56.}); 57.var app = new Vue({ 58. el: '#app', 59. template: 60. `<div> 61. <A></A> 62. </div>` 63.}); 四、中央事件总线上面几种方式处理的都是父子组件之间的数据传递,而如果两个组件不是父子关系呢?这种情况下可以使用中央事件总线的方式。新建一个Vue事件bus对象,然后通过bus.$emit触发事件,bus.$on监听触发的事件。示例代码1.Vue.component('brother1', { 2. data() { 3. return { 4. mymessage: 'hello brother1' 5. } 6. }, 7. template: 8. `<div> 9. <p>this is brother1 compoent!</p> 10. <input type="text" v-model="mymessage" @input="passData(mymessage)"> 11. </div>`, 12. methods: { 13. passData(val) { 14. //触发全局事件globalEvent 15. bus.$emit('globalEvent', val); 16. } 17. } 18.}); 19.Vue.component('brother2', { 20. template: 21. `<div> 22. <p>this is brother2 compoent!</p> 23. <p>brother1传递过来的数据:</p> 24. </div>`, 25. data() { 26. return { 27. mymessage: 'hello brother2', 28. brothermessage: '' 29. } 30. }, 31. mounted() { 32. //绑定全局事件globalEvent 33. bus.$on('globalEvent', (val) => { 34. this.brothermessage = val; 35. }) 36. } 37.}); 38.//中央事件总线 39.var bus = new Vue(); 40.var app = new Vue({ 41. el: '#app', 42. template: 43. `<div> 44. <brother1></brother1> 45. <brother2></brother2> 46. </div>` 47.}); 五、boradcast和dispatchvue1.0中提供了这种方式,但vue2.0中没有,但很多开源软件都自己封装了这种方式,比如min ui、element ui和iview等。比如如下代码,一般都作为一个mixins去使用, broadcast是向特定的父组件,触发事件,dispatch是向特定的子组件触发事件,本质上这种方式还是$on和$on和$on和$emit的封装,但在一些基础组件中却很实用。1.function broadcast(componentName, eventName, params) { 2. this.$children.forEach(child => { 3. var name = child.$options.componentName; 4. if (name === componentName) { 5. child.$emit.apply(child, [eventName].concat(params)); 6. } else { 7. broadcast.apply(child, [componentName, eventName].concat(params)); 8. } 9. }); 10.} 11.export default { 12. methods: { 13. dispatch(componentName, eventName, params) { 14. var parent = this.$parent; 15. var name = parent.$options.componentName; 16. while (parent && (!name || name !== componentName)) { 17. parent = parent.$parent; 18. if (parent) { 19. name = parent.$options.componentName; 20. } 21. } 22. if (parent) { 23. parent.$emit.apply(parent, [eventName].concat(params)); 24. } 25. }, 26. broadcast(componentName, eventName, params) { 27. broadcast.call(this, componentName, eventName, params); 28. } 29. } 30.}; 六、provide和inject父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。不论子组件有多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。1.Vue.component('child', { 2. inject: ['for'],//得到父组件传递过来的数据 3. data() { 4. return { 5. mymessage: this.for 6. } 7. }, 8. template: 9. `<div> 10. <input type="tet" v-model="mymessage"> 11. </div>` 12.}); 13.Vue.component('parent', { 14. template: 15. `<div> 16. <p>this is parent compoent!</p> 17. <child></child> 18. </div>`, 19. provide: { 20. for: 'test' 21. }, 22. data() { 23. return { 24. message: 'hello' 25. } 26. } 27.}); 28.var app = new Vue({ 29. el: '#app', 30. template: 31. `<div> 32. <parent></parent> 33. </div>` 34.}); 七、Vuex处理组件之间的数据交互如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。八、sessionStorage或localStorage或Cookie这个是利用缓存来实现页面及应用间的通讯,现在一样可以用于组件间的通讯。九、URL参数URL参数和缓存一样的道理都可以用于页面间的数据通讯,但是只适合简单的参数传递,复杂的组件及页面通讯还是建议使用上面介绍的几种方法来实现。]]></content>
<categories>
<category>Vue</category>
</categories>
<tags>
<tag>Vue</tag>
<tag>组件</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Angular一小时快速上手]]></title>
<url>%2Fposts%2F2b17981e%2F</url>
<content type="text"><![CDATA[Angular2是google在2014年9月发布全新的MVVM框架,由于不是基于AngularJS1.X简单升级后得到的,很多核心思想都不同甚至被抛弃。例:$scope的概念被抛弃,controller被抛弃,ES6的支持,TypeScript作为官方开发语言等等,所以全新的angular在命名规则上也有了一些变化,即:Angular。官方的寓意是Angular不在是一个简单的前端JS框架,他可以运行在任何在平台上,所以本文全部以官方名字为准。准备工作首先需要安装npm包管理工具,去官网下载对应自己系统的node和npm套件安装即可。123456789git clone https://github.com/angular/quickstart.git quickstartnpm install -g cnpm --registry=https://registry.npm.taobao.orgcd quickstartcnpm installnpm start此时浏览器中如果出现了‘Hello Angular’的字样,说明你已经成功的启动了第一个Angular项目,Good Luck。注:如果已经安装过cnpm的同学可跳过第二步。目录结构说明文件说明e2e端到端测试文件夹(可以忽略)src项目主要资源文件夹–/app/app资源文件夹–/app/app.component.tsapp根组件逻辑文件(.js文件是编译后自己生成的,可忽略)–/app/app.module.tsapp根模块–/index.htmlapp入口文件–/main.tsapp启动文件–/styles.cssapp主要样式文件–/tsconfig.jsontypescript编译配置文件–/systemjs.config.jssystemjs模块加载配置文件bs-config.e2e.json端到端测试基础配置文件(可以忽略)bs-config.json基础配置文件(可以忽略)karma.conf.js单元测试配置文件(可以忽略)karma-test-shim.js单元测试配置文件(可以忽略)package.jsonnpm配置文件protractor.config.jsprotractor 端对端 (e2e) 测试器运行器的配置。tslint.json该文件定义了 Angular 风格指南与本文档站作者喜爱的语法检查规则。更多文件说明请查看:https://www.angular.cn/docs/ts/latest/guide/setup-systemjs-anatomy.htmlIDE的选择       因为TypeScript是官方推荐的Angular开发语言,而TypeScript又是Microsoft在2013年6月发布的一种开元的编程语言。TypeScript和ES6一样是JavaScript的超集,不同的是TypeScript同时还是ES6的超集。因为是C#之父主导开发的TypeScript,所以可想而知TypeScript相对JavaScript更像是一门面向对象的编程语言,同时支持静态类型、类的继承、多态、接口、命名空间、装饰器等特性。       由于TypeScript面世时间不长,所以对应的IDE选择不是很多。首先推荐微软官方的VS code完全免费,且轻量级,对自家TypeScript最好的支持及语法提示,还有可视化的调试方案,丰富的插件可以安装,内部集成了命令行,可以快速的定位项目的文件夹,很方便。       github官方出品的Atom 也是现在前端非常火的开发工具,丰富的插件可供安装,官方提供TypeScript语法提示插件。自己还可以安装汉化插件,主题视觉插件,其他的语法提示插件,类似less、sass、ES6、css、HTML等等。功能相对没有VS code强大但是相对比较轻量级,尤其是mac系统的同学如果已经有了主力开发工具,可以考虑用Atom作为辅助的开发工具。       WebStorm 是老牌IDE厂商jetbrains公司旗下一款JavaScript 开发工具。在开发JavaScript代码项目的时候就体现出了强大的功能,开发TypeScript的项目一样需要安装插件来实现对TypeScript的语法提示及项目的构建。相对比较重量一些,因为内置了web服务器,不知道是否可以配置内部的服务器直接浏览TypeScript项目,如果可以的话应该非常方便,因为其他的IDE都需要使用node来起开发服务器,然后在浏览器中才可以看到构建好的项目。       Sublime Text 这个应该很多人在用了,和Atom差不多,比较轻量级,没有内置服务器,可以安装插件。主题插件、提示插件等等,建议作为辅助开发工具或者文档查看工具。       最后推荐一个APICloud Studio 2 ,这个是中国的公司开发的一款IDE。它基于刚才提到的Atom,在功能及界面上做了符合中国人的改进。界面全部原生汉语,不需要汉化,对于英文不好的同学可以考虑一手。另外开发中一些常用的功能,如代码的版本管理,它集成了SVN和GIT两种代码管理的方式。还有一些其他的非常好的功能,有兴趣的同学可以下去自己了解。下载地址:Atom: https://atom.io/VS code:https://code.visualstudio.com/WebStorm:http://www.jetbrains.com/webstorm/ASublime Text: https://www.sublimetext.com/3APICloud Studio 2: http://www.apicloud.com/devtools了解代码使用刚才下载的IDE打开./src/index.html,代码如下。12345678910111213141516171819202122<!DOCTYPE html><html> <head> <title>Angular QuickStart</title> <base href="/"> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="styles.css"> <!-- Polyfill(s) for older browsers --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/dist/zone.js"></script> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('main.js').catch(function(err){ console.error(err); }); </script> </head> <body> <my-app>Loading AppComponent content here ...</my-app> </body></html>代码说明:头部链接的js主要看system和system.config这两个js,其作用和requirejs和require.config.js基本一样。下面的<my-app></my-app>是app根组件的所在,也是整个app内容的入口。接着打开./src/main.ts文件,代码如下。12345import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';import { AppModule } from './app/app.module';platformBrowserDynamic().bootstrapModule(AppModule);代码说明:这个文件是app的引导文件,导入动态引导模块儿,然后启动app的跟模块AppModule即可。有静态引导和动态引导两种方式,区别在于浏览器编译和非浏览器编译,一般开发都使用动态引导即可,想了解的更深入的同学可以去查阅相关资料。打开./src/app/app.module.ts文件,代码如下。1234567891011import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { AppComponent } from './app.component';@NgModule({ imports: [ BrowserModule ], declarations: [ AppComponent ], bootstrap: [ AppComponent ]})export class AppModule { }代码说明:import、export和@NgModule都是TypeScript的语法,import是载入一个文件,后面中括号中是载入文件后,对应文件输出的变量、方法、类等等。项目中需要的组件、模块、自定义的服务、第三方插件等等都需要用这种办法载入。以@开头的叫做装饰器,你可以理解为是一个组件或者模块的配置选项,里面是一些依赖、模板、样式等等。export字面理解就是输出、导出的意思,不管是模块、服务还是组件,想要让其他的文件访问到你本身的逻辑,一定要有导出,具体导出什么,怎么导出,根据业务需要可以不同,一般整个class导出即可。打开./src/app/app.component.ts,你会看到以下的代码。1234567import { Component } from '@angular/core';@Component({ selector: 'my-app', template: `<h1>Hello {{name}}</h1>`,})export class AppComponent { name = 'Angular'; }代码说明:       如果是一个组件,必须在上面从angular核心模块儿中导入Component这个类,然后在装饰器@Component中配置你的组件。       selector的意思是你组件的表现方式或者说是自定义标签是什么,这里要注意的地方是和AngularJS1.X版本的指令不太一样的地方是标签如果是’-‘连接的,组件中不需要使用驼峰命名法。       template的意思是组件的HTML部分,可以是用多行引号,这是ES6的新特性,可以加载多行文本内容。templateUrl是加载外部模板的配置项,template和templateUrl不可以同时存在,否则会报错。       styles是模板的内联样式,可以是css、less、sass。stylesUrl为模板指定外联样式文件,这里需要注意的是styles和stylesUrl可以同时存在。如果同时存在的话,styles会被先解析,然后是stylesUrl换句话说styles会被stylesUrl的样式覆盖。细心的同学可能注意到了stylesUrl是复数,所以stylesUrl的值可以是一个数组,类似['a.css','b.css']。       export class AppComponent这段是实现这个组件逻辑的主要区域,字面理解是一个类,熟悉java的同学应该不陌生。类的封装、继承及多态,它也一样都有。在类的主体中,定义了一个变量name,且它的值是Angular。此时我们再回头看template中的内容<h1>Hello </h1>,其中是插值表达式,用过js模板引擎、AngularJS1.X版本或者是vue的同学应该都不陌生。和AngularJS1.X不同的是在逻辑层我们不需要再把对应的变量或者方法绑定到某个angular暴露出来的变量或者方法上(类似AngularJS1.X的$scope和$rootScope)。我们要做的就是名字对应上即可,剩下的交给Angular即可,框架会自动寻找组件对应的类进行匹配。继续深入官方示例扩展继续刚才打开的./src/app/app.component.ts,我们先声明一个Hero的类,然后在AppComponent这个类中做一些改造,代码如下。12345678910export class Hero { id: number; name: string;}export class AppComponent { hero: Hero = { id: 2, name: 'Windstorm' };}接着需要修改下template的内容。1<h1>{{hero.id}}</h1><h2>{{hero.name}} details!</h2>浏览器会自动刷新,切换过去可以看下效果。现在来点稍微复杂的功能,继续改造template这个类。123456<h2>{{hero.name}} details!</h2><div><label>id: </label>{{hero.id}}</div><div> <label>name: </label> <input [(ngModel)]="hero.name" placeholder="name"></div>可以看到有输入框了,我们希望在input中输入内容,对应绑定的内容也变化,即双向绑定。双向绑定的写法是[(ngModel)]后面的值是要接收内容的变量名,下面我们来实现这个功能。打开./src/app/app.module.ts,修改成如下的样子。123456789101112131415import { NgModule } from '@angular/core';import { BrowserModule } from '@angular/platform-browser';import { FormsModule } from '@angular/forms'; // <-- NgModel lives hereimport { AppComponent } from './app.component';@NgModule({ imports: [ BrowserModule, FormsModule // <-- import the FormsModule before binding with [(ngModel)] ], declarations: [ AppComponent ], bootstrap: [ AppComponent ]})export class AppModule { }可以看到相比修改之前,我们载入了FormsModule这个模块,然后在跟模块装饰器的imports配置项中加入了我们所依赖的FormsModule,这样就可以在整个app中使用FormsModule这个模块的所有功能了。这里可以理解为在项目启动时候,需要告诉Angular你需要用到哪些模块儿及组件。这时切换到浏览器,然后在输入框中随意输入,我们看到双向绑定的功能已经实现。列表循环在./src/app/app.component.ts尾部添加如下代码。123456789101112const HEROES: Hero[] = [ { id: 11, name: 'Mr. Nice' }, { id: 12, name: 'Narco' }, { id: 13, name: 'Bombasto' }, { id: 14, name: 'Celeritas' }, { id: 15, name: 'Magneta' }, { id: 16, name: 'RubberMan' }, { id: 17, name: 'Dynama' }, { id: 18, name: 'Dr IQ' }, { id: 19, name: 'Magma' }, { id: 20, name: 'Tornado' }];接着在AppComponent的类中添加1heroes = HEROES;然后修改我们模板下面加上列表的html代码123456<h2>My Heroes</h2><ul class="heroes"> <li *ngFor="let hero of heroes"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li></ul>给列表加上样式123456789101112131415161718192021222324252627282930313233343536373839404142434445464748styles: [` .selected { background-color: #CFD8DC !important; color: white; } .heroes { margin: 0 0 2em 0; list-style-type: none; padding: 0; width: 15em; } .heroes li { cursor: pointer; position: relative; left: 0; background-color: #EEE; margin: .5em; padding: .3em 0; height: 1.6em; border-radius: 4px; } .heroes li.selected:hover { background-color: #BBD8DC !important; color: white; } .heroes li:hover { color: #607D8B; background-color: #DDD; left: .1em; } .heroes .text { position: relative; top: -3px; } .heroes .badge { display: inline-block; font-size: small; color: white; padding: 0.8em 0.7em 0 0.7em; background-color: #607D8B; line-height: 1em; position: relative; left: -1px; top: -4px; height: 1.8em; margin-right: .8em; border-radius: 4px 0 0 4px; }`]这时候切换到浏览器,已经可以看到一个带有样式的列表已经渲染好,为了保证我们的组件逻辑文件简介,我们把组件的样式单独拿出来形成单独的css文件,然后用外联的方式引入它。首先新建./app/app.css,然后修改AppComponent,删除原来的styles,添加新的属性,如下1styleUrls:['./app.css'],处理事件接着刚才列表循环的例子来做修改,在<li>标签上加上一下代码1(click)="onSelect(hero)"写法类似AngularJS的事件绑定,但是不一样的是前面少了ng-,但是多了中括号的包裹。Angular中[]是属性绑定的写法,例[disabled]="choose"。()是我们刚用到的事件绑定写法,还有之前用到的[(ngModel)]是双向绑定的写法。在AppComponent的类中添加1234selectedHero: Hero;onSelect(hero: Hero): void { this.selectedHero = hero;}现在已经绑了<li>的click事件,点击的逻辑可以在onSelect方法中饭实现。一般我们绑定事件以后可能会需要基于触发事件的详细信息来做一些逻辑,Angular提供了这样的方法,只需要在绑定事件后运行的方法中加入$event这个参数即可,代码如下。1(click)="onSelect(hero,$event)"12345selectedHero: Hero;onSelect(hero: Hero,e:any): void {console.log(e); this.selectedHero = hero;}修改后再次点击<li>我们会看到控制台中输出了1MouseEvent {isTrusted: true, screenX: 184, screenY: 381, clientX: 184, clientY: 291…}这就是刚才点击事件的详细信息,想看全部内容的同学可以点开这个对象。常用内置指令ngIf基于我们上一步的代码再做如下的修改123456<h2>{{selectedHero.name}} details!</h2><div><label>id: </label>{{selectedHero.id}}</div><div> <label>name: </label> <input [(ngModel)]="selectedHero.name" placeholder="name"/></div>这时我们切换到浏览器中,会发下如下的错误信息1EXCEPTION: TypeError: Cannot read property 'name' of undefined in [null]原因是我们在AppComponent的类中只给selectedHero制定了类型而没有给它复制,然后在[(ngModel)]="selectedHero.name"这段代码中需要访问selectedHero中不存在的name属性,所以会报错,我们修改代码让程序运行起来。12345678<div *ngIf="selectedHero"> <h2>{{selectedHero.name}} details!</h2> <div><label>id: </label>{{selectedHero.id}}</div> <div> <label>name: </label> <input [(ngModel)]="selectedHero.name" placeholder="name"/> </div></div>       大家可以看到我们在刚才最初添加的代码外层又包了一个<div>,而且它的上面有一条指令*ngIf="selectedHero",表示如果selectedHero有值,才会显示<div>标签所包含的这段代码。这里注意下是有值,逻辑类似你在JavaScript中使用if(test)一个道理,如果test是0、null、undefined这类型的值,在if判断中test会被认为是false,所以需要注意下。       这时切换到浏览器查看效果,发现报错没有了,而且只有当你点击了某个<li>后,上面有<input>标签的区域才会显示出来,这个就是ngIf指令的一般用法,注意别忘记前面的*。ngClass       通常我们需要通过某个事件或者方法,改变视图层某个DOM的样式,最常用的方法就是给这个元素添加class。Angular提供这样的内置指令来帮助我们实现这样的功能,下面来继续改造我们的代码。12345<li *ngFor="let hero of heroes" [class.selected]="hero === selectedHero" (click)="onSelect(hero)"> <span class="badge">{{hero.id}}</span> {{hero.name}}</li>       在<li>标签上添加[class.selected]="hero === selectedHero"这一条指令,意思是当selectedHero和当前<li>循环的hero相等时,添加class:selected,切换到浏览器,点击某个<li>你已经可以看到效果。       但是有时候,我们需要不同的事件或方法作用在同一个DOM上让它在不同状态的表现形式也不一样,这就需要添加多个class,当然你可以依照上面的思路添加多个[class.selected]指令也是可以的。但是Angular已经考虑到有这样的情况发生,为我们提供了更优美的解决方案。1[ngClass]="{'selected':hero === selectedHero,'normal':hero!=selectedHero}"其中[ngClass] 表示一个 class 的对象集合,对象的 key 是我们需要操作的 class , value 是这个 class 对应的表达式。管道操作符 ( | )在模板插值的时候我们最常用的应该就是管道操作符 ( | )这个了,Angular一样提供了一些内置的过滤器,常见的大小写、日期、货币、数字、百分比等等,当然也可以自定义过滤器。多个过滤器可以链式操作,管道的参数可以跟在这个管道的后面。1{{ birthday | date:'fullDate' | uppercase}}这里再说一个比较常用的管道,在AngularJS1中官方内置了LimitTo的过滤器,但是Angular中并没有类似的过滤器,但是有一个slice的过滤器,使得我们操作数据更加灵活,用法和JavaScript原生的Array.slice一样。官方同样没有提供OrderByPipe这样的管道,需要的话都是需要自己写的。12345<li *ngFor="let hero of heroes | slice:0:5" [ngClass]="{'selected':hero === selectedHero,'normal':hero!=selectedHero}" (click)="onSelect(hero,$event)"> <span class="badge">{{hero.id}}</span> {{hero.name}}</li>组件的嵌套一个大型的应用是很多个组件嵌套组成的,Angular的组件嵌套实现起来非常简单,耦合程度也非常低,复用性高,我们把之前的代码进行修改便可以体=验一下。我们在app这个目录下新建一个hero-detail.component.ts的文件,代码如123456789101112131415161718import { Component, Input } from '@angular/core';import { Hero } from './hero';@Component({ selector: 'hero-detail', template: ` <div *ngIf="hero"> <h2>{{hero.name}} details!</h2> <div><label>id: </label>{{hero.id}}</div> <div> <label>name: </label> <input [(ngModel)]="hero.name" placeholder="name"/> </div> </div> `})export class HeroDetailComponent { @Input() hero: Hero;}我们的详情组件已经建好,需要在app.module.ts中配置一下即可使用,在头部添加1import { HeroDetailComponent } from './hero-detail.component';然后在declarations中添加HeroDetailComponent1234declarations: [ AppComponent, HeroDetailComponent],因为列表组件和详情组件都用到了Hero这个class,所以我们把它剥离出来,在app目录下新建hero.ts。1234export class Hero { id: number; name: string;}最后我们要修改app.component.ts来调用我们刚才写好的详情组件。1234567891011template: ` <h1>{{title}}</h1> <hero-detail [hero]="selectedHero"></hero-detail> <h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes | slice:0:5" [ngClass]="{'selected':hero === selectedHero,'normal':hero!=selectedHero}" (click)="onSelect(hero,$event)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul>`,在最上面引入Hero这个class,否则会报错。1import { Hero } from './hero';然后切到浏览器,发现和以前没什么区别,但其实详情组件已经被载入进来,而且工作正常。代码说明:详情组件中,比较陌生的是@Input(),组件之间通讯肯定会有输入和输出。刚才我们的详情组件,内容是由父组件提供,所以它需要接受父组件给他的数据,Angular组件接受数据使用@Input()。对应的有@Output(),我们这里不展开讲解,有兴趣的同学可以下去了解。服务       现在我们的应用已经初具规模,常见的功能也都体验过了。顺着上一步的思路我们继续想,组件的嵌套是为了提高组件的复用性、降低每个组件的耦合程度。组件是视图层面的一个个小的单元,那么逻辑层面可不可以有一些复用的东西形成单独的功能模块被组件或者其他的服务所调用呢?答案是可以的,类似于AngularJS1.x的各种服务service、factory等等,Angular也可以自定义服务,但是种类不像AngularJS1.x版本分的那么详细,写法也相对简单了很多。       接着上面的代码我们思考,列表页需要展示Heros,详情页需要展示hero,但是他们的数据源都是一样的。那么我们可不可以把数据源单独剥离出来形成一个服务呢?甚至我们可以通过后台来获取我们要显示的数据,下面我们就来改造代码。首先新建app/app.service.ts文件1234567891011121314151617181920212223import { Injectable } from '@angular/core';import { Hero } from './hero';@Injectable()export class AppService { getHeroes(): Hero[] { return HEROES; }}const HEROES: Hero[] = [ {id: 11, name: 'Mr. Nice'}, {id: 12, name: 'Narco'}, {id: 13, name: 'Bombasto'}, {id: 14, name: 'Celeritas'}, {id: 15, name: 'Magneta'}, {id: 16, name: 'RubberMan'}, {id: 17, name: 'Dynama'}, {id: 18, name: 'Dr IQ'}, {id: 19, name: 'Magma'}, {id: 20, name: 'Tornado'}];需要注意的是,和新建组件不一样的地方是服务不需要引入Component这个类,但是需要Injectable这个类,所用是告诉Angular我们所写的服务中需要用到Angular的任何东西,Angular都会帮我们自动注入进来切一一对应好。写好我们的服务以后,我们需要注入到我们的应用中即可使用,在app.module.ts中添加1import { AppService } from './app.service';然后在@NgModule装饰器的对象中添加一个属性1providers: [ AppService ],这一条的意思是我们的应用所依赖的服务,都可以在这里注入,而且在app.module.ts这个模块儿中注入的服务应用的所有组件都可以访问,所以一般会把一些全局的服务从这里注入,类似的有自己封装HTTP服务或者是整个应用的API服务等等。当然你也可以只在某个组件中注入这个组件所需要的服务,当父组件注入一个服务,该组件下的所有子组件都可以访问到这个服务。当子组件也需要注入这个服务,但是需要和父组件隔离或者需要不同的状态时,在子组件中再次注入这个服务,可以保证子组件注入的服务和父组件的服务相隔离。然后回到我们的app.component.ts修改如下1234567891011121314151617181920212223242526272829303132333435import { Component,OnInit } from '@angular/core';import { Hero } from './hero';import { AppService } from './app.service';@Component({ selector: 'my-app', styleUrls:['./app.css'], template: ` <h1>{{title}}</h1> <h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes | slice:0:5" [ngClass]="{'selected':hero === selectedHero,'normal':hero!=selectedHero}" (click)="onSelect(hero,$event)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> `,})export class AppComponent implements OnInit { title = 'Tour of Heroes'; heroes: Hero[]; selectedHero: Hero; constructor(private appService: AppService) { } getHeroes(): void { this.heroes=this.appService.getHeroes(); } ngOnInit(): void { this.getHeroes(); } onSelect(hero: Hero): void { this.selectedHero = hero; }}切到浏览器,我们已经可以看到列表已经渲染好了,说明我们的服务已经注入到应用中,并且我们在组件中成功的调用了它。因为我们的数据现在是模拟的,都是同步加载,但是实际情况中,我们展示的数据往往来自服务器,所以都是异步的操作,这时需要我们引入一个Promise的概念,熟悉ES6的同学都应该不陌生,我们引入“承诺与异步编程”的概念来解决这个问题,修改app.service.ts代码如下123getHeroes(): Promise < Hero[] > { return Promise.resolve(HEROES);}这样一个简单的模拟服务器返回数据的获取数据的方法已经写好了,对应的调用此方法的地方也需要调整一下,修改app.component.ts代码如下123getHeroes(): void { this.heroService.getHeroes().then(heroes => this.heroes = heroes);}这里用到了ES6的箭头函数,可以优雅的处理 this 指针。上面箭头函数等同于下面的代码12345678getHeroes(): void { let that = this; this.heroService.getHeroes().then( function(heroes){ that.heroes = heroes; } );}这里粗略的讲一下箭头函数,更详细的可以去读一下“阮一峰”的ECMAScript 6 入门,这里就不展开讲了。修改完成以后,我们切到浏览器,发现列表也是正常渲染的,说明刚才的服务已经正常工作了。路由       路由对于一个SPA(单页应用)来说是必不可少的,Angular给我们提供了相对于AngfularJS1.x更为便捷的路由服务,下面我们就来实现一个简单的路由功能。       想要体验路由功能,最少需要两个页面,所以我们把英雄列表和英雄详情拿出来每个都形成单独的组件,在./app下新建hero-list.component.ts和hero-detail.component.ts,然后把app.component.ts中对应的每个组件的代码复制到响应组件的.ts文件中,最终代码如下app.component.ts1234567891011121314import { Component, OnInit } from '@angular/core';@Component({ selector: 'my-app', template: ` <h1>{{title}}</h1> <router-outlet></router-outlet> `,})export class AppComponent implements OnInit { title = 'Tour of Heroes'; constructor() {} ngOnInit(): void {}}这里需要注意的是<router-outlet></router-outlet>这个标签,这个标签的意思是告诉Angular路由切换的内容需要呈现在哪里。hero-list.component.ts123456789101112131415161718192021222324252627282930313233343536import { Component } from '@angular/core';import { Router } from '@angular/router';import { AppService } from './app.service';import { Hero } from './hero';@Component({ selector: 'hero-list', template: ` <h2>My Heroes</h2> <ul class="heroes"> <li *ngFor="let hero of heroes | slice:0:5" [ngClass]="{'selected':hero === selectedHero,'normal':hero!=selectedHero}" (click)="onSelect(hero,$event)"> <span class="badge">{{hero.id}}</span> {{hero.name}} </li> </ul> `})export class HeroListComponent { heroes:any; constructor( private appService: AppService, private router: Router ) {} getHeroes(): void { this.appService.getHeroes().then(heroes => this.heroes = heroes); } ngOnInit(): void { this.getHeroes(); } onSelect(hero: Hero,e:any): void { this.router.navigate(['/detail', hero.id]); }}这里需要注意的是需要引入路由服务,因为我们要从列表页导航到详情页面,当然你可以使用最原始的办法:window.location.href这样的方式来实现,需要自己去拼接字符串。hero-detail.component.ts123456789101112131415161718192021222324252627282930313233343536373839import { Component, OnInit } from '@angular/core';import { ActivatedRoute, Params } from '@angular/router';import { Location } from '@angular/common';import { AppService } from './app.service';import { Hero } from './hero';@Component({ selector: 'hero-detail', template: ` <div *ngIf="hero"> <h2>{{hero.name}} details!</h2> <div><label>id: </label>{{hero.id}}</div> <div> <label>name: </label> <input [(ngModel)]="hero.name" placeholder="name"/> </div> <button (click)="goBack()">Back</button> </div> `})export class HeroDetailComponent implements OnInit { hero: any; constructor( private appService: AppService, private route: ActivatedRoute, private location: Location ) {} ngOnInit(): void { this.getHeroe(this.route.params); } getHeroe(obj:any): void { this.appService.getHeroes(obj).then(hero => this.hero = hero); } goBack(): void { this.location.back(); }}详情页需要引入import { ActivatedRoute, Params } from '@angular/router'和import { Location }from '@angular/common';一个是获取URL参数的服务,一个是返回列表页要用到。基本工作我们已经搞定,然后需要在app.module.ts配置我们的路由表来实现路由的功能。在顶部引入路由模块1import { RouterModule } from '@angular/router';还有刚才新建的两个组件12import { HeroListComponent } from './hero-list.component';import { HeroDetailComponent } from './hero-detail.component';然后在对应位置把他们注入到我们的应用中即可1234567891011121314151617181920212223242526@NgModule({ imports: [ BrowserModule, FormsModule, RouterModule.forRoot([ { path: '', redirectTo: '/heroes', pathMatch: 'full' }, { path: 'heroes', component: HeroListComponent }, { path: 'detail/:id', component: HeroDetailComponent } ]) ], declarations: [ AppComponent,HeroListComponent,HeroDetailComponent ], providers: [ AppService ], bootstrap: [ AppComponent]})这时路由的功能已经实现,但是我们发现到详情页的时候会报错,因为我们刚才的服务是单独为列表页写的,如果详情页和列表页公用一个服务的话,需要我们稍作改动。1234567891011121314getHeroes(obj?: any): Promise < Hero[] > { let id = obj?obj.value.id:undefined; let result:any; if(id===undefined){ result = HEROES; }{ HEROES.forEach(function(e:any){ if(e.id==id){ result = e; } }); } return Promise.resolve(result);}       修改完成后,应用已经可以正常运行,可以点击英雄到详情页,然后再点击back按钮返回列表页,路由功能已经实现。然而我们发现列表的样式没有了,打开开发者工具查看,发现Angular会把我们的样式自动隔离在某个组件中,实现单个组件的样式只服务于这个组件本身,所以会造成路由过来的内容是无法享受到app.component.ts这个组件下的样式。所以我们需要把app.css添加到index.html中作为全局样式,这样任何组件都可被渲染到。Http       任何一个需要与服务端通讯的客户端应用都缺少不了与服务端通讯的服务,有的是自己封装的,有的是通过插件实现的,也有的是框架提供的。相比AngularJS1.x,Angular为我们提供了更为完善,功能更为强大的HTTP服务,下面我们来初步体验一下。       在./app/下新建service目录,然后在service目录下新建appHttp.ts和appApi.ts,代码如下appHttp.ts12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849import { Injectable } from '@angular/core';import { Http, RequestOptions, Headers,URLSearchParams } from '@angular/http';import 'rxjs/add/operator/map';const baseUrl = 'http://api.jisuapi.com/car';const appKey = 'c115b803134d35f2';@Injectable()export class appHttp { constructor(private http: Http) {} request(url: string, opts: any) { console.log(opts); return this.http.request(url, new RequestOptions(opts)).map(res = > { console.log(res); return res.json(); }) }; post(url: string, opts ? : Object) { let params = new URLSearchParams(); params.set('appkey', appKey); console.log(params); let body = JSON.stringify({ appkey: appKey }); let headers = new Headers({ 'Content-Type': 'application/json' }); let options = new RequestOptions({ body: body, headers: headers, method: 'post' }); return this.request(baseUrl + url, ( < any > Object).assign(options, opts)); }; get(url: string, opts ? : Object) { console.log(opts); let params = new URLSearchParams(); params.set('appkey', appKey); let body: any = { search: params } console.log(params); return this.request(baseUrl + url, ( < any > Object).assign(body, opts)); };}appApi.ts1234567891011import { Injectable } from '@angular/core';import { appHttp } from './appHttp';@Injectable()export class appApi{ constructor(private _http: appHttp) {} getCar(){ return this._http.get('/brand'); }}然后修改我们的app.module.ts,在顶部引入我们刚才所写的两个服务并且注入到我们的应用中1234567891011121314import { HttpModule } from "@angular/http";import { appHttp } from './service/appHttp';import { appApi } from './service/appApi';imports:[...HttpModule,...],providers:[...appHttp,appApi]这时我们自定义的http服务已经注入到应用中,可以在应用下的任何组件使用,让我们来修改app.component.ts,代码如下123456789101112131415161718192021222324import { Component, OnInit } from '@angular/core';import { appApi } from './service/appApi';@Component({ selector: 'my-app', template: ` <h1>{{title}}</h1> <router-outlet></router-outlet> `,})export class AppComponent implements OnInit { title = 'Tour of Heroes'; private cars:any; constructor(private api:appApi) {} getCar(){ this.api.getCar().subscribe(data => { this.cars = data; console.log(data); }); } ngOnInit(): void { this.getCar(); }}切换到浏览器,打开开发者工具,刷新页面,可以看到控制台已经打印出从服务端返回的数据,说明我们刚才自定义的http服务已经正常工作。Angular的http服务功能非常强大,这里就不展开讲解了,想了解的可以下去自己到官网查看示例和API文档。6、整合第三方插件我们在开发应用的过程中,往往需要借助第三方插件来实现某些功能。最常见的就是各种jquery插件,还有echarts图标插件、moment日期操作插件、datepicker日期插件、swiper滑块插件等等。在以往的项目中,我们用过最原始的方式<script>标签来载入我们需要的插件,也用过requireJS、seaJS、CommonJS这样前端模块化的解决方案,还有AngularJS1.x时期通过依赖注入的方式。但是因为Angular使用编程语言的特殊性,以往的引入方式都无法正常使用插件或者不是最好的解决方案,下面我们来大致介绍下Angular中引入第三方插件的方法。首先使用cnpm来安装我们需要的插件123cnpm install -S jquerycnpm install -s moment....然后在app.component.ts顶部引入我们刚才安装的插件,在Oninit方法中添加一些测试代码来验证插件是否正常工作。1234567...import * as $ from 'jquery';import * as moment from 'moment';...console.log($);onsole.log(moment().subtract(6, 'days').format('YYYY MM DD'));...然后切换到浏览器,发现控制台提示我们jquery找不到,服务器404错误代码,这时候我们需要在systemjs.config.js中配置一下我们刚才添加的插件,类似requireJS的config文件,需要现配置一下,然后在项目中使用。在systemjs.config.js的map字段中添加1234...'jquery':'npm:jquery/dist/jquery.min.js','moment': 'npm:moment/moment.js'...       保存以后刷新浏览器,发现已经不报错,而且控制台已经打印出jquery的$和moment的方法,说明第三方插件已经成功加载。       因为Angular项目的特殊性,搭建开发环境有很多种方案,现在主要的方式有angular-cli、angular-seed、system.js这几种,我们使用的官方的这个quickstart的例子就是system.js方式,其他两种开发环境引入第三方插件的方式大同小异,有兴趣的同学可以深入了解下,本文不展开讲解。注:如果最初package.json中依赖没有jquery,后续安装的话,虽然可以运行,再次npm start时可能会报错,需要cd到你项目tsconfig.json所在的文件夹下,执行以下命令来解决报错。123cnpm install typings -gtypings install dt~jquery --global --save这是因为jquery没有指定任何类型,不符合typescript的语法标准,需要安装一个插件来帮助我们在typescript的项目中使用JavaScript写的插件。]]></content>
<categories>
<category>Angular</category>
</categories>
<tags>
<tag>Angular</tag>
</tags>
</entry>
<entry>
<title><![CDATA[前端编码规范—jQuery规范]]></title>
<url>%2Fposts%2Fffdda357%2F</url>
<content type="text"><![CDATA[前端编码规范—jQuery规范前端编码规范—jQuery规范前端编码规范—jQuery规范使用单引号缓存变量缓存父元素避免全局变量使用驼峰式命名使用单 var 模式使用 on 来处理事件精简 jquery链式操作维持代码的可读性选择短路求值避免通用选择符避免隐式通用选择符优化选择符避免多个 id 选择符熟记技巧坚持最新版本摒弃弃用方法利用 CDN必要时组合 jquery 和 javascript 原生代码使用单引号不推荐$("div").html("<img src='1.jpg'/>");推荐$('div').html('<img src="1.jpg"/>');缓存变量DOM 遍历是昂贵的,所以尽量将会重用的元素缓存。不推荐var h = $('#element').height();$('#element').css('height', h - 20);推荐var $element = $('#element'), h = $element.height();$element.css('height', h - 20);缓存父元素正如前面所提到的,DOM 遍历是一项昂贵的操作。典型做法是缓存父元素并在选择子元素时重用这些缓存元素。不推荐var $container = $('#container'), $containerLi = $('#container li'), $containerLiSpan = $('#container li span');推荐var $container = $('#container '), $containerLi = $container.find('li'), $containerLiSpan= $containerLi.find('span');避免全局变量jquery 与 javascript 一样,一般来说,最好确保你的变量在函数作用域内。不推荐$element = $('#element');h = $element.height();$element.css('height',h - 20);推荐var $element = $('#element'), h = $element.height();$element.css('height',h - 20);使用驼峰式命名使用驼峰式命名,在前面添加 $ 作为前缀,以便于标示为 jquery 对象。不推荐var first = $('#first'), second = $('#second'), value = $first.val();推荐var $first = $('#first'), $second = $('#second'), value = $first.val();使用单 var 模式将多条 var 语句合并为一条语句,建议将未赋值的变量放到后面。var $first = $('#first'), $second = $('#second'), value = $first.val(), k = 3, cookiestring = 'SOMECOOKIESPLEASE', i, j, myArray = {};使用 on 来处理事件在新版 jquery 中,更短的 on(‘click’) 用来取代类似 click() 这样的函数。在之前的版本中 on() 就是 bind()。自从 jquery 1.7 版本后,on() 是附加事件处理程序的首选方法。出于一致性考虑,你可以简单的全部使用 on() 方法。不推荐$first.click(function(){ $first.css('border', '1px solid red'); $first.css('color', 'blue');});$first.hover(function(){ $first.css('border', '1px solid red');});推荐$first.on('click', function(){ $first.css('border', '1px solid red'); $first.css('color', 'blue');});$first.on('hover', function(){ $first.css('border', '1px solid red');});精简 jquery一般来说,最好尽可能合并属性。不推荐$first.click(function(){ $first.css('border', '1px solid red'); $first.css('color', 'blue');});推荐$first.on('click', function(){ $first.css({ 'border':'1px solid red', 'color':'blue' });});链式操作jquery 能够很轻易的实现链式操作。不推荐$second.html(value);$second.on('click', function(){ alert('hello everybody');});$second.fadeIn('slow');$second.animate({height: '120px'}, 500);推荐$second.html(value).on('click', function(){ alert('hello everybody');}).fadeIn('slow').animate({height: '120px'}, 500);维持代码的可读性伴随着精简代码和使用链式的同时,可能带来代码的难以阅读。添加缩进和换行能起到很好的效果。不推荐$second.html(value).on('click', function(){ alert('hello everybody');}).fadeIn('slow').animate({height: '120px'}, 500);推荐$second.html(value) .on('click', function() { alert('hello everybody'); }) .fadeIn('slow') .animate({ height: '120px' }, 500);选择短路求值短路求值是一个从左到右求值的表达式,用 && 或 || 操作符。不推荐function initVar($myVar) { if (!$myVar) { $myVar = $('#selector'); }}推荐function initVar($myVar) { $myVar = $myVar || $('#selector');}避免通用选择符不推荐$('.container > *');推荐$('.container').children();避免隐式通用选择符通用选择符有时是隐式的,不容易发现。不推荐$(':button');推荐$('input:button');优化选择符例如,id 选择符应该是唯一的,所以没有必要添加额外的选择符。不推荐$('div#myid');$('div#footer a.myLink');推荐$('#myid');$('#footer .myLink');避免多个 id 选择符在此强调,id 选择符应该是唯一的,不需要添加额外的选择符,更不需要多个后代 id 选择符。不推荐$('#outer #inner');推荐$('#inner');熟记技巧你可能对使用 jquery 中的方法缺少经验,一定要多查看文档,可能会有一个更好或更快的方法来使用它。不推荐$('#id').data(key, value);推荐$.data('#id', key, value);坚持最新版本在条件允许的情况下,坚持使用最新版本。如果某些插件只支持特定的某一版本,可以在require中单独配置。新版本通常更好:更轻量级,更高效。当然你需要考虑你要支持的代码的兼容性。例如,2.0 版本不支持 ie 6/7/8。摒弃弃用方法关注每个新版本的废弃方法是非常重要的并尽量避免使用这些方法。不推荐$('#stuff').live('click', function() { console.log('hooray');});推荐$('#stuff').on('click', function() { console.log('hooray');});利用 CDNCDN 能保证选择离用户最近的缓存并迅速响应。如果网站为国内网站,因为强大的长城防火墙,所以可以选择国内例如阿里、百度、360等大公司的CDN加速。如果是国外网站,则可以使用google、jquery官网等CDN加速服务。必要时组合 jquery 和 javascript 原生代码如上所述,jquery 就是 javascript,这意味着用 jquery 能做的事情,同样可以用原生代码来做。原生代码的可读性和可维护性可能不如 jquery,而且代码更长。但也意味着更高效(通常更接近底层代码可读性越差,性能越高)。牢记没有任何框架能比原生代码更小,更轻,更高效。]]></content>
<categories>
<category>规范</category>
</categories>
<tags>
<tag>规范</tag>
<tag>jQuery</tag>
</tags>
</entry>
<entry>
<title><![CDATA[前端编码规范—JavaScript规范]]></title>
<url>%2Fposts%2F691566fb%2F</url>
<content type="text"><![CDATA[前端编码规范—JavaScript规范前端编码规范—JavaScript规范前端编码规范—JavaScript规范全局命名空间污染与 IIFEIIFE(立即执行的函数表达式)严格模式变量声明变量的命名理解 javascript 的定义域声明提前总是使用带类型判断的比较判断明智地使用真假判断变量赋值时的逻辑操作三元条件判断(if 的快捷方法)分号嵌套函数语句块内的函数声明异常标准特性简易的原型继承使用闭包切勿在循环中创建函数eval 函数(魔鬼)this 关键字数组和对象的属性迭代数组和对象字面量修改内建对象的原型链自定义 toString() 方法圆括号字符串全局命名空间污染与 IIFE总是将代码包裹成一个 IIFE即时执行方法(Immediately-Invoked Function Expression),用以创建独立隔绝的定义域。这一举措可防止全局命名空间被污染。IIFE 还可确保你的代码不会轻易被其它全局命名空间里的代码所修改(比如第三方库,window 引用,被覆盖的未定义的关键字等等)。现在很多第三方的类库、插件、控件、框架、模块儿等很多都是 IIFE这样的写法。不推荐var x = 10, y = 100; console.log(window.x + ' ' + window.y);推荐(function(w){ 'use strict'; var x = 10, y = 100; w.console.log((w.x === undefined) + ' ' + (w.y === undefined));}(window));IIFE(立即执行的函数表达式)无论何时,想要创建一个新的封闭的定义域,那就用 IIFE。它不仅避免了干扰,也使得内存在执行完后立即释放。所有脚本文件建议都从 IIFE 开始。立即执行的函数表达式的执行括号应该写在外包括号内。虽然写在内还是写在外都是有效的,但写在内使得整个表达式看起来更像一个整体,因此推荐这么做。IIFE的写法很多、也很灵活,为了方便项目的结合、代码的易读性和代码格式的统一,我们推荐下面的书写格式。不推荐(function(){})();推荐(function(){}());所以用下列写法来格式化你的 IIFE 代码:(function($, w, d){ 'use strict'; $(function() { w.alert(d.querySelectorAll('div').length); });}(jQuery, window, document));严格模式ECMAScript5 严格模式可在整个脚本或独个方法内被激活。它对应不同的 javascript 语境会做更加严格的错误检查。严格模式也确保了 javascript 代码更加的健壮,运行的也更加快速。严格模式会阻止使用在未来很可能被引入的预留关键字。你应该在你的脚本中启用严格模式,最好是在独立的 IIFE 中应用它。避免在你的脚本第一行使用它而导致你的所有脚本都启动了严格模式,这有可能会引发一些第三方类库的问题。不推荐'use strict';(function(){ // some code}());推荐(function(){ 'use strict'; // some code}());变量声明总是使用 var 来声明变量。如不指定 var,变量将被隐式地声明为全局变量,这将对变量难以控制。如果没有声明,变量处于什么定义域就变得不清(可以是在 Document 或 Window 中,也可以很容易地进入本地定义域)。所以,请总是使用 var 来声明变量。采用严格模式带来的好处是,当你手误输入错误的变量名时,它可以通过报错信息来帮助你定位错误出处。不推荐x = 10;y = 100;推荐var x = 10, y = 100;变量的命名变量名必须以大写字母(A到Z)、小写宇母(a到z)或下划线(_)开头,其他的字符可以用字母、下划线或数字(0到9)。变量名称中不能有空格、+ 、-号等其他符号。不能使用JavaScript中的关键字作为变量名。在JavaScript中定义了多个关键字,这些关键字是JavaScript内部使用的,不能作为变量的名称。例如var、int、double、true等都不能作为变量的名称。在对变量命名时,最好把变暈名的意义与其代表的内容对应起来,以便能方便地区分变量的食义。例如,today这样的变量就很容易让人明白其代表的内容。JavaScript变量名是区分大小写的,因此在使用时必须确保大小写相同。不同大小写的变量,例如sum、Sum、SUM,将被视为不同的变量。变量命名法:项目中使用驼峰命名法。例:var myObj = { a:'a', b:'b' };var myFn = function(a){ console.log(a);};var myArray = ['a','b','c'];关于驼峰命名的更多了解请移步。传送门理解 javascript 的定义域在 javascript 中变量和方法定义会自动提升到执行之前。javascript 只有 function级的定义域,而无其他很多编程语言中的块定义域,所以使得你在某一 function 内的某语句和循环体中定义了一个变量,此变量可作用于整个 function内,而不仅仅是在此语句或循环体中,因为它们的声明被 javascript 自动提升了。我们通过例子来看清楚这到底是怎么一回事:原 function(function(){ 'use strict'; var a = 10; for(var i = 0; i < a; i++) { var b = i * i; console.log(b); } if(a === 10) { var f = function() { console.log(a); }; f(); } function x() { console.log('Mr. X!'); } x();}());被 js 提升过后(function(){ 'use strict'; var a, i, b, f; function x() { console.log('Mr. X!'); } a = 10; for(i = 0; i < a; i++) { b = i * i; console.log(b); } if(a === 10) { f = function() { console.log(a); }; f(); } x();}());根据以上提升过程,你是否可理解以下代码?有效代码(function(){ 'use strict'; var a = 10; i = 5; x(); for(var i; i < a; i++) { console.log(b); var b = i * i; } if(a === 10) { f = function() { console.log(a); }; f(); var f; } function x() { console.log('Mr. X!'); }}());正如你所看到的这段令人充满困惑与误解的代码导致了出人意料的结果。只有良好的声明习惯,也就是下一章节我们要提到的声明规则,才能尽可能的避免这类错误风险。声明提前为避免上一章节所述的变量和方法定义被自动提升造成误解,把风险降到最低,我们应该手动地显示地去声明变量与方法。也就是说,所有的变量以及方法,应当定义在 function 内的首行。只用一个 var 关键字声明,多个变量用逗号隔开。不推荐(function(){ 'use strict'; var a = 10; var b = 10; for(var i = 0; i < 10; i++) { var c = a * b * i; }; function f() { }; var d = 100; var x = function() { return d * d; }; console.log(x());}());推荐(function(){ 'use strict'; var a = 10, b = 10, i, c, d, x; function f() { }; for(i = 0; i < 10; i++) { c = a * b * i; }; d = 100; x = function() { return d * d; }; console.log(x());}());把赋值尽量写在变量申明中。不推荐var a, b, c; a = 10;b = 10;c = 100;推荐var a = 10, b = 10, c = 100;总是使用带类型判断的比较判断总是使用 === 精确的比较操作符,避免在判断的过程中,由 javascript 的强制类型转换所造成的困扰。如果你使用 === 操作符,那比较的双方必须是同一类型为前提的条件下才会有效。在只使用 == 的情况下,javascript 所带来的强制类型转换使得判断结果跟踪变得复杂,下面的例子可以看出这样的结果有多怪了:(function(){ 'use strict'; console.log('0' == 0); // true console.log('' == false); // true console.log('1' == true); // true console.log(null == undefined); // true var x = { valueOf: function() { return 'X'; } }; console.log(x == 'X');}());明智地使用真假判断当我们在一个 if 条件语句中使用变量或表达式时,会做真假判断。if(a == true) 是不同于 if(a) 的。后者的判断比较特殊,我们称其为真假判断。这种判断会通过特殊的操作将其转换为 true 或 false,下列表达式统统返回 false:false,0,undefined,null,NaN,''(空字符串)。这种真假判断在我们只求结果而不关心过程的情况下,非常的有帮助。以下示例展示了真假判断是如何工作的:(function(){ 'use strict'; function logTruthyFalsy(expr) { if(expr) { console.log('truthy'); } else { console.log('falsy'); } }; logTruthyFalsy(true); // truthy logTruthyFalsy(1); // truthy logTruthyFalsy({}); // truthy logTruthyFalsy([]); // truthy logTruthyFalsy('0'); // truthy logTruthyFalsy(false); // falsy logTruthyFalsy(0); // falsy logTruthyFalsy(undefined); // falsy logTruthyFalsy(null); // falsy logTruthyFalsy(NaN); // falsy logTruthyFalsy(''); // falsy}());变量赋值时的逻辑操作逻辑操作符 || 和 && 也可被用来返回布尔值。如果操作对象为非布尔对象,那每个表达式将会被自左向右地做真假判断。基于此操作,最终总有一个表达式被返回回来。这在变量赋值时,是可以用来简化你的代码的。不推荐if(!x) { if(!y) { x = 1; } else { x = y; }}推荐x = x || y || 1;这一小技巧经常用来给方法设定默认的参数。(function(){ 'use strict'; function multiply(a, b) { a = a || 1; b = b || 1; console.log('Result ' + a * b); } multiply(); // Result 1 multiply(10); // Result 10 multiply(3, NaN); // Result 3 multiply(9, 5); // Result 45}());三元条件判断(if 的快捷方法)用三元操作符分配或返回语句。在比较简单的情况下使用,避免在复杂的情况下使用。没人愿意用 10 行三元操作符把自己的脑子绕晕。不推荐if(x === 10) { return 'valid';} else { return 'invalid';}推荐return x === 10 ? 'valid' : 'invalid';分号总是使用分号,因为隐式的代码嵌套会引发难以察觉的问题。尤其是在压缩文件时,缺少分号往往会造成很多不必要的麻烦, 当然我们更要从根本上来杜绝这些问题。javascript 中语句要以分号结束,否则它将会继续执行下去,不管换不换行。澄清:分号与函数分号需要用在表达式的结尾,而并非函数声明的结尾。区分它们最好的例子是:var foo = function() { return true;};function foo() { return true;}当然,如果你不是分得特别清楚,那么最好都加上,因为如果是函数声明加了分号也不会报错,但是如果表达式不加分号,就会报错。嵌套函数嵌套函数是非常有用的,比如用在持续创建和隐藏辅助函数的任务中。你可以非常自由随意地使用它们。但是,函数嵌套一般不超过3层,因为太多的函数嵌套会倒是逻辑的复杂性大幅增加,从而导致代码维护起来、团队中其他成员阅读起来都非常困难。语句块内的函数声明切勿在语句块内声明函数,在 ECMAScript5 的严格模式下,这是不合法的。函数声明应该在定义域的顶层。但在语句块内可将函数申明转化为函数表达式赋值给变量。不推荐if (x) { function foo() {}}推荐if (x) { var foo = function() {};}异常基本上你无法避免出现异常,特别是在做大型开发和开发插件时(使用应用开发框架等等)。在没有自定义异常的情况下,从有返回值的函数中返回错误信息一定非常的棘手,更别提多不优雅了。不好的解决方案包括了传第一个引用类型来接纳错误信息,或总是返回一个对象列表,其中包含着可能的错误对象。以上方式基本上是比较简陋的异常处理方式。适时可做自定义异常处理。在复杂的环境中,你可以考虑抛出对象而不仅仅是字符串(默认的抛出值)。if(name === undefined) { throw { name: 'System Error', message: 'A name should always be specified!' }}标准特性总是优先考虑使用标准特性。为了最大限度地保证扩展性与兼容性,总是首选标准的特性,而不是非标准的特性(例如:首选 string.charAt(3) 而不是 string[3];首选 DOM 的操作方法来获得元素引用,而不是某一应用特定的快捷方法)。简易的原型继承如果你想在 javascript 中继承你的对象,请遵循一个简易的模式来创建此继承。如果你预计你会遇上复杂对象的继承,那可以考虑采用一个继承库,比如 Proto.js。简易继承请用以下方式:(function(){ 'use strict'; // Constructor function function Apple(name) { this.name = name; } // Defining a method of apple Apple.prototype.eat = function() { console.log('Eating ' + this.name); }; // Constructor function function GrannySmithApple() { // Invoking parent constructor Apple.prototype.constructor.call(this, 'Granny Smith'); } // Set parent prototype while creating a copy with Object.create GrannySmithApple.prototype = Object.create(Apple.prototype); // Set constructor to the sub type, otherwise points to Apple GrannySmithApple.prototype.constructor = GrannySmithApple; // Calling a super method GrannySmithApple.prototype.eat = function() { // Be sure to apply it onto our current object with call(this) Apple.prototype.eat.call(this); console.log('Poor Grany Smith'); }; // Instantiation var apple = new Apple('Test Apple'); var grannyApple = new GrannySmithApple(); console.log(apple.name); // Test Apple console.log(grannyApple.name); // Granny Smith // Instance checks console.log(apple instanceof Apple); // true console.log(apple instanceof GrannySmithApple); // false console.log(grannyApple instanceof Apple); // true console.log(grannyApple instanceof GrannySmithApple); // true // Calling method that calls super method grannyApple.eat(); // Eating Granny Smith\nPoor Grany Smith}());使用闭包闭包的创建也许是 js 最有用也是最易被忽略的能力了。关于闭包的深入理解本文不再更多的解释,如果想了解更多请移步。传送门切勿在循环中创建函数在简单的循环语句中加入函数是非常容易形成闭包而带来隐患的。下面的例子就是一个典型的陷阱:不推荐(function(w){ 'use strict'; var numbers = [1, 2, 3], i; for(i = 0; i < numbers.length; i++) { w.setTimeout(function() { w.alert('Index ' + i + ' with number ' + numbers[i]); }, 0); }}(window));接下来的改进虽然已经解决了上述例子中的问题或 bug,但还是违反了不在循环中创建函数或闭包的原则。不推荐(function(w){ 'use strict'; var numbers = [1, 2, 3], i; for(i = 0; i < numbers.length; i++) { (function(index, number){ w.setTimeout(function() { w.alert('Index ' + index + ' with number ' + number); }, 0); }(i, numbers[i])); }}(window));接下来的改进已解决问题,而且也遵循了规范。可是,你会发现看上去似乎过于复杂繁冗了,应该会有更好的解决方案吧。不完全推荐(function(w){ 'use strict'; var numbers = [1, 2, 3], i; function alertIndexWithNumber(index, number) { return function() { w.alert('Index ' + index + ' with number ' + number); }; } for(i = 0; i < numbers.length; i++) { w.setTimeout(alertIndexWithNumber(i, numbers[i]), 0); }}(window));将循环语句转换为函数执行的方式问题能得到立马解决,每一次循环都会对应地创建一次闭包。函数式的风格更加值得推荐,而且看上去也更加地自然和可预料。推荐(function(w){ 'use strict'; var numbers = [1, 2, 3]; numbers.forEach(function(number, index) { w.setTimeout(function() { w.alert('Index ' + index + ' with number ' + number); }, 0); });}(window));eval 函数(魔鬼)eval() 不但混淆语境还很危险,总会有比这更好、更清晰、更安全的另一种方案来写你的代码,因此尽量不要使用 eval() 函数。this 关键字只在对象构造器、方法和在设定的闭包中使用 this 关键字。this 的语义在此有些误导。它时而指向全局对象(大多数时),时而指向调用者的定义域(在 eval 中),时而指向 DOM 树中的某一节点(当用事件处理绑定到 html 属性上时),时而指向一个新创建的对象(在构造器中),还时而指向其它的一些对象(如果函数被 call() 和 apply() 执行和调用时)。正因为它是如此容易地被搞错,请限制它的使用场景:在构造函数中在对象的方法中(包括由此创建出的闭包内)数组和对象的属性迭代用 ECMAScript5 的迭代方法来迭代数组。使用 Array.forEach 或者如果你要在特殊场合下中断迭代,那就用 Array.every。(function(){ 'use strict'; [1, 2, 3, 4, 5].every(function(element, index, arr) { console.log(element + ' at index ' + index + ' in array ' + arr); if(index !== 5) { return true; } }); var obj = { a: 'A', b: 'B', 'c-d-e': 'CDE' }; Object.keys(obj).forEach(function(element, index, arr) { console.log('Key ' + element + ' has value ' + obj[element]); });}());数组和对象字面量用数组和对象字面量来代替数组和对象构造器。数组构造器很容易让人在它的参数上犯错。不推荐var a1 = new Array(x1, x2, x3);var a2 = new Array(x1, x2);var a3 = new Array(x1);var a4 = new Array();推荐var a = [x1, x2, x3];var a2 = [x1, x2];var a3 = [x1];var a4 = [];对象构造器不会有类似的问题,但是为了可读性和统一性,我们应该使用对象字面量。不推荐var o = new Object();var o2 = new Object();o2.a = 0;o2.b = 1;o2.c = 2;o2['strange key'] = 3;推荐var o = {};var o2 = { a: 0, b: 1, c: 2, 'strange key': 3};修改内建对象的原型链修改内建的诸如 Object.prototype 和 Array.prototype 是被严厉禁止的。修改其它的内建对象比如 `Function.prototype,虽危害没那么大,但始终还是会导致在开发过程中难以 debug 的问题,应当也要避免。自定义 toString() 方法你可以通过自定义 toString() 来控制对象字符串化。这很好,但你必须保证你的方法总是成功并不会有其它副作用。如果你的方法达不到这样的标准,那将会引发严重的问题。如果toString() 调用了一个方法,这个方法做了一个断言,当断言失败,它可能会输出它所在对象的名称,当然对象也需要调用 toString()。圆括号一般在语法和语义上真正需要时才谨慎地使用圆括号。不要用在一元操作符上,例如 delete,typeof 和 void,或在关键字之后,例如 return,throw,case,new 等。字符串统一使用单引号(‘’),不使用双引号(“”)。这在创建 html 字符串非常有好处,因为之前我们HTML的规范中提到html属性都是用双引号”“包裹,所以在JavaScript中我们使用单引号从而避免相互干扰。var msg = 'This is some HTML <div class="makes-sense"></div>';]]></content>
<categories>
<category>规范</category>
</categories>
<tags>
<tag>规范</tag>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[前端编码规范—一般规范]]></title>
<url>%2Fposts%2Fdc7c062e%2F</url>
<content type="text"><![CDATA[前端编码规范—一般规范前端编码规范—一般规范前端编码规范—一般规范前言文件/资源命名结构目录编码格式协议文本缩进检查代码后续更新及补充前言这是一份旨在增强团队的开发协作,提高代码质量和打造开发基石的编码风格规范,其中包含了 html,css,javascript,jquery 这几个部分。我们知道,当一个团队开始指定并实行编码规范的话,错误就会变得更加显而易见。如果一段特定的代码不符合规范的话,它有可能只是代码风格错误,而也有可能会是 bug。早期指定规范就使得代码审核得以更好的开展,并且可以更精确的地定位到错误。文件/资源命名项目中,中划线(-)是用来分隔文件名的不二之选。所以理所当然的,中划线应该也是用来分隔资源名称的好选择。尽量保证文件命名总是以字母开头而不是数字。而以特殊字符开头命名的文件,一般都有特殊的含义与用处。文件的字母名称必须全为小写,这是因为在某些对大小写字母敏感的操作系统中,当文件通过工具压缩混淆后,或者人为修改过后,大小写不同而导致引用文-件不同的错误,很难被发现。还有一些情况下,需要对文件增加后缀或特定的扩展名时(比如 .min.js, .min.css),这种情况下,使用点分隔符来区分。不推荐:1. MyScript.js2. myCamelCaseName.css3. i_love_underscores.html4. 1001-scripts.js5. my-file-min.css推荐:1. my-script.js2. my-camel-case-name.css3. i-love-underscores.html4. thousand-and-one-scripts.js5. my-file.min.css结构目录css base(基础样式库) vendor(插件及支持样式库) index.css login.css list.css user-center.cssfonts iconfont.eot iconfont.svg iconfont.ttf iconfont.woffimg index(多页面的话可以分文件夹) body-bg.jpg header-bg.jpg main-bg.jpg list list-bg.png icon.png body-bg.png header-bg.png main-bg.pngjs labs(JavaScript类库、框架) jquery jquery.cookie.js jquery.easings.min.js jquery.min.js jquery.form.min.js bootstrap bootstrap-paginator.min.js bootstrap-datetimepicker.min.js bootstrap.min.js angular angular-route.min.js angular-animate.min.js angular.min.js require css.js text.js require.min.js controller(APP所有业务逻辑js放在此文件夹) index.js list.js json(用到的json文件、模拟的json结构文件) index.json list.json tpl(APP所有视图、模板放在此文件夹) header.html footer.html modal.html sider-bar.html index.html content.html service(APP用到的服务、自定义的服务) app-service.js app-factory.js (js文件夹根目录下一般放配置文件及入口文件) config.js start.js router-config.js main.jslogin.htmlindex.htmlresults.html编码格式文件必须用 UTF-8 编码,使用 UTF-8(无 BOM),请保持 css、js 文件编码与页面编码一致。协议不要指定引入资源所带的具体协议。当引入图片或其他媒体文件,还有样式和脚本时,url 所指向的具体路径,不要指定协议部分(http 和 https),除非这两者协议都不可用。不指定协议使得 url 从绝对的获取路径转变为相对的,在请求资源协议无法确定时非常好用,而且还能为文件大小节省几个字节。不推荐:<script src="http://cdn.bootcss.com/jquery/2.2.1/jquery.min.js"></script>.demo { background:url(http://xxx.com/images/bg.jpg);}推荐:<script src="//cdn.bootcss.com/jquery/2.2.1/jquery.min.js"></script>.demo { background:url(//xxx.com/images/bg.jpg);}文本缩进无论是 html 还是 css 又或者是 js,都使用TAB缩进和对齐,一次缩进1 个TAB,保持项目文件编码的风格及格式的一致性。同一小组别人修改你代码时候也方便阅读、修改。<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="renderer" content="webkit"> <meta name="keywords" content=""> <meta name="description" content=""> <title>Document</title> </head> <body> <ul> <li></li> <li></li> <li></li> </ul> </body></html>.demo { width:100px; height:100px; background:url(//xxx.com/images/bg.jpg);};(function(w, d, $){ var x = 10, y = 20; console.log(x + y);}(window, document, jQuery));检查代码每次写完 html 或者 css 或者 js,都应该检查一遍代码,看看是否有问题,比如 html 标签是否闭合,css 多余的类没有删除,js 的结束符,代码的缩进是否整齐等等。js代码需要特别注意的是if、else、for、function等语句的后面记得要以’;’分号结尾,因为当文件压缩混淆时,不规范的写法有可能造成错误导致js无法运行。后续更新及补充本文有不完善的地方及其他前端开发中的一般规范,希望多多补充,后续不断更新ing。]]></content>
<categories>
<category>规范</category>
</categories>
<tags>
<tag>规范</tag>
</tags>
</entry>
<entry>
<title><![CDATA[前端编码规范—注释规范]]></title>
<url>%2Fposts%2F2dda5698%2F</url>
<content type="text"><![CDATA[前端编码规范—注释规范前端编码规范—注释规范前端编码规范—注释规范文件申明单行注释与多行注释html代码块注释函数或方法注释模块注释样式区块注释结束语:文件申明顶部添加文件申明信息,包括文件描述、原始作者,如果有更新,则需要添加更新内容、更新作者和更新时间/** * @description: 说明文字 * @author: 张三 *//** * @description: 说明文字 * @author: 张三 * @update: 更新内容 by 李四 2013-04-13 18:32 */单行注释与多行注释无论是单行注释还是多行注释,注释中的每一行长度都不能超过 40 个汉字,或者 80 个英文字符。单行注释/* this is a short comment */多行注释/** this is comment line 1.* this is comment line 2.*/html代码块注释<!-- top navbar-->----code-here----<!-- top navbar end--><!-- sidebar-->----code-here----<!-- sidebar end-->函数或方法注释/** * 这是一个求和函数 * @param {Number} a 第一个数字 * @param {Number} b 第二个数字 * @return {Number} 返回两个数字之和 */var sum = function(a, b) { return a + b;}模块注释模块注释必须单独写在一行/* 模块:xxxxxx by 张三 */.../* 模块:xxxxxx by 张三 */样式区块注释/* header */.../* footer */.../* banner */...结束语:规范不是规则,规范的作用诣在提高我们书写代码的效率、质量、可读性以及我们团队的工作效率,减少团队合作中因代码不规范所造成问题。规范不是要约束某一个人或者某些人,每个人可能在你看到这份规范的时候,都有自己的书写习惯,因为每个人在接触编程的时候不一定是先接触的规范。所以养成习惯是一个漫长的过程,改变习惯也是一个漫长的过程。规范像一把尺子,衡量每个人所产出代码的质量、规范、可读性等等,慢慢的改变自己书写代码的习惯,提高自己书写的代码的质量。常常自嘲的一句话:感觉自己写的代码就值五毛钱,别人的代码怎么那么好,整齐、可读性高,这代码值50啊。当你慢慢改变自己的编码习惯,朝着更高的要求去努力,当有一天你不再需要规范,因为你所产出的代码就是规范,这时你已经走在成为大神的路上了。]]></content>
<categories>
<category>规范</category>
</categories>
<tags>
<tag>规范</tag>
<tag>注释</tag>
</tags>
</entry>
<entry>
<title><![CDATA[前端编码规范—css规范]]></title>
<url>%2Fposts%2F943f7cba%2F</url>
<content type="text"><![CDATA[前端编码规范—css规范前端编码规范—css规范前端编码规范—css规范编码CSS Resetclass 命名减少选择器的嵌套优化选择器属性简写省略 0 后面的单位颜色的使用声明顺序媒体查询带前缀的属性引号声明结束多行规则声明中文字体引用hack 的使用压缩对于 sass 的嵌套编码在 css 首行设置文件编码为 UTF-8。@charset "UTF-8";CSS Reset前端开发人员,整个项目需要一个css reset的文件,以确保不同浏览器的css解释器能对同一句css语句解释一致。当你项目引入框架,尤其是样式框架,例如Bootstrap、jQuery-ui等等。框架一般都会自带css reset的文件,一般都是框架主样式文件的头部。这时候需要看框架css reset后是否是整个项目需要的,适合项目的。如果不是,需要自己写一个css reset文件,在框架样式的下方,在页面样式文件的上方引入。这样既可以覆盖框架的css reset,又可以不影响你缩写的页面样式。目前我们在用的base.css是阿里旗下aliceui的基础样式文件,如下:/* 防止用户自定义背景颜色对网页的影响,添加让用户可以自定义字体 */html { background:#fff; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; -webkit-overflow-scrolling : touch;}/* 内外边距通常让各个浏览器样式的表现位置不同 */body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td,hr,button,article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section { margin:0;padding:0;}/* 重设 HTML5 标签, IE 需要在 js 中 createElement(TAG) */article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section { display:block;}/* HTML5 媒体文件跟 img 保持一致 */audio,canvas,video { display: inline-block;}/* 要注意表单元素并不继承父级 font 的问题 */body,button,input,select,textarea { font:12px/1.5 \5FAE\8F6F\96C5\9ED1,tahoma,arial,"Hiragino Sans GB",\5b8b\4f53,sans-serif;}input,select,textarea { font-size:100%;}/* 去掉各Table cell 的边距并让其边重合 */table { border-collapse:collapse;border-spacing:0;}/* IE bug fixed: th 不继承 text-align*/th { text-align:inherit;}/* 去除默认边框 */fieldset,img { border:0;}/* ie6 7 8(q) bug 显示为行内表现 */iframe { display:block;}/* 去掉 firefox 下此元素的边框 */abbr,acronym { border:0;font-variant:normal;}/* 一致的 del 样式 */del { text-decoration:line-through;}address,caption,cite,code,dfn,em,th,var { font-style:normal; font-weight:500;}/* 去掉列表前的标识, li 会继承 */ol,ul { list-style:none;}/* 对齐是排版最重要的因素, 别让什么都居中 */caption,th { text-align:left;}/* 来自yahoo, 让标题都自定义, 适应多个系统应用 */h1,h2,h3,h4,h5,h6 { font-size:100%; font-weight:500;}q:before,q:after { content:'';}/* 统一上标和下标 */sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline;}sup { top: -0.5em; }sub { bottom: -0.25em; }/* 正常链接 未访问 */a:link { text-decoration:none;}/* 鼠标悬停 */a:hover { text-decoration:none;}/* 默认不显示下划线,保持页面简洁 */ins,a { text-decoration:none;}/* 代码字体 */code,kbd,pre,samp { font-family: monospace, serif; font-size: 1em;}/* 清理浮动 */.fn-clear:after { visibility:hidden; display:block; font-size:0; content:" "; clear:both; height:0;}.fn-clear { zoom:1; /* for IE6 IE7 */}/* 隐藏, 通常用来与 JS 配合 */body .fn-hide { display:none;}/* 设置内联, 减少浮动带来的bug */.fn-left,.fn-right { display:inline;}.fn-left { float:left;}.fn-right { float:right;}/* 单行文字溢出时出现省略号,需设定宽度 */ .fn-text-overflow { overflow: hidden; text-overflow: ellipsis; white-space: nowrap;}/* 人民币符号 */.fn-rmb { font-family: arial; font-style: normal; padding-right: 4px;}/* chrome 下字体过小的问题 */.fn-webkit-adjust { -webkit-text-size-adjust: none;}.fn-overflow{ overflow: hidden;}/* a链接去掉点击时候的虚线*/a,button,input{text-decoration:none; outline:none;/*ff*/ hide-focus:expression(this.hideFocus=true);/*ie*/ }a:hover{ text-decoration: none;}/*初始化系统默认样式*/i,em{ font-style:normal;}input,select,textarea{vertical-align:middle;-webkit-appearance : none; border-radius:0;outline: none; font-family:\5FAE\8F6F\96C5\9ED1;color: #333;}*,*:after,*::before { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box;}/* 清除浏览器默认行为 */*{ -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-touch-callout: none;}a,button{-webkit-user-select:none;}class 命名class 名称全部为小写且应当尽可能短,并且意义明确。使用有意义的名称,使用有组织的或目的明确的名称,不要使用表现形式的名称。不推荐.fw-800 {font-weight:800;}.red {color:red;}推荐.heavy {font-weight:800;}.important {color:red;}使用中划线(-)分隔 class 中的单词。虽然它很不方便的让你双击选择,但是它可以增强理解性。另外属性选择器 [attribute|=value] 也能识别中划线(-),所以最好坚持使用中划线作为分隔符。不推荐.slide_hd {}.slide_bd {}推荐.slide-hd {}.slide-bd {}基于最近的父 class 或基本 class 作为新 class 的前缀。不推荐.header .logo {}.header .banner {}推荐.header-logo {}.header-banner {}避免太过特别的 class不推荐.mainNav320{}.subNav640{}推荐.main-nav{}.sub-nav{}减少选择器的嵌套在写选择器时,要尽可能的减少嵌套层级,一般 2~3 层,不要超过 4 层。不推荐.main ul li a span {}推荐.main span {}优化选择器当构建选择器时应该使用清晰,准确和有语义的 class 名。尽量减少使用标签选择器。如果你只关心你的 class 名,而不是你的代码元素,这样会更容易维护。不推荐div.content > header.content-header > h2.title { font-size: 2em;}推荐.content > .content-header > .title { font-size: 2em;}属性简写css 提供了各种简写属性(font、background 等等),使用简写属性对于代码效率和可读性是有很有用的。不推荐.header{ border-top-style: none; font-family: palatino, georgia, serif; font-size: 100%; line-height: 1.6; padding-bottom: 2px; padding-left: 1px; padding-right: 1px; padding-top: 0;}推荐.header{ border-top: none; font: 100%/1.6 palatino, georgia, serif; padding: 0 1px 2px;}但是不能滥用简写形式,过度使用简写形式的属性声明会导致代码混乱,并且会对属性值带来不必要的覆盖从而引起意外的副作用。例:你只想让一个块元素居中。不推荐div{ width:100px; margin:0 auto;}推荐div{ width:100px; margin-right:auto; margin-left:auto;}省略 0 后面的单位不要在 0 值后面使用单位。不推荐div{ padding-bottom: 0px; margin: 0em;}推荐div{ padding-bottom: 0; margin: 0;}颜色的使用css 中的颜色值可以使用 16 进制来表示,切全部用小写字符。在可能的情况下,可以进行缩写,例如:#fff、#000。如果在允许的情况下,需要透明度和颜色同时使用的可以使用css3的rgba颜色体系,例如rgba(128,128,128,0.5)声明顺序为了保证更好的可读性,我们应该遵循以下顺序:定位:position | z-index | top | right | bottom | left | clip布局:display | float | clear | visibility | overflow | overflow-x | overflow-y尺寸:width | min-width | max-width | height | min-height | max-height外边距:margin | margin-top | margin-right | margin-bottom | margin-left内边距:padding | padding-top | padding-right | padding-bottom | padding-left边框:border | border-top | border-right | border-bottom | border-left | border-radius | box-shadow | border-image背景:background | background-color | background-image | background-repeat | background-attachment | background-position | background-origin | background-clip | background-size颜色:color | opacity字体:font | font-style | font-variant | font-weight | font-size | font-family文本:text-transform | white-space | word-break | word-wrap | overflow-wrap | text-align | word-spacing | letter-spacing | text-indent | vertical-align | line-height文本修饰:text-decoration | text-shadow书写模式:direction | unicode-bidi | writing-mode列表:list-style | list-style-image | list-style-position | list-style-type表格:table-layout | border-collapse | border-spacing | caption-side | empty-cells内容:content | counter-increment | counter-reset | quotes用户界面:appearance | text-overflow | outline | outline-width | outline-color | outline-style | outline-offset | cursor | zoom | box-sizing | resize | user-select多列:columns | column-width | column-count | column-gap | column-rule | column-rule-width | column-rule-style | column-rule-color | column-span | column-fill | column-break-before | column-break-after | column-break-inside伸缩盒:flex变换,过渡,动画:transform | transition | animation媒体查询将媒体查询放在尽可能相关规则的附近。不要将他们打包放在一个单一样式文件中或者放在文档底部。如果你把他们分开了,将来只会被大家遗忘。推荐.element {}.element-avatar {}.element-selected {}@media (min-width: 480px) { .element {} .element-avatar {} .element-selected {}}带前缀的属性当使用特定厂商的带有前缀的属性时,通过缩进的方式,让每个属性的值在垂直方向对齐,这样便于多行编辑。.selector { -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.15); box-shadow: 0 1px 2px rgba(0,0,0,.15);}引号属性选择器或属性值用双引号(”“),而不是单引号(”)括起来。url 的值不要使用引号。不推荐input[type='submit'] { font-family: 'open sans', arial, sans-serif;}body:after { content: 'pause';}推荐input[type="submit"] { font-family: "open sans", arial, sans-serif;}body:after { content: "pause";}声明结束为了保证一致性和可扩展性,每个声明应该用分号结束。不推荐.demo { width:100px; height:100px}推荐.demo { width:100px; height:100px;}多行规则声明为了易读性和便于快速编辑,统一将语句分为多行,即使该样式只包含一条声明。不推荐.demo {width:100px;height:100px;}推荐.demo { width:100px; height:100px;}中文字体引用css 中文字体可以用 unicode 格式来表示,比如“宋体”可以用 \5B8B\4F53 来表示。具体参考下表:中文名英文名unicode宋体SimSun\5B8B\4F53微软雅黑Microsoft YaHei\5FAE\8F6F\96C5\9ED1更多字体编码点击查看。hack 的使用虽然 hacks 能够很方便的解决浏览器之间的兼容问题,但是我们还是尽量不使用 hacks,尽量从根本上解决问题,比如改变结构等等。压缩当项目测试完成准备上线,所有的样式文件需进行压缩,可以使用gulp、webpack等构建工具,也可以使用自己的开发工具或者网站的在线压缩进行css文件的压缩,确保上线的文件是最优的,同时原版未压缩的文件一定要保存完好,方便以后的二开。对于 sass 的嵌套如果你的项目正在使用sass作为样式的预编译工具。在 sass 中你可以嵌套选择器,这可以使代码变得更清洁和可读。嵌套所有的选择器,但尽量避免嵌套没有任何内容的选择器。如果你需要指定一些子元素的样式属性,而父元素将不什么样式属性,可以使用常规的 css 选择器链,这将防止您的脚本看起来过于复杂。不推荐.content { display: block;}.content > .news-article > .title { font-size: 1.2em;}推荐.content { display: block; > .news-article > .title { font-size: 1.2em; }}当使用 sass 的嵌套功能的时候,重要的是有一个明确的嵌套顺序。当前选择器的 @extend 和 @include当前选择器的样式属性父级选择器的伪类选择器(:first-letter,:hover,:active 等等)伪类元素(:before 和 :after)父级选择器的声明样式(.selected,.active 等等)用 sass 的上下文媒体查询子选择器作为最后的部分.test { @extend %clearfix; color:#ccc; &:hover { color:#000; } &:before { border:1px solid #eee; content:""; } &.active { color:#f00; &:hover { color:#ff0; } } @media screen and (max-width: 640px) { display:block; font-size:2em; } > .title { font-size:1.2em; }}]]></content>
<categories>
<category>规范</category>
</categories>
<tags>
<tag>规范</tag>
<tag>CSS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[前端gulp自动构建说明]]></title>
<url>%2Fposts%2Fb2743838%2F</url>
<content type="text"><![CDATA[前端gulp自动构建说明前端gulp自动构建说明一、将package.json、gulpfile.js两个文件拷贝到和自己项目同级别的目录下。例:kingnetio(项目文件夹)、package.json、gulpfile.js二、运行npm install或者cnpm install都可以,安装构建所需的依赖包资源。替换gulp-rev.zip压缩包里的module(压缩资源只改变版本号,不会修改文件名)三检查所需要构建的项目。1、html代码中的<、>符号需要用<、>代替,否则打包时候会出错。2、在js目录下新建common.js并且确保所有项目用到的木块儿在common.js中加载,修改start.js,在start.js中启动common.js.3、保证自己的路由和Controller的js一一对应,且路由对应的tpl模板文件的名字需要和路由的名字对应。例:路由:”/retain”,模板文件:”retain.html”路由:”/usergroup/new”,模板文件:”usergroup-new.html”路由:”/demo/kit/input”,模板文件:”demo-kit-input.html”4、项目中css如果有重写的情况,则需要用css中的权重规则去顶掉前面的样式、而不可以用css文件引入的上下顺序去顶掉之前的样式,因为打包以后所有的样式会被压缩到一个文件中,没有上下关系,样式可能会错乱。例:base.css中 .wrapper{width:100%}index.css中你想要.wrapper的width不是100%,而是auto。可以在index.css中写成div.wrapper{width:auto}或者前面添加父元素的class或者添加自己本身另外一个class都可以,css的权重本文不多做介绍。5、替换angular-route.js文件,或者自己手动把文件写成AMD标准格式。6、项目中的入口HTML文件,css和js引用位置需要加上 构建的注释代码。例:<!-- build:css --><link rel="stylesheet" href="./static/css/index.css"><!-- endbuild --><!-- build:js --><script src="./static/js/libs/require/require.min.js" data-main="./static/js/config.js?v=999"></script><script type="text/javascript"> require(["./static/js/start.js?v=999"])</script><!-- endbuild -->]]></content>
<categories>
<category>规范</category>
</categories>
<tags>
<tag>规范</tag>
<tag>gulp</tag>
</tags>
</entry>
<entry>
<title><![CDATA[前端编码规范—html规范]]></title>
<url>%2Fposts%2Fc5a3f9da%2F</url>
<content type="text"><![CDATA[前端编码规范—html规范前端编码规范—html规范前端编码规范—html规范文档类型语言属性标准HTML文件结构渲染模式设备适配省略自闭合元素的斜线不要省略结束标签语义化实用为王结构,表现与行为分离小写绑定数据布尔型属性html 引号文档类型推荐使用 html5 的文档类型申明:<!DOCTYPE html>文件命名统一以.html结尾。语言属性根据 html5 规范:强烈建议为html 根元素指定 lang 属性,从而为文档设置正确的语言。这将有助于语音合成工具确定其所应该采用的发音,有助于翻译工具确定其翻译时所应遵守的规则等等。这里列出了语言代码表。<html lang="en"></html>标准HTML文件结构标准html文件的基本结构应该由html、head、body等标签构成,结构分明、层次清晰。样式不可以在页面内以style标签存在,需单独生成样式文件,以<link rel="stylesheet" href="./static/css/index.css">的形式载入页面中。样式文件一般可分为基础样式(base.css、init.css)、支持类样式(jquery-easy-ui.min.css、scrollbar.min.css)、以及页面样式(index.css、list.css)。分别在页面的head标签中从上至下引入,这样可以有效的避免样式互相影响。同样的,脚本程序也不可以在页面内、或者行内存在,需要单生成单独的js文件,以<script src="./static/js/controller/login.js" ></script>的形式载入页面。js文件可根据功能及项目需要和实际情况选择载入的位置,可以在head标签内,或者在</body>标签的前面载入。因为js会阻塞页面的渲染,所以一般情况下,js文件都会放在</body>标签的前面载入。除非有特殊情况,例如进入页面时就改变页面的整体宽度,而不是当页面渲染完成后改变,这样就需要在head标签中载入对应的js文件。渲染模式IE 支持通过特定的 meta 标签来确定绘制当前页面所应该采用的 IE 版本。除非有强烈的特殊需求,否则最好是设置为 edge mode,从而通知 IE 采用其所支持的最新的模式。<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"">现在大多数的国内浏览器都是双核浏览器,即IE和webkit内核。为了更好的体验及渲染效果,可以在头部加入以下meta标签,通知双核浏览器用默认用webkit渲染当前网页。<meta name="renderer" content="webkit">设备适配根据项目及产品的不同,可能需要适配不同的设备,手机、pad、PC等,每种设备适配的方法不同,下面分别说明。PC端PC端主要是不同分辨率APP显示效果的一致性,首先要高保真的还原效果图所给出分辨率的样式及交互。其次根据分辨率的不同做出对应的调整,保证网页显示正常、交互合理、不错位、不凌乱。其次是PC端不同浏览器之间的兼容性,根据不同的项目需要及产品要求,对相应的浏览器兼容性做出调整,还有重要的一点是浏览器的兼容性问题在项目的启动阶段就要确定好。因为浏览器的兼容性会影响整个项目框架、类库版本、语言类型、样式及交互的处理方式的选择。各种浏览器及各版本的css hack不在本文中涉及,如有问题,请自行往上查找。如果APP是以响应式开发的,可以在head标签中加入以下标签来实现移动端的缩放。如果是正常开发,没有做响应式及移动端的适配,则不要添加,否则样式会错乱严重。<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">移动端移动端主要是针对不同品牌、不同型号、不同尺寸、不同分辨率手机的适配。目前我们采用js动态渲染meta标签来做到不同分辨率下APP的适配工作,视图部分布局宽度固定,可以是640px,也可以是750px,可以看具体的效果图而定。样式、切图及布局方式基本都和pc端一样,不过可以尽情使用css3及html5,绝大部分的手机浏览器都很好的支持了css3及html5。viewport.js//添加viewport-mate标签缩放网页。!function(userAgent){ if(!!userAgent.match(/AppleWebKit.*Mobile.*/)){ var screen_w = parseInt(window.screen.width), scale = screen_w / 640;//效果图及视图宽度 if (/Android (\d+\.\d+)/.test(userAgent)) { var version = parseFloat(RegExp.$1); if(userAgent.indexOf("MX")>-1&&version>=5){ document.write('<meta name="viewport" content="width=640,minimum-scale = ' + scale + ", maximum-scale = " + scale + ', target-densitydpi=device-dpi">'); }else{ document.write(version > 2.3 ? '<meta name="viewport" content="width=640, minimum-scale = ' + scale + ", maximum-scale = " + scale + ', target-densitydpi=device-dpi">': '<meta name="viewport" content="width=640, target-densitydpi=device-dpi">'); }; } else { document.write('<meta name="viewport" content="width=640, user-scalable=no, target-densitydpi=device-dpi">'); } } }(navigator.userAgent);移动端完美适配的方案有很多,如:rem布局方案、Flexible方案等等,本文不一一列出,有兴趣可以在网上自行查找。不允许使用百分比宽度布局、适配移动端不同屏幕。百分比布局不能够高保真的还原效果图的样式,交互及对dom的操作变得复杂。百分比布局是移动端刚起步时候最初的解决方案,现在随着前端技术的发展,各种“完美适配”的方案出现,请至少去领悟一种方案。不推荐.left-img{width:65.7%}.right-img{width:12.8%}省略自闭合元素的斜线不要在自闭合(self-closing)元素的尾部添加斜线 – html5 规范中明确说明这是可选的。不推荐<input type="text"/>推荐<input type="text">不要省略结束标签不要省略可选的结束标签(closing tag)。不推荐<ul> <li></ul>推荐<ul> <li></li></ul>语义化使用具有语义的标签,比如 h1、p 等等。移动端请使用header、footer、nav等标签。<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="renderer" content="webkit"> <title>Document</title> </head> <body> <h1>标题</h1> <h2>子标题</h2> <p>文本段落</p> </body> <script src="main.js"></script></html>实用为王尽量遵循 html 标准和语义,但是不要以牺牲实用性为代价。任何时候都要尽量使用最少的标签并保持最小的复杂度来实现页面的布局。不推荐<span class="avatar"> <img src="avatar.jpg"></span>推荐<img class="avatar" src="avatar.jpg">结构,表现与行为分离一个完整的页面分为三个部分:结构(html)、表现(css)和行为(js)。为了使它们成为可维护的干净整洁的代码,我们要尽可能的将它们分离开来。严格地保证结构、表现、行为三者分离,并尽量使三者之间没有太多的交互和联系。就是说,尽量在文档和模板中只包含结构性的 html;而将所有表现代码,移入样式表中;将所有动作行为,移入脚本之中。在此之外,为使得它们之间的联系尽可能的小,在文档和模板中也尽量少地引入样式和脚本文件。清晰的分层意味着:不使用超过一到两张样式表尽量合并脚本不使用内嵌样式(<style>.no-good {}</style>)不使用行内样式(<hr style="border-top: 5px solid black">)不使用内嵌脚本(<script>alert('no good')</script>)不使用js入侵式写法(<a onclick="showTime(this)">show</a>)不使用表现元素(<b>,<u>,<center>,<font>)注:关于js入侵式写法的深入了解-传送门小写html 标签及属性(包括自定义属性)都是小写字母,不要使用大写字母。绑定数据如果需要为标签绑定一些数据的话,请使用 html5 的自定义属性 data-* 来绑定相关数据。<h1 data-age="20">张三</h1>布尔型属性布尔型属性可以在声明时不赋值。xhtml 规范要求为其赋值,但是 html5 规范不需要。<input type="text" disabled><input type="checkbox" value="1" checked><select> <option value="1" selected>1</option> <option value="2"></option></select>html 引号html 属性的引号请使用双引号而不是单引号。]]></content>
<categories>
<category>规范</category>
</categories>
<tags>
<tag>规范</tag>
<tag>HTML</tag>
</tags>
</entry>
</search>