如何從零開始開發一個 Chrome 插件?

什么是瀏覽器插件?

簡單來說瀏覽器插件,是瀏覽器上的一種工具,可以提供一些瀏覽器沒有的功能,幫你做一些有趣的事情。開發者可以根據自己的喜歡,去實現一些功能。插件基于Web技術(html、css、js)構建。

舉個栗子????

FeHelper.JSON插件


功能:格式化JSON、編碼轉化、markdown、代碼壓縮等功能。

二維碼生成器


功能:可以根據當前瀏覽的網頁地址,生成一個二維碼。

SwitchyOmega Proxy


功能:你懂的。

Hello World

manifest.json

Chrome 瀏覽器插件沒有嚴格的文件結構約束,只需要保證文件夾根目錄有 manifest.json 文件**,**該文件的內容會概括插件所需的資源、權限等等。

一個段簡單的示例:

{"manifest_version":?2,?//?必填"name":?"my-plugin",?//?必填"version":?"0.1.0"?//?必填
}

manifest_version:代表了manifest文件的版本,瀏覽器會根據這個值去指定該版本擁有的功能。

name:插件的名稱。

version:插件版本。

將manifest.json文件放到一個文件夾內。

chrome://extensions/

在瀏覽器地址欄輸入chrome://extensions/打開“拓展程序”頁面。

注意:需要啟用右上角的 “開發者模式” 才能加載已解壓的插件文件:


加載已解壓的插件

啟用之后點擊加載已解壓的拓展程序,選擇剛剛我們放入了manifest.json的文件夾,之后你會看到:

新增了一個我們剛剛添加的插件,而且瀏覽器右上角也會有我們的一個圖標:

此時已經加載了一個插件了,但是這個插件除了占用瀏覽器的一個位置除外,沒有任何作用。

如果沒有設置插件圖標,那么插件的第一個字符會成為插件的默認icon。

讓插件看起來更“插件”一點

為了讓這個插件更“完善”一點,我們給它加一個icon和描述,并且點擊出現一個popup頁面,popup 頁面一般用來承載臨時性的交互,且生命周期很短:單擊圖標打開popup,焦點離開又立即關閉,可以通過default_popup字段來定義。

{....."description":?"這是一段描述",//?插件管理頁面的icon"icons":?{"84":?"./icon/ball.png"},//?瀏覽器右上角的圖標和內容"browser_action":?{"default_icon":?"./icon/ball.png","default_title":?"我的插件","default_popup":?"./html/popup.html"}
}

此時我們的目錄結構也變成了這樣:

給popup.html加上內容:

<!DOCTYPE?html>
<html?lang="en">
<head><meta?charset="UTF-8"><meta?name="viewport"?content="width=device-width,?initial-scale=1.0"><meta?http-equiv="X-UA-Compatible"?content="ie=edge"><title>my-plugin</title>
</head>
<body><p?style="width:?200px;text-align:center;">hello?world!!</p>
</body>
</html>

之后,我們點擊插件右下角的“刷新”按鈕:

你會發現插件有了icon和描述:

并且右上角的icon也變了,點擊一下,會彈出我們剛剛編寫的popup.html頁面:

現在,我們一個“完整”的插件就已經做好了。

manifest.json 配置介紹

background

{..."background":?{//?提供一個頁面給background"page":?"./html/background.html"//?或者若干個js文件,后臺會默認生成一個空白的html"scripts":?["./js/background.js"]}
}

background配置項,為插件的后臺常駐頁面,生命周期隨著瀏覽器的生命周期一樣,瀏覽器一啟動,后臺頁面就會開始運行,直到瀏覽器被關閉;或者在插件管理頁面,將該插件禁用了,后臺頁面也會停止運行。

另外,background擁有的權限比較高,幾乎可以調用所有的Chrome擴展API(除了devtools),同時擁有直接跨域的能力。

page:指定一個網頁為后臺頁面。

scripts:指定若干個js文件,后臺會自動生成一個html,并按順序調用這些js文件。

注意:pagescripts 選項只能二選一,不然會報錯。

配置好之后,屬性插件,會出現一個背景頁選項:

我使用的是一個background.js文件:

function?_back()?{console.log('background.js')
}
console.log('running...')

點進去看看里面裝的什么玩意:

沒錯,是一個普通的后臺頁面,如果background.js和其他頁面有通信,則可以在這里進行查看請求或者調試代碼。

如果使用page選項,打開也是這個樣子。

另外:由于background是一直在后臺運行的,為了優化性能,可以增加一個配置:

{..."background":?{..."persistent":?false}
}

這樣,插件就會在被需要時加載,在空閑時被關閉。比如安裝、更新插件的時候,或者有其他頁面與background通信的時候才會被加載。

content-scripts

content-scripts能夠在合適的時機(頁面載入前、載入后、空閑時)注入腳本,允許內容腳本更改其JavaScript環境,而不與頁面或其他內容腳本發生沖突。

例如,原頁面有個按鈕,并且給按鈕添加了一個點擊事件:

<html><button?id="mybutton">click?me</button><script>var?greeting?=?"hello,?";var?button?=?document.getElementById("mybutton");button.person_name?=?"Bob";button.addEventListener("click",?function()?{alert(greeting?+?button.person_name?+?".");},?false);</script></html>

在content-scripts中,加入以下代碼:

var?greeting?=?"hola,?";
var?button?=?document.getElementById("mybutton");
button.person_name?=?"Roberto";
button.addEventListener("click",?function()?{
alert(greeting?+?button.person_name?+?".");
},?false);

當頁面運行之后,腳本內容也會在插件定義的時間運行,當頁面點擊按鈕時,會出現兩次彈窗。

content-scripts配置:

{..."content_scripts":?[{//?在匹配的URL中運行,<all_urls>表示所有的URL都會運行。"matches":?["<all_urls>"],//?注入的js,會按順序運行。"js":?["./js/content.js"],//?css引入需謹慎,因為可能會影響全局的樣式,同樣也能接收多個css文件,會按順序插入到頁面中"css":?["./css/style.css"],//?代碼注入的時機,可選值:?"document_start",?"document_end",?or?"document_idle",最后一個表示頁面空閑時,默認document_idle"run_at":?"document_start"},{"matches":?["https://www.baidu.com/"],"js":?["./js/other.js"],"run_at":?"document_start"}],...
}

content.js代碼如下:

console.log('hello,?from?content.js');

other.js代碼如下:

console.log('hello,?from?other.js...')

更新插件,當在 https://bytedance.feishu.cn/drive/home/運行時:

因為【 https://bytedance.feishu.cn/drive/home/】只匹配到了<all_urls>的規則,所以之后運行content.js

當在https://www.baidu.com/運行時:

同時命中了2個規則,所以content.js和other.js都會運行,順序也是正確的。

content-scripts 和原始頁面共享DOM,但是不共享JS,如要訪問頁面JS(例如某個JS變量),只能通過inject-scripts來實現。content-scripts能夠訪問的Chrome API的權限也比較低,只能訪問以下四個API:

  • chrome.extension(getURL , inIncognitoContext , lastError , onRequest , sendRequest)

  • chrome.i18n

  • chrome.runtime(connect , getManifest , getURL , id , onConnect , onMessage , sendMessage)

  • chrome.storage

Inject-scripts

inject-scripts 是通過DOM操作插入的JS代碼,通常在content-scripts只能操作DOM,但是卻無法訪問頁面的JS,借助content-scripts可以操作DOM的能力,往頁面中插入JS文件,給頁面提供調用插件API的能力,以及和background通信的能力。

在插入之前,需配置一下web可訪問的資源,同時content-scripts的調用時機換成"document_end"或者"document_idle",不然會無法獲取DOM,導致插入失敗。在manifest.json中添加以下內容:

{..."content_scripts":?[{"matches":?["<all_urls>"],"js":?["./js/content.js"],"run_at":?"document_end"},...],"web_accessible_resources":?["js/inject.js"],...
}

inject.js的內容如下:

function?mockApi?()?{console.log('this?is?from?inject.js')
}

content.js增加以下代碼:

(function?()?{let?path?=?'js/inject.js';let?script?=?document.createElement('script');script.setAttribute('type',?'text/javascript');//?注意,路徑需用Chrome API 生成,這個方法可以獲得插件的資源的真實路徑。//?類似:chrome-extension://ihcokhadfjfchaeagdoclpnjdiokfakg/js/inject.jsscript.src?=?chrome.extension.getURL(path);script.onload?=?function?()?{//?在執行完代碼之后移除script標簽this.parentNode.removeChild(this);}document.body.appendChild(script);
})();

更新插件后,頁面就可以訪問inject.js的方法:

permissions

插件后臺有的操作需要配置相應的權限,例如本地存儲、網絡請求、通知等等,示例:

{..."permissions":?["contextMenus",?//?右鍵菜單"tabs",?//?標簽"notifications",?//?通知"webRequest",?//?web請求"webRequestBlocking","storage"?//?插件本地存儲],...
}

完整的manifest配置

官方文檔:https://developer.chrome.com/extensions/manifest

通信

popup和background通信

popup可以通過 chrome.extension.getBackgroundPage() API 直接獲取到background的上下文,從而調用background的方法來通信:

//?popup.js
var?backend?=?chrome.extension.getBackgroundPage();
backend.test();?//?訪問bbackground的函數

background可以通過chrome.extension.getViews({type:'popup'}) 獲取到popup的上下文,前提是popup頁面是打開的狀態下。

let?views?=?chrome.extension.getViews({type:'popup'});
let?popup?=?null
if(views.length?>?0)?{popup?=?views[0];//?直接訪問popup的函數popup.test();
}

這里需要注意一點:

在popup頁面,你如果想編寫js,請將js編寫在一個文件里面,然后引入進來,不然會報錯,這是因為Chrome的安全政策規定的:https://developer.chrome.com/extensions/contentSecurityPolicy

popup錯誤示范:

<!DOCTYPE?html>
<html?lang="en">
<head>...
</head>
<body><p?style="width:?200px;text-align:center;">hello?world!!</p><script>//?不能直接在里面寫</script>
</body>
</html>

正確姿勢:

<!DOCTYPE?html>
<html?lang="en">
<head>...
</head>
<body><p?style="width:?200px;text-align:center;">hello?world!!</p><script?src="../js/popup.js"></script>
</body>
</html>

content-scripts和background通信

content-scripts可以通過 chrome.runtime.sendMessage(message) 給background發送消息:

chrome.runtime.sendMessage('message?content',?(res)?=>?{console.log('from?background:',?res)
});

background通過chrome.runtime.onMessage.addListener()監聽content-scripts發送的消息:

chrome.runtime.onMessage.addListener(function(message,?sender,?callback)?{console.log(mesasge);?//?meesage?contentcallback?&&?callback('yes?this?from?background')
});

background主動給content-scripts發消息,首先得查找要給哪個tab發消息,使用chrome.tabs.query 這個方法查找到tab,再使用chrome.tabs.sendMessage 方法給tab發消息:

//?{active:?true,?currentWindow:?true}?表示查找當前屏幕下的active狀態的tab;
chrome.tabs.query({active:?true,?currentWindow:?true},?function?(tabs)?{chrome.tabs.sendMessage(tabs[0].id,?'message?content',?(res)?=>?{console.log('from?content:',?res)});
});

content-scripts通過chrome.runtime.onMessage.addListener 去監聽事件:

chrome.runtime.onMessage.addListener(function?(message,?sender,?callback)?{console.log(message,?sender)callback?&&?callback('yes?this?from?content')
});

注意:

1.消息內容可以直接發送JSON格式的對象。

2.popup和content的通信方式與上面一樣。

3.如果popup和background都監聽了從content發來的消息,兩者都能收到監聽消息,但是callback只會觸發一次,被誰觸發取決與誰先發送。

inject-scripts和content-scripts

inject-scripts和content-scripts通信有兩種方法:

1.window.postMessage發送,window.addEventListener接收

2.還有一種是自定義的DOM事件;

但是很少情況會是content-scripts去調inject-scripts,因為,可以,但是沒必要....content-scripts完全可以自己處理一些API的事件監聽,況且inject-scripts也只是content-scripts生成并插入到DOM里面的,所以在content-scripts眼里,inject-scripts就是個弟弟...

但是,很多用戶觸發的事件,需要通過inject-scripts告訴content-scripts,content-scripts再給background通信并且去做一些事情,然后再發消息告訴inject-scripts,從這個角度看:content-scripts就是一個inject-scripts的工具人!

(扯平了,完美。)

inject-scripts給content-scripts發消息:

window.postMessage({"test":?'你好!工具人!'},?'*');

content-scripts接收消息:

window.addEventListener("message",?function(message)?{console.log('來了老弟!',?message.data);
},?false);

同樣的,content-scripts給inject-scripts發消息是一樣的。

練練手:HTTP Header 插件

實現一個HTTP Header 插件,可以實現動態添加header,并且給網絡請求自動加上header,header參數可以配置。

示例圖:

Background 功能設計

background復制存儲、操作headers,對所有瀏覽器請求做一層攔截,并加上啟用的headers。

注意:因為涉及到網絡請求,所以需在manifest.json中添加權限:

{..."permissions":?["storage",?//?本地存儲"webRequest",?//?網絡請求"webRequestBlocking",?//?網絡請求?阻塞式"<all_urls>"?//?匹配的URL]...
}

Background 功能偽代碼:

// headers數據結構, 附帶默認值;(可以改為本地存儲)。
const?headers?=?[{key:?'Content-Type',value:?'application/x-www-form-urlencoded',enable:?false,},{key:?'Test-Header',value:?'按F進入坦克',enable:?true,},
];//?獲取、新增、刪除、啟用禁用
function?getHeaders?()?{return?headers;
}
function?addHeader?(header)?{headers.push(header);
}
function?deleteHeader?(index)?{headers.splice(index,?1);
}function?toggleHeader(index)?{headers[index].enable?=?!headers[index].enable;
}
...//?請求攔截器
//?On?install?在被安裝的時候去初始化
chrome.runtime.onInstalled.addListener(function(){//?添加事件??chrome.webRequest.onBeforeSendHeaders.addListener(requestHandle,?{urls:?["<all_urls>"],//?攔截所有URL的請求},["blocking",?"requestHeaders"]);?//?阻塞式console.log('load');
});//?添加header
function?requestHandle(request)?{let?requestHeaders?=?request.requestHeaders;//?添加headersheaders.forEach(item?=>?{if?(item.enable)?{requestHeaders.push({name:?item.key,value:?item.value,});}});return?{requestHeaders};
}

chrome.webRequest的生命周期:

詳細參考:https://developer.chrome.com/extensions/webRequest

popup 頁面設計

popup頁面提供增加、刪除、啟用禁用功能接口,并且在每次打開popup頁面的時候去background獲取最新的header數據,展示在前臺。

popup.js 功能偽代碼:

//?popup頁面被打開時,去后臺獲取最新header
window.onload?=?function?()?{let?backend?=?chrome.extension.getBackgroundPage();//?調用background方法,獲得headerslet?headers?=?backend.getHeaders();//?渲染headercreateElement(headers);
}//?增加按鈕
function?addHeader()?{let?backend?=?chrome.extension.getBackgroundPage();let?key?=?document.querySelector('.key');let?value?=??document.querySelector('.value');let?header?=?{key:?key.value,value:?value.value,enable:?true}//?調用background方法,新增headersbackend.addHeader(header);createElement(header);
}
//?啟用禁用、刪除功能
function?toggleHeader(index)?{let?backend?=?chrome.extension.getBackgroundPage();backend.toggleHeader(index);
}function?delHeader(index)?{let?backend?=?chrome.extension.getBackgroundPage();backend.deleteHeader(index);
}

效果

打開popup,添加一個header:

隨便打開一個網頁,打開控制臺查看RequestHeaders:

總結

  • 很多權限、功能需要在manifest.json配置。

  • content-scripts、popup、background、inject-scripts擁有的權限不一樣,通信方式也不一樣,理解各個腳本的特點,組合使用。

  • 開發調試可在后臺背景頁查看信息,popup、inject-scripts、content-scripts可直接審查元素調試。

Chrome 插件還有很多功能這里沒有詳細介紹,例如devtools。感興趣的同學可以查閱下面的參考文檔。

參考文檔

官方文檔:https://developer.chrome.com/extensions

參考博客:https://www.cnblogs.com/liuxianan/p/chrome-plugin-develop.html


今天組建了一個江西人的前端交流群,如果你也是江西人可以加我微信ruochuan12 拉你進群。


你好,我是若川,江西人~(點擊藍字了解我)歷時一年只寫了一個學習源碼整體架

構系列?有哪些必看的JS庫:jQuery、underscore、lodash、sentry、vuex、axios、koa、redux

  1. 關注若川視野,回復"pdf" 領取優質前端書籍pdf,回復"1",可加群長期交流學習

  2. 我的博客地址:https://lxchuan12.gitee.io?歡迎收藏

  3. 覺得文章不錯,可以?分享、點贊、在看?呀^_^另外歡迎留言交流~

小提醒:若川視野公眾號面試、源碼等文章合集在菜單欄中間【源碼精選】按鈕,歡迎點擊閱讀,也可以星標我的公眾號,便于查找

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/276180.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/276180.shtml
英文地址,請注明出處:http://en.pswp.cn/news/276180.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

mysql 重復字段查詢及排除重復值

轉載鏈接&#xff1a;http://blog.sina.com.cn/s/blog_3edc5e2e010131ys.html mysql 重復字段查詢及排除重復值 SELECT a.id,a.title FROM dede_archives a left join dede_taglist t on t.taga.title WHERE t.typeid$id and t.arcrank>-1 and a.typeid28 group by t.tag; …

swiper移入暫停_react中swiper注意事項及鼠標劃入停止輪播

首先是實例化swiper這里有一個注意點&#xff0c;就是實例化的時機如果你的swiper內容是寫死的&#xff0c;可以在componentDidMount中實例化&#xff0c;但是如果你的內容是通過接口異步請求過來的&#xff0c;就必須在componentDidUpdate里實例化&#xff0c;因為如果在 comp…

轉Excel的一種簡單方法

寫了這么久的程序﹐越來越喜歡那種簡單的解決方法﹐這段時間在做一個報表系統﹐其中有需要轉Excel﹐而且要求兼容openoffice﹐遂利用asp語法,asp.net的控件封裝特性以及excel 2003的xml試算清格式做了一個看起來比較"清爽"的excel轉檔方案。一.開始原理很簡單﹐excel…

詳解MySQL中EXPLAIN解釋命令

轉載鏈接&#xff1a;http://database.51cto.com/art/200912/168453.htm explain顯示了mysql如何使用索引來處理select語句以及連接表。可以幫助選擇更好的索引和寫出更優化的查詢語句。 使用方法&#xff0c;在select語句前加上explain就可以了&#xff1a; 如&#xff1a;…

Shell編程基礎

我們可以使用任意一種文字編輯器&#xff0c;比如gedit、kedit、emacs、vi等來編寫shell腳本&#xff0c;它必須以如下行開始&#xff08;必須放在文件的第一行&#xff09;&#xff1a; # !/bin/sh ...注意&#xff1a;最好使用“!/bin/bash”而不是“!/bin/sh”&#xff0c;…

總結:自學前端的高效學習路線

提到前端&#xff0c;大多數人都會想到薪資高。也正因為如此&#xff0c;很多人想要從事前端開發這個崗位&#xff0c;也由此衍生出來一個問題&#xff1a;為什么前端工程師供不應求&#xff0c;但還是有很多學前端的人找不到工作&#xff1f;其實行業不是缺前端工程師&#xf…

機器人出魔切還是三相_英雄聯盟:輔助也要去上單,機器人布里茨玩法介紹

英雄聯盟&#xff1a;輔助也要去上單&#xff0c;機器人布里茨玩法介紹出裝方面我們都知道他的被動是可以將法力值化為機的護盾的&#xff0c;而這樣的話裝備就可以選擇魔切&#xff0c;然后再出一個鞋子&#xff0c;為什么不先出三項呢&#xff1f;三項的性價比是比較高的&…

vmware創建虛擬機不識別網卡

今天在給虛擬機添加網卡的時候&#xff0c;出現了虛擬機不識別新加的網卡&#xff0c;很納悶&#xff0c;連的一樣的端口組&#xff0c;為什么新加的網卡識別不了呢 然后查看pci設備&#xff0c;發現網卡的驅動為 AMD 79C970 PCnet32- LANCE 然后都vc上查看&#xff0c;果真驅動…

轉:26個Jquery使用小技巧(jQuery tips, tricks solutions)

26個Jquery使用小技巧(jQuery tips, tricks & solutions) 前段時間發布了Jquery類庫1.4版本&#xff0c;使用者也越來越多&#xff0c;為了方便大家對Jquery的使用&#xff0c;下面列出了一些Jquery使用技巧。比如有禁止右鍵點擊、隱藏搜索文本框文字、在新窗口中打開鏈接…

周末包郵送書和小紅包中獎名單公布

大家好&#xff0c;我是若川。周末送福利&#xff0c;給大家送紅包、包郵送新書&#xff01;這篇文章中&#xff0c;準備了3本自選前端新書&#xff0c;10個2元小紅包&#xff0c;在看抽10人每人5元紅包&#xff0c;2月28日晚8點開獎。現將名單公布如下&#xff1a;在看抽獎&am…

Ubuntu 命令行修改網絡配置方法

轉載鏈接&#xff1a;http://www.jb51.net/article/15807.htm Ubuntu 命令行修改網絡配置方法 /etc/network/interfaces 打開后里面可設置DHCP或手動設置靜態ip。前面auto eth0&#xff0c;讓網卡開機自動掛載. 1. 以DHCP方式配置網卡 編輯文件/etc/network/interfaces: sudo v…

python treeview底部加個按鈕_Python爬取京東商品信息(GUI版本)

前言本文的文字及圖片來源于網絡,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯系我們以作處理。作者&#xff1a;DYblog轉載&#xff1a;https://www.cnblogs.com/dy8888/p/13257918.htmlPS&#xff1a;如有需要Python學習資料的小伙伴可以加點擊…

Linux下編譯安裝Mysql簡單步驟

常規方式編譯安裝MySQL時&#xff0c;適合用第一條最正宗的MySQL產品線5.2及以前版本&#xff1a;所謂常規方式編譯安裝MySQL就是延續早期MySQL的3部曲安裝方式&#xff0c;即./configure;make;make install&#xff0c;下面是老男孩在早期的企業生產場景下操作過的具體命令及參…

Vue.js 3.0 響應式 API 比 2.x 好在哪兒?

Hello&#xff0c;各位小伙伴&#xff0c;接下來的一段時間里&#xff0c;我會把我的課程《Vue.js 3.0 核心源碼解析》中問題的答案陸續在我的公眾號發布&#xff0c;由于課程的問題大多數都是開放性的問題&#xff0c;所以我的答案也不一定是標準的&#xff0c;僅供你參考喔。…

招聘.NET程序員

人才難找啊&#xff0c;順便發個招聘啟事。 西安瀚博科技有限公司招聘.NET程序員&#xff0c;有工作經驗者優先 如有意向&#xff0c;請發郵件到 slzhanghiweb.cn 轉載于:https://www.cnblogs.com/shengli/archive/2010/03/08/1680861.html

xml解析類

轉載鏈接&#xff1a;http://zyan.cc/post/253 今天在PHP4環境下重新寫一個接口程序&#xff0c;需要大量分析解析XML&#xff0c;PHP的xml_parse_into_struct()函數不能直接生成便于使用的數組&#xff0c;而SimpleXML擴展在PHP5中才支持&#xff0c;于是逛逛搜索引擎&#x…

jmeter學習指南之聚合報告

jmeter視頻地址&#xff1a;https://edu.51cto.com/course/14305.html 上一篇文章中我們講了Jmeter結果分析最常用的一個Listener查看結果樹&#xff0c;今天接著講另一個最常用的listener--聚合報告Aggregate Report。我們先來看看聚合報告中的主要名稱的含意&#xff1a;Labe…

敏捷開發概述

敏捷方法強調適應性而非預見性。 目前列入敏捷方法的有&#xff1a; 軟件開發節奏&#xff0c;Software Development Rhythms 敏捷數據庫技術&#xff0c;AD/Agile Database Techniques 敏捷建模&#xff0c;AM/Agile Modeling 自適應軟件開發&#xff0c;ASD/Adaptive Softwar…

2021 整理的最全學習資源,送給每一個努力著的人

時間來到了 2021 年&#xff0c;新的一年有新的期待&#xff0c;而我亦有新的祝福如果說在過去的一年&#xff0c;經歷太多&#xff0c;心酸、迷茫、焦慮、幸福、喜悅那么在 2021 年&#xff0c;希望你可以去過一種遇見自己的生活&#xff0c;恬淡、熱情&#xff0c;喜歡自己而…

ubuntu+php環境下的Memcached 安裝方法

轉載鏈接&#xff1a;http://www.jb51.net/article/28887.htm Memcached是一套分散式的高速緩存系統&#xff0c;當初是Danga Interactive為了LiveJournal所發展。 目前被很多系統所使用&#xff0c;例如Flick、Twitter等。這是一套開放源代碼軟件&#xff0c;以BSD license授…