NodeJs01 文件瀏覽器

ES6經常使用新語法

前言

是時候學點新的JS了!javascript

爲了在學習NodeJs以前,能及時用上語言的新特性,咱們打算從一開始先學習一下JavaScript語言的最基本最經常使用新語法。本課程的內容,是已經假設你有過一些JavaScript的使用經驗的,並非純粹的零基礎。html

ES6新語法

什麼是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
        }
    }

Class繼承

因爲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/


Node的發展歷史和異步IO機制

故事的開端

我給你們講個故事。

好久好久之前,瀏覽器只能展現文本和圖片,並不能像如今這樣有動畫,彈窗等絢麗的特效。爲了提高瀏覽器的交互性,Javascript就被設計出來;並且很快統一了全部瀏覽器,成爲了前端腳本開發的惟一標準。

瀏覽器之戰

隨着互聯網的不斷普及和Web的迅速發展,幾家巨頭公司開始了瀏覽器之戰。微軟推出了IE系列瀏覽器,Mozilla推出了Firefox瀏覽器,蘋果推出了Safari瀏覽器,谷歌推出了Chrome瀏覽器。其中,微軟的IE6因爲推出的早,並和Windows系統綁定,在早期成爲了瀏覽器市場的霸主。沒有競爭就沒有發展。微軟認爲IE6已經很是完善,幾乎沒有可改進之處,就解散了IE6的開發團隊。而Google卻認爲支持現代Web應用的新一代瀏覽器纔剛剛起步,尤爲是瀏覽器負責運行JavaScript的引擎性能還可提高10倍,因而本身偷偷開發了一個高性能的Javascript解析引擎,取名V8,而且開源。在瀏覽器大戰中,微軟因爲解散了最有經驗、戰鬥力最強的瀏覽器團隊,被Chrome遠遠的拋在身後。。。

Node的誕生

瀏覽器大戰和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一會兒就火了起來。

瀏覽器端JS和Node端JS的區別

相同點就是都使用了Javascript這門語言來開發。

瀏覽器端的JS,受制於瀏覽器提供的接口。好比瀏覽器提供一個彈對話框的Api,那麼JS就能彈出對話框。瀏覽器爲了安全考慮,對文件操做,網絡操做,操做系統交互等功能有嚴格的限制,因此在瀏覽器端的JS功能沒法強大,就像是壓在五行山下的孫猴子。

NodeJs徹底沒有了瀏覽器端的限制,讓Js擁有了文件操做,網絡操做,進程操做等功能,和Java,Python,Php等語言已經沒有什麼區別了。並且因爲底層使用性能超高的V8引擎來解析執行,和自然的異步IO機制,讓咱們編寫高性能的Web服務器變得垂手可得。Node端的JS就像是被唐僧解救出來的齊天大聖同樣,法力無邊。

理解NodeJS的事件驅動和異步IO

NodeJS在用戶代碼層,只啓動一個線程來運行用戶的代碼。每當遇到耗時的IO操做,好比文件讀寫,網絡請求,則將耗時操做丟給底層的事件循環去執行,而本身則不會等待,繼續執行下面的代碼。當底層的事件循環執行完耗時IO時,會執行咱們的回調函數來做爲通知。

同步就是你去銀行排隊辦業務,排隊的時候啥也不能幹(阻塞);異步就是你去銀行用取號機取了一個號,此時你能夠自由的作其餘事情,到你的時候會用大喇叭對你進行事件通知。而銀行系統至關於底層的事件循環,不斷的處理耗時的業務(IO)。

可是NodeJs只有一個線程用來執行用戶代碼,若是耗時的是CPU計算操做,好比for循環100000000次,那麼在循環的過程當中,下面的代碼將會沒法執行,阻塞了惟一的一個線程。因此,Node適合大併發的IO處理,不適合CPU密集型的計算操做。Web開發大部分都是耗時IO操做,因此Node很是適合進行Web開發。若是真的遇到了CPU密集的計算,好比從1億個用戶中計算出哪些人和你興趣相投的這個功能,就很是耗CPU,那這個功能就交由C++,C,Go,Java這些語言實現。像淘寶,京東這種大型網站絕對不是一種語言就能夠實現的。

語言只是工具,讓每一種語言作它最擅長的事,才能構建出穩定,強大的系統。

NodeJs能作什麼?


NodeJs經常使用模塊

前言

在瀏覽器端寫JS,其實就是使用瀏覽器給咱們提供的功能和方法來寫代碼。

在Node端寫JS,就是用Node封裝好的一系列功能模塊來寫代碼。NodeJS封裝了網絡,文件,安全加密,壓縮等等不少功能模塊,咱們只須要學會經常使用的一些,而後在須要的時候去查詢文檔便可。

NodeJs下載與安裝

下載地址:http://nodejs.cn/download/

安裝完畢,在命令行輸入:node -v查看node的版本,若是能成功輸出,證實安裝沒有問題。

npm介紹

npm是Nodejs自帶的包管理器,當你安裝Node的時候就自動安裝了npm。通俗的講,當咱們想使用一個功能的時候,而Node自己沒有提供,那麼咱們就能夠從npm上去搜索並下載這個模塊。每一個開發語言都有本身的包管理器,好比,java有maven,python有pip。而npm是目前世界上生態最豐富,可用模塊最多的一個社區,沒有之一。基本上,你所能想到的功能都不用本身手寫了,它已經在npm上等着你下載使用了。

npm的海量模塊,使得咱們開發複雜的NodeJs的程序變得更爲簡單。

學習2個知識點:

  • 怎麼生成package.json
  • 怎麼從npm安裝包,並保存到package.json文件中?

全局變量

全局變量是指咱們在任何js文件的任何地方均可以使用的變量。

  • __dirname:當前文件的目錄
  • __filename:當前文件的絕對路徑
  • console:控制檯對象,能夠輸出信息
  • process:進程對象,能夠獲取進程的相關信息,環境變量等
  • setTimeout/clearTimeout:延時執行
  • setInterval/clearInterval:定時器

path模塊

path模塊供了一些工具函數,用於處理文件與目錄的路徑

  • path.basename:返回一個路徑的最後一部分
  • path.dirname:返回一個路徑的目錄名
  • path.extname:返回一個路徑的擴展名
  • path.join:用於拼接給定的路徑片斷
  • path.normalize:將一個路徑正常化

fs模塊

文件操做相關的模塊

  • 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:監視文件的變化

stream操做大文件

傳統的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)

Promise和asnyc/await

咱們知道,若是咱們以同步的方式編寫耗時的代碼,那麼就會阻塞JS的單線程,形成CPU一直等待IO完成纔去執行後面的代碼;而CPU的執行速度是遠遠大於硬盤IO速度的,這樣等待只會形成資源的浪費。異步IO就是爲了解決這個問題的,異步能儘量不讓CPU閒着,它不會在那等着IO完成;而是傳遞給底層的事件循環一個函數,本身去執行下面的代碼。等磁盤IO完成後,函數就會被執行來做爲通知。

雖然異步和回調的編程方式能充分利用CPU,可是當代碼邏輯變的愈來愈複雜後,新的問題出現了。請嘗試用異步的方式編寫如下邏輯代碼:

先判斷一個文件是文件仍是目錄,若是是目錄就讀取這個目錄下的文件,找出結尾是txt的文件,而後獲取它的文件大小。

恭喜你,當你完成上面的任務時,你已經進入了終極關卡:Callback hell回調地域!

爲了解決Callback hell的問題,Promiseasync/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來重寫上面的邏輯代碼,來感覺一下強大的力量吧!。

異步代碼的終極寫法:

  1. 先使用promise包裝異步回調代碼,可以使用node提供的util.promisify方法;
  2. 使用async/await編寫異步代碼。

http 模塊

封裝了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>
    `
}
相關文章
相關標籤/搜索