`也可。
104 |
105 | ## React 组件的参数
106 |
107 | ### 实验目的
108 |
109 | 1. 学会向 React 组件传参数
110 |
111 | ### 操作步骤
112 |
113 | 1. 浏览器打开`demos/react-component-demo/index2.html`,仔细查看源码。
114 |
115 | ### 注意事项
116 |
117 | 1. 组件内部通过`this.props`对象获取参数。
118 |
119 | ### 练习
120 |
121 | 1. 将组件的颜色,从红色(`red`)换成黄色(`yellow`)。
122 |
123 | ## React 组件的状态
124 |
125 | ### 实验目的
126 |
127 | 1. 学会通过状态变动,引发组件的重新渲染。
128 |
129 | ### 操作步骤
130 |
131 | 1. 浏览器打开`demos/react-component-demo/index3.html`,仔细查看源码。
132 |
133 | ### 注意事项
134 |
135 | ```javascript
136 | class MyTitle extends React.Component {
137 | constructor(...args) {
138 | super(...args);
139 | this.state = {
140 | name: '访问者'
141 | };
142 | }
143 | // ...
144 | ```
145 |
146 | `constructor`是组件的构造函数,会在创建实例时自动调用。`...args`表示组件参数,`super(...args)`是 ES6 规定的写法。`this.state`对象用来存放内部状态,这里是定义初始状态。
147 |
148 | ```html
149 |
150 |
154 |
你好,{this.state.name}
155 |
;
156 | ```
157 |
158 | `this.state.name`表示读取`this.state`的`name`属性。每当输入框有变动,就会自动调用`onChange`指定的监听函数,这里是`this.handleChange`,`.bind(this)`表示该方法内部的`this`,绑定当前组件。
159 |
160 | ```javascript
161 | handleChange(e) {
162 | let name = e.target.value;
163 | this.setState({
164 | name: name
165 | });
166 | }
167 | ```
168 |
169 | `this.setState`方法用来重置`this.state`,每次调用这个方法,就会引发组件的重新渲染。
170 |
171 | ## React 组件实战
172 |
173 | ### 实验目的
174 |
175 | 1. 学会自己写简单的 React 组件。
176 |
177 | ### 操作步骤
178 |
179 | 1. 浏览器打开`demos/react-component-demo/index4.html`。
180 | 1. 点击`Hello World`,看看会发生什么。
181 |
182 | ### 练习
183 |
184 | 1. 修改源码,使得点击`Hello World`后,会显示当前的日期,比如`Hello 2016年1月1日`。
185 |
186 | 2. 请在上一步练习的基础上,进一步修改。现在`Hello World`点击一次,会改变内容,再点击就不会有反应了。请将其改成,再点击一次变回原样。
187 |
188 | ### 提示
189 |
190 | 练习一、下面的代码可以得到当前日期。
191 |
192 | ```javascript
193 | var d = new Date();
194 | d.getFullYear() // 当前年份
195 | d.getMonth() + 1 // 当前月份
196 | d.getDate() // 当前是每个月的几号
197 | ```
198 |
199 | 练习二、可以在`this.state`里面设置一个开关变量`isClicked`。
200 |
201 | ```javascript
202 | this.state = {
203 | text: 'World',
204 | isClicked: false
205 | };
206 | ```
207 |
208 | 然后,在`this.handleClick`方法里面,做一个`toggle`效果。
209 |
210 | ```javascript
211 | let isClicked = !this.state.isClicked;
212 | this.setState({
213 | isClicked: isClicked,
214 | text: isClicked ? 'Clicked' : 'World'
215 | });
216 | ```
217 |
218 | ## React 组件的生命周期
219 |
220 | ### 实验目的
221 |
222 | 1. 掌握钩子方法的基本用法
223 | 1. 掌握组件如何通过 Ajax 请求获取数据,并对数据进行处理
224 |
225 | ### 操作步骤
226 |
227 | 1. 打开`demos/react-lifecycle-demo/index.html`,仔细查看源码。
228 |
229 | ### 注意事项
230 |
231 | ```javascript
232 | componentDidMount() {
233 | const url = '...';
234 | $.getJSON(url)
235 | .done()
236 | .fail();
237 | }
238 | ```
239 |
240 | - `componentDidMount`方法在组件加载后执行,只执行一次。本例在这个方法里向服务器请求数据,操作结束前,组件都显示`Loading`。
241 | - `$.getJSON`方法用于向服务器请求 JSON 数据。本例的数据从 Github API 获取,可以打开源码里面的链接,看看原始的数据结构。
242 |
243 | ### 练习
244 |
245 | 1. 本例的 JSON 数据是 Github 上面最受欢迎的 JavaScript 项目。请在网页上显示一个列表,列出这些项目。
246 |
247 | ### 提示
248 |
249 | (1) `this.state.loading`记录数据加载是否结束。只要数据请求没有结束,`this.state.loading`就一直是`true`,网页上显示`loading`。
250 |
251 | (2) `this.state.error`保存数据请求失败时的错误信息。如果请求失败,`this.state.error`就是返回的错误对象,网页上显示报错信息。
252 |
253 | (3) `this.state.data`保存从服务器获取的数据。如果请求成功,可以先用`console.log`方法,将它在控制台里打印出来,看看数据结构。
254 |
255 | ```javascript
256 | render() {
257 | // 加一行打印命令,看看数据结构
258 | console.log(this.state.data);
259 | return {
260 | // ...
261 | ```
262 |
263 | (4) `this.state.data`里面的`this.state.data.items`应该是一个数组,保存着每个项目的具体信息。可以使用`forEach`方法进行遍历处理。
264 |
265 | ```javascript
266 | var projects = this.state.data.items;
267 | var results = [];
268 | projects.forEach(p => {
269 | var item = {p.name};
270 | results.push(item);
271 | });
272 | ```
273 |
274 | (5)然后,将上一步的`results`插入网页即可。
275 |
276 | ```javascript
277 |
280 | ```
281 |
282 | ## ReCharts
283 |
284 | ### 实验目的
285 |
286 | 1. 了解如何使用第三方组件库。
287 |
288 | ### 操作步骤
289 |
290 | 1. 浏览器打开`demos/recharts-demo/index.html`,查看效果。
291 |
292 | ## MobX
293 |
294 | ### 实验目的
295 |
296 | 1. 理解 MobX 框架
297 |
298 | ### 操作步骤
299 |
300 | (1)浏览器打开`demos/mobx-demo/browser-demo/index.html`,仔细查看源码
301 |
302 | (2) 命令行进入`demos/mobx-demo/`目录,执行如下的命令。
303 |
304 | ```bash
305 | $ npm install
306 | $ npm start
307 | ```
308 |
309 | (3) 打开浏览器,访问 http://localhost:8080 ,查看结果,并仔细研究`app/`目录下面的代码。
310 |
311 | ### 注意事项
312 |
313 | ```javascript
314 | @observer
315 | class App extends React.Component {
316 | render() {
317 | // ...
318 | }
319 | }
320 | ```
321 |
322 | `@observer`是一种新的语法,叫做“装饰器”,表示对整个类的行为进行修改,即将`App`类作为参数传入`observer`函数。这里的意思是,整个`App`类都是一个“观察者”,观察`store`的变化,只要一有变化,立刻重新渲染。
323 |
324 | 数据保存在`Store`里面。`Store`的属性分成两种:被观察的属性(`@observable`),和自动计算得到的属性`@computed`。
325 |
326 | ```javascript
327 | class Store {
328 | @observable name = 'Bartek';
329 | @computed get decorated() {
330 | return `${this.name} is awesome!`;
331 | }
332 | }
333 | ```
334 |
335 | `Store`的变化由用户引发。组件观察到`Store`的变化,自动重新渲染。
336 |
337 | ```javascript
338 |
339 | {this.props.store.decorated}
340 |
341 |
345 | this.props.store.name = event.currentTarget.value
346 | }
347 | />
348 | ```
349 |
350 | ## Redux
351 |
352 | ### 实验目的
353 |
354 | 1. 理解 Redux 架构
355 |
356 | ### 操作步骤
357 |
358 | (1) 命令行下进入`demos/redux-demo`目录,执行如下的命令。
359 |
360 | ```bash
361 | $ npm install
362 | $ npm start
363 | ```
364 |
365 | (2)打开浏览器,访问 http://localhost:8080 ,查看结果,并仔细研究代码。
366 |
367 | ### 注意事项
368 |
369 | (1) Redux 要求 UI 的渲染组件都是纯组件,即不包含任何状态(`this.state`)的组件。
370 |
371 | ```javascript
372 |
373 |
{this.props.text}
374 |
378 |
379 | ```
380 |
381 | (2) 进行数据处理、并包含状态的组件,称为“容器组件”。Redux 使用`connect`方法,自动生成 UI 组件对应的“容器组件”。
382 |
383 | ```javascript、
384 | // MyComponent 是纯的 UI 组件
385 | const App = connect(
386 | mapStateToProps,
387 | mapDispatchToProps
388 | )(MyComponent);
389 | ```
390 |
391 | (3) `mapStateToProps`函数返回一个对象,表示一种映射关系,将 UI 组件的参数映射到`state`。
392 |
393 | ```javascript
394 | function mapStateToProps(state) {
395 | return {
396 | text: state.text,
397 | name: state.name
398 | };
399 | }
400 | ```
401 |
402 | (4) `mapDispatchToProps`函数也是返回一个对象,表示一种映射关系,但定义的是哪些用户的操作应该当作`Action`,传给`Store`。
403 |
404 | ```javascript
405 | function mapDispatchToProps(dispatch) {
406 | return {
407 | onChange: (e) => dispatch({
408 | type: 'change',
409 | payload: e.target.value
410 | })
411 | }
412 | }
413 | ```
414 |
415 | (5) `reducer`函数用来接收`action`,算出新的`state`。
416 |
417 | ```javascript
418 | function reducer(state = {
419 | text: '你好,访问者',
420 | name: '访问者'
421 | }, action) {
422 | switch (action.type) {
423 | case 'change':
424 | return {
425 | name: action.payload,
426 | text: '你好,' + action.payload
427 | };
428 | }
429 | }
430 | ```
431 |
432 | `Store`由 Redux 提供的`createStore`方法生成,该方法接受`reducer`作为参数。
433 |
434 | ```javascript
435 | const store = createStore(reducer);
436 |
437 | ReactDOM.render(
438 |
439 |
440 | ,
441 | document.body.appendChild(document.createElement('div'))
442 | );
443 | ```
444 |
445 | 为了把`Store`传入组件,必须使用 Redux 提供的`Provider`组件在应用的最外面,包裹一层。
446 |
447 | ## Simple App
448 |
449 | ### 实验目的
450 |
451 | 1. 学会使用 Node 编写简单的前端应用。
452 |
453 | ### 操作步骤
454 |
455 | (1)新建一个目录
456 |
457 | ```bash
458 | $ mkdir simple-app-demo
459 | $ cd simple-app-demo
460 | ```
461 |
462 | (2)在该目录下,新建一个`package.json`文件。
463 |
464 | ```bash
465 | $ npm init -y
466 | ```
467 |
468 | `package.json`是项目的配置文件。
469 |
470 | (3)安装`jquery`、`webpack`、`webpack-cli`这三个模块。
471 |
472 | ```bash
473 | $ npm install -S jquery
474 | $ npm install -S webpack webpack-cli
475 | ```
476 |
477 | 打开`package.json`文件,会发现`jquery`、`webpack`和`webpack-cli`都加入了`dependencies`字段,并且带有版本号。
478 |
479 | (4)在项目根目录下,新建一个网页文件`index.html`。
480 |
481 | ```html
482 |
483 |
484 | Hello World
485 |
486 |
487 |
488 | ```
489 |
490 | (5)在项目根目录下,新建一个脚本文件`app.js`。
491 |
492 | ```javascript
493 | const $ = require('jquery');
494 | $('h1').css({ color: 'red'});
495 | ```
496 |
497 | 上面代码中,`require`方法是 Node 特有的模块加载命令。
498 |
499 | (6)打开`package.json`,在`scripts`字段里面,添加一行。
500 |
501 | ```javascript
502 | "scripts": {
503 | "build": "webpack --mode production ./app.js -o ./bundle.js",
504 | "test": "...."
505 | },
506 | ```
507 |
508 | (7) 在项目根目录下,执行下面的命令,将脚本打包。
509 |
510 | ```bash
511 | $ npm run build
512 | ```
513 |
514 | 执行完成,可以发现项目根目录下,新生成了一个文件`bundle.js`。
515 |
516 | (8)浏览器打开`index.html`,可以发现`Hello World`变成了红色。
517 |
518 | ### 练习
519 |
520 | 1. 修改样式,将标题变为蓝色,然后重新编译生成打包文件。
521 |
522 | ## REST API
523 |
524 | ### 实验目的
525 |
526 | 1. 熟悉 REST API 的基本用法
527 |
528 | ### 操作步骤
529 |
530 | (1) 命令行进入`demos/rest-api-demo`目录,执行下面的命令。
531 |
532 | ```bash
533 | $ npm install -S json-server
534 | ```
535 |
536 | (2) 在项目根目录下,新建一个 JSON 文件`db.json`。
537 |
538 | ```javascript
539 | {
540 | "posts": [
541 | { "id": 1, "title": "json-server", "author": "typicode" }
542 | ],
543 | "comments": [
544 | { "id": 1, "body": "some comment", "postId": 1 }
545 | ],
546 | "profile": { "name": "typicode" }
547 | }
548 | ```
549 |
550 | (3) 打开`package.json`,在`scripts`字段添加一行。
551 |
552 | ```javascript
553 | "scripts": {
554 | "server": "json-server db.json",
555 | "test": "..."
556 | },
557 | ```
558 |
559 | (4) 命令行下执行下面的命令,启动服务。
560 |
561 | ```bash
562 | $ npm run server
563 | ```
564 |
565 | (5)打开 Chrome 浏览器的 Postman 应用。依次向`http://127.0.0.1:3000/posts`、`http://127.0.0.1:3000/posts/1`发出`GET`请求,查看结果。
566 |
567 | (6)向`http://127.0.0.1:3000/comments`发出`POST`请求。注意,数据体`Body`要选择`x-www-form-urlencoded`编码,然后依次添加下面两个字段。
568 |
569 | ```javascript
570 | body: "hello world"
571 | postId: 1
572 | ```
573 |
574 | 发出该请求后,再向`http://127.0.0.1:3000/comments`发出`GET`请求,查看结果。
575 |
576 | (7) 向`http://127.0.0.1:3000/comments/2`发出`PUT`请求,数据体`Body`要选择`x-www-form-urlencoded`编码,然后添加下面的字段。
577 |
578 | ```javascript
579 | body: "hello react"
580 | ```
581 |
582 | 发出该请求后,再向`http://127.0.0.1:3000/comments`发出`GET`请求,查看结果。
583 |
584 | (8)向`http://127.0.0.1:3000/comments/2`发出`delete`请求。
585 |
586 | 发出该请求后,再向`http://127.0.0.1:3000/comments`发出`GET`请求,查看结果。
587 |
588 | ## Express
589 |
590 | ### 实验目的
591 |
592 | 1. 学会 Express 搭建 Web 应用的基本用法。
593 |
594 | ### 操作步骤
595 |
596 | (1)进入`demos/express-demo`目录,命令行执行下面的命令,安装依赖。
597 |
598 | ```bash
599 | $ cd demos/express-demo
600 | $ npm install
601 | ```
602 |
603 | (2)打开`app1.js`,尝试看懂这个脚本。
604 |
605 | ```javascript
606 | var express = require('express');
607 | var app = express();
608 | ```
609 |
610 | 上面代码调用`express`,生成一个 Web 应用的实例。
611 |
612 | ```javascript
613 | var router = express.Router();
614 |
615 | router.get('/', function(req, res) {
616 | res.send('Hello World
');
617 | });
618 |
619 | app.use('/home', router);
620 | ```
621 |
622 | 上面代码新建了一个路由对象,该对象指定访问根路由(`/`)时,返回`Hello World`。然后,将该路由加载在`/home`路径,也就是说,访问`/home`会返回`Hello World`。
623 |
624 | `router.get`方法的第二个参数是一个回调函数,当符合指定路由的请求进来,会被这个函数处理。该函数的两个参数,`req`和`res`都是Express 内置的对象,分别表示用户的请求和 Web 服务器的回应。`res.send`方法就表示服务器回应所送出的内容。
625 |
626 | ```javascript
627 | var port = process.env.PORT || 8080;
628 |
629 | app.listen(port);
630 | console.log('Magic happens on port ' + port);
631 | ```
632 |
633 | 上面代码指定了外部访问的端口,如果环境变量没有指定,则端口默认为`8080`。最后两行是启动应用,并输出一行提示文字。
634 |
635 | (3)在命令行下,启动这个应用。
636 |
637 | ```bash
638 | $ node app1.js
639 | ```
640 |
641 | 浏览器访问`localhost:8080/home`,看看是否输出`Hello World`。
642 |
643 | 然后,命令行下按 Ctrl + C,退出这个进程。
644 |
645 | (4)通过环境变量,自定义启动端口。
646 |
647 | 假定我们指定必须启动在`7070`端口,命令行可以这样操作。
648 |
649 | ```bash
650 | # Linux & Mac
651 | $ PORT=7070 node app1.js
652 |
653 | # windows cmd / (git cmd)
654 | $ set PORT=7070
655 | $ node app1.js
656 |
657 | # windows powershell
658 | $ $env:PORT=7070
659 | $ node app1.js
660 | ```
661 |
662 | 浏览器就可以访问`localhost:7070/home`了。
663 |
664 | 然后,命令行下按 Ctrl + C,退出这个进程。
665 |
666 | 思考题:Node 应用能否直接在`80`端口启动?
667 |
668 | (5)打开`app2.js`,查看新增的那个路由。
669 |
670 | ```javascript
671 | router.get('/:name', function(req, res) {
672 | res.send('Hello ' + req.params.name + '
');
673 | });
674 | ```
675 |
676 | 上面代码新增了一个路由,这个路由的路径是一个命名参数`:name`,可以从`req.params.name`拿到这个传入的参数。
677 |
678 | 在命令行下,启动这个应用。
679 |
680 | ```bash
681 | $ node app2.js
682 | ```
683 |
684 | 浏览器访问`localhost:8080/home/张三`,看看是否输出`Hello 张三`。
685 |
686 | 然后,命令行下按 Ctrl + C,退出这个进程。
687 |
688 | (6)打开`app3.js`,先查看页面头部新增的两行代码。
689 |
690 | ```javascript
691 | var express = require('express');
692 | var app = express();
693 |
694 | // 新增代码...
695 | var bodyParser = require('body-parser');
696 | app.use(bodyParser.urlencoded({ extended: true }));
697 |
698 | // ...
699 | ```
700 |
701 | 上面代码中,`body-parser`模块的作用,是对`POST`、`PUT`、`DELETE`等 HTTP 方法的数据体进行解析。`app.use`用来将这个模块加载到当前应用。有了这两句,就可以处理`POST`、`PUT`、`DELETE`等请求了。
702 |
703 | 下面查看新增的那个路由。
704 |
705 | ```javascript
706 | router.post('/', function (req, res) {
707 | var name = req.body.name;
708 | res.json({message: 'Hello ' + name});
709 | });
710 | ```
711 |
712 | 上面代码表示,如果收到了`/`路径(实际上是`/home`路径)的`POST`请求,先从数据体拿到`name`字段,然后返回一段 JSON 信息。
713 |
714 | 在命令行下,启动这个应用。
715 |
716 | ```bash
717 | $ node app3.js
718 | ```
719 |
720 | 然后,在 Chrome 浏览器的 Postman 插件里面,向`http://127.0.0.1:8080/home`发出一个`POST`请求。数据体的编码方法设为`x-www-form-urlencoded`,里面设置一个`name`字段,值可以随便取,假定设为`Alice`。也就是说,发出这样一个请求。
721 |
722 | ```
723 | POST /home HTTP/1.1
724 | Host: 127.0.0.1:8080
725 | Content-Type: application/x-www-form-urlencoded
726 |
727 | name=Alice
728 | ```
729 |
730 | 如果一切正常,服务器会返回一段 JSON 信息。
731 |
732 | ```javascript
733 | {
734 | "message": "Hello Alice"
735 | }
736 | ```
737 |
738 | (7)打开`app4.js`,查看在所有路由之前新增的那个函数。
739 |
740 | ```javascript
741 | var router = express.Router();
742 |
743 | // 新增的代码
744 | router.use(function(req, res, next) {
745 | console.log('There is a requesting.');
746 | next();
747 | });
748 |
749 | router.get('/', function(req, res) {
750 | // ...
751 | ```
752 |
753 | `router.use`的作用是加载一个函数。这个函数被称为中间件,作用是在请求被路由匹配之前,先进行一些处理。上面这个中间件起到 logging 的作用,每收到一个请求,就在命令行输出一条记录。请特别注意,这个函数内部的`next()`,它代表下一个中间件,表示将处理过的请求传递给下一个中间件。这个例子只有一个中间件,就进入路由匹配处理(实际上,`bodyparser`、`router`本质都是中间件,整个 Express 的设计哲学就是不断对 HTTP 请求加工,然后返回一个 HTTP 回应)。
754 |
755 | ### 练习
756 |
757 | 1. 请增加一个中间件,服务器每次收到用户请求,会在服务器的控制台打印出收到请求的时间。
758 |
759 | 2. URL 的查询字符串,比如`localhost:8080?name=Alice`里面的`name`,可以用`req.query.name`拿到。请修改一个路由,使之可以收到查询字符串,然后输出`'Hello ' + req.query.name`。
760 |
761 | ## ESLint
762 |
763 | ### 实验目的
764 |
765 | 1. 学会使用 ESLint 进行代码检查。
766 |
767 | ### 操作步骤
768 |
769 | (1)进入`demos/eslint-demo`目录,安装 ESLint。
770 |
771 | ```bash
772 | $ cd demos/eslint-demo
773 | $ npm install eslint --save-dev
774 | ```
775 |
776 | (2)通常,我们会使用别人已经写好的代码检查规则,这里使用的是 Airbnb 公司的规则。所以,还要安装 ESLint 这个规则模块。
777 |
778 | ```bash
779 | $ npm install eslint-plugin-import eslint-config-airbnb-base --save-dev
780 | ```
781 |
782 | 上面代码中,`eslint-plugin-import`是运行这个规则集必须的,所以也要一起安装。
783 |
784 | (3)ESLint 的配置文件是`.eslintrc.json`,放置在项目的根目录下面。新建这个文件,在里面指定使用 Airbnb 的规则。
785 |
786 | ```javascript
787 | {
788 | "extends": "airbnb-base"
789 | }
790 | ```
791 |
792 | (4)打开项目的`package.json`,在`scripts`字段里面添加三个脚本。
793 |
794 | ```javascript
795 | {
796 | // ...
797 | "scripts" : {
798 | "test": "echo \"Error: no test specified\" && exit 1",
799 | "lint": "eslint **/*.js",
800 | "lint-html": "eslint **/*.js -f html -o ./reports/lint-results.html",
801 | "lint-fix": "eslint --fix **/*.js"
802 | },
803 | // ...
804 | }
805 | ```
806 |
807 | 除了原有的`test`脚本,上面代码新定义了三个脚本,它们的作用如下。
808 |
809 | - `lint`:检查所有`js`文件的代码
810 | - `lint-html`:将检查结果写入一个网页文件`./reports/lint-results.html`
811 | - `lint-fix`:自动修正某些不规范的代码
812 |
813 | (5)运行静态检查命令。
814 |
815 | ```bash
816 | $ npm run lint
817 |
818 | 1:5 error Unexpected var, use let or const instead no-var
819 | 2:5 warning Unexpected console statement no-console
820 |
821 | ✖ 2 problems (1 error, 1 warning)
822 | ```
823 |
824 | 正常情况下,该命令会从`index.js`脚本里面,检查出来两个错误:一个是不应该使用`var`命令,另一个是不应该在生产环境使用`console.log`方法。
825 |
826 | (6)修正错误。
827 |
828 | ```bash
829 | $ npm run lint-fix
830 | ```
831 |
832 | 运行上面的命令以后,再查看`index.js`,可以看到`var x = 1;`被自动改成了`const x = 1;`。这样就消除了一个错误,但是还留下一个错误。
833 |
834 | (7)修改规则。
835 |
836 | 由于我们想要允许使用`console.log`方法,因此可以修改`.eslintrc.json`,改变`no-console`规则。请将`.eslintrc.json`改成下面的样子。
837 |
838 | ```javascript
839 | {
840 | "extends": "airbnb-base",
841 |
842 | "rules": {
843 | "no-console": "off"
844 | }
845 | }
846 | ```
847 |
848 | 再运行`npm run lint`,就不会报错了。
849 |
850 | ```bash
851 | $ npm run lint
852 | ```
853 |
854 | ## Mocha
855 |
856 | ### 实验目的
857 |
858 | 1. 学会使用 Mocha 进行单元测试。
859 |
860 | ### 操作步骤
861 |
862 | (1) 进入`demos/mocha-demo`目录,安装 Mocha 和 Chai。
863 |
864 | ```bash
865 | $ cd demos/mocha-demo
866 | $ npm install -D mocha
867 | $ npm install -D chai
868 | ```
869 |
870 | (2)打开`add.js`文件,查看源码,我们要测试的就是这个脚本。
871 |
872 | ```javascript
873 | function add(x, y) {
874 | return x + y;
875 | }
876 |
877 | module.exports = add;
878 | ```
879 |
880 | (3)编写一个测试脚本`add.test.js`。
881 |
882 | ```javascript
883 | var add = require('./add.js');
884 | var expect = require('chai').expect;
885 |
886 | describe('加法函数的测试', function() {
887 | it('1 加 1 应该等于 2', function() {
888 | expect(add(1, 1)).to.be.equal(2);
889 | });
890 | });
891 | ```
892 |
893 | 测试脚本与所要测试的源码脚本同名,但是后缀名为`.test.js`(表示测试)或者`.spec.js`(表示规格)。比如,`add.js`的测试脚本名字就是`add.test.js`。
894 |
895 | 测试脚本里面应该包括一个或多个`describe`块,每个`describe`块应该包括一个或多个`it`块。
896 |
897 | `describe`块称为"测试套件"(test suite),表示一组相关的测试。它是一个函数,第一个参数是测试套件的名称("加法函数的测试"),第二个参数是一个实际执行的函数。
898 |
899 | `it`块称为"测试用例"(test case),表示一个单独的测试,是测试的最小单位。它也是一个函数,第一个参数是测试用例的名称("1 加 1 应该等于 2"),第二个参数是一个实际执行的函数。
900 |
901 | 上面的测试脚本里面,有一句断言。
902 |
903 | ```javascript
904 | expect(add(1, 1)).to.be.equal(2);
905 | ```
906 |
907 | 所谓"断言",就是判断源码的实际执行结果与预期结果是否一致,如果不一致就抛出一个错误。上面这句断言的意思是,调用`add(1, 1)`,结果应该等于`2`。
908 |
909 | 所有的测试用例(`it`块)都应该含有一句或多句的断言。它是编写测试用例的关键。断言功能由断言库来实现,Mocha本身不带断言库,所以必须先引入断言库。
910 |
911 | ```javascript
912 | var expect = require('chai').expect;
913 | ```
914 |
915 | 断言库有很多种,Mocha并不限制使用哪一种。上面代码引入的断言库是`chai`,并且指定使用它的`expect`断言风格。
916 |
917 | (4)打开`package.json`文件,改写`scripts`字段的`test`脚本。
918 |
919 | ```javascript
920 | "scripts": {
921 | "test": "echo \"Error: no test specified\" && exit 1"
922 | },
923 |
924 | // 改成
925 |
926 | "scripts": {
927 | "test": "mocha *.test.js"
928 | },
929 | ```
930 |
931 | (5)命令行下,执行下面的命令,运行测试用例。
932 |
933 | ```bash
934 | $ npm test
935 | ```
936 |
937 | 正常情况下,命令行会有提示,表示测试用例已经通过了。
938 |
939 | ### 练习
940 |
941 | 1. 请在`add.test.js`里面添加一个测试用例,测试`3`加上`-3`,`add`函数应该返回`0`。
942 |
943 | ## Nightmare
944 |
945 | ### 实验目的
946 |
947 | 1. 学会使用 Nightmare 完成功能测试。
948 |
949 | ### 操作步骤
950 |
951 | (1)进入`./demos/nightmare-demo`目录,安装依赖。
952 |
953 | ```bash
954 | $ cd demos/nightmare-demo
955 |
956 | # Linux & Mac
957 | $ env ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/ npm install
958 |
959 | # Windows
960 | $ set ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/
961 | $ npm install
962 | ```
963 |
964 | 注意,Nightmare 会先安装 Electron,而 Electron 的安装需要下载境外的包,有时会连不上,导致安装失败。所以,这里先设置了环境变量,指定使用国内的 Electron 源,然后才执行安装命令。
965 |
966 | (2)查看一下浏览器自动化脚本`taobao.test.js`。
967 |
968 | ```javascript
969 | var Nightmare = require('nightmare');
970 | var nightmare = Nightmare({ show: true });
971 | ```
972 |
973 | 上面代码表示新建一个 Nightmare 实例,并且运行功能中,自动打开浏览器窗口。
974 |
975 | ```javascript
976 | nightmare
977 | .goto('https://www.taobao.com/')
978 | .type('#q', '电视机')
979 | .click('form[action*="/search"] [type=submit]')
980 | .wait('#spulist-grid')
981 | .evaluate(function () {
982 | return document.querySelector('#spulist-grid .grid-item .info-cont')
983 | .textContent.trim();
984 | })
985 | .end()
986 | ```
987 |
988 | 上面代码表示,打开淘宝首页,在搜索框键入`电视机`,点击“搜索”按钮,等待`#spulist-grid`元素出现,在页面内注入(`evaluate`)代码,将执行结果返回。
989 |
990 | ```javascript
991 | .then(function (result) {
992 | console.log(result);
993 | })
994 | .catch(function (error) {
995 | console.error('Search failed:', error);
996 | });
997 | ```
998 |
999 | Nightmare 会返回一个 Promise 对象,`then`方法指定操作成功的回调函数,`catch`方法指定操作失败的回调函数。
1000 |
1001 | (3)命令行下运行这个示例脚本。
1002 |
1003 | ```bash
1004 | $ node taobao.test.js
1005 | ```
1006 |
1007 | 正常情况下,运行结束后,命令行会显示淘宝“电视机”搜索结果的第一项。
1008 |
1009 | (4)浏览器打开`index.html`文件,这是 React 练习时做过的一个例子,点击`Hello World`,标题会变成`Hello Clicked`。我们就要编写测试脚本,测试这个功能。
1010 |
1011 | (5)打开测试脚本`test.js`。
1012 |
1013 | ```javascript
1014 | var Nightmare = require('nightmare');
1015 | var expect = require('chai').expect;
1016 | var fork = require('child_process').fork;
1017 |
1018 | describe('test index.html', function() {
1019 | var child;
1020 |
1021 | before(function (done) {
1022 | child = fork('./server.js');
1023 | child.on('message', function (msg) {
1024 | if (msg === 'listening') {
1025 | done();
1026 | }
1027 | });
1028 | });
1029 |
1030 | after(function () {
1031 | child.kill();
1032 | });
1033 | ```
1034 |
1035 | 上面代码中,`before`和`after`是 Mocha 提供的两个钩子方法,分别在所有测试开始前和结束后运行。这里,我们在`before`方法里面,新建一个子进程,用来启动 HTTP 服务器;测试结束后,再杀掉这个子进程。
1036 |
1037 | 注意,`before`方法的参数是一个函数,它接受`done`作为参数。`done`是 Mocha 提供的一个函数,用来表示异步操作完成。如果不调用`done`,Mocha 就会认为异步操作没有结束,一直停在这一步,不往下执行,从而导致超时错误。
1038 |
1039 | 子进程脚本`server.js`的代码非常简单,只有四行。
1040 |
1041 | ```javascript
1042 | var httpServer = require('http-server');
1043 | var server = httpServer.createServer();
1044 | server.listen(8080);
1045 | process.send('listening');
1046 | ```
1047 |
1048 | 上面代码中,我们在`8080`端口启动 HTTP 服务器,然后向父进程发消息,表示启动完成。
1049 |
1050 | (6)真正的自动化测试脚本如下。
1051 |
1052 | ```javascript
1053 | it('点击后标题改变', function(done) {
1054 | var nightmare = Nightmare({ show: true });
1055 | nightmare
1056 | .goto('http://127.0.0.1:8080/index.html')
1057 | .click('h1')
1058 | .wait(1000)
1059 | .evaluate(function () {
1060 | return document.querySelector('h1').textContent;
1061 | })
1062 | .end()
1063 | .then(function(text) {
1064 | expect(text).to.equal('Hello Clicked');
1065 | done();
1066 | })
1067 | });
1068 | ```
1069 |
1070 | 上面代码中,首先打开网页,点击`h1`元素,然后等待 1 秒钟,注入脚本获取`h1`元素的文本内容。接着,在`then`方法里面,做一个断言,判断获取的文本是否正确。
1071 |
1072 | (7)运行这个测试脚本。
1073 |
1074 | ```bash
1075 | $ npm test
1076 | ```
1077 |
1078 | 如果一切正常,命令行下会显示测试通过。
1079 |
1080 | ### 练习
1081 |
1082 | 1. 请写一个测试用例,验证``的字体颜色是红色。(提示:可以使用`Window.getComputedStyle()`方法,获取元素的最终样式。)
1083 |
1084 | ## Travis CI
1085 |
1086 | ### 实验目的
1087 |
1088 | 1. 了解持续集成的做法,学会使用 Travis CI。
1089 |
1090 | ### 操作步骤
1091 |
1092 | (1)注册 [Github](https://github.com) 的账户。如果你已经注册过,跳过这一步。
1093 |
1094 | (2)访问这个代码库[`github.com/ruanyf/travis-ci-demo`](https://github.com/ruanyf/travis-ci-demo),点击右上角的`Fork`按钮,将它克隆到你自己的空间里面。
1095 |
1096 | (3)将你`fork`的代码库,克隆到本地。注意,要将下面网址之中的`[your_username]`改成你的 Github 用户名。
1097 |
1098 | ```bash
1099 | // Linux & Mac
1100 | $ git clone git@github.com:[your_username]/travis-ci-demo.git
1101 |
1102 | // Windows
1103 | $ git clone https://github.com:[your_username]/travis-ci-demo
1104 | ```
1105 |
1106 | (4)使用你的 Github 账户,登录 [Travis CI](https://travis-ci.org/auth) 的首页。然后,访问 [Profile](https://travis-ci.org/profile) 页面,选定`travis-ci-demo`代码库运行自动构建。
1107 |
1108 | (5)回到命令行,进入你本地的`travis-ci-demo`目录,切换到`demo01`分支。
1109 |
1110 | ```bash
1111 | $ cd travis-ci-demo
1112 | $ git checkout demo01
1113 | ```
1114 |
1115 | 项目根目录下面有一个`.travis.yml`文件,这是 Travis CI 的配置文件。如果没有这个文件,就不会触发 Travis CI 的自动构建。打开看一下。
1116 |
1117 | ```bash
1118 | language: node_js
1119 | node_js:
1120 | - "node"
1121 | ```
1122 |
1123 | 上面代码指定,使用 Node 完成构建,版本是最新的稳定版。
1124 |
1125 | 指定 Node 的版本号也是可以的。
1126 |
1127 | ```javascript
1128 | language: node_js
1129 | node_js:
1130 | - "4.1"
1131 | ```
1132 |
1133 | 上面代码指定使用 Node 4.1 版。
1134 |
1135 | (6)Travis CI 默认依次执行以下九个脚本。
1136 |
1137 | - `before_install`
1138 | - `install`
1139 | - `before_script`
1140 | - `script`
1141 | - `after_success` 或者 `after_failure`
1142 | - `after_script`
1143 | - `before_deploy`(可选)
1144 | - `deploy`(可选)
1145 | - `after_deploy`(可选)
1146 |
1147 | 用户需要用到哪个脚本,就需要提供该脚本的内容。
1148 |
1149 | 对于 Node 项目,以下两个脚本有默认值,可以不用自己设定。
1150 |
1151 | ```javascript
1152 | "install": "npm install",
1153 | "script": "npm test"
1154 | ```
1155 |
1156 | (7)打开当前分支的`package.json`,可以发现它的`test`脚本是一个`lint`命令。
1157 |
1158 | ```javascript
1159 | "scripts": {
1160 | "test": "jshint hello.js"
1161 | },
1162 | ```
1163 |
1164 | (8)在项目根目录下,新建一个新文件`NewUser.txt`,内容是你的用户名。提交这个文件,就会触发 Travis CI 的自动构建。
1165 |
1166 | ```bash
1167 | $ git add -A
1168 | $ git commit -m 'Testing Travis CI'
1169 | $ git push
1170 | ```
1171 |
1172 | (9)等到 Travis CI 完成自动构建,到页面上[检查](https://travis-ci.org/repositories)构建结果。
1173 |
1174 | (10)切换到`demo02`分支,打开`package.json`,可以看到`test`脚本,现在需要完成两步操作了。
1175 |
1176 | ```javascript
1177 | "scripts": {
1178 | "lint": "jshint hello.js hello.test.js",
1179 | "test": "npm run lint && mocha hello.test.js"
1180 | },
1181 | ```
1182 |
1183 | (11)重复上面第 8 步和第 9 步。
1184 |
1185 | ### 练习
1186 |
1187 | 1. 修改`hello.js`,让其输出`Hello Node`。并修改测试用例`hello.test.js`,使之能够通过 Travis CI 的自动构建。
1188 |
--------------------------------------------------------------------------------
/demos/angular-demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
姓名 :
8 |
你好,{{name}}
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/demos/backbone-demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Backbone Demo
6 |
7 |
8 |
9 | Backbone Routing Demo
10 |
11 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/demos/backbone-demo/js/backbone.js:
--------------------------------------------------------------------------------
1 | // Backbone.js 0.9.2
2 |
3 | // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
4 | // Backbone may be freely distributed under the MIT license.
5 | // For all details and documentation:
6 | // http://backbonejs.org
7 | (function(){var l=this,y=l.Backbone,z=Array.prototype.slice,A=Array.prototype.splice,g;g="undefined"!==typeof exports?exports:l.Backbone={};g.VERSION="0.9.2";var f=l._;!f&&"undefined"!==typeof require&&(f=require("underscore"));var i=l.jQuery||l.Zepto||l.ender;g.setDomLibrary=function(a){i=a};g.noConflict=function(){l.Backbone=y;return this};g.emulateHTTP=!1;g.emulateJSON=!1;var p=/\s+/,k=g.Events={on:function(a,b,c){var d,e,f,g,j;if(!b)return this;a=a.split(p);for(d=this._callbacks||(this._callbacks=
8 | {});e=a.shift();)f=(j=d[e])?j.tail:{},f.next=g={},f.context=c,f.callback=b,d[e]={tail:g,next:j?j.next:f};return this},off:function(a,b,c){var d,e,h,g,j,q;if(e=this._callbacks){if(!a&&!b&&!c)return delete this._callbacks,this;for(a=a?a.split(p):f.keys(e);d=a.shift();)if(h=e[d],delete e[d],h&&(b||c))for(g=h.tail;(h=h.next)!==g;)if(j=h.callback,q=h.context,b&&j!==b||c&&q!==c)this.on(d,j,q);return this}},trigger:function(a){var b,c,d,e,f,g;if(!(d=this._callbacks))return this;f=d.all;a=a.split(p);for(g=
9 | z.call(arguments,1);b=a.shift();){if(c=d[b])for(e=c.tail;(c=c.next)!==e;)c.callback.apply(c.context||this,g);if(c=f){e=c.tail;for(b=[b].concat(g);(c=c.next)!==e;)c.callback.apply(c.context||this,b)}}return this}};k.bind=k.on;k.unbind=k.off;var o=g.Model=function(a,b){var c;a||(a={});b&&b.parse&&(a=this.parse(a));if(c=n(this,"defaults"))a=f.extend({},c,a);b&&b.collection&&(this.collection=b.collection);this.attributes={};this._escapedAttributes={};this.cid=f.uniqueId("c");this.changed={};this._silent=
10 | {};this._pending={};this.set(a,{silent:!0});this.changed={};this._silent={};this._pending={};this._previousAttributes=f.clone(this.attributes);this.initialize.apply(this,arguments)};f.extend(o.prototype,k,{changed:null,_silent:null,_pending:null,idAttribute:"id",initialize:function(){},toJSON:function(){return f.clone(this.attributes)},get:function(a){return this.attributes[a]},escape:function(a){var b;if(b=this._escapedAttributes[a])return b;b=this.get(a);return this._escapedAttributes[a]=f.escape(null==
11 | b?"":""+b)},has:function(a){return null!=this.get(a)},set:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c||(c={});if(!d)return this;d instanceof o&&(d=d.attributes);if(c.unset)for(e in d)d[e]=void 0;if(!this._validate(d,c))return!1;this.idAttribute in d&&(this.id=d[this.idAttribute]);var b=c.changes={},h=this.attributes,g=this._escapedAttributes,j=this._previousAttributes||{};for(e in d){a=d[e];if(!f.isEqual(h[e],a)||c.unset&&f.has(h,e))delete g[e],(c.silent?this._silent:
12 | b)[e]=!0;c.unset?delete h[e]:h[e]=a;!f.isEqual(j[e],a)||f.has(h,e)!=f.has(j,e)?(this.changed[e]=a,c.silent||(this._pending[e]=!0)):(delete this.changed[e],delete this._pending[e])}c.silent||this.change(c);return this},unset:function(a,b){(b||(b={})).unset=!0;return this.set(a,null,b)},clear:function(a){(a||(a={})).unset=!0;return this.set(f.clone(this.attributes),a)},fetch:function(a){var a=a?f.clone(a):{},b=this,c=a.success;a.success=function(d,e,f){if(!b.set(b.parse(d,f),a))return!1;c&&c(b,d)};
13 | a.error=g.wrapError(a.error,b,a);return(this.sync||g.sync).call(this,"read",this,a)},save:function(a,b,c){var d,e;f.isObject(a)||null==a?(d=a,c=b):(d={},d[a]=b);c=c?f.clone(c):{};if(c.wait){if(!this._validate(d,c))return!1;e=f.clone(this.attributes)}a=f.extend({},c,{silent:!0});if(d&&!this.set(d,c.wait?a:c))return!1;var h=this,i=c.success;c.success=function(a,b,e){b=h.parse(a,e);if(c.wait){delete c.wait;b=f.extend(d||{},b)}if(!h.set(b,c))return false;i?i(h,a):h.trigger("sync",h,a,c)};c.error=g.wrapError(c.error,
14 | h,c);b=this.isNew()?"create":"update";b=(this.sync||g.sync).call(this,b,this,c);c.wait&&this.set(e,a);return b},destroy:function(a){var a=a?f.clone(a):{},b=this,c=a.success,d=function(){b.trigger("destroy",b,b.collection,a)};if(this.isNew())return d(),!1;a.success=function(e){a.wait&&d();c?c(b,e):b.trigger("sync",b,e,a)};a.error=g.wrapError(a.error,b,a);var e=(this.sync||g.sync).call(this,"delete",this,a);a.wait||d();return e},url:function(){var a=n(this,"urlRoot")||n(this.collection,"url")||t();
15 | return this.isNew()?a:a+("/"==a.charAt(a.length-1)?"":"/")+encodeURIComponent(this.id)},parse:function(a){return a},clone:function(){return new this.constructor(this.attributes)},isNew:function(){return null==this.id},change:function(a){a||(a={});var b=this._changing;this._changing=!0;for(var c in this._silent)this._pending[c]=!0;var d=f.extend({},a.changes,this._silent);this._silent={};for(c in d)this.trigger("change:"+c,this,this.get(c),a);if(b)return this;for(;!f.isEmpty(this._pending);){this._pending=
16 | {};this.trigger("change",this,a);for(c in this.changed)!this._pending[c]&&!this._silent[c]&&delete this.changed[c];this._previousAttributes=f.clone(this.attributes)}this._changing=!1;return this},hasChanged:function(a){return!arguments.length?!f.isEmpty(this.changed):f.has(this.changed,a)},changedAttributes:function(a){if(!a)return this.hasChanged()?f.clone(this.changed):!1;var b,c=!1,d=this._previousAttributes,e;for(e in a)if(!f.isEqual(d[e],b=a[e]))(c||(c={}))[e]=b;return c},previous:function(a){return!arguments.length||
17 | !this._previousAttributes?null:this._previousAttributes[a]},previousAttributes:function(){return f.clone(this._previousAttributes)},isValid:function(){return!this.validate(this.attributes)},_validate:function(a,b){if(b.silent||!this.validate)return!0;var a=f.extend({},this.attributes,a),c=this.validate(a,b);if(!c)return!0;b&&b.error?b.error(this,c,b):this.trigger("error",this,c,b);return!1}});var r=g.Collection=function(a,b){b||(b={});b.model&&(this.model=b.model);b.comparator&&(this.comparator=b.comparator);
18 | this._reset();this.initialize.apply(this,arguments);a&&this.reset(a,{silent:!0,parse:b.parse})};f.extend(r.prototype,k,{model:o,initialize:function(){},toJSON:function(a){return this.map(function(b){return b.toJSON(a)})},add:function(a,b){var c,d,e,g,i,j={},k={},l=[];b||(b={});a=f.isArray(a)?a.slice():[a];c=0;for(d=a.length;c=b))this.iframe=i('').hide().appendTo("body")[0].contentWindow,this.navigate(a);this._hasPushState?i(window).bind("popstate",this.checkUrl):this._wantsHashChange&&"onhashchange"in window&&!b?i(window).bind("hashchange",this.checkUrl):this._wantsHashChange&&(this._checkUrlInterval=setInterval(this.checkUrl,
30 | this.interval));this.fragment=a;a=window.location;b=a.pathname==this.options.root;if(this._wantsHashChange&&this._wantsPushState&&!this._hasPushState&&!b)return this.fragment=this.getFragment(null,!0),window.location.replace(this.options.root+"#"+this.fragment),!0;this._wantsPushState&&this._hasPushState&&b&&a.hash&&(this.fragment=this.getHash().replace(s,""),window.history.replaceState({},document.title,a.protocol+"//"+a.host+this.options.root+this.fragment));if(!this.options.silent)return this.loadUrl()},
31 | stop:function(){i(window).unbind("popstate",this.checkUrl).unbind("hashchange",this.checkUrl);clearInterval(this._checkUrlInterval);m.started=!1},route:function(a,b){this.handlers.unshift({route:a,callback:b})},checkUrl:function(){var a=this.getFragment();a==this.fragment&&this.iframe&&(a=this.getFragment(this.getHash(this.iframe)));if(a==this.fragment)return!1;this.iframe&&this.navigate(a);this.loadUrl()||this.loadUrl(this.getHash())},loadUrl:function(a){var b=this.fragment=this.getFragment(a);return f.any(this.handlers,
32 | function(a){if(a.route.test(b))return a.callback(b),!0})},navigate:function(a,b){if(!m.started)return!1;if(!b||!0===b)b={trigger:b};var c=(a||"").replace(s,"");this.fragment!=c&&(this._hasPushState?(0!=c.indexOf(this.options.root)&&(c=this.options.root+c),this.fragment=c,window.history[b.replace?"replaceState":"pushState"]({},document.title,c)):this._wantsHashChange?(this.fragment=c,this._updateHash(window.location,c,b.replace),this.iframe&&c!=this.getFragment(this.getHash(this.iframe))&&(b.replace||
33 | this.iframe.document.open().close(),this._updateHash(this.iframe.location,c,b.replace))):window.location.assign(this.options.root+a),b.trigger&&this.loadUrl(a))},_updateHash:function(a,b,c){c?a.replace(a.toString().replace(/(javascript:|#).*$/,"")+"#"+b):a.hash=b}});var v=g.View=function(a){this.cid=f.uniqueId("view");this._configure(a||{});this._ensureElement();this.initialize.apply(this,arguments);this.delegateEvents()},F=/^(\S+)\s*(.*)$/,w="model,collection,el,id,attributes,className,tagName".split(",");
34 | f.extend(v.prototype,k,{tagName:"div",$:function(a){return this.$el.find(a)},initialize:function(){},render:function(){return this},remove:function(){this.$el.remove();return this},make:function(a,b,c){a=document.createElement(a);b&&i(a).attr(b);c&&i(a).html(c);return a},setElement:function(a,b){this.$el&&this.undelegateEvents();this.$el=a instanceof i?a:i(a);this.el=this.$el[0];!1!==b&&this.delegateEvents();return this},delegateEvents:function(a){if(a||(a=n(this,"events"))){this.undelegateEvents();
35 | for(var b in a){var c=a[b];f.isFunction(c)||(c=this[a[b]]);if(!c)throw Error('Method "'+a[b]+'" does not exist');var d=b.match(F),e=d[1],d=d[2],c=f.bind(c,this),e=e+(".delegateEvents"+this.cid);""===d?this.$el.bind(e,c):this.$el.delegate(d,e,c)}}},undelegateEvents:function(){this.$el.unbind(".delegateEvents"+this.cid)},_configure:function(a){this.options&&(a=f.extend({},this.options,a));for(var b=0,c=w.length;b");
21 | },
22 |
23 | show: function(id) {
24 | $(document.body).append("调用了 Show 路由,id 等于 " + id + "
");
25 | },
26 |
27 | download: function(random) {
28 | $(document.body).append("调用了 Download 路由,参数等于 " + random + "
");
29 | },
30 |
31 | search: function(query) {
32 | $(document.body).append("调用了 Search 路由,参数等于 " + query + "
");
33 | },
34 |
35 | default: function(other) {
36 | $(document.body).append("你访问的 " + other + " 路由未定义
");
37 |
38 | }
39 |
40 | });
41 |
42 | new App.Router();
43 | Backbone.history.start();
44 |
45 | })();
46 |
--------------------------------------------------------------------------------
/demos/eslint-demo/index.js:
--------------------------------------------------------------------------------
1 | var x = 1;
2 | console.log('x is', x);
3 |
--------------------------------------------------------------------------------
/demos/eslint-demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eslint-demo",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC"
12 | }
13 |
--------------------------------------------------------------------------------
/demos/express-demo/app1.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = express();
3 |
4 | var port = process.env.PORT || 8080;
5 | var router = express.Router();
6 |
7 | router.get('/', function(req, res) {
8 | res.send('Hello World
');
9 | });
10 |
11 | app.use('/home', router);
12 |
13 | app.listen(port);
14 | console.log('Magic happens on port ' + port);
15 |
--------------------------------------------------------------------------------
/demos/express-demo/app2.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = express();
3 |
4 | var port = process.env.PORT || 8080;
5 | var router = express.Router();
6 |
7 | router.get('/', function(req, res) {
8 | res.send('Hello World
');
9 | });
10 |
11 | router.get('/:name', function(req, res) {
12 | res.send('Hello ' + req.params.name + '
');
13 | });
14 |
15 | app.use('/home', router);
16 |
17 | app.listen(port);
18 | console.log('Magic happens on port ' + port);
19 |
--------------------------------------------------------------------------------
/demos/express-demo/app3.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = express();
3 | var bodyParser = require('body-parser');
4 |
5 | app.use(bodyParser.urlencoded({ extended: true }));
6 |
7 | var port = process.env.PORT || 8080;
8 | var router = express.Router();
9 |
10 | router.get('/', function(req, res) {
11 | res.send('Hello World
');
12 | });
13 |
14 | router.get('/:name', function(req, res) {
15 | res.send('Hello ' + req.params.name + '
');
16 | });
17 |
18 | router.post('/', function (req, res) {
19 | var name = req.body.name;
20 | res.json({message: 'Hello ' + name});
21 | });
22 |
23 | app.use('/home', router);
24 |
25 | app.listen(port);
26 | console.log('Magic happens on port ' + port);
27 |
--------------------------------------------------------------------------------
/demos/express-demo/app4.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var app = express();
3 | var bodyParser = require('body-parser');
4 |
5 | app.use(bodyParser.urlencoded({ extended: true }));
6 |
7 | var port = process.env.PORT || 8080;
8 | var router = express.Router();
9 |
10 | router.use(function(req, res, next) {
11 | console.log('There is a requesting.');
12 | next();
13 | });
14 |
15 | router.get('/', function(req, res) {
16 | res.send('Hello World
');
17 | });
18 |
19 | router.get('/:name', function(req, res) {
20 | res.send('Hello ' + req.params.name + '
');
21 | });
22 |
23 | router.post('/', function (req, res) {
24 | var name = req.body.name;
25 | res.json({message: 'Hello ' + name});
26 | });
27 |
28 | app.use('/home', router);
29 |
30 | app.listen(port);
31 | console.log('Magic happens on port ' + port);
32 |
--------------------------------------------------------------------------------
/demos/express-demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-api",
3 | "main": "server.js",
4 | "dependencies": {
5 | "express": "~4.17.1",
6 | "body-parser": "~1.0.1"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/demos/jsx-demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/demos/jsx-demo/react-dom.js:
--------------------------------------------------------------------------------
1 | /**
2 | * ReactDOM v15.2.1
3 | *
4 | * Copyright 2013-present, Facebook, Inc.
5 | * All rights reserved.
6 | *
7 | * This source code is licensed under the BSD-style license found in the
8 | * LICENSE file in the root directory of this source tree. An additional grant
9 | * of patent rights can be found in the PATENTS file in the same directory.
10 | *
11 | */
12 | // Based off https://github.com/ForbesLindesay/umd/blob/master/template.js
13 | ;(function(f) {
14 | // CommonJS
15 | if (typeof exports === "object" && typeof module !== "undefined") {
16 | module.exports = f(require('react'));
17 |
18 | // RequireJS
19 | } else if (typeof define === "function" && define.amd) {
20 | define(['react'], f);
21 |
22 | //
6 |