第3章 前端开发基础 本章首先介绍Web前端开发的基本知识,然后介绍Vue.js入门,最后介绍Node.js入门。 3.1Web前端 本节介绍Web前端的基础知识,主要包括浏览器架构、HTML、CSS、渲染流程和JavaScript。 3.1.1浏览器架构 通常,浏览器架构如图31所示,最上层是用户界面(User Interface),主要负责与用户的交互,包括地址栏输入、刷新/向前/后退、书签、浏览记录、偏好设置等功能。 图31浏览器架构 下面一层是浏览器引擎(Browser Engine)。协调上层的用户界面和下层的渲染引擎(Rendering Engine)。浏览器引擎主要是实现浏览器的动作(初始化加载、刷新、向前、后退等)。 渲染引擎则是为给定的URL提供可视化展示,它解析HTML、XML、JavaScript。不同的浏览器使用不同的渲染引擎。例如,IE使用Trident,Firefox使用Gecko,Safari、Chrome和Opera使用Webkit。 最底层是一些组件库。XML解析器(XML Parser)负责解析XML。Firefox使用的是Expat库,Chrome使用的是libXML库。网络模块负责基于HTTP和FTP处理网络请求,还提供文档的缓存功能以减少网络传输。JavaScript解释器负责解释执行页面的JavaScript代码,将得到的结果传输给渲染引擎。Chrome用的是C++实现的V8引擎,Firefox用的SpiderMonkey。UI 后端是UI的基础控件,实现隔离平台,提供与平台无关的接口给上层。数据存储模块则负责对用户数据、书签、Cookie和偏好设置等数据的持久化工作。 3.1.2用HTML生成内容 HTML(HyperText Markup Language,超文本标记语言)是一种用于创建网页的标准标记语言,用来描述网页的内容。HTML 使用标记标签(Tag)来描述网页,通常包含HTML标签及文本内容。HTML标签通常是成对出现的,比如。标签内可以嵌套标签。 Title

Heading 1

This is a paragraph.

This is a paragraph.

上述代码在浏览器中的显示如图32所示。 HTML运行在浏览器上,由浏览器来解析,生成DOM(Document Object Model,文档对象模型)树。DOM树是由HTML文件解析生成代表内容的树状结构。比如下列HTML代码生成的DOM树如图33所示。 A few quotes Franklin said that "A penny saved is a penny earned." FDR said "We have nothing to fear but fear itself." 图32HTML在浏览器中的显示 图33DOM树案例 3.1.3用CSS生成样式 CSS(Cascading Style Sheets,层叠样式表) 用于控制网页的样式和布局。CSS 3是最新的CSS标准。CSS不能单独使用,必须与HTML或XML一起协同工作,对HTML或XML起装饰作用。其中,HTML负责确定网页中有哪些内容,CSS确定以何种外观(大小、粗细、颜色、对齐和位置)展现这些元素。CSS可以用于设定页面布局、设定页面元素样式、设定适用于所有网页的全局样式。CSS可以零散地直接添加在要应用样式的网页元素上,也可以集中内置于网页、链接式引入网页以及导入式引入网页。 下列CSS代码中定义了doc、title、para的样式,以及class值为"emph"的元素的样式。 /* rule 1 */ doc { display: block; text-indent: 1em; } /* rule 2 */ title { display: block; font-size: 3em; } /* rule 3 */ para { display: block; } /* rule 4 */ [class="emph"] { font-style: italic; } 图34CSSOM树案例 同样,由CSS代码解析生成的样式规则的树状结构称为CSSOM(CSS Object Model,层叠样式表对象模型)树,如图34所示。 图35所示的内容树(Content Tree)则同时包含内容和样式信息。 图35内容树案例 3.1.4渲染流程 渲染树(Render Tree)是渲染流程的输出目标。它包含具有显示属性(颜色和大小)的长方形组成的树状结构。如图36所示,渲染的目标就是将最初写的页面内容HTML、样式CSS渲染为最后的渲染树,然后调用系统图形API来显示。 图36渲染树案例 渲染流程如图37所示。 图37渲染流程 (1) 解析。解析HTML/SVG/XHTML文件,产生DOM树; 解析CSS文件生成CSSOM树。 (2) 附着合成。DOM树和CSSOM树连接在一起合成内容树。 (3) 布局。通过内容树计算出渲染树每个节点的具体大小和位置。 (4) 绘制。调用系统图形API,通过显卡,将布局后的节点内容分别呈现到屏幕上。 3.1.5用JavaScript完成交互 用HTML和CSS已经可以显示页面的静态内容,但是网页还需要与用户服务器进行交互。所以,可以用JavaScript语言完成与用户的交互。可以直接在HTML文件中加入JavaScript脚本,也可以使用单独的文件,再导入。JavaScript语言可以通过API完成对DOM树和CSS树的操作,可以完成与用户的交互,完成与远端服务器的交互,也可以执行简单的前端和业务的逻辑。 1. 脚本在HTML文件中 下面是一个脚本在HTML文件中的例子。通过 2. 脚本在JavaScript文件中 如果希望代码可读性更强,可以将脚本写在单独的文件中。不过必须在 下面的JavaScript文件与上面的例子中的代码相同。 var span = document.getElementsByTagName('span')[0]; span.textContent = 'interactive'; span.style.display = 'inline'; var loadTime = document.createElement('div'); loadTime.textContent = 'You loaded this page on: ' + new Date(); loadTime.style.color = 'blue'; document.body.appendChild(loadTime); 3. 添加async关键字 如果将async关键字添加到 3.2Vue.js入门 本节介绍Vue.js的特性、安装步骤和简单的入门案例。 3.2.1Vue.js介绍 Vue.js是一个用于创建用户界面的开源JavaScript框架,也是一个创建单页应用的Web应用框架。 Vue.js具有如图38所示的特性,可以随意组合需要用到的模块,如vue+component+vuerouter+vuex+vuecli。 图38Vue.js 特性 1. 声明式渲染 Vue.js的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统。下列代码中界面
标签内的内容和数据message实现了双向绑定,当message变量值改变时,界面也会自动随之而改变。
{{ message }}
var app = new Vue({ el: '#app', data: { message: 'Hello Vue!' } }) 2. 组件化应用构建 组件系统是 Vue.js的另一个重要概念,因为它很抽象,允许使用小型、独立和通常可复用的组件构建大型应用。如图39所示,几乎任意类型的应用界面都可以抽象为一个组件树。 图39组件树 在Vue中,一个组件本质上是一个拥有预定义选项的一个Vue实例。在Vue中注册组件很简单,例如: //定义名为 todo-item 的新组件 Vue.component('todo-item', { template: '
  • 这是个待办项
  • ' }) var app = new Vue(...) 3. 前端路由 路由是将URL请求映射到代码的过程。当在Web应用中单击链接时,URL的改变将会给用户提供新的数据或者跳转到新的网页。 传统的路由是服务器端路由,或者说是后端路由。对于如下网页上的链接: Hello! 操作的过程如图310所示。详细说明如下: 图310后端路由 (1) 浏览器向当前URL路径下的子路径/hello发送GET请求(ContentType一般为text/html)。 (2) 服务器端解析请求并进行路由,向浏览器发送HTML。 (3) 浏览器解析HTML中的DOM、CSS、JavaScript并进行渲染。 后端路由每次都是一个完整网页的加载,网页的HTML一般是在后端服务器中通过模板引擎渲染好后再交给前端的。但是对于同一个网站的页面中很多DOM结构(导航栏、脚注、广告位)都是相似的甚至是重复的。每次都反复请求是对网络资源的浪费。 如果单击链接之后,不需要后端发送完整的页面,甚至不需要向后端请求,这就是SPA(Single Page Application,单页面应用)。单页面应用一般采用前端路由,如图311所示。 图311前端路由 SPA中单击链接加载页面的完整过程如下。 (1) 在首屏加载时,需要对应用需要的资源进行请求,得到完整HTML。 (2) 单击链接时检测请求事件,阻止浏览器发送GET请求。 (3) 前端路由代码改变地址栏URL(利用Hash、HTML 5 History API等)。 (4) 如果需要从服务器端接收数据,发送Ajax请求获取JSON数据。 (5) 路由代码利用接收的JSON数据对页面需要改变的DOM结构进行加载和渲染。 这样虽然首次加载的耗时较长,但是之后的加载速度大幅提高。浏览器从服务器端拿到的初始HTML,可能只有一个
    入口的div以及下面配套的一系列JavaScript文件。之后看到的页面其实是通过那些JavaScript渲染出来的。前端渲染把渲染的任务交给了浏览器,通过客户端的算力来解决页面的构建,这在很大程度上缓解了服务器端的压力。而且配合前端路由,无缝的页面切换体验自然是对用户友好的。 而Vue.js中vueouter模块提供了如下功能。 (1) 前端路由: 让页面中的部分内容可以无刷新地跳转,就像原生App一样。 (2) 懒加载: 结合异步组件以及在组件的created hook(已创建钩子)上触发获取数据的Ajax请求可以最大化地降低加载时间,减少流量消耗。 (3) 重定向: 可以实现某些需要根据特定逻辑改变页面原本路由的需求,比如未登录状态下访问“个人信息”时应该重定向到登录页面。 (4) 美化URL: 通过HTML 5 History模式优化URL。 4. 大规模状态管理 Vuex是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态。 Vuex包含以下几部分。 (1) state,驱动应用的数据源。 (2) view,以声明方式将 state 映射到视图。 (3) action,响应在 view 上的用户输入导致的状态变化。 Vuex强调数据流的单向流动。界面中view被单击后,回调action,然后改变状态state,又重新渲染view。但是,当应用遇到多个组件处于共享状态时,单向数据流的简洁性很容易被破坏。比如: (1) 多个视图依赖于同一状态。 (2) 来自不同视图的行为需要变更同一状态。 对于问题(1),传参的方法对于多层嵌套的组件将会非常烦琐,并且无法在兄弟组件间进行状态传递。对于问题(2),采用父子组件直接引用或者通过事件来变更与同步状态的多份复制的方法通常会导致代码无法维护。 为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为。 通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,代码将会变得更具结构化且易于维护。 这就是Vuex背后的基本思想。如图312所示,Vue组件接收交互行为,调用dispatch()方法触发action相关处理,若页面状态需要改变,则调用commit()方法提交mutation修改state,通过getter()方法获取到state新值,重新render(渲染)Vue Components,界面随之更新。 图312Vuex原理 5. 强大的构建工具 vuecli是一个基于 Vue.js 进行快速开发的完整系统。 (1) 通过@vue/cli命令实现交互式的项目脚手架的搭建。 (2) 通过@vue/cli + @vue/cliserviceglobal 实现零配置原型开发。 (3) 提供一个运行时依赖(@vue/cliservice)。该依赖的特点如下: ① 可升级; ② 基于Webpack构建,并带有合理的默认配置; ③ 可以通过项目内的配置文件进行配置; ④ 可以通过插件进行扩展。 (4) 一个丰富的官方插件集合,集成了前端生态中最好的工具。 (5) 一套完全图形化的创建和管理Vue.js项目的用户界面。 vuecli致力于将Vue生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样可以专注在撰写应用上,而不必花时间去纠结配置的问题。 3.2.2Vue.js的安装 1. 安装Node.js和npm 如图313所示,安装Node.js可以访问https://nodejs.org/en/官方网站,选择对应操作系统的安装软件。 图313Node.js官方网站安装页面 npm为Node.js下的包管理器。一般安装Node.js后,会自动安装npm。如图314所示,运行命令node v查看Node.js版本。运行命令npm v查看npm版本。 图314查看Node.js和npm版本 2. 安装cnpm cnpm是中国npm镜像的客户端,用以访问npm相关的镜像文件在国内的备份站点,以提高国内用户镜像文件的下载速度。 运行命令npm install g cnpm registry=http://registry.npm.taobao.org 可安装cnpm。 3. 安装vuecli vuecli是一种全局脚手架,用于帮助搭建所需的模板框架。 运行命令npm install g vuecli可安装vuecli。 运行命令vue V可查看vue版本。 4. 创建第一个Vue.js项目 运行命令vue init webpack helloworld可创建Vue.js项目、设置项目名称,其他项可以直接按Enter键选择默认参数。具体项目的文件夹可以查看图315。 5. 安装依赖 运行命令npm install 或者cnpm install可安装依赖。安装完成之后,会在项目文件夹中多出一个node_modules文件夹,这就是项目需要的依赖包资源。 6. 运行项目 如图315所示,进入项目的文件夹,运行npm run dev,启动本地服务器。最后,通过浏览器访问http://localhost:8080/#/,如图316所示。 图315运行项目 图316浏览器显示 3.2.3Vue.js基本使用 1. 计数器 下列代码是一个简单计数器的案例。 Vue Example

    This button is clicked {{ count }} times。

    图317计数器案例显示效果 单击按钮,button显示计数的次数。将count数据和View中的单击次数进行双向绑定。事件响应每次单击按钮,调用increment方向counter数据加1,View中button的单击次数也随之改变。 单击3次之后,显示效果如图317所示。 2. 网站列表 客户端ViewModel有时会异步请求Model数据。比如下列代码的info数据是向服务器发送GET请求得来的。当服务器应答后,再根据response数据设置ViewModel,从而达到View的改变。response.data.sites数据赋给了info参数。

    网站列表

    {{ site.name }}
    具体显示效果如图318所示。 上述代码如果是在本地运行,由于要访问www.runoob.com域名的接口,因此会出现跨域问题。可以在https://www.runoob.com/try/try.php?filename=vue2ajaxaxios3页面查看效果。 3. Vuex实现计数器 下面将根据一个Vuex实现的计数器介绍Vue.js的基本使用。项目的主要文件夹如图319所示。 图318网站列表案例 显示效果 图319Vuex实现的计数器项目主要文件夹 App.vue是根页面级组件,main.js是入口文件。在src文件夹下,router子文件夹下有index.js,实现了路由。store子文件夹下有index.js文件和modules文件夹,把相关的状态分离到modules子文件夹下,再在index.js中引入。component子文件夹是界面组件。 App.vue根页面级组件中引入了components子文件夹的demo组件。具体代码如下: main.js中引入了router和store。具体代码如下: import Vue from 'vue' import App from './App' import router from './router' import store from './store' Vue.config.productionTip = false new Vue({ el: '#app', router, store, components: { App }, template: '' }) 下面是store文件夹的index.js代码。当应用比较简单时,可以把状态都写在一个store对象index.js中,但当应用变得非常复杂时,index.js就有可能很臃肿。所以将store分割成模块(Module)。这里定义了一个count模块,其内容来自modules文件夹的count_store.js。 import Vue from 'vue' import Vuex from 'vuex' import countStore from './modules/count_store.js' Vue.use(Vuex) export default new Vuex.Store({ modules: { count: countStore } }) 下面是count_store.js代码。其中定义了保存状态count。Commit mutation(提交更改)是更改Vuex的store中的状态的唯一方法。Vuex中的mutation非常类似于事件: 每个mutation都有一个字符串的事件类型(Type)和一个回调函数(Handler)。这个回调函数就是实际进行状态更改的地方,并且它会接受state作为第一个参数。代码中的increment是使计数加1,decrement是使计数减1。 export default{ state: { count: 1 }, mutations: { increment(state) { state.count++ }, decrement(state) { state.count-- } } } demo.vue定义一个

    标签,MVVM双向绑定了{{ $store.state.count.count }}值。另外有两个按钮,单击之后的回调分别执行"$store.commit('increment')"和"$store.commit('decrement')"提交变更。具体代码如下: 可以通过命令行执行npm run dev命令来运行项目。如果使用类似WebStorm的IDE成功编译并启动服务器,可以在IDE的终端中看到如图320所示的输出。如果失败,则及时修改代码,系统会自动运行命令来完成编译和启动服务器的工作。 图320在WebStorm IDE的终端中编译运行项目 图321Vuex实现的计数器运行效果 如图321所示,访问http://localhost:8080网站,就可以看到项目的显示效果。单击+按钮4次,单击-按钮1次,数字显示为3。 3.3Node.js入门 本节介绍Node.js的特性、HelloWorld项目和简单的基于Node.js的服务器案例。 3.3.1Node.js介绍 Node.js就是能够在服务器端运行的JavaScript开放源代码、跨平台JavaScript环境。Node.js采用Google公司开发的V8引擎运行代码,使用事件驱动、非阻塞和异步输入输出模型等技术来提高性能,可优化应用程序的传输量和规模。这些技术通常用于资料密集的即时应用程序。 Node.js大部分基本模块都用JavaScript语言编写。在Node.js出现之前,JavaScript通常作为客户端程序设计语言使用,用来完成前端的交互逻辑,用JavaScript写出的程序常在用户的浏览器上运行。Node.js出现之后,使得JavaScript应用于服务器端编程成为可能。Node.js的一系列内置模块,使得程序可以脱离Apache HTTP服务器或微软Web服务器,作为独立服务器运行。这使得JavaScript成了全栈开放语言,大大提升了其在编程语言中的地位。 由于大量第三方库的存在,在安装和使用Node.js时会给用户带来相当大的困扰。Node.js附带了包管理器——npm。npm是一个命令行工具,用于从NPM Registry中下载、安装Node.js程序,同时解决依赖问题,大大提高了开发的速度。 3.3.2Node.js基本使用 1. Hello World,Hello Node.js 在任何文本编辑器上输入下列代码,然后保存为hello.js。 'use strict'; console.log('Hello, World.'); 第一行代码总是写上“'use strict';”,这是因为以严格模式运行JavaScript代码,避免各种潜在陷阱。 在控制台,进入hello.js所在文件夹,执行node hello.js,控制台就会得到如下输出。 Hello, World. 2. Hello World, Hello Server 服务器端使用CommonJS模块化方案,用require指令来载入CommonJS模块。require是同步加载的,后面的代码必须等待这个命令执行完才会执行。如果是浏览器端则使用ES6模块化方案。由于浏览器通过网络加载ES6模块无法保证速度,因此一般以import命令进行异步加载,或者更准确地说,ES6模块有一个独立的静态解析阶段,依赖关系的分析是在那个阶段完成的,最底层的模块第一个执行。 Node.js中自带 http 模块,在代码中请求它并将返回它的实例化对象赋给一个本地变量,通过这个本地变量可以调用http 模块所提供的公共方法。然后,使用http.createServer()方法来创建服务器,并使用listen()方法绑定8888端口。服务器通过request、response参数来接收和响应数据。 var http = require('http'); http.createServer(function(request, response) { //发送 HTTP 头部 //HTTP 状态值: 200 : OK //内容类型: text/plain response.writeHead(200, {'Content-Type': 'text/plain'}); //发送响应数据 "Hello World" response.end('Hello World\n'); }).listen(8888); //终端打印如下信息 console.log('Server running at http://127.0.0.1:8888/'); 如图322所示,执行命令node server.js 启动服务器。通过浏览器访问http://127.0.0.1:8888/网址,可以看到如图323所示的效果。 图322启动Node.js服务器 图323Node.js HelloWorld案例显示效果 3. 网站列表服务器 在3.2节中访问网站列表是通过第三方服务器得到数据的。如果想从自己的Node.js服务器得到数据,只需要向自己的服务地址发出REST请求。 修改之前创建第一个Vue.js项目的HelloWorld项目中router文件夹下的index.js。具体代码如下: import Vue from 'vue' import Router from 'vue-router' import WebSites from '@/components/WebSites' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'OurWebSites', component: WebSites } ] }) 在components文件夹下创建新的Vue component: WebSites.vue。具体代码如下: Express是一种保持最低程度规模的灵活Node.js Web应用程序框架,为Web和移动应用程序提供一组强大的功能。Express框架的核心特性: 可以设置中间件来响应HTTP请求; 定义路由表用于执行不同的HTTP请求动作; 可以通过向模板传递参数来动态渲染HTML 页面。下面的案例中,服务器根据URL映射到具体的处理函数,通过req参数得到请求,经过业务逻辑处理后,通过res参数设置回复。通过JSON.stringify()将数据转换为JSON格式返回。 __dirname()方法可以获得当前文件夹名称。下面代码中用__dirname()方法构建数据文件的路径。 var express = require('express'); var app = express(); var fs = require("fs"); var cors = require('cors') //解决跨域问题 app.use(cors({ credentials: true, origin: '*', })) //映射URL和处理函数 app.get('/getWebSites', function(req, res) { fs.readFile( __dirname + "/" + "data.json", 'utf8', function(err, data) { data = JSON.parse(data); console.log(data); res.end(JSON.stringify(data)); }); }) //设置监听端口,启动服务器 var server = app.listen(8081, function() { var host = server.address().address var port = server.address().port console.log("应用实例,访问地址为 http://%s:%s", host, port) }) 本案例中服务器是从本地一个JSON文件中读取的数据。JSON文件内容如下。 { "sites":[ { "name":"Sina", "info":["News", "Blog"] }, { "name":"Baidu", "info":["Search", "Netdisk"] }, { "name":"Taobao", "info":["Shopping"] } ] } 最后,显示效果如图324所示。 图324Node.js网站列表案例显示效果 练习3 1. 【多选题】浏览器架构由以下()部分组成。 A. 用户界面 B. 浏览器引擎 C. 渲染引擎 D. 数据存储、网络、JavaScript引擎、UI后端等基本库 2. 【多选题】浏览器渲染包括以下()步骤。 A. 解析B. 附着合成C. 布局D. 绘制 3. 【单选题】下列说法不正确的是()。 A. HTML使用标记标签(Tag)来描述网页的内容 B. CSS确定以何种外观(大小、粗细、颜色、对齐和位置)展现这些元素 C. JavaScript语言可以通过API完成对DOM树和CSS树的操作 D. JavaScript脚本只能写在HTML中才有效,不能作为单独的文件引用 4. 【多选题】下列()是Vue.js的特点。 A. 声明式渲染B. 组件化应用构建 C. 前端路由D. 大规模状态管理 E. 强大的构建工具 5. 【简答题】论述前端路由和后端路由的区别和优缺点。 6. 【单选题】下列关于Vuex的说法错误的是()。 A. Vuex强调数据流的单向流动 B. Vuex采用集中式存储管理应用的所有组件的状态 C. Vue组件接收交互行为,调用dispatch()方法触发action相关处理,若页面状态需要改变,则调用commit()方法提交mutation()方法修改state,通过getters获取到state新值,重新渲染Vue Components,界面随之更新 D. Vuex既可以直接调用mutation()方法,也可以通过commit()方法来提交mutation()方法 7. 【简答题】简述Vuex项目各文件夹与文件的作用。 8. 【简答题】简述Node.js Express框架的功能。