Docker上部署一个PHP+MySQL网站的方法

1. 前言

1.1 Docker适合什么类型的项目?

Docker存在的意义是解决项目移植时兼容的问题。
好吧,那么Docker是否对于任何项目都合适呢?
就我目前的体验而言,使用Docker做负载均衡是比较合适的,用Docker装载静态的,较小的项目也是合适的。
如果是想用Docker装载一个大型项目,个人感觉不太合适。这个理论有待后期使用的时候再慢慢探索。

1.2 PHP+MySQL项目部署在Docker的几种思路。

1.2.1 使用tomsik68/xampp

这也是本文要重点探讨的,详情见后面内容。

1.2.2 使用httpd然后再自行安装mysql和php

我会单独写一篇文章记录这个方法。

1.2.3 使用pch18/baota

这种方法是将宝塔软件放在Docker里面。我也会单独写一篇文章记录这个方法。但是这种方法我认为有个致命的问题,就是pch18/baota镜像文件太大了,要5G。你没看错,要5G。所以这种方案的可行性非常值得商榷,因为这很明显违背了Docker存在的意义。

2. 安装部署tomsik68/xampp

下面的内容参考:https://hub.docker.com/r/tomsik68/xampp

2.1 拉取镜像

docker pull tomsik68/xampp

2.2 为宿主机创建mysql数据卷

docker volume create mysql

上述命令的mysql名称任意,为了方便识别用的mysql。

2.3 运行容器

宿主机bind mount网页文件的位置:/opt/www
宿主机映射端口地址:41062
mysql目录不能使用文件夹bind mounts的形式,以下方法执行后会因为权限问题报错。

docker run --name myXampp -p 41061:22 -p 41062:80 -it -v /opt/www:/www -v /opt/mysql:/opt/lampp/var/mysql tomsik68/xampp

必须使用下方数据卷的方法:

docker run --name myXampp -p 41061:22 -p 41062:80 -it -v /opt/www:/www -v mysql:/opt/lampp/var/mysql tomsik68/xampp

2.4 测试运行

在/opt/www 中新建index.php文件,写入如下代码:

<?php
echo "hello world";
echo 1+3;
?>

然后在浏览器中打开地址:http://宿主机IP地址或域名:41062/www/index.php
如果看到hello world4,证明容器运行成功了!
如果运行不成功,可能是因为宿主机防火墙问题,或者关闭防火墙,或者放行41062端口即可。

2.5 进入容器查看

如果想要查看容器内部情况,使用以下命令:

docker exec -it myXampp bash

3. 导入一个PHP+MYSQL网站

我们就以安装一个“帝国CMS”为例演示网站导入过程。

3.1 下载解压安装包。

进入/opt/www目录后下载安装包

wget http://ecms.phome.net/downcenter/empirecms/ecms75/download/EmpireCMS_7.5_SC_UTF8.zip

解压

unzip EmpireCMS_7.5_SC_UTF8.zip

为了方便我把解压后的upload文件夹重名名为了ecms

mv upload ecms

3.2 更改文件夹权限

如果不更改,安装时会提示权限不足。

chmod 777 -R ecms

3.3 完成安装

http://宿主机IP地址或域名:41062/www/ecms/e/install/

按步骤提示完成安装即可。
注:安装时使用xampp默认的mysql用户名:root,密码是空。

4. 数据持久化问题

A. 对于一个web站点,有两部分数据需要持久化:数据库+网页代码。
B. 在tomsik68/xampp给出的官方文档中,run命令是这样写的:

docker run --name myXampp -p 41061:22 -p 41062:80 -d -v /opt/www:/www tomsik68/xampp

上面的命令只实现了网页文件的持久化,没有实现数据库持久化。这样的问题就是每当服务器重启的时候,数据库数据就会丢失。
C. 为了解决数据库持久化的问题,在2.3中我将命令修改为以下形式:

docker run --name myXampp -p 41061:22 -p 41062:80 -it -v /opt/www:/www -v mysql:/opt/lampp/var/mysql tomsik68/xampp

这样,即便服务器重启,只需要使用以下命令就可以重新恢复网站:

docker start myXampp

Xshell6 Portable提示“要继续使用此程序,您必须应用最新的更新或使用新版本

1. 前言

Xshell6是收费软件,同时提供免费的体验版本Xshell6 Portable。
然而,在使用Xshell6 Portable一段时间后,会突然间弹出这样的框:
file
其实意思很简单,就是告诉你试用期到了。

2. 推荐解决方案

如果你觉得Xshell6好用,就尽快去官网购买正版软件吧。这是最明智的做法。

3. 应急解决方案

很多时候你可能不方便马上就购买,需要应急。这时有两个方法:

3.1 调整系统时间

只需将电脑的系统时间往前调整一段时间,比如1年。就可以正常打开网站了。

3.2 修改nslicense.dll文件

3.2.1 为编辑器安装二进制插件

为二进制编辑器(UltraEdit、notepad++)安装HEX-Editor。

3.2.2 用编辑器打开nslicense.dll文件

nslicense.dll位置如下:

安装目录\Xshell6Portable\App\Xshell

文件打开后,需要切换Hex视图:
file

3.2.3 编辑nslicense.dll文件
  • 搜索“7F 0C 81 F9 80 33 E1 01 0F 86 81”
  • 修改“86”为“83”
  • 保存文件
  • 重新打开Xshell发现已经可以使用

注:如果是xshell 5,搜索“7F 0C 81 F9 80 33 E1 01 0F 86 80”然后修改“86”为“83”。

使用命令行安装FastAdmin的详细步骤

1. 为什么要写这篇文章

  • 首先,FastAdmin是个好东西,值得一用和一学。
  • 第二,官方推荐使用命令行安装FastAdmin,因为采用命令行安装的方式可以和FastAdmin随时保持更新同步。
  • 官方默认你已经对以下这些知识和概念有了解:PHP、Git、Node.js、Composer、Bower。然而事实情况是,很多人只是了解PHP,对其它几个技术不甚了解,或者说的直白点,压根没听过。其实这不是什么丢人的事情,因为对于做中小项目的PHPer而言,搞清楚PHP的常规用法就足够了。
  • 写这篇文章的一个主要意图是通过安装过程让大家明白Git、Node.js、Composer、Bower这四个东东都是做什么的。

2. 官方给出的安装说明

强烈建议使用命令行安装,因为采用命令行安装的方式可以和FastAdmin随时保持更新同步。使用命令行安装请提前准备好Git、Node.js、Composer、Bower环境,Linux下FastAdmin的安装请使用以下命令进行安装。
1. 克隆FastAdmin到你本地
git clone https://gitee.com/karson/fastadmin.git
2. 进入目录
cd fastadmin
3. 下载前端插件依赖包
bower install
4. 下载PHP依赖包
composer install
5. 一键创建数据库并导入数据
php think install -u 数据库用户名 -p 数据库密码
6. 添加虚拟主机并绑定到项目的public目录
7. 为了安全,安装完成后会在public目录生成随机后台入口,请通过随机后台入口登录管理后台

3. 分步详细解释

3.1 克隆FastAdmin到你本地

  • git是什么?git是和svn一样的版本管理工具。目前git主要应用的场景还是在开源领域。对于类似FastAdmin这样的开源项目而言,一般都是第一时间将最新的代码放到git的服务器上。通过git clone命令,就可以从git服务器下载最新的项目代码。
  • 这里面的git clone命令是运行在git bash客户端里面的。而git bash则需要在电脑里面安装git软件后才能使用。
  • git软件的官方网址如下:https://git-scm.com/ ,下载安装步骤和普通软件无异,基本上就是【下一步】就可以了。
  • git安装好后,在任何地方点击鼠标右键都会出现Git Bash Here命令选项,点击Git Bash Here后,就会出现git bash小黑窗。
  • 在哪里打开git bash小黑窗,git bash的当前目录就被确定在哪里。
    file
  • 所以呢,在你想要安装FastAdmin的目录下打开Git Bash小黑窗,然后运行命令,就可以将最新的FastAdmin代码下载下来了(注意:下载时会创建一个新的名为FastAdmin的目录):
    git clone https://gitee.com/karson/fastadmin.git

3.2 进入目录

待所有代码都下载完成后,打开一个cmd窗口,然后进入到刚刚生成的fastadmin目录中:

cd fastadmin

向下承接【A】(如果你的电脑已经有Node.js和bower环境)

3.3 下载前端插件依赖包

3.3.1 先安装npm

你想要使用bower的前提是你必须在机器上安装bower。而安装bower的时候需要使用npm,不仅是bower,下面的less也需要用npm来安装。
npm来自哪里呢?来自Node.js!
是不是快绕晕了?没事儿,再梳理下:

  1. 先在本机安装Node.js。具体安装方法见:http://www.uyts.com/archives/278
  2. 我们安装Node.js其实不是为了用Node.js本身,而是为了用安装Node.js时候自动附带安装的npm(有点“买椟还珠”的意思)。有关npm命令的详细介绍,可以参考:http://www.uyts.com/archives/359
  3. npm怎么用呢?打开cmd窗口,直接输入相关的npm命令即可。
3.3.2 理解bower是什么?
  • 先说一句以便更好理解:bower和下面的composer的用处是非常相似的。只不过bower用于管理前端资源,composer用于管理后端php资源。
  • 那么bower是什么呢?bower是一个前端资源的管理器。
  • 举个例子:你在开发网站的时候,会用到很多前端的库,比如:Jquery、Bootstrap、Less等等。在没有bower之前,这些东西你都要自己一个个下载好,然后放到你的项目目录里面。这样做一来麻烦,二来下载的版本很可能每次都不一样,容易出现版本兼容的问题。有了bower后,bower会在你的项目根目录保存一个bower.json文件,里面写明白了这个项目需要用到哪些前端库以及这些库的版本号。这样只需要运行bower命令,就可以自动将bower.json里面提到的前端库自动从bower的服务器上下载下来并放到指定的位置。这种管理机制是非常有用的。
3.3.3 安装bower

打开cmd窗口,输入以下命令:

cnpm install bower -g 
3.3.4 使用bower下载前端插件依赖包

向上承接【A】

bower install

这样FastAdmin依赖的前端资源就都安装好了。
向下承接【B】(如果你的电脑已经有composer环境)

3.4 下载PHP依赖包

3.4.1 安装composer

上面讲bower的时候也提到了,composer和bower的作用非常相似。只不过composer是用来管理php资源库的。
composer的安装方法请见:http://www.uyts.com/archives/233
composer安装好后,在cmd窗口中就可以直接使用composer命令了。

3.4.2 下载PHP依赖包

向上承接【B】

composer install

这时候,FastAdmin全部的代码就都齐全了。

你肯定会问,这好麻烦啊!直接使用安装包解压不就好了吗?何必搞得这么麻烦?这个问题现在我如何说你也是不会理解的,你需要做一段时间开发后才能明白这么做的意义!

3.5 一键创建数据库并导入数据

首先,确保你的php已经加入了环境变量,这样可以直接在cmd中使用php命令。
然后打开cmd,输入如下命令:

php think install -u 数据库用户名 -p 数据库密码

这样数据库就导入到指定的数据库里面了。

3.6 添加虚拟主机并绑定到项目的public目录

具体方法见:http://www.uyts.com/archives/413

3.7 为了安全,安装完成后会在public目录生成随机后台入口,请通过随机后台入口登录管理后台

3.8 安装less

3.8.1 什么是less

用更加类似程序化的语法去编写CSS,比如可以在less里面创建变量,实现循环等等。
less文件本身不能被浏览器识别,需要被编译成(转换成)CSS后才能运行。

3.8.2 安装

这个安装教程里面没有提,但是为了后面编译less文件方便。可以在cmd里面使用以下命令进行安装:

cnpm install less -g

忘记MySQL数据库的root密码的重置方法(适合WAMP和普通MySQL环境)

1. 场景

1.1 安装WAMP环境

初始状态下,WAMP环境中MySQL的数据库密码是空。可以直接登陆MySQL数据库后修改。如果修改后的root密码忘记了,可以用本文方法进行密码重置。

1.2 普通MySQL环境

忘记了初始密码,需要重置初始密码,可以用本文方法进行密码重置。

2. 重置流程(安装了MySQL服务)

以下操作均在cmd命令行中完成。

2.1 停止MySQL服务

在cmd中通过以下命令停止MySQL服务

net stop mysql

2.2 以安全模式运行MySQL

mysqld –skip-grant-tables

2.3 免密登陆MySQL

保持2.2的cmd窗口不懂,另外打开一个cmd窗口后输入:

mysql -uroot -p

这时会提示输入密码,啥也不用输入直接回即可(我试了,随便输入任何内容回车都可以进入MySQL)。

2.4 重置root密码

2.4.1 MySQL5.7之前的版本
use mysql;
update user set password=password("111111") where user="root";
flush privileges;
exit
2.4.2 MySQL5.7及之后的版本
use mysql;
update user set authentication_string=password("111111") where user="root";
flush privileges;
exit

2.5 重置完成

这时候就可以使用111111这个密码登陆root用户了。

3. 重置流程(没有安装MySQL服务)

3.1 停止MySQL服务

ctrl+alt+delete进入任务管理器,然后结束掉mysqld进程。
file

3.2 以安全模式运行MySQL

在cmd中,先切换到mysql的bin目录,然后在bin目录里面运行:

mysqld –skip-grant-tables

3.3 免密登陆MySQL

保持2.2的cmd窗口不懂,另外打开一个cmd窗口后输入,同样的,在cmd中,先切换到mysql的bin目录,然后在bin目录里面运行:

mysql -uroot -p

这时会提示输入密码,啥也不用输入直接回即可(我试了,随便输入任何内容回车都可以进入MySQL)。

3.4 重置root密码

3.4.1 MySQL5.7之前的版本
use mysql;
update user set password=password("111111") where user="root";
flush privileges;
exit
3.4.2 MySQL5.7及之后的版本
use mysql;
update user set authentication_string=password("111111") where user="root";
flush privileges;
exit

3.5 重置完成

这时候就可以使用111111这个密码登陆root用户了。

4. 注意事项

如果使用wamp环境,有可能在重启MySQL的时候不成功。
解决方法是先关掉所有的cmd窗口,然后在任务管理器中停止掉mysqld进程。
再重启MySQL就可以了。

如何在Apache2.4中建立虚拟目录(虚拟主机)

1. Apache版本号

WAMP环境,Apache版本号:2.4.23
此方法同样适用于普通Apache环境。

2. 目标

在浏览器中输入http://localhost:8787的时候,访问F:\PHP_WORKPLACE\www\fastadmin\public文件夹。

3. 两个文件需要编辑

两个文件的目录如下:
如果非wamp环境,则在Apache安装目录:

  • apache安装目录\conf\extra\httpd-vhosts.conf
  • apache安装目录\conf\httpd.conf

如果是wamp环境,可以直接在wamp菜单下打开这两个文件。
file

3.1 httpd-vhosts.conf

增加如下代码:

<VirtualHost *:8787>
    ServerName localhost
    DocumentRoot F:/PHP_WORKPLACE/www/fastadmin/public
    <Directory  "F:/PHP_WORKPLACE/www/fastadmin/public/">
        Options +Indexes +Includes +FollowSymLinks +MultiViews
        AllowOverride All
        Require local
    </Directory>
</VirtualHost>

其中:
8787为访问时使用的端口号,可以任意指定。
DocumentRoot和Directory为绑定的目录地址。

3.2 httpd.conf

在文件中搜索Listen关键字,找到:
Listen 0.0.0.0:80
在这句的下面,输入:
Listen 0.0.0.0:8787
这里面的8787和httpd-vhosts.conf中的端口号对应。

4. 重启Apache

重启Apache,大功告成!

使用Grunt合并及压缩Javascript代码

1. Grunt安装

cnpm install -g grunt-cli 
cnpm install grunt --save-dev

2. 安装合并和压缩两个插件

cnpm install grunt-contrib-concat --save-dev
cnpm install grunt-contrib-uglify --save-dev

3. 根目录创建Gruntfile.js文件

文件内容如下:

module.exports = function(grunt){
    // 1. 初始化插件配置
    grunt.initConfig({
        //主要编码处
        concat: {
            options: { //可选项配置
                separator: ';'   //使用;连接合并
            },
            build: { //此名称任意
                src:  [
                        'src/com_0_adjustArray.js',
                        'src/com_1_globalVars.js',
                        'src/com_2_1_publicFunsSecret.js',
                        'src/com_2_2_publicFuns.js',
                        'src/com_3_subFuns.js',
                        'src/com_4_execFuns.js'
                    ], 
                dest: "dist/all.js" //输出的js文件
            }
        }
    });
    // 2. 加载插件任务
    grunt.loadNpmTasks('grunt-contrib-concat');
    // 3. 注册构建任务
    grunt.registerTask('default', ['concat']);
};

4. 目录结构

file

4. 执行

cmd中切换到目标目录后,直接运行grunt命令就可以了。
运行成功后,在dist文件夹下回出现合并的all.js文件及压缩后的all.min.js文件了。

5. 错误解决

运行grunt命令式弹出如下错误:

basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")

解决方案很简答,将node_modules文件夹中的依赖全部删除,将package.json里面无关的依赖也删除,重新运行cnpm install命令安装依赖即可。
估计是之前在这个文件夹下测试了太多的程序,导致逻辑混乱吧。

Egg.js随学随记

1. Egg.js安装

1.1 安装Egg.js

cnpm install egg-init -g

采用全局安装,而非--save,原因有待后面分解。

1.2 在VS Code下安装Egg.js代码提示

file

2. 初始化Egg.js项目

egg-init project_name

如果这样输入命令,则在命令运行过程中会出现一个选项,让你选择所要创建的项目类型:
file
这里我选择新建一个simple项目,安装完成后,会弹出安装依赖和启动项目的提示命令:
file
这里要注意,创建项目的是新建一个文件夹。所以请合理的安排安装目录。
已经学到Egg.js了,各种cnpm的命令是什么意思应该很清楚了吧?
所以接下来安装依赖:

cnpm install

安装完成后,启动项目:

npm run dev

3. Egg.js目录约定

3.1 官方约定。

egg-project
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app
|   ├── router.js
│   ├── controller
│   |   └── home.js
│   ├── service (可选)
│   |   └── user.js
│   ├── middleware (可选)
│   |   └── response_time.js
│   ├── schedule (可选)
│   |   └── my_task.js
│   ├── public (可选)
│   |   └── reset.css
│   ├── view (可选)
│   |   └── home.tpl
│   └── extend (可选)
│       ├── helper.js (可选)
│       ├── request.js (可选)
│       ├── response.js (可选)
│       ├── context.js (可选)
│       ├── application.js (可选)
│       └── agent.js (可选)
├── config
|   ├── plugin.js
|   ├── config.default.js
│   ├── config.prod.js
|   ├── config.test.js (可选)
|   ├── config.local.js (可选)
|   └── config.unittest.js (可选)
└── test
    ├── middleware
    |   └── response_time.test.js
    └── controller
        └── home.test.js

3.2 MVC架构

我们的开发都在app目录下完成,Egg.js使用MVC架构,与文件夹对应关系如下:

├── app
|   ├── router.js(路由规则)
│   ├── controller(Controller)
│   |   └── home.js
│   ├── service (Model)
│   |   └── user.js
│   ├── middleware (中间件)
│   |   └── response_time.js
│   ├── public (静态资源)
│   |   └── reset.css
│   ├── view (View)
│   |   └── home.tpl
│   └── extend (扩展方法)
│       ├── helper.js (可选)
│       ├── request.js (可选)
│       ├── response.js (可选)
│       ├── context.js (可选)
│       ├── application.js (可选)
│       └── agent.js (可选)

4. 一个mini的Egg.js项目

4.1 安装egg-view-ejs插件

cnpm install ejs --save
cnpm install egg-view-ejs --save

安装插件后需要配置两个文件:
文件1:/config/plugin.js

'use strict';

/** @type Egg.EggPlugin */
module.exports = {
  // had enabled by egg
  // static: {
  //   enable: true,
  // }
  ejs:{//增加部分
      enable: true,
      package: 'egg-view-ejs',
    }
};

文件2:/config/config.default.js

/* eslint valid-jsdoc: "off" */

'use strict';

/**
 * @param {Egg.EggAppInfo} appInfo app info
 */
module.exports = appInfo => {
  /**
   * built-in config
   * @type {Egg.EggAppConfig}
   **/
  const config = exports = {};

  // use for cookie sign key, should change to your own and keep security
  config.keys = appInfo.name + '_1582681748898_3732';

  // add your middleware config here
  config.middleware = [];

  // add your user config here
  const userConfig = {
    // myAppName: 'egg',
  };

  config.view = {//增加部分
    mapping: {
      '.html': 'ejs',
    },
  };

  return {
    ...config,
    ...userConfig,
  };
};

4.2 文件结构

├── app
|   ├── router.js
│   ├── controller
│   |   └── news.js
│   ├── service
│   |   └── news.js
│   ├── public
│       └── images
│              └── welcome.gif
│   ├── view
│       └── index.html
│       └── content.html
│       └── list.html

4.3 文件代码

4.3.1 router.js

'use strict';

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.news.index);
  router.get('/list', controller.news.list);
  router.get('/content/:newsid/:newscategory', controller.news.content);
};

4.3.2 service/news.js 模拟从数据库获取并返回数据

'use strict';

const Service = require("egg").Service;

class NewsService extends Service {
    async getNewsList(){
        //模拟从数据库模拟数据
        var dbData = ["A news about Tom","Jerry's news is cooler!","Popeye finally married Olive!"];
        return dbData;
    }
}

module.exports = NewsService;

4.3.3 controller/news.js 处理业务逻辑

'use strict';

const Controller = require('egg').Controller;

class NewsController extends Controller {
  async index() {
    const { ctx } = this;
    await ctx.render("index.html");
  }
  async list() {
    const { ctx } = this;
    var newsList = await ctx.service.news.getNewsList();
    await ctx.render("list.html",{
        newsList:newsList
    });
  }
  async content() {
    const { ctx } = this;
    var getInfo = ctx.query;
    var paraInfo = ctx.params;

    var name = getInfo.name;
    var level = getInfo.level;

    var newsId = paraInfo["newsid"];
    var newsCategory = paraInfo["newscategory"];

    await ctx.render("content.html",{
        name:name,
        level:level,
        newsid:newsId,
        newscategory:newsCategory
    });
  }
}

module.exports = NewsController;

4.3.4 view/三个文件

文件1:view/index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <h1>你好!欢迎进入迷你新闻系统!</h1>
        <h2>在Egg.js中,静态资源直接使用资源目录路径引用即可!比如下面的图片</h2>
        <img src="/public/images/welcome.gif" >
        <h3><a href="/list">点我进入新闻列表</a></h3>
    </body>
</html>

文件2:view/list.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <h1>新闻列表</h1>
        <ul>
            <%for(let i=0;i<newsList.length;i++){%>
            <li><%=newsList[i]%></li>
            <%}%>
        </ul>

        <h3>
            <a href="/content/6/HotNews?name=Tom&level=3">
                这条新闻通过参数和动态路由传递数据,链接地址是:    /content/6/HotNews?name=Tom&level=3
            </a>
        </h3>
    </body>
</html>

文件3:view/content.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <h3>Name is: <%=name%></h3>
        <h3>Level is: <%=level%></h3>
        <h3>News ID is: <%=newsid%></h3>
        <h3>News Category is: <%=newscategory%></h3>
    </body>
</html>

欲学乾坤大挪移,必先搞定九阳神功!

1. 大学为什么先学C语言?

大学计算机系大一课程中,C语言几乎是标配语言。一个学校是否以C语言作为启蒙计算机语言,很大程度上决定了这个学校的教学品质。
为什么这么说呢?因为C语言是最基础(从当代学习计算机编程角度)的计算机语言,把C语言学透,学扎实,后面学任何新语言都会很快。

这点上看是不是很像《倚天屠龙记》里面张无忌学习“九阳神功”和“乾坤大挪移”的桥段?

《倚天屠龙记》里面张无忌被困明教密道的时候,在小昭的帮助下偶得“乾坤大挪移”心法。张无忌在几个时辰里面学会了别人一生都不一定能学会的“乾坤大挪移”。

书中说的很明白,因为张无忌学习了几年的“九阳神功”,明白了很多学习高等武功的基础知识,再学习高等武功的时候便能融会贯通。

那些没有“九阳神功”基础的人,拿到“乾坤大挪移”后只能看的云里雾里,里面的武功招式看似相识,却又非常陌生,无法实际操练。

2.前端程序员先搞定ES6!

之所以提出这个话题,是因为最近学习前端技术的时候感触颇深。

这些年前端火的不得了,所以就决定自学Node.js,Vue.js,Koa2,Egg.js等等技术。由于自己有着很多年Javascript的基础,觉得直接上手这些技术应该不是难事儿。

最开始学了几天Node.js,觉得一知半解就开始学Vue.js,更加一知半解,然后进入Egg.js,基本上就蒙了。

学习的整体感觉就是,觉得里面的东西都是Javascript,觉得都似曾相识,但是就是觉得别扭,就是记不住。

然后重