早的前端,沒有模塊加載規(guī)范,只能在HTML中通過<script>來引入js文件,同時(shí)無法區(qū)分函數(shù)來源于哪個(gè)js文件,而且要用過多全局變量。而隨著前端工程復(fù)雜度的提升,使用這種方式已經(jīng)無法滿足日益增長(zhǎng)的開發(fā)需求,js的模塊化應(yīng)運(yùn)而生。
CommonJS 是屬于 Node.js 的模塊化方案,最早是叫 ServerJS,隨著 Node.js 的火爆發(fā)展而成名的。Module1.0 規(guī)范在 Node.js 上實(shí)踐的很好。
而 JavaScript 在當(dāng)時(shí)(ES6 Modules 規(guī)范還未誕生)是沒有模塊化方案的,所以又更名從 CommonJS,想要統(tǒng)一服務(wù)端與客戶端的模塊加載方案。
但是,require 函數(shù)是同步的,在瀏覽器端由于網(wǎng)絡(luò)的瓶頸而不適用。于是,AMD 和 CMD 的規(guī)范相繼涌現(xiàn),和 CommonJS 一起服務(wù)于 JavaScript 模塊化。
而正是規(guī)則的不統(tǒng)一,這也是目前兼容方案 UMD 會(huì)出現(xiàn)的原因。
不過AMD 和 CMD 的瀏覽器端模塊化,有很明顯的問題:
現(xiàn)如今,當(dāng)打包這一環(huán)節(jié)被引入了前端工程化,CommonJS 以與服務(wù)端可以類庫(kù)共用和 NPM(Node Package Manager) 這個(gè)后臺(tái)的優(yōu)勢(shì),成為了 es5 JavaScript 模塊化的首選
CommonJS 是一個(gè)旨在構(gòu)建涵蓋web服務(wù)器端、桌面應(yīng)用、命令行app和瀏覽器JS的JS生態(tài)系統(tǒng)。
CommonJS 的標(biāo)準(zhǔn)符合 module1.1 規(guī)范,暴露給使用者的有三個(gè)全局變量:
本文講的是如何模擬一個(gè) $require 函數(shù),先來捋一捋 require 函數(shù)的主邏輯
require
require是一個(gè)基于AMD規(guī)范實(shí)現(xiàn)的函數(shù),它區(qū)別于傳統(tǒng)的CommonJS require規(guī)范。因?yàn)樗軌虍惒降丶虞d動(dòng)態(tài)的依賴,所以,我們對(duì)基于require的回調(diào)方式有了更多的需求。
API Specification
局部require vs 全局require。局部require可以被解析成一個(gè)符合AMD工廠函數(shù)規(guī)范的require函數(shù)。
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也支持其他標(biāo)準(zhǔn)實(shí)現(xiàn)的API。
全局require函數(shù)作用于全局,和define()類似。 全局require和局部require有著相同的行為,包含以下特征:
實(shí)際中,我們經(jīng)常會(huì)遇到一些阻塞模塊加載的依賴,如果交互次數(shù)很多,需要大量的模塊加載,應(yīng)該采用全局依賴的形式去加載頂層模塊。
require(String)
基于以下規(guī)范CommonJS Modules 1.1.1 require.根據(jù)參數(shù),同步地返回模塊ID所代表的模塊。
如果模塊沒有加載或者執(zhí)行完成,就會(huì)拋出錯(cuò)誤。特別需要指出的是,在同步加載的回調(diào)中,如果模塊沒有加載完成,禁止動(dòng)態(tài)的獲取模塊,否則,就會(huì)拋出異常。
使用define()定義模塊時(shí),依賴項(xiàng)中可以找到一個(gè)AMD模塊:
define(function (require) { var a=require('a'); });
工廠方法可以被解析成require('')的調(diào)用形式(例如,使用語(yǔ)法解析器或者使用Function.prototype.toString()或者正則表達(dá)式)去找到依賴,加載并且執(zhí)行依賴,然后執(zhí)行工廠方法內(nèi)部代碼,通過這樣的方式,就可以獲取到模塊。
require(Array, Function)
參數(shù)Array是一個(gè)由模塊ID組成的數(shù)組。當(dāng)模塊ID所以代表的模塊加載完成且可用時(shí),回調(diào)函數(shù)Function才開始執(zhí)行,并且只被執(zhí)行一次。各個(gè)模塊按照依賴數(shù)組中的位置順序以參數(shù)的形式傳入到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'**這種字符形式轉(zhuǎn)化成URL路徑。
require.toUrl()方法采用通用的模塊ID路徑轉(zhuǎn)化規(guī)則,將模塊ID字符解析成URL路徑.但它不支持以".js"這種擴(kuò)展形式。所以,我們必須將'.extension'添加到了解析路徑里。
例如:
//cart.js contents: define (function(require) { // 模塊ID名 './templates/a' // 擴(kuò)展名 '.html' // 模板路徑大致以這樣的形式結(jié)尾 'modules/cart/templates/a.html' var templatePath=require.toUrl('./templates/a.html'); });
以上是require的具體用法展示。
根據(jù)這個(gè)邏輯,我們先寫一個(gè) main 函數(shù),以及定義一些需要的接口
至此已經(jīng)有一個(gè)不可執(zhí)行的 require 函數(shù)了,邏輯中會(huì)依次判斷是否有緩存,是否核心模塊,加載相應(yīng)文件以及加載執(zhí)行模塊并緩存。
可以看到?jīng)]有具體實(shí)現(xiàn),只有接口被定義出來,這種編碼方式,同樣可以借鑒到在其他的開發(fā)需求中:
這一步驟,主要是對(duì)上述過程需要的接口進(jìn)行實(shí)現(xiàn)。
思想上是一個(gè)分而治之的思想,實(shí)現(xiàn)一個(gè)很復(fù)雜的東西比較困難,但是實(shí)現(xiàn)具體的功能要求,且在一定輸入輸出的限制下,每個(gè)人都能輕易的寫出符合需求的算法,并進(jìn)行調(diào)優(yōu)。
數(shù)據(jù)結(jié)構(gòu)與工具函數(shù)類
棧 -- 存儲(chǔ)當(dāng)前模塊的所在目錄
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 }
這個(gè)棧的作用是存放當(dāng)前模塊的所在目錄,用于模塊內(nèi) require 函數(shù)傳入相對(duì)路徑時(shí),解析成絕對(duì)路徑
獲取文件所在目錄
function getParent(pathname) { return path.parse(pathname).dir }
檢測(cè)模塊類型與定位包的位置
這個(gè)函數(shù)要做下面的事情
以下部分,是 getModuleLocation 自身需要實(shí)現(xiàn)的接口,往往是開發(fā)過程中自行提煉的,其他模塊不通用
定位引用的真實(shí)路徑
這個(gè)函數(shù)是 CommonJS 模塊化的核心體現(xiàn),理解這個(gè)函數(shù),對(duì) module 和 exports 的實(shí)際使用也會(huì)有幫助
以為到這里就完成了嗎,不,至少還有一些問題需要考慮:
鏈接文章
https://github.com/kaola-fed/blog/issues/16
https://github.com/amdjs/amdjs-api/wiki/require-(中文版)
文講解怎樣用 Node.js 高效地從 Web 爬取數(shù)據(jù)。
前提條件
本文主要針對(duì)具有一定 JavaScript 經(jīng)驗(yàn)的程序員。如果你對(duì) Web 抓取有深刻的了解,但對(duì) JavaScript 并不熟悉,那么本文仍然能夠?qū)δ阌兴鶐椭?/p>
你將學(xué)到
通過本文你將學(xué)到:
了解 Node.js
Javascript 是一種簡(jiǎn)單的現(xiàn)代編程語(yǔ)言,最初是為了向?yàn)g覽器中的網(wǎng)頁(yè)添加動(dòng)態(tài)效果。當(dāng)加載網(wǎng)站后,Javascript 代碼由瀏覽器的 Javascript 引擎運(yùn)行。為了使 Javascript 與你的瀏覽器進(jìn)行交互,瀏覽器還提供了運(yùn)行時(shí)環(huán)境(document、window等)。
這意味著 Javascript 不能直接與計(jì)算機(jī)資源交互或?qū)ζ溥M(jìn)行操作。例如在 Web 服務(wù)器中,服務(wù)器必須能夠與文件系統(tǒng)進(jìn)行交互,這樣才能讀寫文件。
Node.js 使 Javascript 不僅能夠運(yùn)行在客戶端,而且還可以運(yùn)行在服務(wù)器端。為了做到這一點(diǎn),其創(chuàng)始人 Ryan Dahl 選擇了Google Chrome 瀏覽器的 v8 Javascript Engine,并將其嵌入到用 C++ 開發(fā)的 Node 程序中。所以 Node.js 是一個(gè)運(yùn)行時(shí)環(huán)境,它允許 Javascript 代碼也能在服務(wù)器上運(yùn)行。
與其他語(yǔ)言(例如 C 或 C++)通過多個(gè)線程來處理并發(fā)性相反,Node.js 利用單個(gè)主線程并并在事件循環(huán)的幫助下以非阻塞方式執(zhí)行任務(wù)。
要?jiǎng)?chuàng)建一個(gè)簡(jiǎn)單的 Web 服務(wù)器非常簡(jiǎn)單,如下所示:
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,可以試著運(yùn)行上面的代碼。Node.js 非常適合 I/O 密集型程序。
HTTP 客戶端:訪問 Web
HTTP 客戶端是能夠?qū)⒄?qǐng)求發(fā)送到服務(wù)器,然后接收服務(wù)器響應(yīng)的工具。下面提到的所有工具底的層都是用 HTTP 客戶端來訪問你要抓取的網(wǎng)站。
Request
Request 是 Javascript 生態(tài)中使用最廣泛的 HTTP 客戶端之一,但是 Request 庫(kù)的作者已正式聲明棄用了。不過這并不意味著它不可用了,相當(dāng)多的庫(kù)仍在使用它,并且非常好用。用 Request 發(fā)出 HTTP 請(qǐng)求是非常簡(jiǎn)單的:
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 庫(kù),安裝它非常簡(jiǎn)單。你還可以在 https://github.com/request/request/issues/3142 找到棄用通知及其含義。
Axios
Axios 是基于 promise 的 HTTP 客戶端,可在瀏覽器和 Node.js 中運(yùn)行。如果你用 Typescript,那么 axios 會(huì)為你覆蓋內(nèi)置類型。通過 Axios 發(fā)起 HTTP 請(qǐng)求非常簡(jiǎn)單,默認(rèn)情況下它帶有 Promise 支持,而不是在 Request 中去使用回調(diào):
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 語(yǔ)法糖,那么你也可以用,但是由于頂級(jí) await 仍處于 stage 3 ,所以我們只好先用異步函數(shù)來代替:
async function getForum() {
try {
const response = await axios.get(
'https://www.reddit.com/r/programming.json'
)
console.log(response)
} catch (error) {
console.error(error)
}
}
你所要做的就是調(diào)用 getForum!可以在 https://github.com/axios/axios 上找到Axios庫(kù)。
Superagent
與 Axios 一樣,Superagent 是另一個(gè)強(qiáng)大的 HTTP 客戶端,它支持 Promise 和 async/await 語(yǔ)法糖。它具有像 Axios 這樣相當(dāng)簡(jiǎn)單的 API,但是 Superagent 由于存在更多的依賴關(guān)系并且不那么流行。
用 promise、async/await 或回調(diào)向 Superagent 發(fā)出HTTP請(qǐng)求看起來像這樣:
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。
正則表達(dá)式:艱難的路
在沒有任何依賴性的情況下,最簡(jiǎn)單的進(jìn)行網(wǎng)絡(luò)抓取的方法是,使用 HTTP 客戶端查詢網(wǎng)頁(yè)時(shí),在收到的 HTML 字符串上使用一堆正則表達(dá)式。正則表達(dá)式不那么靈活,而且很多專業(yè)人士和業(yè)余愛好者都難以編寫正確的正則表達(dá)式。
讓我們?cè)囈辉嚕僭O(shè)其中有一個(gè)帶有用戶名的標(biāo)簽,我們需要該用戶名,這類似于你依賴正則表達(dá)式時(shí)必須執(zhí)行的操作
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() 通常返回一個(gè)數(shù)組,該數(shù)組包含與正則表達(dá)式匹配的所有內(nèi)容。第二個(gè)元素(在索引1中)將找到我們想要的 <label> 標(biāo)記的 textContent 或 innerHTML。但是結(jié)果中包含一些不需要的文本( “Username: “),必須將其刪除。
如你所見,對(duì)于一個(gè)非常簡(jiǎn)單的用例,步驟和要做的工作都很多。這就是為什么應(yīng)該依賴 HTML 解析器的原因,我們將在后面討論。
Cheerio:用于遍歷 DOM 的核心 JQuery
Cheerio 是一個(gè)高效輕便的庫(kù),它使你可以在服務(wù)器端使用 JQuery 的豐富而強(qiáng)大的 API。如果你以前用過 JQuery,那么將會(huì)對(duì) Cheerio 感到很熟悉,它消除了 DOM 所有不一致和與瀏覽器相關(guān)的功能,并公開了一種有效的 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 用起來非常相似。
但是,盡管它的工作方式不同于網(wǎng)絡(luò)瀏覽器,也就這意味著它不能:
因此,如果你嘗試爬取的網(wǎng)站或 Web 應(yīng)用是嚴(yán)重依賴 Javascript 的(例如“單頁(yè)應(yīng)用”),那么 Cheerio 并不是最佳選擇,你可能不得不依賴稍后討論的其他選項(xiàng)。
為了展示 Cheerio 的強(qiáng)大功能,我們將嘗試在 Reddit 中抓取 r/programming 論壇,嘗試獲取帖子名稱列表。
首先,通過運(yùn)行以下命令來安裝 Cheerio 和 axios:npm install cheerio axios。
然后創(chuàng)建一個(gè)名為 crawler.js 的新文件,并復(fù)制粘貼以下代碼:
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() 是一個(gè)異步函數(shù),將對(duì)舊的 reddit 的 r/programming 論壇進(jìn)行爬取。首先,用帶有 axios HTTP 客戶端庫(kù)的簡(jiǎn)單 HTTP GET 請(qǐng)求獲取網(wǎng)站的 HTML,然后用 cheerio.load() 函數(shù)將 html 數(shù)據(jù)輸入到 Cheerio 中。
然后在瀏覽器的 Dev Tools 幫助下,可以獲得可以定位所有列表項(xiàng)的選擇器。如果你使用過 JQuery,則必須非常熟悉 $('div> p.title> a')。這將得到所有帖子,因?yàn)槟阒幌M麊为?dú)獲取每個(gè)帖子的標(biāo)題,所以必須遍歷每個(gè)帖子,這些操作是在 each() 函數(shù)的幫助下完成的。
要從每個(gè)標(biāo)題中提取文本,必須在 Cheerio 的幫助下獲取 DOM元素( el 指代當(dāng)前元素)。然后在每個(gè)元素上調(diào)用 text() 能夠?yàn)槟闾峁┪谋尽?/p>
現(xiàn)在,打開終端并運(yùn)行 node crawler.js,然后你將看到大約存有標(biāo)題的數(shù)組,它會(huì)很長(zhǎng)。盡管這是一個(gè)非常簡(jiǎn)單的用例,但它展示了 Cheerio 提供的 API 的簡(jiǎn)單性質(zhì)。
如果你的用例需要執(zhí)行 Javascript 并加載外部源,那么以下幾個(gè)選項(xiàng)將很有幫助。
JSDOM:Node 的 DOM
JSDOM 是在 Node.js 中使用的文檔對(duì)象模型的純 Javascript 實(shí)現(xiàn),如前所述,DOM 對(duì) Node 不可用,但是 JSDOM 是最接近的。它或多或少地模仿了瀏覽器。
由于創(chuàng)建了 DOM,所以可以通過編程與要爬取的 Web 應(yīng)用或網(wǎng)站進(jìn)行交互,也可以模擬單擊按鈕。如果你熟悉 DOM 操作,那么使用 JSDOM 將會(huì)非常簡(jiǎn)單。
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 創(chuàng)建一個(gè) DOM,然后你可以用和操縱瀏覽器 DOM 相同的方法和屬性來操縱該 DOM。
為了演示如何用 JSDOM 與網(wǎng)站進(jìn)行交互,我們將獲得 Reddit r/programming 論壇的第一篇帖子并對(duì)其進(jìn)行投票,然后驗(yàn)證該帖子是否已被投票。
首先運(yùn)行以下命令來安裝 jsdom 和 axios:npm install jsdom axios
然后創(chuàng)建名為 crawler.js的文件,并復(fù)制粘貼以下代碼:
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() 是一個(gè)異步函數(shù),它將在 r/programming 中獲取第一個(gè)帖子,然后對(duì)其進(jìn)行投票。axios 發(fā)送 HTTP GET 請(qǐng)求獲取指定 URL 的HTML。然后通過先前獲取的 HTML 來創(chuàng)建新的 DOM。JSDOM 構(gòu)造函數(shù)把HTML 作為第一個(gè)參數(shù),把 option 作為第二個(gè)參數(shù),已添加的 2 個(gè) option 項(xiàng)執(zhí)行以下功能:
創(chuàng)建 DOM 后,用相同的 DOM 方法得到第一篇文章的 upvote 按鈕,然后單擊。要驗(yàn)證是否確實(shí)單擊了它,可以檢查 classList 中是否有一個(gè)名為 upmod 的類。如果存在于 classList 中,則返回一條消息。
打開終端并運(yùn)行 node crawler.js,然后會(huì)看到一個(gè)整潔的字符串,該字符串將表明帖子是否被贊過。盡管這個(gè)例子很簡(jiǎn)單,但你可以在這個(gè)基礎(chǔ)上構(gòu)建功能強(qiáng)大的東西,例如,一個(gè)圍繞特定用戶的帖子進(jìn)行投票的機(jī)器人。
如果你不喜歡缺乏表達(dá)能力的 JSDOM ,并且實(shí)踐中要依賴于許多此類操作,或者需要重新創(chuàng)建許多不同的 DOM,那么下面將是更好的選擇。
Puppeteer:無頭瀏覽器
顧名思義,Puppeteer 允許你以編程方式操縱瀏覽器,就像操縱木偶一樣。它通過為開發(fā)人員提供高級(jí) API 來默認(rèn)控制無頭版本的 Chrome。
Puppeteer 比上述工具更有用,因?yàn)樗梢允鼓阆裾嬲娜嗽谂c瀏覽器進(jìn)行交互一樣對(duì)網(wǎng)絡(luò)進(jìn)行爬取。這就具備了一些以前沒有的可能性:
它還可以在 Web 爬取之外的其他任務(wù)中發(fā)揮重要作用,例如 UI 測(cè)試、輔助性能優(yōu)化等。
通常你會(huì)想要截取網(wǎng)站的屏幕截圖,也許是為了了解競(jìng)爭(zhēng)對(duì)手的產(chǎn)品目錄,可以用 puppeteer 來做到。首先運(yùn)行以下命令安裝 puppeteer,:npm install puppeteer
這將下載 Chromium 的 bundle 版本,根據(jù)操作系統(tǒng)的不同,該版本大約 180 MB 至 300 MB。如果你要禁用此功能。
讓我們嘗試在 Reddit 中獲取 r/programming 論壇的屏幕截圖和 PDF,創(chuàng)建一個(gè)名為 crawler.js的新文件,然后復(fù)制粘貼以下代碼:
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() 是一個(gè)異步函數(shù),它將獲 URL 變量中 url 對(duì)應(yīng)的屏幕截圖和 pdf。首先,通過 puppeteer.launch() 創(chuàng)建瀏覽器實(shí)例,然后創(chuàng)建一個(gè)新頁(yè)面。可以將該頁(yè)面視為常規(guī)瀏覽器中的選項(xiàng)卡。然后通過以 URL 為參數(shù)調(diào)用 page.goto() ,將先前創(chuàng)建的頁(yè)面定向到指定的 URL。最終,瀏覽器實(shí)例與頁(yè)面一起被銷毀。
完成操作并完成頁(yè)面加載后,將分別使用 page.screenshot() 和 page.pdf() 獲取屏幕截圖和 pdf。你也可以偵聽 javascript load 事件,然后執(zhí)行這些操作,在生產(chǎn)環(huán)境級(jí)別下強(qiáng)烈建議這樣做。
在終端上運(yùn)行 node crawler.js ,幾秒鐘后,你會(huì)注意到已經(jīng)創(chuàng)建了兩個(gè)文件,分別名為 screenshot.jpg 和 page.pdf。
Nightmare:Puppeteer 的替代者
Nightmare 是類似 Puppeteer 的高級(jí)瀏覽器自動(dòng)化庫(kù),該庫(kù)使用 Electron,但據(jù)說速度是其前身 PhantomJS 的兩倍。
如果你在某種程度上不喜歡 Puppeteer 或?qū)?Chromium 捆綁包的大小感到沮喪,那么 nightmare 是一個(gè)理想的選擇。首先,運(yùn)行以下命令安裝 nightmare 庫(kù):npm install nightmare
然后,一旦下載了 nightmare,我們將用它通過 Google 搜索引擎找到 ScrapingBee 的網(wǎng)站。創(chuàng)建一個(gè)名為crawler.js的文件,然后將以下代碼復(fù)制粘貼到其中:
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)
})
首先創(chuàng)建一個(gè) Nighmare 實(shí)例,然后通過調(diào)用 goto() 將該實(shí)例定向到 Google 搜索引擎,加載后,使用其選擇器獲取搜索框,然后使用搜索框的值(輸入標(biāo)簽)更改為“ScrapingBee”。完成后,通過單擊 “Google搜索” 按鈕提交搜索表單。然后告訴 Nightmare 等到第一個(gè)鏈接加載完畢,一旦完成,它將使用 DOM 方法來獲取包含該鏈接的定位標(biāo)記的 href 屬性的值。
最后,完成所有操作后,鏈接將打印到控制臺(tái)。
總結(jié)
紹 官網(wǎng) github Vue.js 是一套構(gòu)建用戶界面(UI)的漸進(jìn)式 JavaScript 框架 庫(kù)和框架的區(qū)別 庫(kù): ?> 庫(kù),本質(zhì)上是一些函數(shù)的集合。每次調(diào)用函數(shù),實(shí)現(xiàn)一個(gè)特定的功能,接著把控制權(quán)交給使用者 jQuery:DOM 操作,即:封裝 DOM 操作,簡(jiǎn)化 DOM 操作 框架: ?> 框架,是一套完整的解決方案,使用框架的時(shí)候,需要把你的代碼放到框架合適的地方,框架會(huì)在合適的時(shí)機(jī)調(diào)用你的代碼 框架規(guī)定了自己的編程方式,是一套完整的解決方案 使用框架的時(shí)候,由框架控制一切,我們只需要按照規(guī)則寫代碼 主要區(qū)別: !> 核心點(diǎn):誰起到主導(dǎo)作用(控制反轉(zhuǎn))框架的侵入性很高(從頭到尾) MVVM 和 MVC 的介紹: MVVM,一種更好的 UI 模式解決方案 MVVM===> M / V / VM M:model 數(shù)據(jù)模型 V:view 視圖 VM:ViewModel 視圖模型 MVC 模型-視圖-控制器 M: Model 數(shù)據(jù)模型(專門用來操作數(shù)據(jù),數(shù)據(jù)的 CRUD) V:View 視圖(對(duì)于前端來說,就是頁(yè)面) C:Controller 控制器(是視圖和數(shù)據(jù)模型溝通的橋梁,用于處理業(yè)務(wù)邏輯) MVVM 和 MVC 的優(yōu)勢(shì)對(duì)比 MVC 模式,將應(yīng)用程序劃分為三大部分,實(shí)現(xiàn)了職責(zé)分離 在前端中經(jīng)常要通過 JS 代碼 來進(jìn)行一些邏輯操作,最終還要把這些邏輯操作的結(jié)果現(xiàn)在頁(yè)面中。也就是需要頻繁的操作 DOM MVVM 通過數(shù)據(jù)雙向綁定讓數(shù)據(jù)自動(dòng)地雙向同步 V(修改數(shù)據(jù)) -> M M(修改數(shù)據(jù)) -> V 數(shù)據(jù)是核心 Vue 這種 MVVM 模式的框架,不推薦開發(fā)人員手動(dòng)操作 DOM Vue 中的 MVVM 雖然沒有完全遵循 MVVM 模型,Vue 的設(shè)計(jì)無疑受到了它的啟發(fā)。因此在文檔中經(jīng)常會(huì)使用 vm (ViewModel 的簡(jiǎn)稱) 這個(gè)變量名表示 Vue 實(shí)例 !> 學(xué)習(xí) Vue 要轉(zhuǎn)化思想 不要在想著怎么操作 DOM,而是想著如何操作數(shù)據(jù)!!! 起步 - 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 實(shí)例 !> 注意 1:先在 data 中聲明數(shù)據(jù),再使用數(shù)據(jù) !> 注意 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 數(shù)據(jù)綁定 最常用的方式:Mustache(插值語(yǔ)法),也就是 {{}} 語(yǔ)法 解釋:{{}}從數(shù)據(jù)對(duì)象data中獲取數(shù)據(jù) 說明:數(shù)據(jù)對(duì)象的屬性值發(fā)生了改變,插值處的內(nèi)容都會(huì)更新 說明:{{}}中只能出現(xiàn) JavaScript 表達(dá)式 而不能解析 js 語(yǔ)句 注意:Mustache 語(yǔ)法不能作用在 HTML 元素的屬性上
{{ 1 + 2 }}
{{ isOk ? 'yes': 'no' }}
1 2 3 4 5 6 1 2 3 4 5 6 數(shù)據(jù)雙向綁定 雙向數(shù)據(jù)綁定:將 DOM 與 Vue 實(shí)例的 data 數(shù)據(jù)綁定到一起,彼此之間相互影響 數(shù)據(jù)的改變會(huì)引起 DOM 的改變 DOM 的改變也會(huì)引起數(shù)據(jù)的變化 原理:Object.defineProperty中的get和set方法 getter和setter:訪問器 作用:指定讀取或設(shè)置對(duì)象屬性值的時(shí)候,執(zhí)行的操作 深入響應(yīng)式原理 /* defineProperty語(yǔ)法 介紹 */ var obj={}; Object.defineProperty(obj, "msg", { // 設(shè)置 obj.msg="1" 時(shí)set方法會(huì)被系統(tǒng)調(diào)用 參數(shù)分別是設(shè)置后和設(shè)置前的值 set: function(newVal, oldVal) {}, // 讀取 obj.msg 時(shí)get方法會(huì)被系統(tǒng)調(diào)用 get: function(newVal, oldVal) {} }); 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 Vue 雙向綁定的極簡(jiǎn)實(shí)現(xiàn) 剖析 Vue 原理&實(shí)現(xiàn)雙向綁定 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 動(dòng)態(tài)添加數(shù)據(jù)的注意點(diǎn) 注意:只有 data 中的數(shù)據(jù)才是響應(yīng)式的,動(dòng)態(tài)添加進(jìn)來的數(shù)據(jù)默認(rèn)為非響應(yīng)式 可以通過以下方式實(shí)現(xiàn)動(dòng)態(tài)添加數(shù)據(jù)的響應(yīng)式 1 Vue.set(object, key, value) - 適用于添加單個(gè)屬性 2 Object.assign() - 適用于添加多個(gè)屬性 var vm=new Vue({ data: { stu: { name: "jack", age: 19 } } }); /* Vue.set */ Vue.set(vm.stu, "gender", "male"); /* Object.assign 將參數(shù)中的所有對(duì)象屬性和值 合并到第一個(gè)參數(shù) 并返回合并后的對(duì)象*/ 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 異步執(zhí)行 DOM 更新,監(jiān)視所有數(shù)據(jù)改變,一次性更新 DOM 優(yōu)勢(shì):可以去除重復(fù)數(shù)據(jù),對(duì)于避免不必要的計(jì)算和 避免重復(fù) DOM 操作上,非常重要 如果需要那到更新后 dom 中的數(shù)據(jù) 則需要通過 Vue.nextTick(callback):在 DOM 更新后,執(zhí)行某個(gè)操作(屬于 DOM 操作) 實(shí)例調(diào)用 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- 前綴的特殊屬性 作用:當(dāng)表達(dá)式的值改變時(shí),將其產(chǎn)生的連帶影響,響應(yīng)式地作用于 DOM v-text 解釋:更新 DOM 對(duì)象的 textContent 1 2 1 2 v-html 解釋:更新 DOM 對(duì)象的 innerHTML 1 1 v-bind 作用:當(dāng)表達(dá)式的值改變時(shí),將其產(chǎn)生的連帶影響,響應(yīng)式地作用于 DOM 語(yǔ)法:v-bind:title="msg" 簡(jiǎn)寫::title="msg" 1 2 3 4 1 2 3 4 v-on 作用:綁定事件 語(yǔ)法:v-on:click=“say” or v-on:click=“say(‘參數(shù)’, $event)” 簡(jiǎn)寫:@click=“say” 說明:綁定的事件定義在 methods 1 2 3 4 1 2 3 4 事件修飾符 .stop 阻止冒泡,調(diào)用 event.stopPropagation() .prevent 阻止默認(rèn)行為,調(diào)用 event.preventDefault() .capture 添加事件偵聽器時(shí)使用事件捕獲模式 .self 只當(dāng)事件在該元素本身(比如不是子元素)觸發(fā)時(shí),才會(huì)觸發(fā)事件 .once 事件只觸發(fā)一次 v-model 作用:在表單元素上創(chuàng)建雙向數(shù)據(jù)綁定 說明:監(jiān)聽用戶的輸入事件以更新數(shù)據(jù) 你輸入了: {{ message }}
1 2 1 2 v-for 作用:基于源數(shù)據(jù)多次渲染元素或模板塊 {{ 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 的時(shí)候提供 key 屬性,以獲得性能提升。 說明:使用 key,VUE 會(huì)基于 key 的變化重新排列元素順序,并且會(huì)移除 key 不存在的元素。
1 2 3 1 2 3 樣式處理 class 和 style 使用方式:v-bind:class=“expression” or :class=“expression” 表達(dá)式的類型:字符串、數(shù)組、對(duì)象(重點(diǎn))===> 解析后===>解析后===>解析后 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:根據(jù)表達(dá)式的值的真假條件,銷毀或重建元素 v-show:根據(jù)表達(dá)式之真假值,切換元素的 display CSS 屬性
這個(gè)元素展示出來了嗎???
這個(gè)元素,在HTML結(jié)構(gòu)中嗎???
1 2 1 2 提升性能:v-pre vue 會(huì)跳過這個(gè)元素和它的子元素的編譯過程。可以用來顯示原始 Mustache 標(biāo)簽。跳過大量沒有指令的節(jié)點(diǎn)會(huì)加快編譯。 {{ 這將不會(huì)被編譯 }} 1 1 提升性能:v-once 說明:vue 只渲染元素和組件一次。隨后的重新渲染,元素/組件及其所有的子節(jié)點(diǎn)將被視為靜態(tài)內(nèi)容并跳過。這可以用于優(yōu)化更新性能。 這不會(huì)被改變: {{msg}} 1 1 過濾器 filter 作用:文本數(shù)據(jù)格式化 過濾器可以用在兩個(gè)地方:{{}}和 v-bind 表達(dá)式 兩種過濾器:1.全局過濾器 2. 局部過濾器 全局過濾器 說明:通過全局方式創(chuàng)建的過濾器,在任何一個(gè) vue 實(shí)例中都可以使用 注意:使用全局過濾器的時(shí)候,需要先創(chuàng)建全局過濾器,再創(chuàng)建 Vue 實(shí)例 顯示的內(nèi)容由過濾器的返回值決定
{{ 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 局部過濾器 說明:局部過濾器是在某一個(gè) vue 實(shí)例的內(nèi)容創(chuàng)建的,只在當(dāng)前實(shí)例中起作用 { data: {}, // 通過 filters 屬性創(chuàng)建局部過濾器 // 注意:此處為 filters filters: { filterName: function(value, format) {} } } 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 按鍵值修飾符 說明:在監(jiān)聽鍵盤事件時(shí),Vue 允許為 v-on 在監(jiān)聽鍵盤事件時(shí)添加關(guān)鍵修飾符 其他:修飾鍵(.ctrl 等)、鼠標(biāo)按鍵修飾符(.left 等) 鍵盤事件 - 鍵值修飾符 // 只有在 keyCode 是 13 時(shí)調(diào)用 vm.submit() @keyup.13="submit" // 使用全局按鍵別名 @keyup.enter="add" --- // 通過全局 config.keyCodes 對(duì)象自定義鍵值修飾符別名 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 監(jiān)視數(shù)據(jù)變化 - watch 概述:watch 是一個(gè)對(duì)象,鍵是需要觀察的表達(dá)式,值是對(duì)應(yīng)回調(diào)函數(shù) 作用:當(dāng)表達(dá)式的值發(fā)生變化后,會(huì)調(diào)用對(duì)應(yīng)的回調(diào)函數(shù)完成響應(yīng)的監(jiān)視操作 vue$watch new Vue({ data: { a: 1, b: { age: 10 } }, watch: { a: function(val, oldVal) { // val 表示當(dāng)前值 // oldVal 表示舊值 console.log("當(dāng)前值為:" + val, "舊值為:" + oldVal); }, // 監(jiān)聽對(duì)象屬性的變化 b: { handler: function(val, oldVal) { /* ... */ }, // deep : true表示是否監(jiān)聽對(duì)象內(nèi)部屬性值的變化 deep: true }, // 只監(jiān)視user對(duì)象中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 計(jì)算屬性 說明:計(jì)算屬性是基于它們的依賴進(jìn)行緩存的,只有在它的依賴發(fā)生改變時(shí)才會(huì)重新求值 注意:Mustache 語(yǔ)法({{}})中不要放入太多的邏輯,否則會(huì)讓模板過重、難以理解和維護(hù) 注意:computed 中的屬性不能與 data 中的屬性同名,否則會(huì)報(bào)錯(cuò) 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 實(shí)例生命周期 所有的 Vue 組件都是 Vue 實(shí)例,并且接受相同的選項(xiàng)對(duì)象即可 (一些根實(shí)例特有的選項(xiàng)除外)。 實(shí)例生命周期也叫做:組件生命周期 生命周期介紹 ?> 生命周期鉤子函數(shù)的定義:從組件被創(chuàng)建,到組件掛載到頁(yè)面上運(yùn)行,再到頁(yè)面關(guān)閉組件被卸載,這三個(gè)階段總是伴隨著組件各種各樣的事件,這些事件,統(tǒng)稱為組件的生命周期函數(shù)! vue 生命周期鉤子函數(shù) 簡(jiǎn)單說:一個(gè)組件從開始到最后消亡所經(jīng)歷的各種狀態(tài),就是一個(gè)組件的生命周期 注意:Vue 在執(zhí)行過程中會(huì)自動(dòng)調(diào)用生命周期鉤子函數(shù),我們只需要提供這些鉤子函數(shù)即可 注意:鉤子函數(shù)的名稱都是 Vue 中規(guī)定好的! 鉤子函數(shù) - beforeCreate() 說明:在實(shí)例初始化之后,數(shù)據(jù)觀測(cè) (data observer) 和 event/watcher 事件配置之前被調(diào)用 注意:此時(shí),無法獲取 data 中的數(shù)據(jù)、methods 中的方法 鉤子函數(shù) - created() 注意:這是一個(gè)常用的生命周期,可以調(diào)用 methods 中的方法、改變 data 中的數(shù)據(jù) vue 實(shí)例生命周期 參考 1 vue 實(shí)例生命周期 參考 2 使用場(chǎng)景:發(fā)送請(qǐng)求獲取數(shù)據(jù) 鉤子函數(shù) - beforeMounted() 說明:在掛載開始之前被調(diào)用 鉤子函數(shù) - mounted() 說明:此時(shí),vue 實(shí)例已經(jīng)掛載到頁(yè)面中,可以獲取到 el 中的 DOM 元素,進(jìn)行 DOM 操作 鉤子函數(shù) - beforeUpdated() 說明:數(shù)據(jù)更新時(shí)調(diào)用,發(fā)生在虛擬 DOM 重新渲染和打補(bǔ)丁之前。你可以在這個(gè)鉤子中進(jìn)一步地更改狀態(tài),這不會(huì)觸發(fā)附加的重渲染過程。 注意:此處獲取的數(shù)據(jù)是更新后的數(shù)據(jù),但是獲取頁(yè)面中的 DOM 元素是更新之前的 鉤子函數(shù) - updated() 說明:組件 DOM 已經(jīng)更新,所以你現(xiàn)在可以執(zhí)行依賴于 DOM 的操作。 鉤子函數(shù) - beforeDestroy() 說明:實(shí)例銷毀之前調(diào)用。在這一步,實(shí)例仍然完全可用。 使用場(chǎng)景:實(shí)例銷毀之前,執(zhí)行清理任務(wù),比如:清除定時(shí)器等 鉤子函數(shù) - destroyed() 說明:Vue 實(shí)例銷毀后調(diào)用。調(diào)用后,Vue 實(shí)例指示的所有東西都會(huì)解綁定,所有的事件監(jiān)聽器會(huì)被移除,所有的子實(shí)例也會(huì)被銷毀。 vue 單文件組件 vue-loader single-file components(單文件組件) 后綴名:.vue,該文件需要被預(yù)編譯后才能在瀏覽器中使用 注意:?jiǎn)挝募M件依賴于兩個(gè)包 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"; // 導(dǎo)入 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 創(chuàng)建 App.vue 單文件組件,注意:App 可以是任意名稱 4 在 main.js 入口文件中,導(dǎo)入 vue 和 App.vue 組件,通過 render 將組件與實(shí)例掛到一起 單文件組件+路由 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 導(dǎo)入 路由模塊 import VueRouter from "vue-router"; // 2 ** 調(diào)用use方法使用插件 ** Vue.use(VueRouter); // 3 創(chuàng)建路由對(duì)象 const router=new VueRouter({ routes: [ { path: "/home", component: Home }, { path: "/login", component: Login } ] }); // ------------- vue路由配置 結(jié)束 -------------- const vm=new Vue({ el: "#app", render: c=> c(App), // 4 掛載到 vue 實(shí)例中 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 的移動(dòng)端組件庫(kù) Mint-UI 安裝:cnpm i -S mint-ui // 1 導(dǎo)入 mint-ui模塊 import MintUI from "mint-ui"; // 2 導(dǎo)入 樣式 import "mint-ui/lib/style.css"; // 3 注冊(cè)插件 Vue.use(MintUI); 1 2 3 4 5 6 1 2 3 4 5 6 MUI MUI MUI 也是移動(dòng)端的 UI 庫(kù) 使用:從 github 下載包,找到 dist 文件夾,只需要導(dǎo)入樣式即可 // 只需要導(dǎo)入 MUI的樣式 即可,根據(jù)MUI的例子,直接使用HTML結(jié)果即可 // 導(dǎo)入樣式 import "./lib/mui/css/mui.min.css"; 1 2 3 1 2 3 ElementUI 這是 PC 端的 UI 組件庫(kù) 安裝: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 為基礎(chǔ)的 HTTP 客戶端,適用于:瀏覽器和 node.js 封裝 ajax,用來發(fā)送請(qǐng)求,異步獲取數(shù)據(jù) 安裝:cnpm i -S axios github axios 文檔 // 在瀏覽器中使用,直接引入js文件使用下面的GET/POST請(qǐng)求方式即可 // 1 引入 axios.js // 2 直接調(diào)用axios提供的API發(fā)送請(qǐng)求 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 請(qǐng)求 const url="http://vue.studyit.io/api/getnewslist"; // url中帶有query參數(shù) axios .get("/user?id=89") .then(function(response) { console.log(response); }) .catch(function(error) { console.log(error); }); // url和參數(shù)分離,使用對(duì)象 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 請(qǐng)求 不同環(huán)境中處理 POST 請(qǐng)求 ?> 默認(rèn)情況下,axios 會(huì)將 JS 對(duì)象序列化為 JSON 對(duì)象。為了使用 application/x-www-form-urlencoded 格式發(fā)送請(qǐng)求,我們可以這樣: // 使用 qs 包,處理將對(duì)象序列化為字符串 // 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=點(diǎn)個(gè)贊不過份"); 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 全局配置 // 設(shè)置請(qǐng)求公共路徑: axios.defaults.baseURL="http://vue.studyit.io"; 1 2 1 2 axios 攔截器 攔截器會(huì)攔截發(fā)送的每一個(gè)請(qǐng)求,請(qǐng)求發(fā)送之前執(zhí)行 request 中的函數(shù),請(qǐng)求發(fā)送完成之后執(zhí)行 response 中的函數(shù) // 請(qǐng)求攔截器 axios.interceptors.request.use( function(config) { // 所有請(qǐng)求之前都要執(zhí)行的操作 return config; }, function(error) { // 錯(cuò)誤處理 return Promise.reject(error); } ); // 響應(yīng)攔截器 axios.interceptors.response.use( function(response) { // 所有請(qǐng)求完成后都要執(zhí)行的操作 return response; }, function(error) { // 錯(cuò)誤處理 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 自定義指令 作用:進(jìn)行 DOM 操作 使用場(chǎng)景:對(duì)純 DOM 元素進(jìn)行底層操作,比如:文本框獲得焦點(diǎn) vue 自定義指令用法實(shí)例 兩種指令:1. 全局指令 2. 局部指令 全局自定義指令 // 第一個(gè)參數(shù):指令名稱 // 第二個(gè)參數(shù):配置對(duì)象,指定指令的鉤子函數(shù) Vue.directive('directiveName', { // bind中只能對(duì)元素自身進(jìn)行DOM操作,而無法對(duì)父級(jí)元素操作 // 只調(diào)用一次 指令第一次綁定到元素時(shí)調(diào)用。在這里可以進(jìn)行一次性的初始化設(shè)置。 bind( el,binding, vnode ) { // 參數(shù)詳解 // el:指令所綁定的元素,可以用來直接操作 DOM 。 // binding:一個(gè)對(duì)象,包含以下屬性: // name:指令名,不包括 v- 前綴。 // value:指令的綁定值,等號(hào)后面的值 。 // oldValue:指令綁定的前一個(gè)值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。 // expression:字符串形式的指令表達(dá)式 等號(hào)后面的字符串 形式 // arg:傳給指令的參數(shù),可選。例如 v-my-directive:foo 中,參數(shù)為 "foo"。 // modifiers:指令修飾符。例如:v-directive.foo.bar中,修飾符對(duì)象為 { foo: true, bar: true }。 // vnode:Vue 編譯生成的虛擬節(jié)點(diǎn)。。 // oldVnode:上一個(gè)虛擬節(jié)點(diǎn),僅在 update 和 componentUpdated 鉤子中可用。 }, // inserted這個(gè)鉤子函數(shù)調(diào)用的時(shí)候,當(dāng)前元素已經(jīng)插入頁(yè)面中了,也就是說可以獲取到父級(jí)節(jié)點(diǎn)了 inserted ( el,binding, vnode ) {}, // DOM重新渲染前 update(el,binding, vnode,oldVnode) {}, // DOM重新渲染后 componentUpdated ( el,binding, vnode,oldVnode ) {}, // 只調(diào)用一次,指令與元素解綁時(shí)調(diào)用 unbind ( el ) { // 指令所在的元素在頁(yè)面中消失,觸發(fā) } }) // 簡(jiǎn)寫 如果你想在 bind 和 update 時(shí)觸發(fā)相同行為,而不關(guān)心其它的鉤子: Vue.directive('自定義指令名', function( el, binding ) {}) // 例: Vue.directive('color', function(el, binding) { el.style.color=binging.value }) // 使用 注意直接些會(huì)被i成data中的數(shù)據(jù)“red” 需要字符串則嵌套引號(hào)"'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 原理&實(shí)現(xiàn)雙向綁定 MVVM var vm=new Vue({ el: "#app", directives: { directiveName: {} } }); 1 2 3 4 5 6 1 2 3 4 5 6 組件 組件系統(tǒng)是 Vue 的另一個(gè)重要概念,因?yàn)樗且环N抽象,允許我們使用小型、獨(dú)立和通常可復(fù)用的組件構(gòu)建大型應(yīng)用。仔細(xì)想想,幾乎任意類型的應(yīng)用界面都可以抽象為一個(gè)組件樹 #### 全局組件 說明:全局組件在所有的 vue 實(shí)例中都可以使用 注意:先注冊(cè)組件,再初始化根實(shí)例 // 1 注冊(cè)全局組件 Vue.component('my-component', { // template 只能有一個(gè)根元素 template: '
A custom component!
', // 組件中的 `data` 必須是函數(shù) 并且函數(shù)的返回值必須是對(duì)象 data() { return { msg: '注意:組件的data必須是一個(gè)函數(shù)!!!' } } }) // 2 使用:以自定義元素的方式
//=====> 渲染結(jié)果
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:使用基礎(chǔ) Vue 構(gòu)造器,創(chuàng)建一個(gè)“子類”。參數(shù)是一個(gè)包含組件選項(xiàng)的對(duì)象。 // 注冊(cè)組件,傳入一個(gè)擴(kuò)展過的構(gòu)造器 Vue.component( "my-component", Vue.extend({ /* ... */ }) ); // 注冊(cè)組件,傳入一個(gè)選項(xiàng)對(duì)象 (自動(dòng)調(diào)用 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 局部組件 說明:局部組件,是在某一個(gè)具體的 vue 實(shí)例中定義的,只能在這個(gè) vue 實(shí)例中使用 var Child={ template: "
A custom component!
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屬性來傳遞數(shù)據(jù) props是一個(gè)數(shù)組 注意:屬性的值必須在組件中通過props屬性顯示指定,否則,不會(huì)生效 說明:傳遞過來的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 子組件到父組件 方式:父組件給子組件傳遞一個(gè)函數(shù),由子組件調(diào)用這個(gè)函數(shù) 說明:借助 vue 中的自定義事件(v-on:cunstomFn=“fn”) 步驟: 1、在父組件中定義方法 parentFn 2、在子組件 組件引入標(biāo)簽 中綁定自定義事件 v-on:自定義事件名=“父組件中的方法”==> @pfn=“parentFn” 3、子組件中通過emit()觸發(fā)自定義事件事件this. emit()觸發(fā)自定義事件事件 this.emit()觸發(fā)自定義事件事件this.emit(pfn,參數(shù)列表。。。) 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 非父子組件通訊 ?> 在簡(jiǎn)單的場(chǎng)景下,可以使用一個(gè)空的 Vue 實(shí)例作為事件總線 $on():綁定自定義事件 var bus=new Vue(); // 在組件 B 綁定自定義事件 bus.$on("id-selected", function(id) { // ... }); // 觸發(fā)組件 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 內(nèi)容分發(fā) 通過 標(biāo)簽指定內(nèi)容展示區(qū)域 我是額外的內(nèi)容
1 2 3 4 5 6 7 1 2 3 4 5 6 7 // js代碼 new vue({ el: "#app", components: { hello: { template: `
我是子組件中的內(nèi)容
` } } }); 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 一個(gè)對(duì)象,持有已注冊(cè)過 ref 的所有子組件(或 HTML 元素) 使用:在 HTML 元素 中,添加 ref 屬性,然后在 JS 中通過 vm.$refs.屬性來獲取 注意:如果獲取的是一個(gè)子組件,那么通過 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 -單頁(yè)應(yīng)用程序 SPA: Single Page Application ?> 單頁(yè) Web 應(yīng)用(single page application,SPA),就是只有一個(gè) Web 頁(yè)面的應(yīng)用, 是加載單個(gè) HTML 頁(yè)面,并在用戶與應(yīng)用程序交互時(shí)動(dòng)態(tài)更新該頁(yè)面的 Web 應(yīng)用程序。 單頁(yè)面應(yīng)用程序: 只有第一次會(huì)加載頁(yè)面, 以后的每次請(qǐng)求, 僅僅是獲取必要的數(shù)據(jù).然后, 由頁(yè)面中 js 解析獲取的數(shù)據(jù), 展示在頁(yè)面中 傳統(tǒng)多頁(yè)面應(yīng)用程序: 對(duì)于傳統(tǒng)的多頁(yè)面應(yīng)用程序來說, 每次請(qǐng)求服務(wù)器返回的都是一個(gè)完整的頁(yè)面 優(yōu)勢(shì) 1 減少了請(qǐng)求體積,加快頁(yè)面響應(yīng)速度,降低了對(duì)服務(wù)器的壓力 2 更好的用戶體驗(yàn),讓用戶在 web app 感受 native app 的流暢 實(shí)現(xiàn)思路和技術(shù)點(diǎn) 1 ajax 2 錨點(diǎn)的使用(window.location.hash #) 3 hashchange 事件 window.addEventListener(“hashchange”,function () {}) 4 監(jiān)聽錨點(diǎn)值變化的事件,根據(jù)不同的錨點(diǎn)值,請(qǐng)求相應(yīng)的數(shù)據(jù) 5 原本用作頁(yè)面內(nèi)部進(jìn)行跳轉(zhuǎn),定位并展示相應(yīng)的內(nèi)容 路由 路由即:瀏覽器中的哈希值(# hash)與展示視圖內(nèi)容(template)之間的對(duì)應(yīng)規(guī)則 vue 中的路由是:hash 和 component 的對(duì)應(yīng)關(guān)系 在 Web app 中,通過一個(gè)頁(yè)面來展示和管理整個(gè)應(yīng)用的功能。 SPA 往往是功能復(fù)雜的應(yīng)用,為了有效管理所有視圖內(nèi)容,前端路由 應(yīng)運(yùn)而生! 簡(jiǎn)單來說,路由就是一套映射規(guī)則(一對(duì)一的對(duì)應(yīng)規(guī)則),由開發(fā)人員制定規(guī)則。 當(dāng) URL 中的哈希值(# hash)發(fā)生改變后,路由會(huì)根據(jù)制定好的規(guī)則,展示對(duì)應(yīng)的視圖內(nèi)容 基本使用 安裝:cnpm i -S vue-router
首頁(yè) 登錄
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 路由其他配置 路由導(dǎo)航高亮 說明:當(dāng)前匹配的導(dǎo)航鏈接,會(huì)自動(dòng)添加 router-link-exact-active router-link-active 類 配置:linkActiveClass 匹配路由模式 配置:mode new Router({ routers: [], mode: "hash", //默認(rèn)hash | history 可以達(dá)到隱藏地址欄hash值 | abstract,如果發(fā)現(xiàn)沒有瀏覽器的 API 則強(qiáng)制進(jìn)入 linkActiveClass: "now" //當(dāng)前匹配的導(dǎo)航鏈接將被自動(dòng)添加now類 }); 1 2 3 4 5 1 2 3 4 5 路由參數(shù) 說明:我們經(jīng)常需要把某種模式匹配到的所有路由,全都映射到同一個(gè)組件,此時(shí),可以通過路由參數(shù)來處理 語(yǔ)法:/user/:id 使用:當(dāng)匹配到一個(gè)路由時(shí),參數(shù)值會(huì)被設(shè)置到 this.$route.params 其他:可以通過 $route.query 獲取到 URL 中的查詢參數(shù) 等 如果你需要在模版中使用路由參數(shù) 可以這樣 {{$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 嵌套路由 - 子路由 路由是可以嵌套的,即:路由中又包含子路由 規(guī)則:父組件中包含 router-view,在路由規(guī)則中使用 children 配置 // 父組件: const User=Vue.component("user", { template: `
User Center
個(gè)人資料 崗位
` }); // 子組件[簡(jiǎn)寫] const UserProfile={ template: "
個(gè)人資料:張三
" }; const UserPosts={ template: "
崗位:FE
" }; // 路由 var router=new Router({ routers: [ { path: "/user", component: User, // 子路由配置: children: [ { // 當(dāng) /user/profile 匹配成功, // UserProfile 會(huì)被渲染在 User 的 中 path: "profile", component: UserProfile }, { // 當(dāng) /user/posts 匹配成功 // UserPosts 會(huì)被渲染在 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 就是為了實(shí)現(xiàn)客戶端驗(yàn)證以及一些簡(jiǎn)單的效果 后來,js 得到重視,應(yīng)用越來越廣泛,前端開發(fā)的復(fù)雜度越來越高 舊版本的 js 中沒有提供與模塊(module)相關(guān)的內(nèi)容 模塊的概念 在 js 中,一個(gè)模塊就是實(shí)現(xiàn)特定功能的文件(js 文件) 遵循模塊的機(jī)制,想要什么功能就加載什么模塊 模塊化開發(fā)需要遵循規(guī)范 模塊化解決的問題 命名沖突 文件依賴(加載文件) 模塊的復(fù)用 統(tǒng)一規(guī)范和開發(fā)方式 JS 實(shí)現(xiàn)模塊化的規(guī)范 AMD 瀏覽器端 requirejs CommonJS nodejs 加載模塊:require() 導(dǎo)出模塊:module.exports={} / exports={} ES6 中的 import / export CMD 瀏覽器端 玉伯(阿里前端大神) -> seajs UMD 通用模塊化規(guī)范,可以兼容 AMD、CommonJS、瀏覽器中沒有模塊化規(guī)范 等這些語(yǔ)法 AMD 的使用 Asynchronous Module Definition:異步模塊定義,瀏覽器端模塊開發(fā)的規(guī)范 代表:require.js 特點(diǎn):模塊被異步加載,模塊加載不影響后面語(yǔ)句的運(yùn)行 1、定義模塊 // 語(yǔ)法:define(name, dependencies?, factory); // name表示:當(dāng)前模塊的名稱,是一個(gè)字符串 可有可無 // dependencies表示:當(dāng)前模塊的依賴項(xiàng),是一個(gè)數(shù)組無論依賴一項(xiàng)還是多項(xiàng) 無則不寫 // factory表示:當(dāng)前模塊要完成的一些功能,是一個(gè)函數(shù) // 定義對(duì)象模塊 define({}); // 定義方法模塊 define(function() { return {}; }); // 定義帶有依賴項(xiàng)的模塊 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 的第一個(gè)參數(shù)必須是數(shù)組 // 參數(shù)必須是數(shù)組 表示模塊路徑 以當(dāng)前文件為基準(zhǔn),通過回調(diào)函數(shù)中的參數(shù)獲取加載模塊中的變量 參數(shù)與模塊按照順序一一對(duì)應(yīng) require(["a", "js/b"], function(a, b) { // 使用模塊a 和 模塊b 中的代碼 }); 1 2 3 4 5 1 2 3 4 5 3、路徑查找配置 requirejs 默認(rèn)使用 baseUrl+paths 的路徑解析方式 可以使用以下方式避開此設(shè)置: 以.js結(jié)尾 以 / 開始 包含協(xié)議:https:// 或 http:// // 配置示例 // 注意配置應(yīng)當(dāng)在使用之前 require.config({ baseUrl: "./js" // 配置基礎(chǔ)路徑為:當(dāng)前目錄下的js目錄 }); require(["a"]); // 查找 基礎(chǔ)路徑下的 ./js/a.js // 簡(jiǎn)化加載模塊路徑 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、非模塊化和依賴項(xiàng)支持 添加模塊的依賴模塊,保證加載順序(deps) 將非模塊化模塊,轉(zhuǎn)化為模塊化(exports) // 示例 require.config({ baseUrl: "./js", paths: { // 配置路徑 noModule: "assets/demo/noModule" }, // 配置不符合規(guī)范的模塊項(xiàng) shim: { // 模塊名稱 noModule: { deps: [], // 依賴項(xiàng) exports: "sayHi" // 導(dǎo)出模塊中存在的函數(shù)或變量 } } }); // 注意點(diǎn) 如果定義模塊的時(shí)候,指定了模塊名稱,需要使用該名稱來引用模塊 // 定義 這個(gè)模塊名稱與paths中的名稱相同 define("moduleA", function() {}); // 導(dǎo)入 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、路徑加載規(guī)則 路徑配置的優(yōu)先級(jí): 通過 config 配置規(guī)則查找 通過 data-main 指定的路徑查找 以引入 requirejs 的頁(yè)面所在路徑為準(zhǔn)查找 1 2 3 4 5 6 1 2 3 4 5 6 Webpack webpack 文檔 bundle [?b?ndl] 捆綁,收集,歸攏,把…塞入 webpack 將帶有依賴項(xiàng)的各個(gè)模塊打包處理后,變成了獨(dú)立的瀏覽器能夠識(shí)別的文件 webpack 合并以及解析帶有依賴項(xiàng)的模塊 概述 webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的模塊打包器(特點(diǎn) module、 bundler) webpack 是一個(gè)模塊化方案(預(yù)編譯) webpack 獲取具有依賴關(guān)系的模塊,并生成表示這些模塊的靜態(tài)資源 四個(gè)核心概念:入口(entry)、輸出(output)、加載器loader、插件(plugins) 對(duì)比 模塊化方案: webpack 和 requirejs(通過編寫代碼的方式將前端的功能,劃分成獨(dú)立的模塊) browserify 是與 webpack 相似的模塊化打包工具 webpack 預(yù)編譯 (在開發(fā)階段通過 webpack 進(jìn)行模塊化處理, 最終項(xiàng)目上線, 就不在依賴于 webpack) requirejs 線上的編譯( 代碼運(yùn)行是需要依賴于 requirejs 的 ) webpack 起源 webpack 解決了現(xiàn)存模塊打包器的兩個(gè)痛點(diǎn): 1 Code Spliting - 代碼分離 按需加載 2 靜態(tài)資源的模塊化處理方案 webpack 與模塊 前端模塊系統(tǒng)的演進(jìn) 在 webpack 看來:所有的靜態(tài)資源都是模塊 webpack 模塊能夠識(shí)別以下等形式的模塊之間的依賴: JS 的模塊化規(guī)范: ES2015 import export CommonJS require() module.exports AMD define 和 require 非 JS 等靜態(tài)資源: css/sass/less 文件中的 @import 圖片連接,比如:樣式 url(...) 或 HTML
字體 等 入門 Webpack,看這篇就夠了 安裝 webpack 全局安裝:cnpm i -g webpack 目的:在任何目錄中通過 CLI 使用 webpack 這個(gè)命令 項(xiàng)目安裝:cnpm i -D webpack 目的:執(zhí)行當(dāng)前項(xiàng)目的構(gòu)建 webpack 的基本使用 安裝:npm i -D webpack webpack 的兩種使用方式:1 命令行 2 配置文件(webpack.config.js) 命令行方式演示 - 案例:隔行變色 1 使用npm init -y 初始package.json,使用 npm 來管理項(xiàng)目中的包 2 新建 index.html 和 index.js,實(shí)現(xiàn)隔行變色功能 3 運(yùn)行webpack src/js/index.js dist/bundle.js進(jìn)行打包構(gòu)建,語(yǔ)法是:webpack 入口文件 輸出文件 4 注意:需要在頁(yè)面中引入 輸出文件 的路徑(此步驟可通過配置 webpack 去掉) /* src/js/index.js */ // 1 導(dǎo)入 jQuery import $ from "jquery"; // 2 獲取頁(yè)面中的li元素 const $lis=$("#ulList").find("li"); // 3 隔行變色 // jQuery中的 filter() 方法用來過濾jquery對(duì)象 $lis.filter(":odd").css("background-color", "#def"); $lis.filter(":even").css("background-color", "skyblue"); //命令行運(yùn)行 `webpack src/js/index.js dist/bundle.js 目錄生成在命令行運(yùn)行目錄 /* 運(yùn)行流程: 1、webpack 根據(jù)入口找到入口文件 2、分析js中的模塊化語(yǔ)法 3、將所有關(guān)聯(lián)文件 打包合并輸出到出口 */ 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,創(chuàng)建開發(fā)環(huán)境(啟動(dòng)服務(wù)器、監(jiān)視文件變化、自動(dòng)編譯、刷新瀏覽器等),提高開發(fā)效率 注意:無法直接在終端中執(zhí)行 webpack-dev-server,需要通過 package.json 的 scripts 實(shí)現(xiàn) 使用方式:cnpm run dev // 參數(shù)解釋 注意參數(shù)是無序的 有值的參數(shù)空格隔開 // --open 自動(dòng)打開瀏覽器 // --contentBase ./ 指定瀏覽器 默認(rèn)打開的頁(yè)面路徑中的 index.html 文件 // --open 自動(dòng)打開瀏覽器 // --port 8080 端口號(hào) // --hot 熱更新,只加載修改的文件(按需加載修改的內(nèi)容),而非全部加載 "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: { // 服務(wù)器的根目錄 Tell the server where to serve content from // https://webpack.js.org/configuration/dev-server/#devserver-contentbase contentBase: path.join(__dirname, './'), // 自動(dòng)打開瀏覽器 open: true, // 端口號(hào) 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 作用:根據(jù)模板,自動(dòng)生成 html 頁(yè)面 優(yōu)勢(shì):頁(yè)面存儲(chǔ)在內(nèi)存中,自動(dòng)引入bundle.js、css等文件 /* webpack.config.js */ const htmlWebpackPlugin=require("html-webpack-plugin"); plugins: [ new htmlWebpackPlugin({ // 模板頁(yè)面路徑 template: path.join(__dirname, "./index.html"), // 在內(nèi)存中生成頁(yè)面路徑,默認(rèn)值為: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 靜態(tài)資源 1、 CSS 打包 安裝:cnpm i -D style-loader css-loader 注意:use 中模塊的順序不能顛倒,加載順序:從右向左加載 /* 在index.js 導(dǎo)入 css 文件*/ import "./css/app.css"; /* webpack.config.js 配置各種資源文件的loader加載器*/ module: { // 配置匹配規(guī)則 rules: [ // test 用來配置匹配文件規(guī)則(正則) // use 是一個(gè)數(shù)組,按照從后往前的順序執(zhí)行加載 { 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 創(chuàng)建style標(biāo)簽 // "css-loader" :translates CSS into CommonJS 將css轉(zhuǎn)化為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:將圖片或字體轉(zhuǎn)化為 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 圖片打包細(xì)節(jié) limit 參數(shù)的作用:(單位為:字節(jié)(byte)) 當(dāng)圖片文件大小(字節(jié))小于指定的 limit 時(shí),圖片被轉(zhuǎn)化為base64編碼格式 當(dāng)圖片文件大小(字節(jié))大于等于指定的 limit 時(shí),圖片被重命名以 url 路徑形式加載(此時(shí),需要 file-loader 來加載圖片) 圖片文件重命名,保證相同文件不會(huì)被加載多次。例如:一張圖片(a.jpg)拷貝一個(gè)副本(b.jpg),同時(shí)引入這兩張圖片,重命名后只會(huì)加載一次,因?yàn)檫@兩張圖片就是同一張 文件重命名以后,會(huì)通過 MD5 加密的方式,來計(jì)算這個(gè)文件的名稱 /* 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 第二步:在項(xiàng)目根目錄中新建.babelrc配置文件 /* 創(chuàng)建 .babelrc 文件*/ // 將來babel-loader運(yùn)行的時(shí)候,會(huì)檢查這個(gè)配置文件,并讀取相關(guān)的語(yǔ)法和插件配置 { "presets": ["env"] } 1 2 3 4 5 1 2 3 4 5 babel 的說明 babel 的作用: 1 語(yǔ)法轉(zhuǎn)換:將新的 ES 語(yǔ)法轉(zhuǎn)化為瀏覽器能識(shí)別的語(yǔ)法(babel-preset-*) 2 polyfill 瀏覽器兼容:讓低版本瀏覽器兼容最新版 ES 的 API babel-preset-* Babel 通過語(yǔ)法轉(zhuǎn)換器,能夠支持最新版本的 JavaScript 語(yǔ)法 babel-preset-* 用來指定我們書寫的是什么版本的 JS 代碼 作用:將新的 ES 語(yǔ)法轉(zhuǎn)化為瀏覽器能識(shí)別的 ES5 代碼 ES6 語(yǔ)法提案的批準(zhǔn)流程 ES2015 也就是 ES6, 下一個(gè)版本是 ES7, 從 ES6 到 ES7 之間經(jīng)歷了 5 個(gè)階段 babel-preset-es2015 轉(zhuǎn)換 es6 的語(yǔ)法 babel-preset-stage-0 轉(zhuǎn)換比 es6 更新的語(yǔ)法 Stage 0 - Strawman(展示階段) Stage 1 - Proposal(征求意見階段) Stage 2 - Draft(草案階段) Stage 3 - Candidate(候選人階段) Stage 4 - Finished(定案階段) 1 2 3 4 5 總結(jié) babel-core babel 核心包 babel-loader 用來解析 js 文件 babel-preset-* 新 ES 語(yǔ)法的解析和轉(zhuǎn)換 transform-runtime / babel-polyfill 兼容舊瀏覽器,到達(dá)支持新 API 目的 // 判斷瀏覽器是否兼容 padStart 這個(gè) API if (!String.prototype.padStart) { // 如果不兼容, 就自己模擬 padStart的功能實(shí)現(xiàn)一份 String.prototype.padStart=function padStart(targetLength, padString) {}; } 1 2 3 4 5 1 2 3 4 5 Webpack 發(fā)布項(xiàng)目 webpack 打包的各種坑 webpack 命令能夠生成dist目錄到磁盤中,最終,把打包后的代碼,部署服務(wù)器中去 webpack-dev-server 僅是在內(nèi)存中生成的文件,并沒有寫到磁盤中,所以,只能在開發(fā)期間使用 創(chuàng)建項(xiàng)目發(fā)布配置文件 開發(fā)期間配置文件:webpack.config.js 項(xiàng)目發(fā)布配置文件:webpack.prod.js (文件名稱非固定 production 生產(chǎn)環(huán)境) 命令:webpack --config webpack.prod.js 指定配置文件名稱運(yùn)行 webpack 參數(shù):–display-error-details 用于顯示 webpack 打包的錯(cuò)誤信息 /* package.json */ "scripts": { "build": "webpack --config webpack.prod.js" } 1 2 3 4 5 1 2 3 4 5 在項(xiàng)目根目錄中創(chuàng)建 webpack.prod.js 文件 在 package.json 中, 配置一個(gè) scripts 在 終端中 通過 npm run build 對(duì)項(xiàng)目進(jìn)行打包 打包處理過程 刪除掉 devServer 相關(guān)的配置項(xiàng) 將圖片和字體文件輸出到指定的文件夾中 自動(dòng)刪除 dist 目錄 分離第三方包(將使用的 vue 等第三方包抽離到 vender.js 中) 壓縮混淆 JS 以及 指定生成環(huán)境 抽取和壓縮 CSS 文件 壓縮 HTML 頁(yè)面 配合 vue 的異步組件,實(shí)現(xiàn)按需加載功能 處理圖片路徑 注意:如果 limit 小于比圖片大,那么圖片將被轉(zhuǎn)化為 base64 編碼格式 name 參數(shù)介紹 /* 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),并且保持原來的圖片文件擴(kuò)展名 // name:指定文件輸出路徑和輸出文件命令規(guī)則 // [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 自動(dòng)刪除 dist 目錄 安裝:cnpm i -D clean-webpack-plugin 作用: 每次打包之前, 刪除上一次打包的 dist 目錄 /* webpack.prod.js */ const cleanWebpackPlugin=require("clean-webpack-plugin"); plugins: [ // 創(chuàng)建一個(gè)刪除文件夾的插件,刪除dist目錄 new cleanWebpackPlugin(["./dist"]) ]; 1 2 3 4 5 6 7 1 2 3 4 5 6 7 分離第三方包 目的:將公共的第三方包,抽離為一個(gè)單獨(dú)的包文件,這樣防止重復(fù)打包! 例如:main.js、router、vuex中都引入了 vue,不分離的話,vue 會(huì)被打包 3 次 抽離后, vue 文件只會(huì)被打包一次, 用到的地方僅僅是引用 /* webpack.prod.js */ // 1 入口 -- 打包文件的入口 entry: { // 項(xiàng)目代碼入口 app: path.join(__dirname, './src/js/main.js'), // 第三方包入口 vendor: ['vue', 'vue-router', 'axios'] }, output: { // 2 修改輸出文件路徑和命名規(guī)則 filename: 'js/[name].[chunkhash].js', }, plugins: [ // 3 抽離第三方包 new webpack.optimize.CommonsChunkPlugin({ // 將 entry 中指定的 ['vue', 'vue-router', 'axios'] 打包到名為 vendor 的js文件中 // 第三方包入口名稱,對(duì)應(yīng) 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: [ // 優(yōu)化代碼 // https://github.com/webpack-contrib/uglifyjs-webpack-plugin/tree/v0.4.6 new webpack.optimize.UglifyJsPlugin({ // 壓縮 compress: { // 移除警告 warnings: false } }), // 指定環(huán)境為生產(chǎn)環(huán)境:vue會(huì)根據(jù)這一項(xiàng)啟用壓縮后的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 報(bào)錯(cuò)的說明: Error processing file: css/style.css postcss-svgo: Error in parsing SVG: Unquoted attribute value 1 2 1 2 原因:壓縮和抽離 CSS 的插件中只允許 SVG 使用雙引號(hào) /* webpack.prod.js */ // 分離 css 到獨(dú)立的文件中 const ExtractTextPlugin=require("extract-text-webpack-plugin"); // 壓縮 css 資源文件 const OptimizeCssAssetsPlugin=require('optimize-css-assets-webpack-plugin') // bug描述: 生成后面的css文件中圖片路徑錯(cuò)誤,打開頁(yè)面找不到圖片 // 解決:google搜索 webpack css loader 樣式圖片路徑 output: { // https://doc.webpack-china.org/configuration/output/#output-publicpath // 設(shè)置公共路徑 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 (參數(shù)) 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 頁(yè)面 詳細(xì)的配置可以參考html-minifier new htmlWebpackPlugin({ // 模板頁(yè)面 template: path.join(__dirname, './index.html'), // 壓縮HTML minify: { // 移除空白 collapseWhitespace: true, // 移除注釋 removeComments: true, // 移除屬性中的雙引號(hào) 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 實(shí)現(xiàn)路由按需加載 Vue 路由懶加載 Vue 異步組件 Vue 組件懶加載淺析 步驟 1 修改組件的引用方式 // 方式一: require.ensure() const NewsList=r=> require.ensure( [], ()=> r(require("../components/news/newslist.vue")), "news" ); // 方式二: import() -- 推薦 // 注意:/* webpackChunkName: "newsinfo" */ 是一個(gè)特殊的語(yǔ)法,表示生成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', }
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。