├── .gitignore
├── LICENSE
├── README.md
├── app.js
├── bin
└── www
├── model
├── articleDB.js
└── commentDB.js
├── package.json
├── public
├── assets
│ ├── bundle.js
│ └── js
│ │ ├── component
│ │ ├── AboutMe.css
│ │ ├── AboutMe.js
│ │ ├── CommentBox.css
│ │ ├── CommentBox.js
│ │ ├── Detile.js
│ │ ├── Home.css
│ │ ├── Home.js
│ │ ├── Lists.css
│ │ ├── Lists.js
│ │ ├── Main.css
│ │ ├── Main.js
│ │ └── PutArtical.js
│ │ └── entry.js
├── images
│ └── 6165847895E8568AE73E6164F3668271B78151E6C.jpg
├── index.html
└── stylesheets
│ └── style.css
├── routes
├── blog.js
├── index.js
└── users.js
├── views
├── error.ejs
└── index.ejs
├── webpack.config.js
└── 项目相关.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Jiaoguibin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # blog
2 | react+react router+node(express)+mongodb+webpack 栈
3 |
4 |
5 |
6 | ##### 简介
7 | * 该项目是一个简单的博客系统
8 | * 实现了浏览文章,发布新文章,文章下留言等功能
9 | * 使用react组建前端组件(view)
10 | * 使用react-router管理路由(单页应用)
11 | * node(express4.X)搭建后台和接口
12 | * mongodb(mongoose)管理数据存取
13 | * 构建工具使用webpack
14 |
15 | ##### 部署
16 | * 根据webpack.config.js和package.json,npm install 所有的依赖项
17 | * 在mongodb的文件目录下运行mongodb
18 | * webpack 命令编译所需的bundle.js文件
19 | * node app.js 运行服务,访问localhost:3000
20 | * 该项目在开发环境下可以使用webpack提供的服务器,使用其热加载功能,正常情况使用nodejs服务
21 | * 使用cssModules引入样式避免全局污染
22 |
23 | ##### tips&other
24 | * 所有的路由在app.js中定义
25 | * 增删改查的操作在文件blog.js
26 | * 关于数据库的Schema定义在model文件夹下(评论和文章),model层在blog.js中定义
27 | * 关于view层的组件在public/asset/js/component
28 | * 此项目链接两个数据库,评论的数据根据文章的_id存取(不同的文章拥有自己的评论)
29 | * 评论列表利用react状态机的原理进行实时更新(setState)
30 | * 由于时间原因该网站样式比较粗糙
31 | * react组件之间的通信使用了嵌套关系,未使用redux,以后可以改进
32 | * 感想:需要整个栈才能做出比较完整的东西,另外就是前端越来越好玩吸引力越来越大
33 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var path = require('path');
3 | var favicon = require('serve-favicon');
4 | var logger = require('morgan');
5 | var cookieParser = require('cookie-parser');
6 | var bodyParser = require('body-parser');
7 | var http = require('http');
8 |
9 | var blog = require('./routes/blog');
10 | var routes = require('./routes/index');
11 | var users = require('./routes/users');
12 |
13 | var app = express();
14 |
15 | // view engine setup
16 | app.set('port', process.env.PORT || 3000);
17 | app.set('views', path.join(__dirname, 'views'));
18 | app.set('view engine', 'ejs');
19 |
20 | // uncomment after placing your favicon in /public
21 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
22 | app.use(logger('dev'));
23 | app.use(bodyParser.json());
24 | app.use(bodyParser.urlencoded({ extended: false }));
25 | app.use(cookieParser());
26 | app.use(express.static(path.join(__dirname, 'public')));
27 |
28 | // 定义所有的增删改查的借口
29 | app.use('/', routes);
30 | app.get('/blog', blog.list);
31 | app.get('/blog/:id', blog.get);
32 | // app.delete('/comments/:id', blog.delete);
33 | app.get('/comment/:id', blog.commentList);
34 | app.post('/comment/:id', blog.commentAdd, blog.commentList);
35 | app.post('/blog', blog.add);
36 | app.use('/users', users);
37 |
38 | // catch 404 and forward to error handler
39 | app.use(function(req, res, next) {
40 | var err = new Error('Not Found');
41 | err.status = 404;
42 | next(err);
43 | });
44 |
45 | // error handlers
46 |
47 | // development error handler
48 | // will print stacktrace
49 | if (app.get('env') === 'development') {
50 | app.use(function(err, req, res, next) {
51 | res.status(err.status || 500);
52 | res.render('error', {
53 | message: err.message,
54 | error: err
55 | });
56 | });
57 | }
58 |
59 | // production error handler
60 | // no stacktraces leaked to user
61 | app.use(function(err, req, res, next) {
62 | res.status(err.status || 500);
63 | res.render('error', {
64 | message: err.message,
65 | error: {}
66 | });
67 | });
68 |
69 |
70 | http.createServer(app).listen(app.get('port'), function () {
71 | console.log('Express server listening on port ' + app.get('port'));
72 | })
73 |
74 | module.exports = app;
75 |
--------------------------------------------------------------------------------
/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | var app = require('../app');
8 | var debug = require('debug')('blog:server');
9 | var http = require('http');
10 |
11 | /**
12 | * Get port from environment and store in Express.
13 | */
14 |
15 | var port = normalizePort(process.env.PORT || '3000');
16 | app.set('port', port);
17 |
18 | /**
19 | * Create HTTP server.
20 | */
21 |
22 | var server = http.createServer(app);
23 |
24 | /**
25 | * Listen on provided port, on all network interfaces.
26 | */
27 |
28 | server.listen(port);
29 | server.on('error', onError);
30 | server.on('listening', onListening);
31 |
32 | /**
33 | * Normalize a port into a number, string, or false.
34 | */
35 |
36 | function normalizePort(val) {
37 | var port = parseInt(val, 10);
38 |
39 | if (isNaN(port)) {
40 | // named pipe
41 | return val;
42 | }
43 |
44 | if (port >= 0) {
45 | // port number
46 | return port;
47 | }
48 |
49 | return false;
50 | }
51 |
52 | /**
53 | * Event listener for HTTP server "error" event.
54 | */
55 |
56 | function onError(error) {
57 | if (error.syscall !== 'listen') {
58 | throw error;
59 | }
60 |
61 | var bind = typeof port === 'string'
62 | ? 'Pipe ' + port
63 | : 'Port ' + port;
64 |
65 | // handle specific listen errors with friendly messages
66 | switch (error.code) {
67 | case 'EACCES':
68 | console.error(bind + ' requires elevated privileges');
69 | process.exit(1);
70 | break;
71 | case 'EADDRINUSE':
72 | console.error(bind + ' is already in use');
73 | process.exit(1);
74 | break;
75 | default:
76 | throw error;
77 | }
78 | }
79 |
80 | /**
81 | * Event listener for HTTP server "listening" event.
82 | */
83 |
84 | function onListening() {
85 | var addr = server.address();
86 | var bind = typeof addr === 'string'
87 | ? 'pipe ' + addr
88 | : 'port ' + addr.port;
89 | debug('Listening on ' + bind);
90 | }
91 |
--------------------------------------------------------------------------------
/model/articleDB.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 |
3 | var Schema = mongoose.Schema;
4 |
5 | var articleSchema = new Schema({
6 | title: String,
7 | author: String,
8 | text: String,
9 | time: Date
10 | });
11 |
12 | // // 将Schema发布为model
13 | // var article = mongoose.model('article', articleSchema);
14 |
15 | module.exports = articleSchema;
16 |
17 | /*
18 | * -- Schema: 一种以文件形式存储的数据库模型骨架,不具备数据库操作能力
19 | * -- Model : 由Schema发布生成的模型,具有抽象属性和行为的数据库操作
20 | * -- Entity: 有Model创建的实体,它的操作也会影响数据库
21 | */
--------------------------------------------------------------------------------
/model/commentDB.js:
--------------------------------------------------------------------------------
1 | var mongoose = require('mongoose');
2 |
3 | var Schema = mongoose.Schema;
4 |
5 | var CommentSchema = new Schema({
6 | articleId: String,
7 | author: String,
8 | text: String
9 | });
10 |
11 | module.exports = CommentSchema;
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blog",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "webpack-dev-server --hot --progress --colors"
7 | },
8 | "dependencies": {
9 | "body-parser": "~1.15.1",
10 | "cookie-parser": "~1.4.3",
11 | "debug": "~2.2.0",
12 | "express": "~4.13.4",
13 | "jade": "~1.11.0",
14 | "morgan": "~1.7.0",
15 | "serve-favicon": "~2.3.0"
16 | },
17 | "devDependencies": {
18 | "babel-core": "^6.18.2",
19 | "babel-loader": "^6.2.8",
20 | "babel-plugin-react-transform": "^2.0.2",
21 | "babel-preset-es2015": "^6.18.0",
22 | "babel-preset-react": "^6.16.0",
23 | "css-loader": "^0.26.1",
24 | "ejs": "^2.5.3",
25 | "jquery": "^3.1.1",
26 | "jsx-loader": "^0.13.2",
27 | "marked": "^0.3.6",
28 | "mongoose": "^4.7.1",
29 | "react": "^15.4.1",
30 | "react-dom": "^15.4.1",
31 | "react-router": "^3.0.0",
32 | "react-tools": "^0.10.0",
33 | "react-transform": "0.0.3",
34 | "react-transform-hmr": "^1.0.4",
35 | "style-loader": "^0.13.1",
36 | "webpack": "^1.13.3",
37 | "webpack-dev-server": "^1.16.2"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/assets/js/component/AboutMe.css:
--------------------------------------------------------------------------------
1 | .title {
2 | margin-left: 5em;
3 | }
4 | .msg {
5 | margin-left: 10em;
6 | margin-top: 5em;
7 | }
--------------------------------------------------------------------------------
/public/assets/js/component/AboutMe.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import style from './AboutMe.css'
3 | class AboutMe extends React.Component {
4 | render() {
5 | return
17 | }
18 | }
19 | module.exports = AboutMe;
--------------------------------------------------------------------------------
/public/assets/js/component/CommentBox.css:
--------------------------------------------------------------------------------
1 | .box {
2 | margin-top: 30px;
3 | }
4 | .list {
5 | margin-left: 30px;
6 | }
7 | .text {
8 | margin-bottom: 10px
9 | }
10 | .auther {
11 | margin-left: 10px;
12 | }
13 | .comment {
14 | width: 80%;
15 | min-height: 80px;
16 | box-shadow: 1px 1px 5px #999;
17 | margin-bottom: 15px;
18 | padding: 15px;
19 | box-sizing: content-box;
20 | }
21 | .form {
22 | margin-top: 20px;
23 | margin-left: 30px;
24 | }
25 | .btn {
26 | width: 65px;
27 | height: 30px;
28 | line-height: 30px;
29 | text-align: center;
30 | font-size: 14px;
31 | display: inline-block;
32 | background-color: #fff;
33 | border: 1px solid #666;
34 | }
35 | .name {
36 | padding: 2px;
37 | background-color: rgb(235, 235, 228);
38 | }
39 | .textInput {
40 | margin-top: 15px;
41 | resize: none;
42 | }
--------------------------------------------------------------------------------
/public/assets/js/component/CommentBox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import marked from 'marked';
4 | import $ from 'jquery';
5 | import style from './CommentBox.css'
6 |
7 | class Comment extends React.Component {
8 | render () {
9 | return
10 |
{this.props.text}
11 |
评论人:
12 | {this.props.author}
13 |
14 |
;
15 | }
16 | }
17 | class CommentList extends React.Component {
18 | render () {
19 | let commentNodes = this.props.data.map(function (comment) {
20 | return
21 | ;
22 | });
23 | return
24 | {commentNodes}
25 |
;
26 | }
27 | }
28 | class CommentForm extends React.Component {
29 | handleSubmit (e) {
30 | e.preventDefault();
31 | let author = this.refs.author.value.trim();
32 | let text = this.refs.text.value.trim();
33 | if (!text || !author) {
34 | return;
35 | }
36 | this.props.onCommentSubmit({articleId: this.props.articleId, author: author, text: text});
37 | this.refs.author.value = '';
38 | this.refs.text.value = '';
39 | return;
40 | }
41 | render () {
42 | return ;
47 | }
48 | }
49 | class CommentBox extends React.Component {
50 | constructor(props){
51 | super(props);
52 | this.state={
53 | data:[]
54 | }
55 | }
56 | loadCommentsFromServer () {
57 | $.ajax({
58 | url: '/comment/'+this.props.id,
59 | type: 'GET',
60 | dataType: 'json',
61 | cache: false,
62 | success: (data) => {
63 | this.setState({data: data});
64 | }
65 | });
66 | }
67 | handleCommentSubmit (comment) {
68 | $.ajax({
69 | url: '/comment/'+this.props.id,
70 | dataType: 'json',
71 | type: 'POST',
72 | data: comment,
73 | success: (data) => {
74 | this.setState({data: data});
75 | console.log(data);
76 | }
77 | });
78 | }
79 | componentDidMount() {
80 | this.loadCommentsFromServer();
81 | }
82 | render() {
83 | return
84 |
留言区
85 |
86 |
87 | ;
88 | }
89 | }
90 | module.exports = CommentBox;
--------------------------------------------------------------------------------
/public/assets/js/component/Detile.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import $ from 'jquery';
3 | import CommentBox from './CommentBox.js';
4 |
5 | class Detile extends React.Component {
6 | loadArticleFromServer () {
7 | $.ajax({
8 | url: '/blog/'+this.props.params.id,
9 | type: 'GET',
10 | dataType: 'json',
11 | success: (data) => {
12 | data = data[0]
13 | document.getElementsByClassName('articleDetile')[0].innerHTML = "" +
14 | "
" + data.title + "
" +
15 | "
发表时间" + data.time + "" +
16 | "
" + data.text + "
" +
17 | "
";
18 | }
19 | })
20 | }
21 | componentDidMount() {
22 | this.loadArticleFromServer();
23 | }
24 | render() {
25 | return ;
30 | }
31 | }
32 | module.exports = Detile;
--------------------------------------------------------------------------------
/public/assets/js/component/Home.css:
--------------------------------------------------------------------------------
1 | .index {
2 | margin-top: 10px;
3 | margin-bottom: 50px;
4 | }
5 | .tip {
6 | margin-left: 20px;
7 | margin-top: 10px;
8 | }
9 | .content {
10 | width: 70%;
11 | margin: 0 auto;
12 | }
13 | .github {
14 | margin: 30px auto;
15 | }
--------------------------------------------------------------------------------
/public/assets/js/component/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import style from './Home.css'
3 |
4 | class Home extends React.Component {
5 | render() {
6 | return
7 |
项目介绍
8 |
9 |
此项目是个人搭建的一个简单的网站,它包括的功能有浏览文章列表,浏览完整文章,发布新文章,进行留言
10 |
该网站利用了:
11 |
12 | - react构建前端的组件
13 | - react-router管理路由使其成为单页应用
14 | - express4.X搭建后台接口
15 | - mongodb(mongoose)数据的存取
16 | - webpack管理+构建
17 |
18 |
github项目地址
19 |
20 |
21 | }
22 | }
23 | module.exports = Home;
--------------------------------------------------------------------------------
/public/assets/js/component/Lists.css:
--------------------------------------------------------------------------------
1 | .article {
2 | width: 600px;
3 | height: 160px;
4 | margin-top: 20px;
5 | margin-left: 20px;
6 | padding: 10px 25px;
7 | overflow: hidden;
8 | border-radius: 2px;
9 | box-shadow: 2px 2px 5px #888;
10 | background-color: #fff;
11 | }
12 | .text {
13 | margin-top: 15px;
14 | overflow: hidden;
15 | text-indent: 2em;
16 | line-height: 1.4em;
17 | height: 4.2em;
18 | }
19 | .read {
20 | color: #3385ff;
21 | }
--------------------------------------------------------------------------------
/public/assets/js/component/Lists.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import $ from 'jquery';
3 | import { Router, Route, hashHistory,Link } from 'react-router'
4 | import style from './Lists.css'
5 |
6 | class ArticleTitle extends React.Component {
7 | render() {
8 | let url = "/detile/" + this.props.atl._id;
9 | return
10 |
11 | {this.props.atl.title}
12 |
13 |
14 | {this.props.atl.text}
15 |
16 |
17 | 阅读原文
18 |
19 |
20 | }
21 | }
22 | class TitleList extends React.Component {
23 | render() {
24 | let titleNodes = this.props.data.map(function (article) {
25 | return ;
26 | });
27 | return
28 | {titleNodes}
29 |
;
30 | }
31 | }
32 | class Lists extends React.Component {
33 | constructor(props) {
34 | super(props);
35 | this.state = {
36 | data: [
37 | {tltle: 'sedfsadf'}
38 | ]
39 | };
40 | }
41 | loadArticleFromServer() {
42 | $.ajax({
43 | url: '/blog',
44 | dataType: 'json',
45 | cache: false,
46 | success: (data) => {
47 | this.setState({data: data});
48 | }
49 | });
50 | }
51 | componentDidMount() {
52 | this.loadArticleFromServer();
53 | }
54 | render() {
55 | return
56 |
文章列表
57 |
58 |
59 | }
60 | }
61 | module.exports = Lists;
--------------------------------------------------------------------------------
/public/assets/js/component/Main.css:
--------------------------------------------------------------------------------
1 | /* :global {
2 | a {
3 | text-decoration-style: none;
4 | }
5 | #contents {
6 | margin: 80px auto;
7 | width: 50%;
8 | min-height: 600px;
9 | }
10 | #contents h3 {
11 | color: #2085c5;
12 | margin-bottom: 20px;
13 | }
14 | };
15 | */
16 | .navbar {
17 | position: fixed;
18 | top: 0;
19 | left: 0;
20 | width: 100%;
21 | vertical-align: middle;
22 | box-shadow: 0 0 10px rgba(14, 14, 14, .26);
23 | background-color: rgba(255, 255, 255, 0.98);
24 | height: 55px;
25 | overflow: hidden;
26 | }
27 | .imgBorder {
28 | width: 45px;
29 | height: 45px;
30 | float: left;
31 | margin-left: 18%;
32 | margin-top: 5px;
33 | overflow: hidden;
34 | border-radius: 22.5px
35 | }
36 | .img {
37 | width: 45px;
38 | height: 45px;
39 | }
40 | .title {
41 | float: left;
42 | margin-left: 2%;
43 | margin-top: 5px;
44 | }
45 | .nav{
46 | float: right;
47 | margin-right: 20%;
48 | margin-bottom: 30px;
49 | text-align: center;
50 | display: inline-block;
51 | height: 70px;
52 | overflow: hidden;
53 | }
54 | .lead {
55 | color: #0085a1;
56 | float: left;
57 | list-style-type: none;
58 | margin-left: 20px;
59 | margin-top: 15px;
60 | }
61 | .contents {
62 | margin: 90px auto;
63 | padding-left: 15px;
64 | padding-right: 15px;
65 | width: 60%;
66 | min-height: 400px;
67 | }
68 | .footer {
69 | width: 40%;
70 | height: 100px;
71 | margin: 30px auto;
72 | text-align: center;
73 | vertical-align: middle;
74 | }
--------------------------------------------------------------------------------
/public/assets/js/component/Main.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router'
3 | import style from './Main.css'
4 |
5 | class Main extends React.Component {
6 | render () {
7 | return
8 |
9 |
10 |
Jiaoguibin's Blog
11 |
12 | - 博文
13 | - 新文章
14 | - 关于
15 |
16 |
17 |
18 | {this.props.children}
19 |
20 |
21 |
22 | Copyright © Jiaoguibin 2016
23 |
24 |
My GitHub
25 |
26 |
27 | }
28 | }
29 | module.exports = Main;
--------------------------------------------------------------------------------
/public/assets/js/component/PutArtical.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import $ from 'jquery';
3 | class ArticalForm extends React.Component {
4 | handleSubmit (e) {
5 | e.preventDefault();
6 | let title = this.refs.title.value.trim();
7 | let text = this.refs.text.value.trim();
8 | let date = new Date();
9 | $.ajax({
10 | url: '/blog',
11 | type: 'POST',
12 | dataType: 'json',
13 | data: {
14 | "title": title,
15 | "text": text,
16 | "time": date
17 | },
18 | success: (data) => {
19 | console.log('chenggong');
20 | window.location.hash = '#list';
21 | }
22 | })
23 | }
24 | render() {
25 | return ;
30 | }
31 | }
32 | class PutArtical extends React.Component {
33 | render() {
34 | return
38 | }
39 | }
40 | module.exports = PutArtical;
--------------------------------------------------------------------------------
/public/assets/js/entry.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Router, Route, hashHistory,IndexRoute } from 'react-router';
4 | import Home from './component/Home'
5 | import Main from './component/Main';
6 | import Lists from './component/Lists.js';
7 | import PutArtical from './component/PutArtical.js';
8 | import AboutMe from './component/AboutMe.js';
9 | import Detile from './component/Detile.js';
10 |
11 | ReactDOM.render(
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | ,
21 | document.getElementById('app')
22 | );
--------------------------------------------------------------------------------
/public/images/6165847895E8568AE73E6164F3668271B78151E6C.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cqupt-yifanwu/blog/b01a87bd5c27accd75cac577e82d0ac1ed7da3f4/public/images/6165847895E8568AE73E6164F3668271B78151E6C.jpg
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | myBlog
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/public/stylesheets/style.css:
--------------------------------------------------------------------------------
1 | /*重置*/
2 | body,html,ul,ol,li,p,h1,h2,h3,h4,h5,h6 {
3 | padding: 0;
4 | margin: 0;
5 | }
6 | a {
7 | text-decoration: none;
8 | color: #404040;
9 | }
10 | body {
11 | font-family: -apple-system, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Microsoft JhengHei", "Source Han Sans SC", "Noto Sans CJK SC", "Source Han Sans CN", "Noto Sans SC", "Source Han Sans TC", "Noto Sans CJK TC", "WenQuanYi Micro Hei", SimSun, sans-serif;
12 | }
13 | h3 {
14 | margin-bottom: 30px;
15 | }
16 | /* 导航样式 */
17 | /* .mainContent {
18 | margin: 0 auto;
19 | width: 50%;
20 | min-height: 500px;
21 | }
22 | .navBar {
23 | margin-top: 20px;
24 | margin-left: -30px;
25 | list-style-type: none;
26 | background-color: ;
27 | overflow: hidden;
28 | }
29 | .navBar li {
30 | float: left;
31 | margin-left: 30px;
32 | }
33 |
34 | Home页
35 | .Index {
36 | margin-top: 10px;
37 | margin-bottom: 50px;
38 | }
39 | .Index h3 {
40 | margin-top: 20px;
41 | margin-bottom: 20px;
42 | }
43 | .Index ul {
44 | margin-left: 30px;
45 | }
46 |
47 | 列表页样式
48 | .partTitle {
49 | margin-top: 25px;
50 | margin-bottom: 20px;
51 | }
52 | .articleTitle {
53 | margin-bottom: 20px;
54 | margin-top: 5px;
55 | border-bottom: 1px solid #666;
56 | cursor: pointer;
57 | }
58 |
59 | 上传文章
60 | .ArticalForm label {
61 | margin-bottom: 10px;
62 | }
63 | .ArticalForm textarea{
64 | margin-top: 10px;
65 | vertical-align: top;
66 | }
67 |
68 | 详情页样式
69 | .detile {
70 | margin-top: 30px;
71 | }
72 | .detile h3 {
73 | margin-top: 20px;
74 | margin-bottom: 10px;
75 | }
76 | .detile p {
77 | width: 70%;
78 | margin-top: 20px;
79 | }
80 |
81 | 评论的样式
82 | .comment p{
83 | border-bottom: 1px solid #666;
84 | }
85 | .commentForm input{
86 | margin-top: 30px;
87 | margin-bottom: 20px;
88 | }
89 |
90 | 关于
91 | .aboutme li{
92 | list-style-type: none;
93 | margin-bottom: 15px;
94 | }
95 |
96 | */
--------------------------------------------------------------------------------
/routes/blog.js:
--------------------------------------------------------------------------------
1 | /*
2 | * 所有的增删改查的操作
3 | */
4 | /*
5 | * GET comments listing.
6 | * 完整的定义增删改查
7 | */
8 |
9 | var articleSchema = require('../model/articleDB.js'); // 引入的model,可用来操作数据库和生成Entity
10 | var CommentSchema = require('../model/commentDB.js');
11 | var mongoose = require('mongoose');
12 |
13 | var db2 = mongoose.createConnection('mongodb://127.0.0.1:27017/comment');
14 | var comment = db2.model('comment', CommentSchema);
15 | var db1 = mongoose.createConnection('mongodb://127.0.0.1:27017/article'); // 链接数据库
16 | var article = db1.model('article', articleSchema);
17 | // db1.connection.on('connected', function () {
18 | // console.log('Mongoose1 connection success');
19 | // });
20 | // db1.connection.on('error', function (err) {
21 | // console.log('connection1 error');
22 | // });
23 | exports.list = function(req, res){
24 | article.find(function (err, article) {
25 | console.log(article);
26 | res.json(article);
27 | });
28 | };
29 |
30 | exports.get = function(req, res){
31 | var q = article.find({_id:req.params.id}, function(err, article) {
32 | if (err) {
33 | console.log("查询文章错误!");
34 | };
35 | console.log(article);
36 | res.json(article)
37 | })
38 | };
39 |
40 |
41 | // exports.delete = function(req, res){
42 | // Comment.remove({_id: req.params.id},function (err) {
43 | // if (err) {
44 | // console.log(err);
45 | // };
46 | // Comment.find(function (err, comment) {
47 | // res.json(comment);
48 | // });
49 | // });
50 | // };
51 |
52 |
53 |
54 | exports.add = function(req, res){
55 | if(!req.body.hasOwnProperty('title') ||
56 | !req.body.hasOwnProperty('text')) {
57 | res.statusCode = 400;
58 | return res.send('Error 400: Post syntax incorrect.');
59 | }
60 | // 实例化新添加的内容
61 | var newArticle = {
62 | title : req.body.title,
63 | text : req.body.text,
64 | time: req.body.time
65 | };
66 | var ArticleEntity = new article(newArticle);
67 | ArticleEntity.save();
68 | res.json(true);
69 | };
70 |
71 |
72 | exports.commentList = function (req, res) {
73 | comment.find({articleId: req.params.id.toString()}, function (err, comments) {
74 | if (err) {
75 | console.log("获取评论出错");
76 | }
77 | res.json(comments);
78 | })
79 | };
80 | exports.commentAdd = function (req, res, next) {
81 | if(!req.body.hasOwnProperty('author') ||
82 | !req.body.hasOwnProperty('text') ||
83 | !req.body.hasOwnProperty('articleId')) {
84 | res.statusCode = 400;
85 | return res.send('Error 400: Post syntax incorrect.');
86 | }
87 | // 实例化新添加的内容
88 | var newComment = {
89 | articleId: req.body.articleId,
90 | author : req.body.author,
91 | text : req.body.text
92 | };
93 | var commentEntity = new comment(newComment);
94 | /*
95 | * 在save的成功回调函数里使用中间件next,再次执行commentList,获取完整的数据
96 | */
97 | commentEntity.save(function () {
98 | next();
99 | });
100 | };
--------------------------------------------------------------------------------
/routes/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 |
4 | /* GET home page. */
5 | router.get('/', function(req, res, next) {
6 | res.render('index', { title: 'Express' });
7 | });
8 |
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------
/routes/users.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 |
4 | /* GET users listing. */
5 | router.get('/', function(req, res, next) {
6 | res.send('respond with a resource');
7 | });
8 |
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------
/views/error.ejs:
--------------------------------------------------------------------------------
1 | <%= message %>
2 | <%= error.status %>
3 | <%= error.stack %>
4 |
--------------------------------------------------------------------------------
/views/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= title %>
5 |
6 |
7 |
8 | <%= title %>
9 | Welcome to <%= title %>
10 |
11 |
12 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack')
2 | module.exports = {
3 | entry: [
4 | './public/assets/js/entry.js'
5 | ],
6 | output: {
7 | path: __dirname + '/public/assets/',
8 | publicPath: "/public/assets/",
9 | filename: 'bundle.js'
10 | },
11 | module: {
12 | loaders: [
13 | {
14 | test:/\.js$/,
15 | exclude:/node_modules/,
16 | loader: 'babel',
17 | query: {
18 | presets: ['es2015','react'], // 这部分内容可以单独写成一个babelrc文件
19 | "env": {
20 | "development": {
21 | "plugins": [
22 | ["react-transform", {
23 | "transforms": [{
24 | "transform": "react-transform-hmr",
25 |
26 | "imports": ["react"],
27 |
28 | "locals": ["module"]
29 | }]
30 | }
31 | ]
32 | ]
33 | }
34 | }
35 | }
36 | },
37 | {
38 | test: /\.css$/,
39 | loader: "style-loader!css-loader?modules"
40 | }
41 | ]
42 | },
43 | plugins: [
44 | new webpack.HotModuleReplacementPlugin()//热加载插件
45 | ],
46 | resolve: {
47 | extensions: ['', '.js', '.jsx']
48 | // 这里可以使用alias配置项,可以显示的指定我们常用的一些库,避免webpack自己的查找
49 | },
50 | devServer: {
51 | contentBase: "./public",//本地服务器所加载的页面所在的目录
52 | colors: true,//终端中输出结果为彩色
53 | historyApiFallback: true,//不跳转
54 | inline: true//实时刷新
55 | }
56 | };
57 |
--------------------------------------------------------------------------------
/项目相关.md:
--------------------------------------------------------------------------------
1 | ### Blog 项目相关
2 |
3 | #### ps: 其中的很多只是我在项目中遇到的一些问题和思考,有一部分还有待深入理解,如果有不对的地方或者是疑问,欢迎在issue中提出,感谢!
4 |
5 | ## react
6 | #### react-router history属性与区别
7 |
8 | history属性用来监听浏览器地址栏的变换,并将URL解析成一个对象,供React-Router匹配 history对象有三个属性browserHistory,hashHistory,creacteMemoryHistory
9 | - hashHistory路由将通过URL的#切换(通过hash完成)
10 | - browserHistory 不通过Hash完成,而显示正常的路径,是会向server发送请求的,所以要对server端做特殊配置
11 | - createMemoryHistory主要用于服务端渲染,他创建一个内存中的history对象,不与浏览器互动。
12 |
13 | #### 在react引用图片的问题
14 | webpack中不太能处理html中引用图片
15 | - 我们可以用es6的import方法(在webpack中一切皆模块)
16 | - 也可以作为css的背景引入进去,这里有点不一样的地方在,如果样式是嵌入在jsx中的那么该图片的路径应该相对于bundle文件的位置,而写在样式表中则是相对于本身(当在render中的HTML的标签里设置内联样式:background-image属性,它的url应该指向bundle文件)
17 | - 用src时可以使用require引入
18 |
19 | #### react中使用ES6的坑
20 | - 类名(组件名)一定要用大写开头,否则自定义的组件无法编译,识别不出来。
21 | - 类中定义render函数要注意两点
22 | - 开头花括号一定要和小括号隔一个空格
23 | - 标签的前一半一定要和return一行(render如果只跟一个标签(不加花括号),必须把开头标签(比如)放在和return一行)
24 | - render中如果使用setState(浏览器前进后退只会经历render周期),会导致无限render
25 | - 在class中使用的变量或者方法,一定要加this
26 | - es6绑定事件需要bind(this) ,这样function和bind里面的参数'this'才绑定到一起.注:在原来的React.creatClass中,在事件处理柄触发的时候会自动绑定到响应的元素上面去,这时候该函数的this的上下文就是该实例,不过在ES6的class写法中,它(facebook)取消了自动绑定(也就是会自动绑定到支撑实例上--backing instance而非实例,关于支撑实例可以看知乎上这个提问[虽然还没人回答。。。](https://www.zhihu.com/question/57635638))
27 |
28 | #### 关于组件化的认识
29 | - 在react中所谓组件就是状态机。当组件处于某个状态时那么就输出这个状态对应的页面。像纯函数一样,容易去保证界面的一致性。
30 | - 将项目拆分成比较小的粒度去组件化,可以提高代码的重用性。
31 | - 采用的是分治的思想,耦合性低,易于管理。
32 | - 在项目中可以将组件细分成含有抽象数据的容器组件,和只有业务逻辑的展示型组件。
33 |
34 | #### 子组件向父组件传递状态
35 | - 使用redux、flux
36 | - 使用回调函数来向父组件,父组件定义事件处理函数,在函数中改变父组件的state,将该函数通过props传递给子组件,然后绑定。等该事件触发时便会改变父元素的状态。
37 | - 可以通过使用context(就相当于一个全局变量),context可以跨级从父组件向子组件传值,也可以实现子获取和设置父暴露出来的属性值
38 |
39 | #### flux
40 | 为了解决MVC数据流混乱的问题flux被提出,它的核心思想就是数据和逻辑永远单项流动。数据从action到dispatcher,再到store,最终到view的路线是单向不可逆的。dispatcher定义了严格的规则来限定我们对数据的修改操作;store中不能暴露setter的设定也强化了输一局修改的纯洁性,保证了store的数据确定唯一的状态。其中flux有三大部分构成:
41 | - dispatcher,负责分发事件,它有两个方法,一个注册监听器,另外一个分发一个action。注意,在flux中只有分发一个action才能修改数据,没有其他方法.
42 | - store,负责保存数据,同时响应并更新数据。当我们使用dispatcher分发一个action时,store注册的监听器就会被调用
43 | - view,负责订阅store中的数据,并且使用这些数据渲染响应的页面(使用react作为 view)
44 |
45 | 在这个结构中类似MVC但是不存在一个controller,但是却像是存在一个controller-view。主要进行store与react组件(view)之间的绑定,定义数据已经更新传递方式,它会调用store的getter获取其中的数据并设置为自己的state,然后调用setState.
46 |
47 | - redux 和 flux 的区别
48 | > 在flux中我们在actionCreator里面调用AppDispatcher.dispath方法来触发action,这样不仅有冗余而且直接修改了store中的数据,**将无法保存数据前后变化的状态**。在react中采用纯函数reducer来修改状态。
49 |
50 | #### react禁止“事件冒泡”失效
51 | 因为react原生事件系统本身就是事件委托,说明事件会一直冒泡到document进行绑定。
52 | - React为了提高效率,把事件委托给了document,所以e.stopPropagation()并非是不能阻止冒泡,而是等他阻止冒泡的时侯,事件已经传递给document了,没东西可阻止了。
53 | - e.stopPropagation()不行,浏览器支持一个好东西,e.stopImmediatePropagation() 他不光阻止冒泡,还能阻止在当前事件触发元素上,触发其它事件。这样即使你都绑定到document上也阻止不了我了吧。
54 | - 这样做还不行,React对原生事件封装,提供了很多好东西,但也省略了某些特性。e.stopImmediatePropagation() 就是被省略的部分,然而,他给了开口:e.nativeEvent ,从原生的事件对象里找到stopImmediatePropagation(),完活。
55 |
56 | 结果: e.nativeEvent.stopImmediatePropagation() 可以完美实现预期。
57 |
58 | #### react的性能优化
59 | ###### 首先进行性能分析
60 | React提供了性能分析插件React.addons.Perf,它让我们可以在需要检测的代码其实为期分别添加Perf.start()和Perf.stop(),并可以通过Perf.printInclusive()方法打印花费时间,然后我们可以结合数据做进一步分析。
61 | ###### 关注shouldComponentUpdate
62 | 当我们最初使用React的时候我们天真的以为React会智能地帮我们在Props与State没有改变的时候取消重渲染,不过事实证明只要你调用了setState或传入了不同的Props,React组件就会重新渲染,不过这种方式也会存在一些缺陷:
63 | - 它并没有深层的比较两个对象,如果它进行了深层的比较那么该操作也会变得十分的缓慢。
64 | - 如果传入的Props是某个回调函数(其实引用类型都会有该隐患),那么该函数会一直返回Ture
65 | - 比较检测本身也是有性能损耗的,应用中过多冗余的比较反而会降低性能。
66 |
67 | 在这里我们总结一下,建议的重载shouldComponentUpdate应该适用于以下类型的组件:
68 | - 使用简单Props的纯组件
69 | - 叶子组件或者在组件树中较深位置的组件
70 | ###### 借助react key表示组件
71 | 在这之前我们可能需要先了解一下diff算法的的相关知识,[看这里](http://www.infoq.com/cn/articles/react-dom-diff),当我们的组件有自己的key的话对diff算法是友好的,通过key标识我们可以组件如:顺序改变、不必要的自组建跟新等情况下告诉react避免不必要的渲染而避免性能的浪费
72 | ###### 关注同构
73 | 见下面node部分,会对性能大幅度提升(针对加载时间)
74 | ###### 最后
75 | 关于js,css,html,http的其他所有优化都有用,同样,下面利用webpack的优化值得关注。
76 |
77 | #### 关于diff算法
78 | 在react中存在虚拟dom,当我们需要更新的时候我们会先对比dom的区别,这里就是采用了diff算法进行一个对比,如果不需要更新就不操作dom(减少dom的操作)。下面谈谈diff算法:
79 | - **不同类型结点的类型比较**;在react中比较两个虚拟dom结点,当两个结点不同时应当如何处理分为两种情况,一种就是**结点类型**不同,一种就是结点类型相同但属性不同。当结点类型不同时diff算法将**不比较两个结点**而是将原结点删除生成一个新的结点(我们可以得出结论,DOM diff算法只会对树进行逐层的比较)。
80 | - **逐层进行结点比较**,上面说到,diff算法只会对同一层的结点进行比较(此时我们将dom看为树,本来其实也是树。。),假设下面的情况:树的左枝A需要移动到右子树下面的我们直观的操作会是
81 |
82 | ```
83 | A.parent.remove(A);
84 | D.append(A);
85 | ```
86 |
87 |
88 | 而diff算法会这样做(因为对于不同层的结点只有简单的创建和删除)
89 |
90 | ```
91 | A.destroy();
92 | A = new A();
93 | A.append(new B());
94 | A.append(new C());
95 | D.append(A);
96 | ```
97 | - **相同类型结点比较**,当比较的是同类型节点时,算法就相对简单容易理解,React会对属性进行重设从而实现结点的转换,需要注意的是虚DOM的style属性稍有不同,其值并不是一个简单的字符串,而必须为一个**对象**,因此转换过程如下
98 | ```
99 | renderA:
100 | renderB:
101 | => [removeStyle color], [addStyle font-weight 'bold']
102 | ```
103 | - **列表结点的比较**,上面说过在不同层的结点即使完全一样也会销毁重建,那么在同一层的结点如果我们没有key的标识React无法识别每一个结点,那么更新效率会很低,而有了key之后他们会相互匹配,然后只需要将新的结点。对于列表节点提供唯一的key属性可以帮助React定位到正确的节点进行比较,从而大幅减少DOM操作次数,提高了性能。
104 |
105 | ## Node.js
106 |
107 | #### 关于异步I/O
108 | - 异步调用将请求对象放入线程池
109 | - 线程池寻找可用的线程,执行异步调用放入的请求对象,然后将执行完成的结果放在请求对象中,通知IOCP完成 (window下面的引擎,管理线程池)
110 | - 事件模型(Event Loop)完成的I/O交给I/o观察者执行回调函数。
111 |
112 | #### 用node同构解决SPA的SEO优化和首屏加载缓慢问题
113 | 同构的意思和i是同时在服务端和客户端渲染页面。因为SPA的内容由js产生所以它不能够被爬虫爬到,所以存在SEO的问题同构JS通常是通过Nodejs实现。在node中通过require将组件引入,服务端通过模板引擎渲染。第一次的页面加载会很快因为渲染发生在服务端
114 | - React在客户端上通过ReactDOM的Render方法渲染到页面
115 | - 服务端上,React提供另外两个方法ReactDOMServer.renderToString和ReactDOMServer.renderToStaticMarkup 可将其渲染为 HTML 字符串.注:也许你会奇怪,为何会存在两个用于服务器渲染的函数,其实这两个函数是有区别的:1. renderToString:将React Component转化为HTML字符串,生成的HTML的DOM会带有额外的属性:各个DOM会有data-react-id,第一个DOM会有data-checksum属性(这个值使用adler32算法算出,如果两个组件有相同的props和DOM结构则这个值会相同,所以这个值会给客户端判断是否要重新渲染)。2.renderToStaticMarkup:同样是将React Component转化为HTML字符串,但是生成的DOM不会有额外的属性,从而节省HTML字符串的大小。
116 | ```
117 | // 这里附上 alloy team 给的一个例子
118 | var express = require('express');
119 | var app = express();
120 |
121 | var React = require('react'),
122 | ReactDOMServer = require('react-dom/server');
123 |
124 | var App = React.createFactory(require('./App'));
125 |
126 | app.get('/', function(req, res) {
127 | var html = ReactDOMServer.renderToStaticMarkup(
128 | React.DOM.body(
129 | null,
130 | React.DOM.div({id: 'root',
131 | dangerouslySetInnerHTML: {
132 | __html: ReactDOMServer.renderToStaticMarkup(App())
133 | }
134 | })
135 | )
136 | );
137 |
138 | res.end(html);
139 | });
140 |
141 | app.listen(3000, function() {
142 | console.log('running on port ' + 3000);
143 | });
144 | ```
145 |
146 | 只有部分DOM的更新在浏览器完成。
147 |
148 | ###### 同构的关键要素
149 | - DOM的一致性,在前后端渲染相同的Compponent,将输出一致的 Dom 结构。
150 | - 不同的生命周期,在服务端上 Component 生命周期只会到componentWillMount,客户端则是完整的。
151 | - 客户端 render时机,同构时,服务端结合数据将 Component 渲染成完整的HTML字符串并将数据状态返回给客户端,客户端会判断是否可以直接使用或需要重新挂载。
152 |
153 | #### 使用node解决跨域的问题
154 | 在项目中遇到这样一个问题,我这里有一个36kr的接口,但是这个接口是未设置cors的,同时后台的数据是XML格式,这意味着我们无法使用jsonp。这里我们可以使用node的请求转发代理来解决这个问题
155 | ```
156 | var request = require('request');
157 | //使用例子
158 | // proxy(app,'/api','http://***');
159 | //app是express中的app,route是本地api接口路径,remoteUrl是被代理的提供JSON数据的地址
160 | function proxy(app,route,remoteUrl){
161 | app.use(route,function(req,res){//使用这个路径,即当浏览器请求route时所做的响应
162 | req.pipe(request(url)).pipe(res);//请求重新组合后的网址,再把请求结果返回给本地浏览器
163 | });
164 | }
165 | exports.setProxy = proxy;//导出函数
166 |
167 | // 用于转发请求
168 | proxy.setProxy(app,'/api/weather','http://op.juhe.cn/onebox/weather/query');
169 | http.createServer(app).listen(port);
170 | ```
171 | ##### express中app.all和app.use的区别
172 | 首先app.use可以不挂载路径,此时应用的每个请求它都会处理。当它挂载路径的时候看似和app.all没有什么区别,其实all执行完整匹配,use只匹配前缀,例如:
173 | ```
174 | app.use('/a', function (req, res) {});
175 | app.all('/a', function (req, res) {});
176 | // 当访问/a的时候use和all都会呗调用;但是访问/a/b的时候只有use被调用
177 | ```
178 | #### express内置中间件
179 | 从4.X开始,Express不再依赖Connect了。除了express.static,express以前内置的中间件现在已经全部单独作为模块安装使用了。也就是说express.static是唯一内置中间件,它基于server-static,负责在Express应用中托管静态资源
180 |
181 | #### express3 到 4的变化
182 | - 对Express内核和中间件系统的改进:不再依赖Connect和内置的中间件,需要自己添加中间件,从内核中移除了express.static外的所有内置中间件。也就是说Express是一个独立的路由和中间件web框架。[关于中间件可以看这里](https://github.com/senchalabs/connect#middleware)
183 | - 路由系统的改变:应用现在隐式的加载路由中间件,因此不需要担心涉及到router中间件对路由中间件加载顺序的问题了,现在有两个新方法可以帮我们组织路由
184 | - app.route(),可以为路由路径创建链式路由语句柄
185 | ```
186 | app.route('/book')
187 | .get(function(req, res) {
188 | res.send('Get a random book');
189 | })
190 | .post(function(req, res) {
191 | res.send('Add a book');
192 | })
193 | .put(function(req, res) {
194 | res.send('Update the book');
195 | });
196 | ```
197 | - express.Router,可以创建可挂载的模块化路由语句柄
198 | ```
199 | var express = require('express');
200 | var router = express.Router();
201 |
202 | // 特针对于该路由的中间件
203 | router.use(function timeLog(req, res, next) {
204 | console.log('Time: ', Date.now());
205 | next();
206 | });
207 | // 定义网站主页的路由
208 | router.get('/', function(req, res) {
209 | res.send('Birds home page');
210 | });
211 | // 定义 about 页面的路由
212 | router.get('/about', function(req, res) {
213 | res.send('About birds');
214 | });
215 |
216 | module.exports = router;
217 | 然后,在应用中加载该路由:
218 |
219 | var birds = require('./birds');
220 | ...
221 | app.use('/birds', birds);
222 | ```
223 | - 其他一些变化:(虽然不大但是非常重要)
224 | - http模块不是必须的了,除非直接使用socket.io/SPDY/HTTPS。现在可以通过app.listen()起服务
225 | - 已经删除了app.configure(),使用app.get('dev')检测环境并配置应用
226 | - req.params,从数组变为对象
227 | - res.locals,从函数变为对象
228 |
229 |
230 |
231 | ## ES6
232 | #### 模块化的优缺点
233 |
234 | ###### 优点
235 | - 可维护性
236 | - 灵活架构,焦点分离
237 | - 模块间组合、分解(重用)
238 | - 方便单个模块调试、升级
239 | - 多人协作互不干扰
240 | - 可测试性
241 | - 可分单元测试
242 |
243 | ###### 缺点
244 | - 性能损耗
245 | - 系统分层,调用链会很长
246 | - 模块间通信,模块间发送消息会很耗性能
247 |
248 | ###### 附
249 | - 内聚度:内聚度指模块内部实现,它是信息隐藏和局部化概念的自然扩展,它标志着一个模块内部各成分彼此结合的紧密程度。
250 | - 耦合度:耦合度则是指模块之间的关联程度的度量。耦合度取决于模块之间接口的复杂性,与模块相反。
251 |
252 | #### 模块化
253 | 在该项目中利用的是es6中的模块化,利用webpack构建,它支持多种模块化的方案,es6模块化与其他方案的不同:
254 | - es6模块化的设计思想是尽量静态化,是的编译时就能确定模块的依赖关系,以及输入输出的变量。CommonJs和AMD模块都只能在运行时确定这些东西。例如
255 | ```
256 | // conmmonjs
257 | let {stat,exists} = require('fs');
258 | // 代码运行的实质是整体加载fs模块(既加载fs的所有方法,这称为运行时加载)
259 |
260 | // es6
261 | import {state,exits} form 'fs';
262 | // 以上代码的实质是从fs模块加载两个方法,其他方法并不加载。这成为“编译时加载”
263 | ```
264 | es6在编译时就可以完成模块的编译,效率要比commonjs的加载要高。
265 | - es6自动在模块中采用严格模式
266 | - es6模块化加载机制与Commonjs完全不同,CommonJs模块输出的是一个值的拷贝,而es6输出的则是值的引用。
267 |
268 | #### 模块化之间的对比
269 | - **CommonJs**:一个单独的文件就是一个模块,每一个模块都是一个单独的作用域,也就是说在该模块内部定义的变量无法被其他模块读取,除非定义为global对象。加载模块使用require方法,该方法读取一个文件并执行,返回文件内部的module.exports对象。**但是,由于在commonJs规范中,require的代码之后的内容必须加载完成后才可以继续运行,使得Coomonjs规范不适用于浏览器环境**(常用于服务器环境,比如node,因为在这些环境中i/o速度要快)。
270 | - **AMD**:上面说到commonjs环境不适合浏览器环境,而AMD就解决了这个问题,它是以异步的方式加载模块,模块的加载不影响后面的语句运行,对于依赖的模块提前执行,依赖前置。require.js就是AMD规范。
271 | ```
272 | define(['dep1','dep2'],function(dep1,dep2){
273 | //内部只能使用制定的模块
274 | return function(){};
275 | });
276 | ```
277 | - **CMD**:它与AMD的区别就是在定义模块的时候它不需要立即声明依赖,在需要的时候require就可以了。
278 | - 关于es6的请看上面一个话题
279 |
280 | ###### 关于模块化的循环加载
281 | ps:这里只谈谈commonjs和es6中的解决方案。假设问题的场景是a脚本的执行依赖b,而b脚本的执行又执行a。
282 | - commonjs:commonjs的加载原理是当require命令第一次加载该脚本时就会执行整个脚本,然后在内存中生成一个对象。也就是说即使再次执行require命令,也不会再次执行,而是到缓存中取值。所以,在出现上面循环加载的情况时,就只输出已经执行的部分,还未执行的部分不会输出。
283 | - es6处理循环加载与commonjs有本质不同。es6是动态引用,需要开发者保证真正取值时能够取到值,这里提出一个疑问:在es6的循环加载中是否必须需要有跳出条件?否则会不断循环引用吗?
284 |
285 | #### 模块化加载的原理
286 | 1. **模块的查找**,通过id和路径的对应原则,加载器才能知道需要加载的js路径。在AMD规范里可以通过配置Paths来特定的id(模块名)配置path。
287 | 2. **请求模块**,知道路径之后就要去请求,一般通过createElement('script') && apppendChild去请求,一般来需要给