前端发展史

1990年,那是一个冬天,有一位老人在欧洲核子研究中心织了一张网,神话般的诞生了万维网。

很久很久以前(1980 年),在欧洲核子研究中心工作的一位英国物理学家「蒂姆·伯纳斯-李」为了让研究人员能够分享和更新他们的研究结果,他与「罗勃·卡力奥」一起建立了一个叫做 ENQUIRE [ɪn’kwaɪə(r)] 的原型系统,这就是万维网的前身。后来他想将这种方式嫁接到因特网上,1990 年圣诞节那天编写出了全球首个网页浏览器 WorldWideWeb(同时也是网页编辑器,后来为了避免与万维网混淆而改名为 Nexus [‘neksəs])和网页服务器。这套服务器在欧洲核子研究中心于 1991 年 8 月 6 日上线,这就是全球第一个万维网网站。

HTML(超文本标记语言)、HTTP(超文本传输协议)以及 URI(统一资源标志符,也就是在浏览器输入的地址规则)都是这位大佬发明的。

WorldWideWeb 的模拟界面如下:

这个浏览器有以下功能:

  • 显示文本、图片
  • 打开超链接
  • 编辑所示内容

点击此链接可以查看此网页版模拟界面:https://worldwideweb.cern.ch/browser/

这个界面也很经典,截止目前(2019 年 11 月 20 日),WPS、Microsoft Office 的界面都和它极为相似

这个时候,万维网这玩意儿在科研领域迅速流传开了。大家都知道,科研领域流行了和普通用户间流行了差别还是很大的,在普通用户中流行不起来的原因是大家没有这种可以读取 HTML 文件的程序,用文本读取软件读取出来的话就和现在浏览器右键查看网页源码一个样。

既然没有这种软件,那下一步就应该有人要做这样的一个软件了。

两年后(1993 年 3 月),伊利诺斯大学的「马克·安德列森(MarcAndreessen)」领着一群学生写出了 Mosaic [məʊ’zeɪɪk],这是第一个面向普通用户使用的读取 HTML 文件的程序,从此万维网这玩意儿才走进了普通人的世界。又过了一年(1994 年),上面开发浏览器的这波人和 SGI 公司的「詹姆斯·克拉克(JamesClark)」合作成立了网景公司,开发了 Netscape Navigator [‘nævɪ.ɡeɪtə(r)] 浏览器。后来 JavaScript 就是因为它进入了大家的视野。

网景的浏览器没用 Mosaic 的代码,但是网景的一个对头 —— 微软开发的 IE 浏览器却用了 Mosaic 的源码。这中间关系有点复杂就不细说了,想了解的小伙伴可以查看词条 Internet ExplorerMosaic 浏览器Netscape 浏览器

网景浏览器现在虽然消失了,但是其另一个分支 —— Mozilla Firefox 还活着,他们之间的关系可查看词条:Mosaic 浏览器Mozilla基金会

1994 年,网景预见到网络需要变得更动态,以及 HTML 需要一种「胶水语言」让网页设计师和兼职程序员可以很容易地使用它来组装图片和插件之类的组件,且代码可以直接编写在网页标记中。

1995年,网景招募了布兰登·艾克,目标是把 Scheme 语言嵌入到 Netscape Navigator 浏览器当中。但更早之前,网景已经跟 Sun 合作在 Netscape Navigator 中支持 Java,这时网景内部产生激烈的争论。后来网景决定发明一种与 Java 搭配使用的辅助脚本语言并且语法上有些类似。最初命名为 Mocha [ˈmɒkə],后来改名为 LiveScript,最后网景公司与 Sun 组成的开发联盟为了让这门语言搭上 Java 这个编程语言「热词」,将其临时改名为 JavaScript(一个临时的名字成了永久的名称)。

JavaScript 在发明初期是针对非程序员设计的脚本语言,其标准版本是 ECMAScript,由网景通讯公司(Netscape)提交给欧洲标准协会制订。尽管它的名字和 Java 类似,但是它是由网景公司开发的而不是由太阳计算机系统公司(Sun Microsystems)开发的,除了两者的语法都是从 C 语言发展而来这一点外,它们之间几乎没有什么关系。之所以叫 JavaScript,只是当时网景公司希望能借助 Java 的名气推广它。时至今日 JavaScript 不仅可以用于网页开发,还强大到可以进行 PC 软件开发、手机 APP 开发、服务后台开发。

现在前端又出现了微软开发的 TypeScript,这玩意儿官方定义是 JavaScript 的超集。什么意思呢?就是说 JavaScript 原有的语言特性太少了,就搞出来一个语言特性更丰富的语言,然后还支持 JavaScript 的所有语法。但是 TypeScript 不能直接在浏览器运行,需要编译成 JavaScript 才能在浏览器运行。绕了这么一圈,那 TypeScript 是不是就感觉很鸡肋呢?其实不是这样的,TypeScript 解决了前端工程化问题和编写时的语法检查能力。其他的我们在后文「前端生态」来综合说明。

前端生态

开发语言

HTML:https://developer.mozilla.org/zh-CN/docs/Web/HTML

超文本标记语言,一种特定标签组合的 XML。

CSS:https://developer.mozilla.org/zh-CN/docs/Web/CSS

层叠样式表,描述 HTML 或 XML 文件在屏幕上绘制成什么样子。

JavaScript:https://www.javascript.com/

浏览器脚本语言(动态解释语言),通过操作 HTML 实现动态交互,学习和查阅资料:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript

TypeScript:https://www.typescriptlang.org/

TypeScript 是静态编译语言,JavaScript 的超集,需编译成 JavaScript 才能在浏览器运行。

解决 JavaScript 工程化问题和 JavaScript 编写时容易导致的语法错误,比 JavaScript 多了如下语法特性:

  • 类型批注和编译时类型检查
  • 接口
  • 模块
  • lambda 函数

语法简介

HTML

html 是一种具有特定元素的 xml,这些特定的元素称为 html 标签,如下所示:

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>测试页面</title>
</head>
<body>
<img src="images/firefox-icon.png" alt="测试图片">
</body>
</html>

我们来和 mybatis 的 mapper.xml 文件对比一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xxxx.mapper.XXMapper">
<resultMap id="BaseResultMap" type="xxxx.XX">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="created_by" jdbcType="VARCHAR" property="createdBy" />
<result column="created_date" jdbcType="TIMESTAMP" property="createdDate" />
<result column="updated_by" jdbcType="VARCHAR" property="updatedBy" />
<result column="updated_date" jdbcType="TIMESTAMP" property="updatedDate" />
</resultMap>

<sql id="Base_Column_List">
id, created_by, created_date, updated_by, updated_date
</sql>

<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from table_name
where id = #{id,jdbcType=INTEGER}
</select>
</mapper>

html 的标签比 xml 文件要求宽松一些,xml 中的标签必须有结束标签,html 部分标签是可以没有结束标签的,如上方的 <img> 标签就没有结束标签 </img> 末尾也不是 />,其它类似还包括 <br><hr><meta><link> 等。

html 标签也有属性,包括标准的和自定义属性都可以。

现在我们来一个个对照解释 html 中的内容:

  • <!DOCTYPE html> — 文档类型。这个的作用和 xml 中的一致,用于申明文件的根元素是什么以及文档中允许出现什么样的标签。html 中一般可以省略不写,在 mybatis 中是必须的,其余 xml 中是否要求必须写解析器有关。
  • <html></html> — 根元素。页面中所有元素一般要求在这个标签里,但是实际上在它外面或没有它也不会有多大问题。和 mybatis 中的 <mapper> 元素作用一致。
  • <head></head><head> 元素。包含标题、搜索关键字(keywords)、页面描述、CSS 样式表和字符编码声明等。
  • <meta charset="utf-8"> — 指定文档编码,这里使用 UTF-8 字符编码,类似 xml 中 <?xml version="1.0" encoding="UTF-8"?><meta> 标签还可以有其它的很多作用,还包括添加页面描述、搜索关键字等。
  • <title></title><title> 元素。设置页面的标题,显示在浏览器标签页上,将网页加入书签的时候显示的描述文字也是这个内容。
  • <body></body><body> 元素。这个元素包含期望让用户在访问页面时看到的内容,可以是文本、图像、视频、游戏、可播放的音轨或其他内容。
  • <img> — 图片元素,用于在浏览器中显示图片。

JavaScript

JavaScript 和 TypeScript 基本语法是一致的,因为 TypeScript 全兼容 JavaScript。

JavaScript 与 Java 语言相比,缺少以下这些语言特性:

  • 继承
  • 方法重载
  • 泛型
  • 注解

这些语法特性又在 TypeScript 上实现了,从使用角度来说 TypeScript 更像 Java,但是即使是 TypeScript 也不能重载。

函数

JavaScript 有一个 Java 没有的特性:可以在任何地方定义方法,包括将方法赋值给变量直接执行。比如:

正常情况下的函数定义

1
2
3
4
5
function fn() {
console.log('fn')
}
// 执行函数,控制台输出 fn
fn()

定义了一个 fn 变量,用于指向一个匿名函数

1
2
3
4
5
var fn = function() {
console.log('fn')
}
// 执行函数,控制台输出 fn
fn()

定义了一个 Map,每一个 key 对应一个匿名函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var fnMap = {
fnA : function() {
console.log('fnA')
},
fnB : function() {
console.log('fnB')
}
}
// 执行 fnA 函数,控制台输出 fnA
fnMap.fnA()
fnMap['fnA']()
// 执行 fnB 函数,控制台输出 fnB
fnMap.fnB()
fnMap['fnB']()

在函数中定义函数,并返回这个函数

1
2
3
4
5
6
7
8
function fnBack() {
function back() {
console.log('back')
}
return back;
}
// 执行两次函数,因为执行 fnBack 返回的是一个函数,所以要再次调用执行返回的函数,,控制台输出 back
fnBack()();

变量

通过以上例子可以看到,定义变量的时候在变量前使用 var 关键字。JavaScript 中只能用 var 来定义关键字,ES6 增加了 constlet 关键字。

ES5 于 2009 年完成标准化

ES6 于 2015 年 6 月发布第一个版本

const 用来定义不可修改的变量,等同于 Java 中变量前加 final 关键字。

let 的目的是替换掉 var 关键字。

为什么使用 let 关键字,看下面两个函数即可明白:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function varTest() {
var x = 1;
{
var x = 2; // 同样的变量!
console.log(x); // 2
}
console.log(x); // 2
}

function letTest() {
let x = 1;
{
let x = 2; // 不同的变量
console.log(x); // 2
}
console.log(x); // 1
}

多的就不说了,大家可以在任意的地方学习这些基础内容,下面我来给大家介绍一些 JavaScript 特殊的用法,这些用法经常用到,但是很少有地方针对性的介绍他们。

使用 || 设置默认值

这个例子是使用百度统计时,要求加入你网站中的代码。

1
2
3
4
5
6
7
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?xxxxx";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();

我们来看这段代码第一行 var _hmt = _hmt || [];

表达的意思是:

  1. 定义 _hmt 变量
  2. 判断 _hmt 变量的值是否为真,若为真则将 _hmt 的值赋值给新定义的 _hmt 变量;若为假,赋值一个空数组给 _hmt 变量

翻译成比较好认的写法:

1
2
3
4
5
6
var _hmt;
if (_hmt) {
_hmt = _hmt;
} else {
_hmt = []
}

这里隐藏两个 JavaScript 的语法规则:

  1. var 声明的变量,如果你重新声明一个 JavaScript 变量,它将不会丢失其值。也就是说无论写多少次 var _hmt; 其效果都一样,而且后定义的值不会覆盖前面赋予的值。

  2. 任何一个值,只要它不是 undefinednull0NaN 或空字符串(""),那么无论是任何对象,即使是值为假的Boolean对象,在条件语句中都为真。例如:

    1
    2
    3
    4
    var b = new Boolean(false);
    if (b) {} // 表达式的值为true
    b = false;
    if (b) {} // 表达式的值为false

立即执行函数

我们继续看百度统计脚本,删掉第一行和函数里的代码如下:

1
2
(function() {
})();

这种写法在大部分的 JS 框架中都能看到,这一段代码表示,定义一个匿名的函数,函数定义完毕后立刻执行这个函数,等同于下面的写法:

1
2
var fn = function() {}
fn();

与之类似有如下写法:

1
2
3
4
5
6
(function(){})()
(function(){}())
!function(){}()
+function(){}()
-function(){}()
~function(){}()

这样的写法有两个好处:

  1. 不污染命名空间。JS 中的函数不能重载,如果框架用的名称和业务代码里的冲突了,那么就会导致功能不一致。
  2. 异步请求。还是用百度统计的代码来说,其实那一串代码最终就是在网页插入里一个 scprit 标签,然后通过这个标签加载一段 JS 代码,通过这种方式写入DOM的 script 标签,能避免对浏览器渲染DOM、加载后续script的阻塞,但是依然会阻塞 window.onload 事件的时间。jQuery 等待页面加载完毕的写法 $(document).ready() 就会被阻塞。

最后看一个错误的写法:

1
function(){}()

这种写法会出现这样的错误:Uncaught SyntaxError: Function statements require a function name。

箭头函数

箭头函数是 ES6 新增特性。等同于 Java 8 支持的 lambda 表达式。

Java 8 的 lambda 表达式是对 函数式接口 进行简写。

带有 @FunctionalInterface 注解的接口叫做 函数式接口

JavaScript 中箭头函数是对函数的缩写。

Java 8 写法:

1
Runnable printHelloWord = () -> System.out.println("hello world");

JavaScript 写法:

1
const printHelloWord = () => console.log('hello word');

现在请集中注意力,要说一下 JavaScript 中和此相关的坑。

以常用的 Vue 框架中函数的定义来举例,这是用 Vue 开发每天都会看到的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用传统函数
var app = new Vue({
methods: {
handleDelete: function(index, row) {
// 不能正确执行,会出现错误:
// Uncaught TypeError: Cannot read property 'error' of undefined
this.$message.error('删除了:' + row.name);
}
}
})

// 使用 lambda 函数
export default {
methods: {
handleDelete(index, row) {
// 可以正确执行
this.$message.error("删除了:" + row.name);
}
}
};

这里涉及到 JavaScript 中 this 关键字的作用域范围。

在 JavaScript 中:

  1. 在任何函数体外部对象内部,都属于全局上下文,this 都指向全局对象(window)。
  2. 函数内部this 的值取决与函数被调用的方式,简单说就是谁调用了这个函数,函数内的 this 就指向谁。

在 Java 中,this 关键字的作用域只有一条标准,就是谁调用就指向谁,但是因为 Java 中的函数只能通过类来调用,所以 this 就恒指向函数所在类的实例(静态方法中不能使用 this 关键字)。

看实际案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 1.在函数体外部使用 this
window.a = 10
console.log(this.a) // 10

// 2.在对象内部使用 this
var obj = {
x: this.a
}
console.log(obj.x === this.a) // true
console.log(obj.x) // 10

// 3.在函数体内部使用 this
var obj = {
name: 'JavaScript',
foo: function () {
console.log(this)
console.log(this.name)
}
}
// 3.1 常规调用
obj.foo()
// {name: "JavaScript", foo: ƒ}
// 'JavaScript'

// 3.2 foo 函数不是作为 obj 的方法调用
var fn = obj.foo // 这里foo函数并没有执行
fn()
// Window {parent: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
// undefined

开发套件

Node.js:https://nodejs.org/zh-cn/

JavaScript 运行环境。对标 JRE,不对标 JDK,因为 JavaScript 本身就不需要编译直接就运行。

在 JavaScript 开发的项目中使用 npm run build 仅是对项目进行压缩、优化、打包等操作,并不会有类似 Java 到 class 的编译过程。

在 TypeScript 开发的项目中使用 npm run build 虽然有 TypeScript 编译为 JavaScript 的过程,但是其编译动作并不是 Node.js 完成的,而是运行在 Node.js 上的 typescript 插件完成的,此插件和 TypeScript 同名,类似 JRE 中有一个名为 java 的可执行文件。

NPM:https://www.npmjs.com/

前端界的包管理工具,附带在 Node.js 安装包中。NPM 和 webpack 的结合对标 Maven 或 Gradle。

webpack:https://webpack.js.org/;https://github.com/webpack/webpack

项目管理工具。NPM 和 webpack 的结合对标 Maven 或 Gradle。

sublime text:https://www.sublimetext.com/

文本编辑工具。安装包小,运行资源占用少,运行速度快。

Atom:https://atom.io/;https://github.com/atom/atom

基于 Electron 使用 JavaScript 开发的可编程文本编辑器。

Visual Studio Code:https://code.visualstudio.com/;https://github.com/microsoft/vscode

通常称 vscode,微软基于 Electron 使用 TypeScript 开发的可编程文本编辑器。因为 atom 刚开始的时候运行很慢,资源占用很大,经常崩溃,后来微软用同样的技术栈开发了 vscode。

主要框架

Vue.js:https://cn.vuejs.org/

JavaScript 或 TypeScript 语言开发,支持混合 JSX 语法。对标 Spring Framework、Spark Framework(不是 Apache Spark)。

Vue CLI:https://cli.vuejs.org/zh/

基于 Vue.js 进行快速开发的完整系统。对标 Spring Initializr。

Angular:https://angular.cn/

JavaScript 或 TypeScript 语言开发,支持混合 JSX 语法,通常使用 TypeScript。对标 Spring Boot。

Angular CLI:https://angular.cn/guide/cli-builder

Angular 命令行构建器。对标 Spring Initializr。

React:https://zh-hans.reactjs.org/

JSX 语法开发。对标 Spring Framework、Spark Framework(不是 Apache Spark)。

(JSX 是一个看起来很像 XML 的 JavaScript 语法扩展)

React CLI:https://github.com/magalhas/react-cli

React 项目快速创建工具。对标 Spring Initializr。

基础库

axios:https://github.com/axios/axios

HTTP 请求工具。对标 HttpClient、OkHttp、Spring Framework 中的 Spring RestTemplate 等。

Vuex:https://vuex.vuejs.org/zh/guide/

专为 Vue.js 应用程序开发的状态管理模式。本质是一个全局数据存储容器,从功能上对标 Redis。

Vue Router:https://router.vuejs.org/zh/

路由管理器,控制 URL 跳转视图。对标 Status 或 Spring Framework 中的 Spring MVC。

UI 组件

ElementUI:https://element.eleme.cn/

饿了么主推基于 VUE 的 PC 端后台 UI 组件

mint-ui:https://github.com/ElemeFE/mint-ui

饿了么开发的基于 VUE 的移动端 UI 组件

Ant Design of React:https://ant.design/

蚂蚁金服主推基于 React 框架的 PC 端后台 UI 组件

Ant Design Mobile of React:https://mobile.ant.design/

蚂蚁金服主推基于 React 框架的移动端 UI 组件

Ant Design of Angular:https://ng.ant.design/

蚂蚁金服维护的基于 Angular 框架的 PC 端后台 UI 组件

Ant Design Mobile of Angular:http://ng.mobile.ant.design/

蚂蚁金服维护的基于 Angular 框架的移动端 UI 组件

扩展资源

Weex:https://weex.apache.org/zh/

使用 Vue.js 来构建 Android、iOS 和 Web 应用。

React Native:https://facebook.github.io/react-native/

使用 JavaScript 和 React 编写原生移动应用。

Ionic:https://ionicframework.com/

使用 Web 技术进行智能设备 APP 开发的框架。原本只支持 Angular,现在还支持 Vue.js、React 以及脱离框架使用纯 JavaScript。

Electron:https://electronjs.org/

基于Node.js 和 Chromium 的开发套件,主要目的是支持 JavaScript、HTML 和 CSS 构建跨平台的桌面应用。

Flutter:https://flutter.cn/

Google 开源的 UI 工具包,帮助开发者通过一套代码库高效构建多平台精美应用,支持移动、Web、桌面和嵌入式平台,使用全新的 dart 语言。

最后我们来整体看一下前端的发展历程:

HTML

  • 1980 年,「伯纳斯-李」编写了 ENQUIRE,这是万维网的前身
  • 1990 年圣诞节,「伯纳斯-李」编写了全球首个网页浏览器(同时也是网页编辑器)和网页服务器
  • 1991 年 8 月 6 日,万维网公共服务的首次亮相,第一个网页地址是:http://info.cern.ch/hypertext/WWW/TheProject.html
  • 1995 年 11 月 24 日,HTML 2.0 发布
  • 1997 年 1 月 14 日,首个完全由 W3C 开发并标准化的版本 HTML 3.2 发布
  • 1997 年 12 月 18 日,W3C 发布 HTML 4.0
  • 2014 年 10 月 28 日,W3C 发布 HTML 5.0

JavaScript

  • 1995年12月4日, Netscape Navigator 2.0 Beta 3 更名为 JavaScript,Netscape 公司与 Sun 公司联合发布了 JavaScript 语言
  • 2005年,Ajax方法诞生
  • 2006年,jQuery函数库诞生
  • 2009年,Node.js项目诞生
  • 2013年5月,Facebook发布UI框架库React
  • 2015年,vuejs发布1.0版本
  • 2016年,vuejs2.x版本发布

搭建项目脚手架

本次使用 Vue.js 框架及相关生态来搭建项目脚手架。

环境准备

安装 node.js

浏览器打开 http://nodejs.org/ 下载适合自己系统的版本,双击安装即可。

设置 node.js 镜像加速

不建议用 cnpm 安装,会有各种诡异的bug。

打开命令行窗口,键入以下两行命令即可解决 npm 下载速度慢的问题。

1
2
npm config set registry https://registry.npm.taobao.org
npm config set disturl https://npm.taobao.org/dist

安装 vue-cli

打开命令行窗口,键入以下命令:

1
npm install -g @vue/cli

输出结果如下:

1
2
3
4
5
6
7
8
9
10
C:\Users\vm>npm install -g @vue/cli
npm WARN deprecated core-js@2.6.10: core-js@<3.0 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your dependencies to the actual version of core-js@3.
npm WARN deprecated fsevents@1.2.9: One of your dependencies needs to upgrade to fsevents v2: 1) Proper nodejs v10+ support 2) No more fetching binaries from AWS, smaller package size
C:\Users\vm\AppData\Roaming\npm\vue -> C:\Users\vm\AppData\Roaming\npm\node_modules\@vue\cli\bin\vue.js
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.9 (node_modules\@vue\cli\node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.9: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ @vue/cli@4.0.5
updated 1 package in 106.467s

到此即安装成功,可以使用 vue --version 验证,能正确输出版本号才可以继续下一步:

1
2
C:\Users\vm>vue --version
@vue/cli 4.0.5

使用 vue-cli 创建项目

打开命令行窗口,键入以下命令:

1
vue create hello-world

按提示选择一种项目模板然后按下回车键:

1
2
3
4
Vue CLI v4.0.5
? Please pick a preset: (Use arrow keys)
> default (babel, eslint)
Manually select features

若选择第二项,则会继续出现如下选项:

1
2
3
4
5
6
7
8
9
10
11
12
Vue CLI v4.0.5
? Please pick a preset: Manually select features
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
( ) Router
( ) Vuex
( ) CSS Pre-processors
(*) Linter / Formatter
( ) Unit Testing
( ) E2E Testing

一般在生产项目中,会在第一步选择 Manually select features 选项,然后在第二步按以下方式选择

1
2
3
4
5
6
7
8
9
>(*) Babel
(*) TypeScript
( ) Progressive Web App (PWA) Support
(*) Router
(*) Vuex
(*) CSS Pre-processors
(*) Linter / Formatter
( ) Unit Testing
(*) E2E Testing

我们这里选择 default 选项后等待创建完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Vue CLI v4.0.5
✨ Creating project in C:\Users\vm\hello-world.
� Initializing git repository...
⚙ Installing CLI plugins. This might take a while...


> yorkie@2.0.0 install C:\Users\vm\hello-world\node_modules\yorkie
> node bin/install.js

setting up Git hooks
done


> core-js@3.4.2 postinstall C:\Users\vm\hello-world\node_modules\core-js
> node -e "try{require('./postinstall')}catch(e){}"


> core-js-pure@3.4.2 postinstall C:\Users\vm\hello-world\node_modules\core-js-pure
> node -e "try{require('./postinstall')}catch(e){}"


> ejs@2.7.4 postinstall C:\Users\vm\hello-world\node_modules\ejs
> node ./postinstall.js

added 1150 packages from 823 contributors in 177.928s
� Invoking generators...
� Installing additional dependencies...

added 57 packages from 53 contributors in 32.891s
⚓ Running completion hooks...

� Generating README.md...

� Successfully created project hello-world.
� Get started with the following commands:

$ cd hello-world
$ npm run serve

执行控制台输出的最后两行命令即可运行创建的项目。

1
2
cd hello-world
npm run serve

执行完毕后控制台显示如下:

1
2
3
4
5
6
7
8
9
10
DONE  Compiled successfully in 8183ms



App running at:
- Local: http://localhost:8080/
- Network: http://172.16.68.128:8080/

Note that the development build is not optimized.
To create a production build, run npm run build.

控制台显示内容包含了两个 url,任意复制其中一个 url 在浏览器打开即可看见如下界面:

到此,你已经学会了用 vue 创建项目了。

我们来认识一下这个项目包含哪些文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
node_modules           -- 项目所需要的依赖库在本地的存储位置
public -- 存放静态资源的目录,此目录下的文件跳过打包处理,直接拷贝到发布目录
|-- favicon.ico
|__ index.html
src -- 我们实际开发时的工作目录
|-- App.vue -- 整个项目的主要工作页面
|-- assets -- 又是一个资源目录,但是这里的资源是会被打包处理的
| |__ logo.png
|-- components -- 通常来说的组件目录,开发可复用的组件
| |__ HelloWorld.vue --
|__ main.js -- 整个项目的入口文件
.gitignore
babel.config.js -- 在进行项目提交时,对所有代码进行统一格式化处理的配置规则
package-lock.json -- 此文件自动生成。项目所有依赖库当前使用的具体版本以及依赖库的依赖规则
package.json -- 项目的依赖库,此文件由开发者编写
README.md -- 项目说明文档

src\main.js 是入口文件,src\App.vue 是整个项目的主要工作页面,其他的所有页面最终都是在 App.vue 中通过 JavaScript 脚本绘制的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- src\App.vue -->
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
name: 'app',
components: {
HelloWorld
}
}
</script>

<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

删去多余的代码后整体文件格式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<!-- 这一部分是 HTML 内容 -->
</template>

<script>
/** 这一部分是 JavaScript 内容 */
export default {
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
/** 这一部分是 CSS 内容 */
</style>

实操功能开发

接下来我们就开始一个简单的数据列表开发

使用原始的方式开发

这也是前端开发的本质,抛弃那些花里胡哨的工具和开发方式,回归 node.js 诞生以前的开发方式。使用此方式可以跳过以上所有环节,只需一台可以联网的电脑,任何环境都无需配置,有记事本即可开发。

第一阶段,编写一个 hello word

  1. 新建一个目录

  2. 在此目录新增文件 index.html

  3. 编辑 index.html 内容如下并保存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <!DOCTYPE html>
    <html>

    <head>
    <meta charset="UTF-8">
    <title>Vue演示</title>
    </head>

    <body>
    <div id="app">
    {{message}}
    </div>
    </body>

    <!-- 引入 vue -->
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script>
    var app = new Vue({
    el: '#app',
    data: {
    message: 'Hello Vue!'
    }
    })
    </script>

    </html>

现在用浏览器打开它,即可看见运行结果。

第二阶段,引入 ElementUI 开发一个数据列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<title>Vue演示</title>
<!-- 引入 element-ui 样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>

<body>
<div id="app">
<el-table :data="tableData" border style="width: 100%">
<el-table-column fixed prop="date" label="日期" width="150"></el-table-column>
<el-table-column prop="name" label="姓名" width="80">
<template slot-scope="scope">
<el-popover trigger="hover" placement="top">
<p>姓名: {{ scope.row.name }}</p>
<p>住址: {{ scope.row.address }}</p>
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ scope.row.name }}</el-tag>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="province" label="省份" width="80"></el-table-column>
<el-table-column prop="city" label="市区" width="80"></el-table-column>
<el-table-column prop="address" label="地址" width="200"></el-table-column>
<el-table-column prop="zip" label="邮编" width="80"></el-table-column>
<el-table-column fixed="right" label="操作">
<template slot-scope="scope">
<el-button @click="handleClick(scope.$index, scope.row)" type="text" size="small">查看</el-button>
<el-button size="mini" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</body>

<!-- 引入 vue -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- 引入 element-ui 组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script>
var app = new Vue({
el: '#app',
data: {
tableData: [{
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-04',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1517 弄',
zip: 200333
}, {
date: '2016-05-01',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1519 弄',
zip: 200333
}, {
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1516 弄',
zip: 200333
}]
},
methods: {
handleClick(index, row) {
this.$message({
dangerouslyUseHTMLString: true,
message: '查看的是第' + (index + 1) + '条数据,<br/>内容为:' + JSON.stringify(row)
});
},
handleEdit(index, row) {
this.$message.success({
dangerouslyUseHTMLString: true,
message: '编辑的是第' + (index + 1) + '条数据,<br/>内容为:' + JSON.stringify(row)
});
},
handleDelete(index, row) {
this.$message.error('删除了:' + row.name);
}
},
})
</script>

</html>

现在用浏览器打开它,即可看见运行结果。

使用工程化方式开发

我们使用前面「搭建项目脚手架」环节创建的项目来继续。

因为此项目本身就是 vue-cli 创建的,所以不需要再引入 vue 依赖,只需要添加 ElementUI 即可。运行下方命令添加 ElementUI 库。

1
npm i element-ui -S

在项目根目录执行上面的命令后输出结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
C:\Users\vm\hello-world>npm i element-ui -S
npm WARN deprecated core-js@2.6.10: core-js@<3.0 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your dependencies to the actual version of core-js@3.

> core-js@2.6.10 postinstall C:\Users\vm\hello-world\node_modules\babel-runtime\node_modules\core-js
> node postinstall || echo "ignore"

Thank you for using core-js ( https://github.com/zloirock/core-js ) for polyfilling JavaScript standard library!

The project needs your help! Please consider supporting of core-js on Open Collective or Patreon:
> https://opencollective.com/core-js
> https://www.patreon.com/zloirock

Also, the author of core-js ( https://github.com/zloirock ) is looking for a good job -)

npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.9 (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.9: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})

+ element-ui@2.12.0
added 9 packages from 8 contributors in 30.554s

接下来我们要开始修改代码了。

第一步

修改 hello-world\src\main.js 文件为下方内容将添加的 ElementUI 引入项目源码里。

1
2
3
4
5
6
7
8
9
10
11
import Vue from 'vue'
import 'element-ui/lib/theme-chalk/index.css';
import ElementUI from 'element-ui'
import App from './App.vue'

Vue.config.productionTip = false

Vue.use(ElementUI)
new Vue({
render: h => h(App),
}).$mount('#app')

相较于修改前,添加了如下的三行代码。

1
2
3
import 'element-ui/lib/theme-chalk/index.css';
import ElementUI from 'element-ui'
Vue.use(ElementUI)

第二步

hello-world\src\components\HelloWorld.vue 文件内容替换成以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<template>
<el-table :data="tableData" border style="width: 100%">
<el-table-column fixed prop="date" label="日期" width="150"></el-table-column>
<el-table-column prop="name" label="姓名" width="80">
<template slot-scope="scope">
<el-popover trigger="hover" placement="top">
<p>姓名: {{ scope.row.name }}</p>
<p>住址: {{ scope.row.address }}</p>
<div slot="reference" class="name-wrapper">
<el-tag size="medium">{{ scope.row.name }}</el-tag>
</div>
</el-popover>
</template>
</el-table-column>
<el-table-column prop="province" label="省份" width="80"></el-table-column>
<el-table-column prop="city" label="市区" width="80"></el-table-column>
<el-table-column prop="address" label="地址" width="200"></el-table-column>
<el-table-column prop="zip" label="邮编" width="80"></el-table-column>
<el-table-column fixed="right" label="操作">
<template slot-scope="scope">
<el-button @click="handleClick(scope.$index, scope.row)" type="text" size="small">查看</el-button>
<el-button size="mini" @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
<el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</template>

<script>
export default {
name: "HelloWorld",
data() {
return {
tableData: [
{
date: "2016-05-02",
name: "王小虎",
province: "上海",
city: "普陀区",
address: "上海市普陀区金沙江路 1518 弄",
zip: 200333
},
{
date: "2016-05-04",
name: "王小虎",
province: "上海",
city: "普陀区",
address: "上海市普陀区金沙江路 1517 弄",
zip: 200333
},
{
date: "2016-05-01",
name: "王小虎",
province: "上海",
city: "普陀区",
address: "上海市普陀区金沙江路 1519 弄",
zip: 200333
},
{
date: "2016-05-03",
name: "王小虎",
province: "上海",
city: "普陀区",
address: "上海市普陀区金沙江路 1516 弄",
zip: 200333
}
]
};
},
methods: {
handleClick(index, row) {
this.$message({
dangerouslyUseHTMLString: true,
message:
"查看的是第" +
(index + 1) +
"条数据,<br/>内容为:" +
JSON.stringify(row)
});
},
handleEdit(index, row) {
this.$message.success({
dangerouslyUseHTMLString: true,
message:
"编辑的是第" +
(index + 1) +
"条数据,<br/>内容为:" +
JSON.stringify(row)
});
},
handleDelete(index, row) {
this.$message.error("删除了:" + row.name);
}
}
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

接下来我们使用以下命令运行这个项目

1
npm run serve

最后在浏览器打开控制台显示的访问地址

最后,如果要移除上面的 logo,删除 C:\Users\vm\hello-world\src\App.vue 文件中的 <img alt="Vue logo" src="./assets/logo.png"> 即可。

参考资料