整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JS模块化 - 浅谈 CommonJS requir

JS模块化 - 浅谈 CommonJS require 函数实现

早的前端,没有模块加载规范,只能在HTML中通过<script>来引入js文件,同时无法区分函数来源于哪个js文件,而且要用过多全局变量。而随着前端工程复杂度的提升,使用这种方式已经无法满足日益增长的开发需求,js的模块化应运而生。

CommonJS 是属于 Node.js 的模块化方案,最早是叫 ServerJS,随着 Node.js 的火爆发展而成名的。Module1.0 规范在 Node.js 上实践的很好。

而 JavaScript 在当时(ES6 Modules 规范还未诞生)是没有模块化方案的,所以又更名从 CommonJS,想要统一服务端与客户端的模块加载方案。

但是,require 函数是同步的,在浏览器端由于网络的瓶颈而不适用。于是,AMD 和 CMD 的规范相继涌现,和 CommonJS 一起服务于 JavaScript 模块化。

而正是规则的不统一,这也是目前兼容方案 UMD 会出现的原因。

不过AMD 和 CMD 的浏览器端模块化,有很明显的问题:

  1. 导致浏览器端请求数过多;
  2. 受限于网络,所有模块都成功加载完只是一个承诺。

现如今,当打包这一环节被引入了前端工程化,CommonJS 以与服务端可以类库共用和 NPM(Node Package Manager) 这个后台的优势,成为了 es5 JavaScript 模块化的首选

简介

CommonJS 是一个旨在构建涵盖web服务器端、桌面应用、命令行app和浏览器JS的JS生态系统。

标准

CommonJS 的标准符合 module1.1 规范,暴露给使用者的有三个全局变量:

  1. require 是一个全局方法,用来加载模块
  2. exports 一个全局对象,用来导入模块的属性或方法
  3. module 一个全局对象。涵盖当前模块的必要信息,有一个只读的id属性,有一个uri属性,还有其它的一些命名规范,可以查看 CommonJS 规范的文档。

面向 require 这个切面

本文讲的是如何模拟一个 $require 函数,先来捋一捋 require 函数的主逻辑

require

require是一个基于AMD规范实现的函数,它区别于传统的CommonJS require规范。因为它能够异步地加载动态的依赖,所以,我们对基于require的回调方式有了更多的需求。

API Specification

局部require vs 全局require。局部require可以被解析成一个符合AMD工厂函数规范的require函数。

 define(['require'], function (require) {
 //the require in here is a local require.
 });
 define(function (require, exports, module) {
 //the require in here is a local require.
 });

局部require也支持其他标准实现的API。

全局require函数作用于全局,和define()类似。 全局require和局部require有着相同的行为,包含以下特征:

  • 模块ID应该认为是一个绝对的模块名称,而不是相对另一个模块的ID。
  • 只有在异步的时候,才可以使用require(id, callback?)的回调形式。因为异步加载模块的方式是先发出一个异步请求,然后等主线程代码段执行完毕才能进行异步回调来处理加载好的模块。

实际中,我们经常会遇到一些阻塞模块加载的依赖,如果交互次数很多,需要大量的模块加载,应该采用全局依赖的形式去加载顶层模块。

require(String)

基于以下规范CommonJS Modules 1.1.1 require.根据参数,同步地返回模块ID所代表的模块。

如果模块没有加载或者执行完成,就会抛出错误。特别需要指出的是,在同步加载的回调中,如果模块没有加载完成,禁止动态的获取模块,否则,就会抛出异常。

使用define()定义模块时,依赖项中可以找到一个AMD模块:

 define(function (require) {
 var a=require('a');
 });

工厂方法可以被解析成require('')的调用形式(例如,使用语法解析器或者使用Function.prototype.toString()或者正则表达式)去找到依赖,加载并且执行依赖,然后执行工厂方法内部代码,通过这样的方式,就可以获取到模块。

require(Array, Function)

参数Array是一个由模块ID组成的数组。当模块ID所以代表的模块加载完成且可用时,回调函数Function才开始执行,并且只被执行一次。各个模块按照依赖数组中的位置顺序以参数的形式传入到Function里。

例如:

 define(function (require) {
 require(['a', 'b'], function (a, b) {
 //modules a and b are now available for use.
 });
 }); 

require.toUrl(String)

将形如**[module ID] + '.extension'**这种字符形式转化成URL路径。

require.toUrl()方法采用通用的模块ID路径转化规则,将模块ID字符解析成URL路径.但它不支持以".js"这种扩展形式。所以,我们必须将'.extension'添加到了解析路径里。

例如:

 //cart.js contents:
 define (function(require) {
 // 模块ID名 './templates/a'
 // 扩展名 '.html'
 // 模板路径大致以这样的形式结尾 'modules/cart/templates/a.html'
 var templatePath=require.toUrl('./templates/a.html');
 });

以上是require的具体用法展示。

根据这个逻辑,我们先写一个 main 函数,以及定义一些需要的接口

至此已经有一个不可执行的 require 函数了,逻辑中会依次判断是否有缓存,是否核心模块,加载相应文件以及加载执行模块并缓存。

可以看到没有具体实现,只有接口被定义出来,这种编码方式,同样可以借鉴到在其他的开发需求中:

  1. 在开始编码前,进行尽可能合理的功能模块划分,可以让代码逻辑清晰,减少重复步骤 (DRY),并增强后期的代码可维护性
  2. 定义你需要哪些接口。如果是比较复杂的功能,且不是独立开发的话,这一环节做的好坏,合理地划分与合理地分配,决定团队合作开发是否可以配合恰当

逐一实现接口

这一步骤,主要是对上述过程需要的接口进行实现。

思想上是一个分而治之的思想,实现一个很复杂的东西比较困难,但是实现具体的功能要求,且在一定输入输出的限制下,每个人都能轻易的写出符合需求的算法,并进行调优。

数据结构与工具函数类

栈 -- 存储当前模块的所在目录

function Stack(...args) {
 this._stack=new Array(...args);
}
Stack.prototype={
 top: function () {
 return this._stack .slice(-1)[0]
 },
 push: function (...args) {
 this._stack.push(...args)
 },
 pop: function () {
 this._stack.pop()
 },
 constructor: Stack
}

这个栈的作用是存放当前模块的所在目录,用于模块内 require 函数传入相对路径时,解析成绝对路径

获取文件所在目录

function getParent(pathname) {
 return path.parse(pathname).dir
}

具体的模块文件查找逻辑

检测模块类型与定位包的位置

这个函数要做下面的事情

  1. 检测模块类型:绝对路径,相对路径 或是 在 node_modules 内
  2. 如果是模块,则需要就近寻找 node_modules 有无这个模块,并且读取 pacakge.json 的 main属性

以下部分,是 getModuleLocation 自身需要实现的接口,往往是开发过程中自行提炼的,其他模块不通用

定位引用的真实路径

  1. 如果是目录,则添加'/index'后缀
  2. 对 '.js','.node', '.json' 可能有的后缀省略进行补齐

_require 函数

这个函数是 CommonJS 模块化的核心体现,理解这个函数,对 module 和 exports 的实际使用也会有帮助

  1. Node.js 的模块化实质是为每个模块的 js 包裹一层 'function module_exports(){}' 用以隔离作用域;
  2. module / exports / require 被作为传参而传入,而 exports 实质是 module.exports 的引用
  3. __dirname / __filename ,其实是这个模块内被先于执行函数定义的变量
  4. JS new Function 的特性常用来动态定义和执行某个方法,这边也同样用来执行模块

以为到这里就完成了吗,不,至少还有一些问题需要考虑:

  1. 如果出现异步 require 的情况,由于当前模块已经执行完,会清空存储模块目录的 stack ,会出现相对路径查找失败的问题,如何解决?
  2. 当发生循环依赖的时候,CommonJS 内部的加载流程是是否会陷入死循环,如果不会那会带来什么其他影响?

链接文章

https://github.com/kaola-fed/blog/issues/16

https://github.com/amdjs/amdjs-api/wiki/require-(中文版)

文讲解怎样用 Node.js 高效地从 Web 爬取数据。

前提条件

本文主要针对具有一定 JavaScript 经验的程序员。如果你对 Web 抓取有深刻的了解,但对 JavaScript 并不熟悉,那么本文仍然能够对你有所帮助。

  • ? 会 JavaScript
  • ? 会用 DevTools 提取元素选择器
  • ? 会一些 ES6 (可选)

你将学到

通过本文你将学到:

  • 学到更多关于 Node.js 的东西
  • 用多个 HTTP 客户端来帮助 Web 抓取的过程
  • 利用多个经过实践考验过的库来爬取 Web

了解 Node.js

Javascript 是一种简单的现代编程语言,最初是为了向浏览器中的网页添加动态效果。当加载网站后,Javascript 代码由浏览器的 Javascript 引擎运行。为了使 Javascript 与你的浏览器进行交互,浏览器还提供了运行时环境(document、window等)。

这意味着 Javascript 不能直接与计算机资源交互或对其进行操作。例如在 Web 服务器中,服务器必须能够与文件系统进行交互,这样才能读写文件。

Node.js 使 Javascript 不仅能够运行在客户端,而且还可以运行在服务器端。为了做到这一点,其创始人 Ryan Dahl 选择了Google Chrome 浏览器的 v8 Javascript Engine,并将其嵌入到用 C++ 开发的 Node 程序中。所以 Node.js 是一个运行时环境,它允许 Javascript 代码也能在服务器上运行。

与其他语言(例如 C 或 C++)通过多个线程来处理并发性相反,Node.js 利用单个主线程并并在事件循环的帮助下以非阻塞方式执行任务。

要创建一个简单的 Web 服务器非常简单,如下所示:

const http = require('http');
const PORT = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World');
});

server.listen(port, () => {
  console.log(`Server running at PORT:${port}/`);
});

如果你已安装了 Node.js,可以试着运行上面的代码。Node.js 非常适合 I/O 密集型程序。

HTTP 客户端:访问 Web

HTTP 客户端是能够将请求发送到服务器,然后接收服务器响应的工具。下面提到的所有工具底的层都是用 HTTP 客户端来访问你要抓取的网站。

Request

Request 是 Javascript 生态中使用最广泛的 HTTP 客户端之一,但是 Request 库的作者已正式声明弃用了。不过这并不意味着它不可用了,相当多的库仍在使用它,并且非常好用。用 Request 发出 HTTP 请求是非常简单的:

const request = require('request')
request('https://www.reddit.com/r/programming.json', function (  error,
  response,
  body) {
  console.error('error:', error)
  console.log('body:', body)
})

你可以在 Github 上找到 Request 库,安装它非常简单。你还可以在 https://github.com/request/request/issues/3142 找到弃用通知及其含义。

Axios

Axios 是基于 promise 的 HTTP 客户端,可在浏览器和 Node.js 中运行。如果你用 Typescript,那么 axios 会为你覆盖内置类型。通过 Axios 发起 HTTP 请求非常简单,默认情况下它带有 Promise 支持,而不是在 Request 中去使用回调:

const axios = require('axios')

axios
 .get('https://www.reddit.com/r/programming.json')
 .then((response) => {
  console.log(response)
 })
 .catch((error) => {
  console.error(error)
 });

如果你喜欢 Promises API 的 async/await 语法糖,那么你也可以用,但是由于顶级 await 仍处于 stage 3 ,所以我们只好先用异步函数来代替:

async function getForum() {
 try {
  const response = await axios.get(
   'https://www.reddit.com/r/programming.json'
  )
  console.log(response)
 } catch (error) {
  console.error(error)
 }
}

你所要做的就是调用 getForum!可以在 https://github.com/axios/axios 上找到Axios库。

Superagent

与 Axios 一样,Superagent 是另一个强大的 HTTP 客户端,它支持 Promise 和 async/await 语法糖。它具有像 Axios 这样相当简单的 API,但是 Superagent 由于存在更多的依赖关系并且不那么流行。

用 promise、async/await 或回调向 Superagent 发出HTTP请求看起来像这样:

const superagent = require("superagent")
const forumURL = "https://www.reddit.com/r/programming.json"

// callbacks
superagent
 .get(forumURL)
 .end((error, response) => {
  console.log(response)
 })

// promises
superagent
 .get(forumURL)
 .then((response) => {
  console.log(response)
 })
 .catch((error) => {
  console.error(error)
 })

// promises with async/await
async function getForum() {
 try {
  const response = await superagent.get(forumURL)
  console.log(response)
 } catch (error) {
  console.error(error)
 }
}

可以在 https://github.com/visionmedia/superagent 找到 Superagent。

正则表达式:艰难的路

在没有任何依赖性的情况下,最简单的进行网络抓取的方法是,使用 HTTP 客户端查询网页时,在收到的 HTML 字符串上使用一堆正则表达式。正则表达式不那么灵活,而且很多专业人士和业余爱好者都难以编写正确的正则表达式。

让我们试一试,假设其中有一个带有用户名的标签,我们需要该用户名,这类似于你依赖正则表达式时必须执行的操作

const htmlString = '<label>Username: John Doe</label>'
const result = htmlString.match(/<label>(.+)<\/label>/)

console.log(result[1], result[1].split(": ")[1])
// Username: John Doe, John Doe

在 Javascript 中,match() 通常返回一个数组,该数组包含与正则表达式匹配的所有内容。第二个元素(在索引1中)将找到我们想要的 <label> 标记的 textContentinnerHTML。但是结果中包含一些不需要的文本( “Username: “),必须将其删除。

如你所见,对于一个非常简单的用例,步骤和要做的工作都很多。这就是为什么应该依赖 HTML 解析器的原因,我们将在后面讨论。

Cheerio:用于遍历 DOM 的核心 JQuery

Cheerio 是一个高效轻便的库,它使你可以在服务器端使用 JQuery 的丰富而强大的 API。如果你以前用过 JQuery,那么将会对 Cheerio 感到很熟悉,它消除了 DOM 所有不一致和与浏览器相关的功能,并公开了一种有效的 API 来解析和操作 DOM。

const cheerio = require('cheerio')
const $ = cheerio.load('<h2 class="title">Hello world</h2>')

$('h2.title').text('Hello there!')
$('h2').addClass('welcome')

$.html()
// <h2 class="title welcome">Hello there!</h2>

如你所见,Cheerio 与 JQuery 用起来非常相似。

但是,尽管它的工作方式不同于网络浏览器,也就这意味着它不能:

  • 渲染任何解析的或操纵 DOM 元素
  • 应用 CSS 或加载外部资源
  • 执行 JavaScript

因此,如果你尝试爬取的网站或 Web 应用是严重依赖 Javascript 的(例如“单页应用”),那么 Cheerio 并不是最佳选择,你可能不得不依赖稍后讨论的其他选项。

为了展示 Cheerio 的强大功能,我们将尝试在 Reddit 中抓取 r/programming 论坛,尝试获取帖子名称列表。

首先,通过运行以下命令来安装 Cheerio 和 axios:npm install cheerio axios

然后创建一个名为 crawler.js 的新文件,并复制粘贴以下代码:

const axios = require('axios');
const cheerio = require('cheerio');

const getPostTitles = async () => {
 try {
  const { data } = await axios.get(
   'https://old.reddit.com/r/programming/'
  );
  const $ = cheerio.load(data);
  const postTitles = [];

  $('div > p.title > a').each((_idx, el) => {
   const postTitle = $(el).text()
   postTitles.push(postTitle)
  });

  return postTitles;
 } catch (error) {
  throw error;
 }
};

getPostTitles()
.then((postTitles) => console.log(postTitles));

getPostTitles() 是一个异步函数,将对旧的 reddit 的 r/programming 论坛进行爬取。首先,用带有 axios HTTP 客户端库的简单 HTTP GET 请求获取网站的 HTML,然后用 cheerio.load() 函数将 html 数据输入到 Cheerio 中。

然后在浏览器的 Dev Tools 帮助下,可以获得可以定位所有列表项的选择器。如果你使用过 JQuery,则必须非常熟悉 $('div> p.title> a')。这将得到所有帖子,因为你只希望单独获取每个帖子的标题,所以必须遍历每个帖子,这些操作是在 each() 函数的帮助下完成的。

要从每个标题中提取文本,必须在 Cheerio 的帮助下获取 DOM元素( el 指代当前元素)。然后在每个元素上调用 text() 能够为你提供文本。

现在,打开终端并运行 node crawler.js,然后你将看到大约存有标题的数组,它会很长。尽管这是一个非常简单的用例,但它展示了 Cheerio 提供的 API 的简单性质。

如果你的用例需要执行 Javascript 并加载外部源,那么以下几个选项将很有帮助。

JSDOM:Node 的 DOM

JSDOM 是在 Node.js 中使用的文档对象模型的纯 Javascript 实现,如前所述,DOM 对 Node 不可用,但是 JSDOM 是最接近的。它或多或少地模仿了浏览器。

由于创建了 DOM,所以可以通过编程与要爬取的 Web 应用或网站进行交互,也可以模拟单击按钮。如果你熟悉 DOM 操作,那么使用 JSDOM 将会非常简单。

const { JSDOM } = require('jsdom')
const { document } = new JSDOM(
 '<h2 class="title">Hello world</h2>'
).window
const heading = document.querySelector('.title')
heading.textContent = 'Hello there!'
heading.classList.add('welcome')

heading.innerHTML
// <h2 class="title welcome">Hello there!</h2>

代码中用 JSDOM 创建一个 DOM,然后你可以用和操纵浏览器 DOM 相同的方法和属性来操纵该 DOM。

为了演示如何用 JSDOM 与网站进行交互,我们将获得 Reddit r/programming 论坛的第一篇帖子并对其进行投票,然后验证该帖子是否已被投票。

首先运行以下命令来安装 jsdom 和 axios:npm install jsdom axios

然后创建名为 crawler.js的文件,并复制粘贴以下代码:

const { JSDOM } = require("jsdom")
const axios = require('axios')

const upvoteFirstPost = async () => {
  try {
    const { data } = await axios.get("https://old.reddit.com/r/programming/");
    const dom = new JSDOM(data, {
      runScripts: "dangerously",
      resources: "usable"
    });
    const { document } = dom.window;
    const firstPost = document.querySelector("div > div.midcol > div.arrow");
    firstPost.click();
    const isUpvoted = firstPost.classList.contains("upmod");
    const msg = isUpvoted
      ? "Post has been upvoted successfully!"
      : "The post has not been upvoted!";

    return msg;
  } catch (error) {
    throw error;
  }
};

upvoteFirstPost().then(msg => console.log(msg));

upvoteFirstPost() 是一个异步函数,它将在 r/programming 中获取第一个帖子,然后对其进行投票。axios 发送 HTTP GET 请求获取指定 URL 的HTML。然后通过先前获取的 HTML 来创建新的 DOM。JSDOM 构造函数把HTML 作为第一个参数,把 option 作为第二个参数,已添加的 2 个 option 项执行以下功能:

  • runScripts:设置为 dangerously 时允许执行事件 handler 和任何 Javascript 代码。如果你不清楚将要运行的脚本的安全性,则最好将 runScripts 设置为“outside-only”,这会把所有提供的 Javascript 规范附加到 “window” 对象,从而阻止在 inside 上执行的任何脚本。
  • resources:设置为“usable”时,允许加载用 <script> 标记声明的任何外部脚本(例如:从 CDN 提取的 JQuery 库)

创建 DOM 后,用相同的 DOM 方法得到第一篇文章的 upvote 按钮,然后单击。要验证是否确实单击了它,可以检查 classList 中是否有一个名为 upmod 的类。如果存在于 classList 中,则返回一条消息。

打开终端并运行 node crawler.js,然后会看到一个整洁的字符串,该字符串将表明帖子是否被赞过。尽管这个例子很简单,但你可以在这个基础上构建功能强大的东西,例如,一个围绕特定用户的帖子进行投票的机器人。

如果你不喜欢缺乏表达能力的 JSDOM ,并且实践中要依赖于许多此类操作,或者需要重新创建许多不同的 DOM,那么下面将是更好的选择。

Puppeteer:无头浏览器

顾名思义,Puppeteer 允许你以编程方式操纵浏览器,就像操纵木偶一样。它通过为开发人员提供高级 API 来默认控制无头版本的 Chrome。

Puppeteer 比上述工具更有用,因为它可以使你像真正的人在与浏览器进行交互一样对网络进行爬取。这就具备了一些以前没有的可能性:

  • 你可以获取屏幕截图或生成页面 PDF。
  • 可以抓取单页应用并生成预渲染的内容。
  • 自动执行许多不同的用户交互,例如键盘输入、表单提交、导航等。

它还可以在 Web 爬取之外的其他任务中发挥重要作用,例如 UI 测试、辅助性能优化等。

通常你会想要截取网站的屏幕截图,也许是为了了解竞争对手的产品目录,可以用 puppeteer 来做到。首先运行以下命令安装 puppeteer,:npm install puppeteer

这将下载 Chromium 的 bundle 版本,根据操作系统的不同,该版本大约 180 MB 至 300 MB。如果你要禁用此功能。

让我们尝试在 Reddit 中获取 r/programming 论坛的屏幕截图和 PDF,创建一个名为 crawler.js的新文件,然后复制粘贴以下代码:

const puppeteer = require('puppeteer')

async function getVisual() {
 try {
  const URL = 'https://www.reddit.com/r/programming/'
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  await page.goto(URL)
  await page.screenshot({ path: 'screenshot.png' })
  await page.pdf({ path: 'page.pdf' })

  await browser.close()
 } catch (error) {
  console.error(error)
 }
}

getVisual()

getVisual() 是一个异步函数,它将获 URL 变量中 url 对应的屏幕截图和 pdf。首先,通过 puppeteer.launch() 创建浏览器实例,然后创建一个新页面。可以将该页面视为常规浏览器中的选项卡。然后通过以 URL 为参数调用 page.goto() ,将先前创建的页面定向到指定的 URL。最终,浏览器实例与页面一起被销毁。

完成操作并完成页面加载后,将分别使用 page.screenshot()page.pdf() 获取屏幕截图和 pdf。你也可以侦听 javascript load 事件,然后执行这些操作,在生产环境级别下强烈建议这样做。

在终端上运行 node crawler.js ,几秒钟后,你会注意到已经创建了两个文件,分别名为 screenshot.jpgpage.pdf

Nightmare:Puppeteer 的替代者

Nightmare 是类似 Puppeteer 的高级浏览器自动化库,该库使用 Electron,但据说速度是其前身 PhantomJS 的两倍。

如果你在某种程度上不喜欢 Puppeteer 或对 Chromium 捆绑包的大小感到沮丧,那么 nightmare 是一个理想的选择。首先,运行以下命令安装 nightmare 库:npm install nightmare

然后,一旦下载了 nightmare,我们将用它通过 Google 搜索引擎找到 ScrapingBee 的网站。创建一个名为crawler.js的文件,然后将以下代码复制粘贴到其中:

const Nightmare = require('nightmare')
const nightmare = Nightmare()

nightmare
 .goto('https://www.google.com/')
 .type("input[title='Search']", 'ScrapingBee')
 .click("input[value='Google Search']")
 .wait('#rso > div:nth-child(1) > div > div > div.r > a')
 .evaluate(
  () =>
   document.querySelector(
    '#rso > div:nth-child(1) > div > div > div.r > a'
   ).href
 )
 .end()
 .then((link) => {
  console.log('Scraping Bee Web Link': link)
 })
 .catch((error) => {
  console.error('Search failed:', error)
 })

首先创建一个 Nighmare 实例,然后通过调用 goto() 将该实例定向到 Google 搜索引擎,加载后,使用其选择器获取搜索框,然后使用搜索框的值(输入标签)更改为“ScrapingBee”。完成后,通过单击 “Google搜索” 按钮提交搜索表单。然后告诉 Nightmare 等到第一个链接加载完毕,一旦完成,它将使用 DOM 方法来获取包含该链接的定位标记的 href 属性的值。

最后,完成所有操作后,链接将打印到控制台。

总结

  • ? Node.js 是 Javascript 在服务器端的运行时环境。由于事件循环机制,它具有“非阻塞”性质。
  • ? HTTP客户端(例如 Axios、Superagent 和 Request)用于将 HTTP 请求发送到服务器并接收响应。
  • ? CheerioJQuery 的优点抽出来,在服务器端 进行 Web 爬取是唯一的目的,但不执行 Javascript 代码。
  • ? JSDOM 根据标准 Javascript规范 从 HTML 字符串中创建一个 DOM,并允许你对其执行DOM操作。
  • ? Puppeteer and Nightmare高级(high-level )浏览器自动化库,可让你以编程方式去操作 Web 应用,就像真实的人正在与之交互一样。

绍 官网 github Vue.js 是一套构建用户界面(UI)的渐进式 JavaScript 框架 库和框架的区别 库: ?> 库,本质上是一些函数的集合。每次调用函数,实现一个特定的功能,接着把控制权交给使用者 jQuery:DOM 操作,即:封装 DOM 操作,简化 DOM 操作 框架: ?> 框架,是一套完整的解决方案,使用框架的时候,需要把你的代码放到框架合适的地方,框架会在合适的时机调用你的代码 框架规定了自己的编程方式,是一套完整的解决方案 使用框架的时候,由框架控制一切,我们只需要按照规则写代码 主要区别: !> 核心点:谁起到主导作用(控制反转)框架的侵入性很高(从头到尾) MVVM 和 MVC 的介绍: MVVM,一种更好的 UI 模式解决方案 MVVM===> M / V / VM M:model 数据模型 V:view 视图 VM:ViewModel 视图模型 MVC 模型-视图-控制器 M: Model 数据模型(专门用来操作数据,数据的 CRUD) V:View 视图(对于前端来说,就是页面) C:Controller 控制器(是视图和数据模型沟通的桥梁,用于处理业务逻辑) MVVM 和 MVC 的优势对比 MVC 模式,将应用程序划分为三大部分,实现了职责分离 在前端中经常要通过 JS 代码 来进行一些逻辑操作,最终还要把这些逻辑操作的结果现在页面中。也就是需要频繁的操作 DOM MVVM 通过数据双向绑定让数据自动地双向同步 V(修改数据) -> M M(修改数据) -> V 数据是核心 Vue 这种 MVVM 模式的框架,不推荐开发人员手动操作 DOM Vue 中的 MVVM 虽然没有完全遵循 MVVM 模型,Vue 的设计无疑受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的简称) 这个变量名表示 Vue 实例 !> 学习 Vue 要转化思想 不要在想着怎么操作 DOM,而是想着如何操作数据!!! 起步 - Hello Vue 安装:cnpm i -S vue {{ msg }}

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Vue 实例 !> 注意 1:先在 data 中声明数据,再使用数据 !> 注意 2:可以通过 vm.$data 或者 vm.msg 访问到data中的所有属性, var vm=new Vue({ data: { msg: "大家好,..." } }); vm.$data.msg===vm.msg; // true 1 2 3 4 5 6 7 1 2 3 4 5 6 7 数据绑定 最常用的方式:Mustache(插值语法),也就是 {{}} 语法 解释:{{}}从数据对象data中获取数据 说明:数据对象的属性值发生了改变,插值处的内容都会更新 说明:{{}}中只能出现 JavaScript 表达式 而不能解析 js 语句 注意:Mustache 语法不能作用在 HTML 元素的属性上

Hello, {{ msg }}.

{{ 1 + 2 }}

{{ isOk ? 'yes': 'no' }}

1 2 3 4 5 6 1 2 3 4 5 6 数据双向绑定 双向数据绑定:将 DOM 与 Vue 实例的 data 数据绑定到一起,彼此之间相互影响 数据的改变会引起 DOM 的改变 DOM 的改变也会引起数据的变化 原理:Object.defineProperty中的get和set方法 getter和setter:访问器 作用:指定读取或设置对象属性值的时候,执行的操作 深入响应式原理 /* defineProperty语法 介绍 */ var obj={}; Object.defineProperty(obj, "msg", { // 设置 obj.msg="1" 时set方法会被系统调用 参数分别是设置后和设置前的值 set: function(newVal, oldVal) {}, // 读取 obj.msg 时get方法会被系统调用 get: function(newVal, oldVal) {} }); 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 Vue 双向绑定的极简实现 剖析 Vue 原理&实现双向绑定 MVVM 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 动态添加数据的注意点 注意:只有 data 中的数据才是响应式的,动态添加进来的数据默认为非响应式 可以通过以下方式实现动态添加数据的响应式 1 Vue.set(object, key, value) - 适用于添加单个属性 2 Object.assign() - 适用于添加多个属性 var vm=new Vue({ data: { stu: { name: "jack", age: 19 } } }); /* Vue.set */ Vue.set(vm.stu, "gender", "male"); /* Object.assign 将参数中的所有对象属性和值 合并到第一个参数 并返回合并后的对象*/ vm.stu=Object.assign({}, vm.stu, { gender: "female", height: 180 }); 1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 10 11 12 异步 DOM 更新 说明:Vue 异步执行 DOM 更新,监视所有数据改变,一次性更新 DOM 优势:可以去除重复数据,对于避免不必要的计算和 避免重复 DOM 操作上,非常重要 如果需要那到更新后 dom 中的数据 则需要通过 Vue.nextTick(callback):在 DOM 更新后,执行某个操作(属于 DOM 操作) 实例调用 vm.$nextTick(function () {}) methods: { fn() { this.msg='change' this.$nextTick(function () { console.log('$nextTick中打印:', this.$el.children[0].innerText); }) console.log('直接打印:', this.$el.children[0].innerText); } } 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 指令 解释:指令 (Directives) 是带有 v- 前缀的特殊属性 作用:当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM v-text 解释:更新 DOM 对象的 textContent 1 2 1 2 v-html 解释:更新 DOM 对象的 innerHTML 1 1 v-bind 作用:当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM 语法:v-bind:title="msg" 简写::title="msg" 1 2 3 4 1 2 3 4 v-on 作用:绑定事件 语法:v-on:click=“say” or v-on:click=“say(‘参数’, $event)” 简写:@click=“say” 说明:绑定的事件定义在 methods 1 2 3 4 1 2 3 4 事件修饰符 .stop 阻止冒泡,调用 event.stopPropagation() .prevent 阻止默认行为,调用 event.preventDefault() .capture 添加事件侦听器时使用事件捕获模式 .self 只当事件在该元素本身(比如不是子元素)触发时,才会触发事件 .once 事件只触发一次 v-model 作用:在表单元素上创建双向数据绑定 说明:监听用户的输入事件以更新数据 你输入了: {{ message }}

1 2 1 2 v-for 作用:基于源数据多次渲染元素或模板块 {{ item.text }}

{{item}} -- {{index}}

{{item}} -- {{key}}

{{item}}

1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 key 属性 推荐:使用 v-for 的时候提供 key 属性,以获得性能提升。 说明:使用 key,VUE 会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

1 2 3 1 2 3 样式处理 class 和 style 使用方式:v-bind:class=“expression” or :class=“expression” 表达式的类型:字符串、数组、对象(重点)===> 解析后===>解析后===>解析后 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 v-if 和 v-show v-if:根据表达式的值的真假条件,销毁或重建元素 v-show:根据表达式之真假值,切换元素的 display CSS 属性

这个元素展示出来了吗???

这个元素,在HTML结构中吗???

1 2 1 2 提升性能:v-pre vue 会跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。 {{ 这将不会被编译 }} 1 1 提升性能:v-once 说明:vue 只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。 这不会被改变: {{msg}} 1 1 过滤器 filter 作用:文本数据格式化 过滤器可以用在两个地方:{{}}和 v-bind 表达式 两种过滤器:1.全局过滤器 2. 局部过滤器 全局过滤器 说明:通过全局方式创建的过滤器,在任何一个 vue 实例中都可以使用 注意:使用全局过滤器的时候,需要先创建全局过滤器,再创建 Vue 实例 显示的内容由过滤器的返回值决定

{{ dateStr | date }}

{{ dateStr | date('YYYY-MM-DD hh:mm:ss') }}

1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 局部过滤器 说明:局部过滤器是在某一个 vue 实例的内容创建的,只在当前实例中起作用 { data: {}, // 通过 filters 属性创建局部过滤器 // 注意:此处为 filters filters: { filterName: function(value, format) {} } } 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 按键值修饰符 说明:在监听键盘事件时,Vue 允许为 v-on 在监听键盘事件时添加关键修饰符 其他:修饰键(.ctrl 等)、鼠标按键修饰符(.left 等) 键盘事件 - 键值修饰符 // 只有在 keyCode 是 13 时调用 vm.submit() @keyup.13="submit" // 使用全局按键别名 @keyup.enter="add" --- // 通过全局 config.keyCodes 对象自定义键值修饰符别名 Vue.config.keyCodes.f2=113 // 使用自定义键值修饰符 @keyup.enter.f2="add" 1 2 3 4 5 6 7 8 9 10 11 1 2 3 4 5 6 7 8 9 10 11 监视数据变化 - watch 概述:watch 是一个对象,键是需要观察的表达式,值是对应回调函数 作用:当表达式的值发生变化后,会调用对应的回调函数完成响应的监视操作 vue$watch new Vue({ data: { a: 1, b: { age: 10 } }, watch: { a: function(val, oldVal) { // val 表示当前值 // oldVal 表示旧值 console.log("当前值为:" + val, "旧值为:" + oldVal); }, // 监听对象属性的变化 b: { handler: function(val, oldVal) { /* ... */ }, // deep : true表示是否监听对象内部属性值的变化 deep: true }, // 只监视user对象中age属性的变化 "user.age": function(val, oldVal) {} } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 计算属性 说明:计算属性是基于它们的依赖进行缓存的,只有在它的依赖发生改变时才会重新求值 注意:Mustache 语法({{}})中不要放入太多的逻辑,否则会让模板过重、难以理解和维护 注意:computed 中的属性不能与 data 中的属性同名,否则会报错 Vue computed 属性原理 var vm=new Vue({ el: "#app", data: { firstname: "jack", lastname: "rose" }, computed: { fullname() { return this.firstname + "." + this.lastname; } } }); 1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 10 11 12 实例生命周期 所有的 Vue 组件都是 Vue 实例,并且接受相同的选项对象即可 (一些根实例特有的选项除外)。 实例生命周期也叫做:组件生命周期 生命周期介绍 ?> 生命周期钩子函数的定义:从组件被创建,到组件挂载到页面上运行,再到页面关闭组件被卸载,这三个阶段总是伴随着组件各种各样的事件,这些事件,统称为组件的生命周期函数! vue 生命周期钩子函数 简单说:一个组件从开始到最后消亡所经历的各种状态,就是一个组件的生命周期 注意:Vue 在执行过程中会自动调用生命周期钩子函数,我们只需要提供这些钩子函数即可 注意:钩子函数的名称都是 Vue 中规定好的! 钩子函数 - beforeCreate() 说明:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用 注意:此时,无法获取 data 中的数据、methods 中的方法 钩子函数 - created() 注意:这是一个常用的生命周期,可以调用 methods 中的方法、改变 data 中的数据 vue 实例生命周期 参考 1 vue 实例生命周期 参考 2 使用场景:发送请求获取数据 钩子函数 - beforeMounted() 说明:在挂载开始之前被调用 钩子函数 - mounted() 说明:此时,vue 实例已经挂载到页面中,可以获取到 el 中的 DOM 元素,进行 DOM 操作 钩子函数 - beforeUpdated() 说明:数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。 注意:此处获取的数据是更新后的数据,但是获取页面中的 DOM 元素是更新之前的 钩子函数 - updated() 说明:组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。 钩子函数 - beforeDestroy() 说明:实例销毁之前调用。在这一步,实例仍然完全可用。 使用场景:实例销毁之前,执行清理任务,比如:清除定时器等 钩子函数 - destroyed() 说明:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 vue 单文件组件 vue-loader single-file components(单文件组件) 后缀名:.vue,该文件需要被预编译后才能在浏览器中使用 注意:单文件组件依赖于两个包 vue-loader / vue-template-compiler 安装:cnpm i -D vue-loader vue-template-compiler 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // webpack.config.js 配置: module: { rules: [ { test: /\.vue$/, loader: "vue-loader" } ]; } 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 使用单文件组件 /* main.js */ import Vue from "vue"; // 导入 App 组件 import App from "./App.vue"; const vm=new Vue({ el: "#app", // 通过 render 方法,渲染App组件 render: c=> c(App) }); 1 2 3 4 5 6 7 8 9 10 11 1 2 3 4 5 6 7 8 9 10 11 单文件组件使用步骤 1 安装:cnpm i -D vue-loader vue-template-compiler 2 在 webpack.config.js 中配置 .vue 文件的 loader { test: /\.vue$/, use: 'vue-loader' } 3 创建 App.vue 单文件组件,注意:App 可以是任意名称 4 在 main.js 入口文件中,导入 vue 和 App.vue 组件,通过 render 将组件与实例挂到一起 单文件组件+路由 vue - Vue.use Vue.use 和 路由 import Vue from "vue"; import App from "./App.vue"; // ------------- vue路由配置 开始 -------------- import Home from "./components/home/Home.vue"; import Login from "./components/login/Login.vue"; // 1 导入 路由模块 import VueRouter from "vue-router"; // 2 ** 调用use方法使用插件 ** Vue.use(VueRouter); // 3 创建路由对象 const router=new VueRouter({ routes: [ { path: "/home", component: Home }, { path: "/login", component: Login } ] }); // ------------- vue路由配置 结束 -------------- const vm=new Vue({ el: "#app", render: c=> c(App), // 4 挂载到 vue 实例中 router }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Mint-UI 基于 Vue.js 的移动端组件库 Mint-UI 安装:cnpm i -S mint-ui // 1 导入 mint-ui模块 import MintUI from "mint-ui"; // 2 导入 样式 import "mint-ui/lib/style.css"; // 3 注册插件 Vue.use(MintUI); 1 2 3 4 5 6 1 2 3 4 5 6 MUI MUI MUI 也是移动端的 UI 库 使用:从 github 下载包,找到 dist 文件夹,只需要导入样式即可 // 只需要导入 MUI的样式 即可,根据MUI的例子,直接使用HTML结果即可 // 导入样式 import "./lib/mui/css/mui.min.css"; 1 2 3 1 2 3 ElementUI 这是 PC 端的 UI 组件库 安装:cnpm i -S element-ui 饿了么 - ElementUI { "presets": [ ["es2015", { "modules": false }], "stage-0" ], "plugins": [ ["component", [ { "libraryName": "mint-ui", "style": true }, { "libraryName": "element-ui", "styleLibraryName": "theme-default" } ]] ] } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 axios 以 Promise 为基础的 HTTP 客户端,适用于:浏览器和 node.js 封装 ajax,用来发送请求,异步获取数据 安装:cnpm i -S axios github axios 文档 // 在浏览器中使用,直接引入js文件使用下面的GET/POST请求方式即可 // 1 引入 axios.js // 2 直接调用axios提供的API发送请求 created: function () { axios.get(url) .then(function(resp) {}) } // 配合 webpack 使用方式如下: import Vue from 'vue' import axios from 'axios' // 将 axios 添加到 Vue.prototype 中 Vue.prototype.$axios=axios // 在组件中使用: methods: { getData() { this.$axios.get('url') .then(res=> {}) .catch(err=> {}) } } // API使用方式: axios.get(url[, config]) axios.post(url[, data[, config]]) axios(url[, config]) axios(config) 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 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 axios - Get 请求 const url="http://vue.studyit.io/api/getnewslist"; // url中带有query参数 axios .get("/user?id=89") .then(function(response) { console.log(response); }) .catch(function(error) { console.log(error); }); // url和参数分离,使用对象 axios.get("/user", { params: { id: 12345 } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 axios - Post 请求 不同环境中处理 POST 请求 ?> 默认情况下,axios 会将 JS 对象序列化为 JSON 对象。为了使用 application/x-www-form-urlencoded 格式发送请求,我们可以这样: // 使用 qs 包,处理将对象序列化为字符串 // npm i -S qs // var qs=require('qs') import qs from 'qs' qs.stringify({ 'bar': 123 })===> "bar=123" axios.post('/foo', qs.stringify({ 'bar': 123 })) // 或者: axios.post('/foo', 'bar=123&age=19') 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 const url="http://vue.studyit.io/api/postcomment/17"; axios.post(url, "content=点个赞不过份"); axios .post( "/user", qs.stringify({ firstName: "Fred", lastName: "Flintstone" }) ) .then(function(response) { console.log(response); }) .catch(function(error) { console.log(error); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 axios 全局配置 // 设置请求公共路径: axios.defaults.baseURL="http://vue.studyit.io"; 1 2 1 2 axios 拦截器 拦截器会拦截发送的每一个请求,请求发送之前执行 request 中的函数,请求发送完成之后执行 response 中的函数 // 请求拦截器 axios.interceptors.request.use( function(config) { // 所有请求之前都要执行的操作 return config; }, function(error) { // 错误处理 return Promise.reject(error); } ); // 响应拦截器 axios.interceptors.response.use( function(response) { // 所有请求完成后都要执行的操作 return response; }, function(error) { // 错误处理 return Promise.reject(error); } ); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 vue 自定义指令 作用:进行 DOM 操作 使用场景:对纯 DOM 元素进行底层操作,比如:文本框获得焦点 vue 自定义指令用法实例 两种指令:1. 全局指令 2. 局部指令 全局自定义指令 // 第一个参数:指令名称 // 第二个参数:配置对象,指定指令的钩子函数 Vue.directive('directiveName', { // bind中只能对元素自身进行DOM操作,而无法对父级元素操作 // 只调用一次 指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。 bind( el,binding, vnode ) { // 参数详解 // el:指令所绑定的元素,可以用来直接操作 DOM 。 // binding:一个对象,包含以下属性: // name:指令名,不包括 v- 前缀。 // value:指令的绑定值,等号后面的值 。 // oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。 // expression:字符串形式的指令表达式 等号后面的字符串 形式 // arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。 // modifiers:指令修饰符。例如:v-directive.foo.bar中,修饰符对象为 { foo: true, bar: true }。 // vnode:Vue 编译生成的虚拟节点。。 // oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。 }, // inserted这个钩子函数调用的时候,当前元素已经插入页面中了,也就是说可以获取到父级节点了 inserted ( el,binding, vnode ) {}, // DOM重新渲染前 update(el,binding, vnode,oldVnode) {}, // DOM重新渲染后 componentUpdated ( el,binding, vnode,oldVnode ) {}, // 只调用一次,指令与元素解绑时调用 unbind ( el ) { // 指令所在的元素在页面中消失,触发 } }) // 简写 如果你想在 bind 和 update 时触发相同行为,而不关心其它的钩子: Vue.directive('自定义指令名', function( el, binding ) {}) // 例: Vue.directive('color', function(el, binding) { el.style.color=binging.value }) // 使用 注意直接些会被i成data中的数据“red” 需要字符串则嵌套引号"'red'" 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 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 局部自定义指令 vue 剖析 Vue 原理&实现双向绑定 MVVM var vm=new Vue({ el: "#app", directives: { directiveName: {} } }); 1 2 3 4 5 6 1 2 3 4 5 6 组件 组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树 #### 全局组件 说明:全局组件在所有的 vue 实例中都可以使用 注意:先注册组件,再初始化根实例 // 1 注册全局组件 Vue.component('my-component', { // template 只能有一个根元素 template: '

A custom component!

', // 组件中的 `data` 必须是函数 并且函数的返回值必须是对象 data() { return { msg: '注意:组件的data必须是一个函数!!!' } } }) // 2 使用:以自定义元素的方式

//=====> 渲染结果

A custom component!

// 3 template属性的值可以是: - 1 模板字符串 - 2 模板id template: '#tpl' 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 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 extend:使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。 // 注册组件,传入一个扩展过的构造器 Vue.component( "my-component", Vue.extend({ /* ... */ }) ); // 注册组件,传入一个选项对象 (自动调用 Vue.extend) Vue.component("my-component", { /* ... */ }); var Home=Vue.extend({ template: "", data() {} }); Vue.component("home", Home); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 局部组件 说明:局部组件,是在某一个具体的 vue 实例中定义的,只能在这个 vue 实例中使用 var Child={ template: "

A custom component!

  • " }; new Vue({ // 注意:此处为 components components: { // 将只在当前vue实例中使用 // my-component 为组件名 值为配置对象 "my-component": { template: ``, data() { return {}; }, props: [] } } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 is 特性 在某些特定的标签中只能存在指定表恰 如 ul > li 如果要浏览器正常解析则需要使用 is 正常识别

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 组件通讯 父组件到子组件(父子组件传值) 方式:通过子组件props属性来传递数据 props是一个数组 注意:属性的值必须在组件中通过props属性显示指定,否则,不会生效 说明:传递过来的props属性的用法与data属性的用法相同

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 子组件到父组件 方式:父组件给子组件传递一个函数,由子组件调用这个函数 说明:借助 vue 中的自定义事件(v-on:cunstomFn=“fn”) 步骤: 1、在父组件中定义方法 parentFn 2、在子组件 组件引入标签 中绑定自定义事件 v-on:自定义事件名=“父组件中的方法”==> @pfn=“parentFn” 3、子组件中通过emit()触发自定义事件事件this. emit()触发自定义事件事件 this.emit()触发自定义事件事件this.emit(pfn,参数列表。。。) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 非父子组件通讯 ?> 在简单的场景下,可以使用一个空的 Vue 实例作为事件总线 $on():绑定自定义事件 var bus=new Vue(); // 在组件 B 绑定自定义事件 bus.$on("id-selected", function(id) { // ... }); // 触发组件 A 中的事件 bus.$emit("id-selected", 1); 1 2 3 4 5 6 7 1 2 3 4 5 6 7 示例:组件 A —> 组件 B 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 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 内容分发 通过 标签指定内容展示区域 我是额外的内容

1 2 3 4 5 6 7 1 2 3 4 5 6 7 // js代码 new vue({ el: "#app", components: { hello: { template: `

我是子组件中的内容

` } } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 2 3 4 5 6 7 8 9 10 11 12 13 14 获取组件(或元素) - refs 说明:vm.$refs 一个对象,持有已注册过 ref 的所有子组件(或 HTML 元素) 使用:在 HTML 元素 中,添加 ref 属性,然后在 JS 中通过 vm.$refs.属性来获取 注意:如果获取的是一个子组件,那么通过 ref 就能获取到子组件中的 data 和 methods

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 SPA -单页应用程序 SPA: Single Page Application ?> 单页 Web 应用(single page application,SPA),就是只有一个 Web 页面的应用, 是加载单个 HTML 页面,并在用户与应用程序交互时动态更新该页面的 Web 应用程序。 单页面应用程序: 只有第一次会加载页面, 以后的每次请求, 仅仅是获取必要的数据.然后, 由页面中 js 解析获取的数据, 展示在页面中 传统多页面应用程序: 对于传统的多页面应用程序来说, 每次请求服务器返回的都是一个完整的页面 优势 1 减少了请求体积,加快页面响应速度,降低了对服务器的压力 2 更好的用户体验,让用户在 web app 感受 native app 的流畅 实现思路和技术点 1 ajax 2 锚点的使用(window.location.hash #) 3 hashchange 事件 window.addEventListener(“hashchange”,function () {}) 4 监听锚点值变化的事件,根据不同的锚点值,请求相应的数据 5 原本用作页面内部进行跳转,定位并展示相应的内容 路由 路由即:浏览器中的哈希值(# hash)与展示视图内容(template)之间的对应规则 vue 中的路由是:hash 和 component 的对应关系 在 Web app 中,通过一个页面来展示和管理整个应用的功能。 SPA 往往是功能复杂的应用,为了有效管理所有视图内容,前端路由 应运而生! 简单来说,路由就是一套映射规则(一对一的对应规则),由开发人员制定规则。 当 URL 中的哈希值(# hash)发生改变后,路由会根据制定好的规则,展示对应的视图内容 基本使用 安装:cnpm i -S vue-router

首页 登录

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 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 重定向 // 将path 重定向到 redirect { path: '/', redirect: '/home' } 1 2 1 2 路由其他配置 路由导航高亮 说明:当前匹配的导航链接,会自动添加 router-link-exact-active router-link-active 类 配置:linkActiveClass 匹配路由模式 配置:mode new Router({ routers: [], mode: "hash", //默认hash | history 可以达到隐藏地址栏hash值 | abstract,如果发现没有浏览器的 API 则强制进入 linkActiveClass: "now" //当前匹配的导航链接将被自动添加now类 }); 1 2 3 4 5 1 2 3 4 5 路由参数 说明:我们经常需要把某种模式匹配到的所有路由,全都映射到同一个组件,此时,可以通过路由参数来处理 语法:/user/:id 使用:当匹配到一个路由时,参数值会被设置到 this.$route.params 其他:可以通过 $route.query 获取到 URL 中的查询参数 等 如果你需要在模版中使用路由参数 可以这样 {{$router.params.id}} 用户 Rose 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 嵌套路由 - 子路由 路由是可以嵌套的,即:路由中又包含子路由 规则:父组件中包含 router-view,在路由规则中使用 children 配置 // 父组件: const User=Vue.component("user", { template: `

User Center

个人资料 岗位

` }); // 子组件[简写] const UserProfile={ template: "

个人资料:张三

" }; const UserPosts={ template: "

岗位:FE

" }; // 路由 var router=new Router({ routers: [ { path: "/user", component: User, // 子路由配置: children: [ { // 当 /user/profile 匹配成功, // UserProfile 会被渲染在 User 的 中 path: "profile", component: UserProfile }, { // 当 /user/posts 匹配成功 // UserPosts 会被渲染在 User 的 中 path: "posts", component: UserPosts } ] } ] }); 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 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 前端模块化 ?> 为什么需要模块化 最开始的 js 就是为了实现客户端验证以及一些简单的效果 后来,js 得到重视,应用越来越广泛,前端开发的复杂度越来越高 旧版本的 js 中没有提供与模块(module)相关的内容 模块的概念 在 js 中,一个模块就是实现特定功能的文件(js 文件) 遵循模块的机制,想要什么功能就加载什么模块 模块化开发需要遵循规范 模块化解决的问题 命名冲突 文件依赖(加载文件) 模块的复用 统一规范和开发方式 JS 实现模块化的规范 AMD 浏览器端 requirejs CommonJS nodejs 加载模块:require() 导出模块:module.exports={} / exports={} ES6 中的 import / export CMD 浏览器端 玉伯(阿里前端大神) -> seajs UMD 通用模块化规范,可以兼容 AMD、CommonJS、浏览器中没有模块化规范 等这些语法 AMD 的使用 Asynchronous Module Definition:异步模块定义,浏览器端模块开发的规范 代表:require.js 特点:模块被异步加载,模块加载不影响后面语句的运行 1、定义模块 // 语法:define(name, dependencies?, factory); // name表示:当前模块的名称,是一个字符串 可有可无 // dependencies表示:当前模块的依赖项,是一个数组无论依赖一项还是多项 无则不写 // factory表示:当前模块要完成的一些功能,是一个函数 // 定义对象模块 define({}); // 定义方法模块 define(function() { return {}; }); // 定义带有依赖项的模块 define(["js/a"], function() {}); 1 2 3 4 5 6 7 8 9 10 11 12 13 1 2 3 4 5 6 7 8 9 10 11 12 13 2、加载模块 // - 注意:require 的第一个参数必须是数组 // 参数必须是数组 表示模块路径 以当前文件为基准,通过回调函数中的参数获取加载模块中的变量 参数与模块按照顺序一一对应 require(["a", "js/b"], function(a, b) { // 使用模块a 和 模块b 中的代码 }); 1 2 3 4 5 1 2 3 4 5 3、路径查找配置 requirejs 默认使用 baseUrl+paths 的路径解析方式 可以使用以下方式避开此设置: 以.js结尾 以 / 开始 包含协议:https:// 或 http:// // 配置示例 // 注意配置应当在使用之前 require.config({ baseUrl: "./js" // 配置基础路径为:当前目录下的js目录 }); require(["a"]); // 查找 基础路径下的 ./js/a.js // 简化加载模块路径 require.config({ baseUrl: "./js", // 配置一次即可,直接通过路径名称(template || jquery)加载模块 paths: { template: "assets/artTemplate/template-native", jquery: "assets/jquery/jquery.min" } }); // 加载jquery template模块 require(["jquery", "template"]); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 4、非模块化和依赖项支持 添加模块的依赖模块,保证加载顺序(deps) 将非模块化模块,转化为模块化(exports) // 示例 require.config({ baseUrl: "./js", paths: { // 配置路径 noModule: "assets/demo/noModule" }, // 配置不符合规范的模块项 shim: { // 模块名称 noModule: { deps: [], // 依赖项 exports: "sayHi" // 导出模块中存在的函数或变量 } } }); // 注意点 如果定义模块的时候,指定了模块名称,需要使用该名称来引用模块 // 定义 这个模块名称与paths中的名称相同 define("moduleA", function() {}); // 导入 require.config({ paths: { // 此处的模块名:moduleA moduleA: "assets/demo/moduleA" } }); 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 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 5、路径加载规则 路径配置的优先级: 通过 config 配置规则查找 通过 data-main 指定的路径查找 以引入 requirejs 的页面所在路径为准查找 1 2 3 4 5 6 1 2 3 4 5 6 Webpack webpack 文档 bundle [?b?ndl] 捆绑,收集,归拢,把…塞入 webpack 将带有依赖项的各个模块打包处理后,变成了独立的浏览器能够识别的文件 webpack 合并以及解析带有依赖项的模块 概述 webpack 是一个现代 JavaScript 应用程序的模块打包器(特点 module、 bundler) webpack 是一个模块化方案(预编译) webpack 获取具有依赖关系的模块,并生成表示这些模块的静态资源 四个核心概念:入口(entry)、输出(output)、加载器loader、插件(plugins) 对比 模块化方案: webpack 和 requirejs(通过编写代码的方式将前端的功能,划分成独立的模块) browserify 是与 webpack 相似的模块化打包工具 webpack 预编译 (在开发阶段通过 webpack 进行模块化处理, 最终项目上线, 就不在依赖于 webpack) requirejs 线上的编译( 代码运行是需要依赖于 requirejs 的 ) webpack 起源 webpack 解决了现存模块打包器的两个痛点: 1 Code Spliting - 代码分离 按需加载 2 静态资源的模块化处理方案 webpack 与模块 前端模块系统的演进 在 webpack 看来:所有的静态资源都是模块 webpack 模块能够识别以下等形式的模块之间的依赖: JS 的模块化规范: ES2015 import export CommonJS require() module.exports AMD define 和 require 非 JS 等静态资源: css/sass/less 文件中的 @import 图片连接,比如:样式 url(...) 或 HTML

字体 等 入门 Webpack,看这篇就够了 安装 webpack 全局安装:cnpm i -g webpack 目的:在任何目录中通过 CLI 使用 webpack 这个命令 项目安装:cnpm i -D webpack 目的:执行当前项目的构建 webpack 的基本使用 安装:npm i -D webpack webpack 的两种使用方式:1 命令行 2 配置文件(webpack.config.js) 命令行方式演示 - 案例:隔行变色 1 使用npm init -y 初始package.json,使用 npm 来管理项目中的包 2 新建 index.html 和 index.js,实现隔行变色功能 3 运行webpack src/js/index.js dist/bundle.js进行打包构建,语法是:webpack 入口文件 输出文件 4 注意:需要在页面中引入 输出文件 的路径(此步骤可通过配置 webpack 去掉) /* src/js/index.js */ // 1 导入 jQuery import $ from "jquery"; // 2 获取页面中的li元素 const $lis=$("#ulList").find("li"); // 3 隔行变色 // jQuery中的 filter() 方法用来过滤jquery对象 $lis.filter(":odd").css("background-color", "#def"); $lis.filter(":even").css("background-color", "skyblue"); //命令行运行 `webpack src/js/index.js dist/bundle.js 目录生成在命令行运行目录 /* 运行流程: 1、webpack 根据入口找到入口文件 2、分析js中的模块化语法 3、将所有关联文件 打包合并输出到出口 */ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 webpack-dev-server 配置 一、package.json 配置方式 安装:cnpm i -D webpack-dev-server 作用:配合 webpack,创建开发环境(启动服务器、监视文件变化、自动编译、刷新浏览器等),提高开发效率 注意:无法直接在终端中执行 webpack-dev-server,需要通过 package.json 的 scripts 实现 使用方式:cnpm run dev // 参数解释 注意参数是无序的 有值的参数空格隔开 // --open 自动打开浏览器 // --contentBase ./ 指定浏览器 默认打开的页面路径中的 index.html 文件 // --open 自动打开浏览器 // --port 8080 端口号 // --hot 热更新,只加载修改的文件(按需加载修改的内容),而非全部加载 "scripts": { "dev": "webpack-dev-server --open --contentBase ./ --port 8080 --hot" } 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 二、webpack.config.js 配置方式(推荐) var path=require('path') module.exports={ // 入口文件 entry: path.join(__dirname, 'src/js/index.js'), // 输出文件 output: { path: path.join(__dirname, 'dist'), // 输出文件的路径 filename: 'bundle.js' // 输出文件的名称 } } const webpack=require('webpack') devServer: { // 服务器的根目录 Tell the server where to serve content from // https://webpack.js.org/configuration/dev-server/#devserver-contentbase contentBase: path.join(__dirname, './'), // 自动打开浏览器 open: true, // 端口号 port: 8888, // --------------- 1 热更新 ----------------- hot: true }, plugins: [ // ---------------- 2 启用热更新插件 ---------------- new webpack.HotModuleReplacementPlugin() ] 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 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 html-webpack-plugin 插件 安装:cnpm i -D html-webpack-plugin 作用:根据模板,自动生成 html 页面 优势:页面存储在内存中,自动引入bundle.js、css等文件 /* webpack.config.js */ const htmlWebpackPlugin=require("html-webpack-plugin"); plugins: [ new htmlWebpackPlugin({ // 模板页面路径 template: path.join(__dirname, "./index.html"), // 在内存中生成页面路径,默认值为:index.html filename: "index.html" }) ]; 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 Loaders(加载器) webpack - Loaders webpack - 管理资源示例 webpack 只能处理 JavaScript 资源 webpack 通过 loaders 处理非 JavaScript 静态资源 1、 CSS 打包 安装:cnpm i -D style-loader css-loader 注意:use 中模块的顺序不能颠倒,加载顺序:从右向左加载 /* 在index.js 导入 css 文件*/ import "./css/app.css"; /* webpack.config.js 配置各种资源文件的loader加载器*/ module: { // 配置匹配规则 rules: [ // test 用来配置匹配文件规则(正则) // use 是一个数组,按照从后往前的顺序执行加载 { test: /\.css$/, use: ["style-loader", "css-loader"] } ]; } 1 2 3 4 5 6 7 8 9 10 11 12 1 2 3 4 5 6 7 8 9 10 11 12 2、 使用 webpack 打包 sass 文件 安装:cnpm i -D sass-loader node-sass 注意:sass-loader 依赖于 node-sass 模块 /* webpack.config.js */ // 参考:https://webpack.js.org/loaders/sass-loader/#examples // "style-loader" :creates style nodes from JS strings 创建style标签 // "css-loader" :translates CSS into CommonJS 将css转化为CommonJS代码 // "sass-loader" :compiles Sass to CSS 将Sass编译为css module: { rules: [ { test: /\.(scss|sass)$/, use: ["style-loader", "css-loader", "sass-loader"] } ]; } 1 2 3 4 5 6 7 8 9 10 11 12 13 1 2 3 4 5 6 7 8 9 10 11 12 13 3、 图片和字体打包 安装:cnpm i -D url-loader file-loader file-loader:加载并重命名文件(图片、字体 等) url-loader:将图片或字体转化为 base64 编码格式的字符串,嵌入到样式文件中 /* webpack.config.js */ module: { rules: [ // 打包 图片文件 { test: /\.(jpg|png|gif|jpeg)$/, use: "url-loader" }, // 打包 字体文件 { test: /\.(woff|woff2|eot|ttf|otf)$/, use: "file-loader" } ]; } 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 图片打包细节 limit 参数的作用:(单位为:字节(byte)) 当图片文件大小(字节)小于指定的 limit 时,图片被转化为base64编码格式 当图片文件大小(字节)大于等于指定的 limit 时,图片被重命名以 url 路径形式加载(此时,需要 file-loader 来加载图片) 图片文件重命名,保证相同文件不会被加载多次。例如:一张图片(a.jpg)拷贝一个副本(b.jpg),同时引入这两张图片,重命名后只会加载一次,因为这两张图片就是同一张 文件重命名以后,会通过 MD5 加密的方式,来计算这个文件的名称 /* webpack.config.js */ module: { rules: [ // {test: /\.(jpg|png|gif|jpeg)$/, use: 'url-loader?limit=100'}, { test: /\.(jpg|png|gif|jpeg)$/, use: [ { loader: "url-loader", options: { limit: 8192 } } ] } ]; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 字体文件打包说明 处理方式与图片相同,可以使用:file-loader或url-loader babel babel babel 全家桶 安装:cnpm i -D babel-core babel-loader 安装:cnpm i -D babel-preset-env 基本使用(两步) 第一步: /* webpack.config.js */ module: { rules: [ // exclude 排除,不需要编译的目录,提高编译速度 { test: /\.js$/, use: "babel-loader", exclude: /node_modules/ } ]; } 1 2 3 4 5 6 7 1 2 3 4 5 6 7 第二步:在项目根目录中新建.babelrc配置文件 /* 创建 .babelrc 文件*/ // 将来babel-loader运行的时候,会检查这个配置文件,并读取相关的语法和插件配置 { "presets": ["env"] } 1 2 3 4 5 1 2 3 4 5 babel 的说明 babel 的作用: 1 语法转换:将新的 ES 语法转化为浏览器能识别的语法(babel-preset-*) 2 polyfill 浏览器兼容:让低版本浏览器兼容最新版 ES 的 API babel-preset-* Babel 通过语法转换器,能够支持最新版本的 JavaScript 语法 babel-preset-* 用来指定我们书写的是什么版本的 JS 代码 作用:将新的 ES 语法转化为浏览器能识别的 ES5 代码 ES6 语法提案的批准流程 ES2015 也就是 ES6, 下一个版本是 ES7, 从 ES6 到 ES7 之间经历了 5 个阶段 babel-preset-es2015 转换 es6 的语法 babel-preset-stage-0 转换比 es6 更新的语法 Stage 0 - Strawman(展示阶段) Stage 1 - Proposal(征求意见阶段) Stage 2 - Draft(草案阶段) Stage 3 - Candidate(候选人阶段) Stage 4 - Finished(定案阶段) 1 2 3 4 5 总结 babel-core babel 核心包 babel-loader 用来解析 js 文件 babel-preset-* 新 ES 语法的解析和转换 transform-runtime / babel-polyfill 兼容旧浏览器,到达支持新 API 目的 // 判断浏览器是否兼容 padStart 这个 API if (!String.prototype.padStart) { // 如果不兼容, 就自己模拟 padStart的功能实现一份 String.prototype.padStart=function padStart(targetLength, padString) {}; } 1 2 3 4 5 1 2 3 4 5 Webpack 发布项目 webpack 打包的各种坑 webpack 命令能够生成dist目录到磁盘中,最终,把打包后的代码,部署服务器中去 webpack-dev-server 仅是在内存中生成的文件,并没有写到磁盘中,所以,只能在开发期间使用 创建项目发布配置文件 开发期间配置文件:webpack.config.js 项目发布配置文件:webpack.prod.js (文件名称非固定 production 生产环境) 命令:webpack --config webpack.prod.js 指定配置文件名称运行 webpack 参数:–display-error-details 用于显示 webpack 打包的错误信息 /* package.json */ "scripts": { "build": "webpack --config webpack.prod.js" } 1 2 3 4 5 1 2 3 4 5 在项目根目录中创建 webpack.prod.js 文件 在 package.json 中, 配置一个 scripts 在 终端中 通过 npm run build 对项目进行打包 打包处理过程 删除掉 devServer 相关的配置项 将图片和字体文件输出到指定的文件夹中 自动删除 dist 目录 分离第三方包(将使用的 vue 等第三方包抽离到 vender.js 中) 压缩混淆 JS 以及 指定生成环境 抽取和压缩 CSS 文件 压缩 HTML 页面 配合 vue 的异步组件,实现按需加载功能 处理图片路径 注意:如果 limit 小于比图片大,那么图片将被转化为 base64 编码格式 name 参数介绍 /* webpack.prod.js */ // 处理URL路径的loader { test: /\.(jpg|png|gif|bmp|jpeg)$/, use: { loader: 'url-loader', options: { limit: 8192, name: 'images/[hash:7].[ext]' // 作用:将图片输出到images文件夹中,文件名采用7位的哈希值(MD5),并且保持原来的图片文件扩展名 // name:指定文件输出路径和输出文件命令规则 // [hash:7]:表示使用7位哈希值代表文件名称 // [ext]:表示保持文件原有后缀名 // name: 'imgs/img-[hash:7].[ext]' } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 自动删除 dist 目录 安装:cnpm i -D clean-webpack-plugin 作用: 每次打包之前, 删除上一次打包的 dist 目录 /* webpack.prod.js */ const cleanWebpackPlugin=require("clean-webpack-plugin"); plugins: [ // 创建一个删除文件夹的插件,删除dist目录 new cleanWebpackPlugin(["./dist"]) ]; 1 2 3 4 5 6 7 1 2 3 4 5 6 7 分离第三方包 目的:将公共的第三方包,抽离为一个单独的包文件,这样防止重复打包! 例如:main.js、router、vuex中都引入了 vue,不分离的话,vue 会被打包 3 次 抽离后, vue 文件只会被打包一次, 用到的地方仅仅是引用 /* webpack.prod.js */ // 1 入口 -- 打包文件的入口 entry: { // 项目代码入口 app: path.join(__dirname, './src/js/main.js'), // 第三方包入口 vendor: ['vue', 'vue-router', 'axios'] }, output: { // 2 修改输出文件路径和命名规则 filename: 'js/[name].[chunkhash].js', }, plugins: [ // 3 抽离第三方包 new webpack.optimize.CommonsChunkPlugin({ // 将 entry 中指定的 ['vue', 'vue-router', 'axios'] 打包到名为 vendor 的js文件中 // 第三方包入口名称,对应 entry 中的 vendor 属性 name: 'vendor', }), ] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 压缩混淆 JS 注意:uglifyjs 无法压缩 ES6 的代码 plugins: [ // 优化代码 // https://github.com/webpack-contrib/uglifyjs-webpack-plugin/tree/v0.4.6 new webpack.optimize.UglifyJsPlugin({ // 压缩 compress: { // 移除警告 warnings: false } }), // 指定环境为生产环境:vue会根据这一项启用压缩后的vue文件 new webpack.DefinePlugin({ "process.env": { NODE_ENV: JSON.stringify("production") } }) ]; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 抽取和压缩 CSS 文件 安装:抽离 cnpm i -D extract-text-webpack-plugin 安装:压缩 cnpm i -D optimize-css-assets-webpack-plugin webpack 抽离 CSS 文档 压缩抽离后的 CSS 压缩和抽离 CSS 报错的说明: Error processing file: css/style.css postcss-svgo: Error in parsing SVG: Unquoted attribute value 1 2 1 2 原因:压缩和抽离 CSS 的插件中只允许 SVG 使用双引号 /* webpack.prod.js */ // 分离 css 到独立的文件中 const ExtractTextPlugin=require("extract-text-webpack-plugin"); // 压缩 css 资源文件 const OptimizeCssAssetsPlugin=require('optimize-css-assets-webpack-plugin') // bug描述: 生成后面的css文件中图片路径错误,打开页面找不到图片 // 解决:google搜索 webpack css loader 样式图片路径 output: { // https://doc.webpack-china.org/configuration/output/#output-publicpath // 设置公共路径 publicPath: '/', }, module: { rules: [ { test: /\.css$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: "css-loader" }) }, { test: /\.scss$/, use: ExtractTextPlugin.extract({ fallback: "style-loader", use: ['css-loader', 'sass-loader'] }) }, ] }, plugins: [ // 通过插件抽离 css (参数) new ExtractTextPlugin("css/style.css"), // 抽离css 的辅助压缩插件 new OptimizeCssAssetsPlugin() ] 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 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 压缩 HTML 页面 详细的配置可以参考html-minifier new htmlWebpackPlugin({ // 模板页面 template: path.join(__dirname, './index.html'), // 压缩HTML minify: { // 移除空白 collapseWhitespace: true, // 移除注释 removeComments: true, // 移除属性中的双引号 removeAttributeQuotes: true } }), 1 2 3 4 5 6 7 8 9 10 11 12 13 14 1 2 3 4 5 6 7 8 9 10 11 12 13 14 vue 配合 webpack 实现路由按需加载 Vue 路由懒加载 Vue 异步组件 Vue 组件懒加载浅析 步骤 1 修改组件的引用方式 // 方式一: require.ensure() const NewsList=r=> require.ensure( [], ()=> r(require("../components/news/newslist.vue")), "news" ); // 方式二: import() -- 推荐 // 注意:/* webpackChunkName: "newsinfo" */ 是一个特殊的语法,表示生成js文件的名称 const NewsInfo=()=> import(/* webpackChunkName: "newsinfo" */ "../components/news/newsinfo.vue"); 1 2 3 4 5 6 7 8 9 10 11 1 2 3 4 5 6 7 8 9 10 11 2 修改 webpack 配置文件的 output output: { // ------添加 chunkFilename, 指定输出js文件的名称------ chunkFilename: 'js/[name].[chunkhash].js', }