是時候學點新的JS了!javascript
爲了在學習NodeJs以前,能及時用上語言的新特性,咱們打算從一開始先學習一下JavaScript語言的最基本最經常使用新語法。本課程的內容,是已經假設你有過一些JavaScript的使用經驗的,並非純粹的零基礎。html
什麼是ES6?前端
因爲JavaScript是上個世紀90年代,由Brendan Eich在用了10天左右的時間發明的;雖然語言的設計者很牛逼,可是也扛不住"時間緊,任務重"。所以,JavaScript在早期有不少的設計缺陷;而它的管理組織爲了修復這些缺陷,會按期的給JS添加一些新的語法特性。JavaScript先後更新了不少個版本,咱們要學的是ES6這個版本。java
ES6是JS管理組織在2015年發佈的一個版本,這個版本和以前的版本大不同,包含了大量實用的,擁有現代化編程語言特點的內容,好比:Promise, async/await, class繼承等。所以,咱們能夠認爲這是一個革命性的版本。node
const
來定義一個常量,常量也就是不能被修改,不能被從新賦值的變量。let
來定義一個變量,而不要再使用var
了,由於var
有不少坑;能夠認爲let
就是修復了bug的var
。好比,var容許重複聲明變量並且不報錯;var的做用域讓人感受疑惑。const
,若是變量須要被修改才用let
;要理解目前不少早期寫的項目中仍然是用var
。ES6 容許咱們按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構(Destructuring)python
數組的解構賦值git
const arr = [1, 2, 3] //咱們獲得了一個數組 let [a, b, c] = arr //能夠這樣同時定義變量和賦值 console.log(a, b, c); // 1 2 3
對象的解構賦值(經常使用)es6
const obj = { name: '俊哥',address:'深圳', age: '100'} //咱們獲得了一個對象 let {name, age} = obj //能夠這樣定義變量並賦值 console.log(name, age); //俊哥 100
函數參數的解構賦值(經常使用)github
const person = { name: '小明', age: 11} function printPerson({name, age}) { // 函數參數能夠解構一個對象 console.log(`姓名:${name} 年齡:${age}`); } printPerson(person) // 姓名:小明 年齡:11
ES6 對函數增長了不少實用的擴展功能。npm
參數默認值,從ES6開始,咱們能夠爲一個函數的參數設置默認值
function foo(name, address = '深圳') { console.log(name, address); } foo("小明") // address將使用默認值 foo("小王", '上海') // address被賦值爲'上海'
箭頭函數,將function
換成=>
定義的函數,就是箭頭函數
function add(x, y) { return x + y } // 這個箭頭函數等同於上面的add函數 (x, y) => x +y; // 若是函數體有多行,則須要用大括號包裹 (x, y) => { if(x >0){ return x + y }else { return x - y } }
因爲js一開始被設計爲函數式語言,萬物皆函數。全部對象都是從函數原型繼承而來,經過繼承某個函數的原型來實現對象的繼承。可是這種寫法會讓新學者產生疑惑,而且和傳統的OOP語言差異很大。ES6 封裝了class語法來大大簡化了對象的繼承。
class Person { constructor(name, age){ this.name = name this.age = age } // 注意:沒有function關鍵字 sayHello(){ console.log(`你們好,我叫${this.name}`); } } class Man extends Person{ constructor(name, age){ super(name, age) } //重寫父類的方法 sayHello(){ console.log('我重寫了父類的方法!'); } } let p = new Person("小明", 33) //建立對象 p.sayHello() // 調用對象p的方法,打印 你們好,我叫小明 let m = new Man("小五", 33) m.sayHello() // 我重寫了父類的方法!
ES6 的新語法有不少,有人將它總結爲了一本書。固然,ES6提出的只是標準,各大瀏覽器和node基本實現了90%以上的新特性,極其個別尚未實現。咱們目前講的是最基本的一些語法,因爲大家還未了解同步和異步的概念;Promise和async/await的內容將會在後面的課程中講解。
ES6 入門教程:http://es6.ruanyifeng.com/
各大瀏覽器的支持程度:http://kangax.github.io/compat-table/es6/
我給你們講個故事。
好久好久之前,瀏覽器只能展現文本和圖片,並不能像如今這樣有動畫,彈窗等絢麗的特效。爲了提高瀏覽器的交互性,Javascript就被設計出來;並且很快統一了全部瀏覽器,成爲了前端腳本開發的惟一標準。
隨着互聯網的不斷普及和Web的迅速發展,幾家巨頭公司開始了瀏覽器之戰。微軟推出了IE系列瀏覽器,Mozilla推出了Firefox瀏覽器,蘋果推出了Safari瀏覽器,谷歌推出了Chrome瀏覽器。其中,微軟的IE6因爲推出的早,並和Windows系統綁定,在早期成爲了瀏覽器市場的霸主。沒有競爭就沒有發展。微軟認爲IE6已經很是完善,幾乎沒有可改進之處,就解散了IE6的開發團隊。而Google卻認爲支持現代Web應用的新一代瀏覽器纔剛剛起步,尤爲是瀏覽器負責運行JavaScript的引擎性能還可提高10倍,因而本身偷偷開發了一個高性能的Javascript解析引擎,取名V8,而且開源。在瀏覽器大戰中,微軟因爲解散了最有經驗、戰鬥力最強的瀏覽器團隊,被Chrome遠遠的拋在身後。。。
瀏覽器大戰和Node有何關係?
話說有個叫Ryan Dahl的歪果仁,他的工做是用C/C++寫高性能Web服務。對於高性能,異步IO、事件驅動是基本原則,可是用C/C++寫就太痛苦了。因而這位仁兄開始設想用高級語言開發Web服務。他評估了不少種高級語言,發現不少語言雖然同時提供了同步IO和異步IO,可是開發人員一旦用了同步IO,他們就再也懶得寫異步IO了,因此,最終,Ryan瞄向了JS。由於JavaScript是單線程執行,根本不能進行同步IO操做,只能使用異步IO。
另外一方面,由於V8是開源的高性能JavaScript引擎。Google投資去優化V8,而他只需拿來改造一下。
因而在2009年,Ryan正式推出了基於JavaScript語言和V8引擎的開源Web服務器項目,命名爲Node.js。雖然名字很土,可是,Node第一次把JavaScript帶入到後端服務器開發,加上世界上已經有無數的JavaScript開發人員,因此Node一會兒就火了起來。
相同點就是都使用了Javascript這門語言來開發。
瀏覽器端的JS,受制於瀏覽器提供的接口。好比瀏覽器提供一個彈對話框的Api,那麼JS就能彈出對話框。瀏覽器爲了安全考慮,對文件操做,網絡操做,操做系統交互等功能有嚴格的限制,因此在瀏覽器端的JS功能沒法強大,就像是壓在五行山下的孫猴子。
NodeJs徹底沒有了瀏覽器端的限制,讓Js擁有了文件操做,網絡操做,進程操做等功能,和Java,Python,Php等語言已經沒有什麼區別了。並且因爲底層使用性能超高的V8引擎來解析執行,和自然的異步IO機制,讓咱們編寫高性能的Web服務器變得垂手可得。Node端的JS就像是被唐僧解救出來的齊天大聖同樣,法力無邊。
NodeJS在用戶代碼層,只啓動一個線程來運行用戶的代碼。每當遇到耗時的IO操做,好比文件讀寫,網絡請求,則將耗時操做丟給底層的事件循環去執行,而本身則不會等待,繼續執行下面的代碼。當底層的事件循環執行完耗時IO時,會執行咱們的回調函數來做爲通知。
同步就是你去銀行排隊辦業務,排隊的時候啥也不能幹(阻塞);異步就是你去銀行用取號機取了一個號,此時你能夠自由的作其餘事情,到你的時候會用大喇叭對你進行事件通知。而銀行系統至關於底層的事件循環,不斷的處理耗時的業務(IO)。
可是NodeJs只有一個線程用來執行用戶代碼,若是耗時的是CPU計算操做,好比for循環100000000次,那麼在循環的過程當中,下面的代碼將會沒法執行,阻塞了惟一的一個線程。因此,Node適合大併發的IO處理,不適合CPU密集型的計算操做。Web開發大部分都是耗時IO操做,因此Node很是適合進行Web開發。若是真的遇到了CPU密集的計算,好比從1億個用戶中計算出哪些人和你興趣相投的這個功能,就很是耗CPU,那這個功能就交由C++,C,Go,Java這些語言實現。像淘寶,京東這種大型網站絕對不是一種語言就能夠實現的。
語言只是工具,讓每一種語言作它最擅長的事,才能構建出穩定,強大的系統。
在瀏覽器端寫JS,其實就是使用瀏覽器給咱們提供的功能和方法來寫代碼。
在Node端寫JS,就是用Node封裝好的一系列功能模塊來寫代碼。NodeJS封裝了網絡,文件,安全加密,壓縮等等不少功能模塊,咱們只須要學會經常使用的一些,而後在須要的時候去查詢文檔便可。
下載地址:http://nodejs.cn/download/
安裝完畢,在命令行輸入:node -v
查看node的版本,若是能成功輸出,證實安裝沒有問題。
npm是Nodejs自帶的包管理器,當你安裝Node的時候就自動安裝了npm。通俗的講,當咱們想使用一個功能的時候,而Node自己沒有提供,那麼咱們就能夠從npm上去搜索並下載這個模塊。每一個開發語言都有本身的包管理器,好比,java有maven,python有pip。而npm是目前世界上生態最豐富,可用模塊最多的一個社區,沒有之一。基本上,你所能想到的功能都不用本身手寫了,它已經在npm上等着你下載使用了。
npm的海量模塊,使得咱們開發複雜的NodeJs的程序變得更爲簡單。
學習2個知識點:
package.json
package.json
文件中?全局變量是指咱們在任何js文件的任何地方均可以使用的變量。
__dirname
:當前文件的目錄__filename
:當前文件的絕對路徑console
:控制檯對象,能夠輸出信息process
:進程對象,能夠獲取進程的相關信息,環境變量等setTimeout/clearTimeout
:延時執行setInterval/clearInterval
:定時器path模塊供了一些工具函數,用於處理文件與目錄的路徑
path.basename
:返回一個路徑的最後一部分path.dirname
:返回一個路徑的目錄名path.extname
:返回一個路徑的擴展名path.join
:用於拼接給定的路徑片斷path.normalize
:將一個路徑正常化文件操做相關的模塊
fs.stat/fs.statSync
:訪問文件的元數據,好比文件大小,文件的修改時間
fs.readFile/fs.readFileSync
:異步/同步讀取文件
fs.writeFile/fs.writeFileSync
:異步/同步寫入文件
fs.readdir/fs.readdirSync
:讀取文件夾內容
fs.unlink/fs.unlinkSync
:刪除文件
fs.rmdir/fs.rmdirSync
:只能刪除空文件夾,思考:如何刪除非空文件夾?
使用
fs-extra
第三方模塊來刪除。
fs.watchFile
:監視文件的變化
傳統的
fs.readFile
在讀取小文件時很方便,由於它是一次把文件所有讀取到內存中;假如咱們要讀取一個3G大小的電影文件,那麼內存不就爆了麼?node提供了流對象來讀取大文件。流的方式其實就是把全部的數據分紅一個個的小數據塊(chunk),一次讀取一個chunk,分不少次就能讀取特別大的文件,寫入也是同理。這種讀取方式就像水龍頭裏的水流同樣,一點一點的流出來,而不是一會兒涌出來,因此稱爲流。
const fs = require('fs') const path = require('path') // fs.readFile('bigfile', (err, data)=>{ // if(err){ // throw err; // } // console.log(data.length); // }) // 需求複製一份MobyLinuxVM.vhdx文件 const reader = fs.createReadStream('MobyLinuxVM.vhdx') const writer = fs.createWriteStream('MobyLinuxVM-2.vhdx') // let total = 0 // reader.on('data', (chunk)=>{ // total += chunk.length // writer.write(chunk) // }) // reader.on('end',()=>{ // console.log('總大小:'+total/(1024*1024*1024)); // }) reader.pipe(writer);
任務:用如下知識點完成大文件的拷貝。
fs.createReadStream/fs.createWriteStream
reader.pipe(writer)
咱們知道,若是咱們以同步的方式編寫耗時的代碼,那麼就會阻塞JS的單線程,形成CPU一直等待IO完成纔去執行後面的代碼;而CPU的執行速度是遠遠大於硬盤IO速度的,這樣等待只會形成資源的浪費。異步IO就是爲了解決這個問題的,異步能儘量不讓CPU閒着,它不會在那等着IO完成;而是傳遞給底層的事件循環一個函數,本身去執行下面的代碼。等磁盤IO完成後,函數就會被執行來做爲通知。
雖然異步和回調的編程方式能充分利用CPU,可是當代碼邏輯變的愈來愈複雜後,新的問題出現了。請嘗試用異步的方式編寫如下邏輯代碼:
先判斷一個文件是文件仍是目錄,若是是目錄就讀取這個目錄下的文件,找出結尾是txt的文件,而後獲取它的文件大小。
恭喜你,當你完成上面的任務時,你已經進入了終極關卡:Callback hell回調地域!
爲了解決Callback hell的問題,Promise
和async/await
誕生。
promise
的做用是對異步回調代碼包裝一下,把原來的一個回調函數拆成2個回調函數,這樣的好處是可讀性更好。語法以下:
語法注意:Promise內部的resolve和reject方法只能調用一次,調用了這個就不能再調用了那個;若是調用,則無效。
// 建立promise對象 let promise = new Promise((resolve, reject)=>{ // 在異步操做成功的狀況選調用resolve,失敗的時候調用reject fs.readFile('xxx.txt',(err, data)=>{ if(err){ reject(err) }else { resolve(data.toString()) } }) }); // 使用promise promise.then((text)=>{ //then方法是當Promise內部調用了resolve的時候執行 }).catch((err)=>{ //catch方法是當Promise內部調用了reject的時候執行 console.log(err); })
async/await
的做用是直接將Promise異步代碼變爲同步的寫法,注意,代碼仍然是異步的。這項革新,具備革命性的意義。
語法要求:
await
只能用在async
修飾的方法中,可是有async
不要求必定有await
。await
後面只能跟async
方法和promise
。假設擁有了一個promise對象,如今使用async/await能夠這樣寫:
async function asyncDemo() { try { // 當promise的then方法執行的時候 let text = await promise // 當你用promise包裝了全部的異步回調代碼後,就能夠一直await,真正意義實現了以同步的方式寫異步代碼 console.log('異步道明執行'); }catch (e){ // 捕獲到promise的catch方法的異常 console.log(e); } } asyncDemo() console.log('我是同步代碼');
小任務
使用promise和async/await來重寫上面的邏輯代碼,來感覺一下強大的力量吧!。
異步代碼的終極寫法:
promise
包裝異步回調代碼,可以使用node提供的util.promisify
方法;async/await
編寫異步代碼。封裝了http server 和 client的功能,就是說能夠充當server處理請求,也能夠發出請求。
http.createServer
:建立server對象http.get
:執行http get請求const http = require('http') const server = http.createServer((req, res)=>{ // console.log(`url: ${req.url} method: ${req.method}`) // res.writeHead(200, {'Content-Type':'text/plain;charset=utf-8'}) // res.end('收到了請求') router(req, res) });
// 執行get請求 // http.get("http://www.baidu.com", (res)=>{ // // console.log(res); // res.setEncoding('utf-8') // // let data = '' // res.on('data', (chunk)=>{ // data += chunk // }) // res.on('end', ()=>{ // console.log(data); // }) // })
功能需求:啓動一個服務,當用戶訪問服務時,給用戶展現指定目錄下的全部文件;若是子文件是目錄,則能繼續點進去瀏覽。
const http = require('http') const fs = require('fs') const path = require('path') const util = require('util') const server = http.createServer((req, res)=>{ console.log(req.url); // 過濾favicon.ico的請求 if(req.url === '/favicon.ico'){ res.end(''); return } showDir(req, res) }); server.listen(4000) /** * 展現出指定目錄下 的文件列表 * @param req * @param res */ async function showDir(req, res) { let target = 'html' if(req.url !== '/'){ target = req.url } const preaddir = util.promisify(fs.readdir) let files = await preaddir(path.join(__dirname, target)) // html -> html/aaa -> html/aaa/ccc let lis = ''; for (let i = 0; i < files.length; i++) { let file = files[i]; let stat = await util.promisify(fs.stat)(path.join(__dirname, target, file)) if(stat.isDirectory()){ let p = target + '/' + file lis += `<li><a href="${p}">${file}</a></li>` }else { lis += `<li>${file}</li>` } } res.writeHead(200, {'Content-type': 'text/html;charset=utf-8'}) res.end(makeHtml(lis)) } function makeHtml(lis) { return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>文件瀏覽器</title> <style> *{padding:0;margin:0} ul{ padding: 15px; background-color:#eee; } ul>li{ list-style: none; padding: 10px; background-color:#eee; transition: all 1s; } li:hover{ background-color:#aaa; } li:not(:first-child){ border-top: 1px solid #ccc; } </style> </head> <body> <ul>${lis}</ul> </body> </html> ` }