第一章:javascript: 數據結構與算法

在前端工程師中,經常有一種聲音,咱們爲何要學數據結構與算法,沒有數據結構與算法,咱們同樣很好的完成工做。
實際上,算法是一個寬泛的概念,咱們寫的任何程序均可以稱爲算法,甚至往冰箱裏放大象,也要經過開門,放入,關門這樣的規劃,咱們也能夠視做爲一種算法。能夠說:簡單的算法是人類的本能。而算法的知識的學習則是吸收前人的經驗。對於複雜的問題進行歸類,抽象,幫助咱們脫離刀耕火種的時代,系統掌握一個算法的過程。javascript

隨着自身知識的增加,不管是作前端,服務端仍是客戶端,任何一個程序員都會開始面對更加複雜的問題,算法和數據結構就變得更不可或缺了前端

前端工程師應該是最須要重視算法和數據結構基礎的人。數據結構和算法的又沒有照顧到入門的須要,因此前端工程師若是自身不重視算法和數據結構這樣的基礎知識,極可能陷入一直重複勞動不多有成長的這樣職業發展困境。將來的網頁UI,毫不是靠幾個選擇器操做加連接就能應付的。愈來愈複雜的產品和基礎庫,須要堅實的數據結構與算法才能駕馭。java

在過去的幾年中,得益於Node.js和SpiderMonkey等平臺,javascript愈來愈多的用於服務器端編程,介於javascript已經走出了瀏覽器,程序員發現他們須要更多的傳統語言(好比C++和JAVA),提供的工具。這些工具包括傳統的數據結構(如鏈表,棧,隊列,圖等)也包括傳統的排序和查找算法。本系列博文在使用javascript進行服務器端編程時,如何使用這些數據結構和算法。c++

javascript程序員會發現本系列內容頗有用,由於本書討論了在javascript語言的限制下,如何實現數據結構和算法。這些限制包括:數組即對象,無處不在的全局變量,基於原型的對象模型等。javascript做爲一種編程語言,名聲不大好,本系列博文將展現javascript好的一面,去實現如何高效的數據結構和算法。程序員

在那些學校沒有學習過計算機的程序員來講,惟一熟悉的數據結構就是數組。在處理一些問題時,數組無疑是很好的選擇,但對於不少複雜的問題,數組就顯得太過簡陋。大多數程序員都緣由認可這樣一個事實:對於不少編程問題,當他們提出一個合適的數據結構,設計和實現解決這些問題的算法就變得手到擒來算法

二叉查找數(BST)就是這樣一個例子。設計二叉差找樹的目的就是爲了方便查找一組數據中的最小值和最大值,因爲這個數據結構天然引伸出一個查找算法,該算法比目前最好的查詢算法效率還高。不熟悉二叉查找樹的程序員可能會使用一個更簡單的數據結構,但效率上就打了個折扣。shell

學習算法很是重要,由於解決一樣的問題,每每可使用多種算法。對於高效程序員來講,知道那種算法效率高很是重要。好比,如今至少有六七種排序算法,若是知道快速排序比選擇排序效率更高,那麼就會讓排序過程變得高效。又好比,實現一個線性查找的算法很簡單,可是若是知道有時二分查找可能比線性查找快兩倍以上,那麼你勢必會寫出一個更好的程序。編程

深刻學習數據結構和算法,不只能夠知道那種數據結構和算法更高效,還會知道如何找出最適合解決手頭問題的數據結構和算法。寫程序,尤爲適用javascript寫程序時 ,常常要權衡。數組

javascript的編程環境和模型瀏覽器

本章描述了javascript編程環境和基本的編程模塊,後續章節將使用這些知識定義數據結構和實現各類算法。

1.javascript從來都是在瀏覽器裏運行的程序語言,然而在過去的幾年中,這些狀況發生了變化,javascript能夠做爲桌面程序執行。或者在服務器上執行。介紹一種編程環境:javascript shell,這是MOzilla提供的綜合javascript編程環境SpiderMonkey中的一部分。

2.聲明和初始化變量

javascript變量默認是全局變量,嚴格的說,甚至不須要在使用前進行聲明。若是對一個事先未聲明的javascript變量進行初始化,該變量就變成了一個全局變量。全部,咱們在編程中,遵循c++或java等編譯型語言的習慣,在使用變量前進行聲明,這樣作的好處就是:聲明的變量都是局部變量。本章稍後將部分詳細討論變量的做用域

在javascript中聲明變量,須要使用關鍵字var,後面跟變量名,或後面跟隨一個賦值表達式。

    var number;
    var name;
    var rate = 1.2;
    var greeting = "hello world!";
    var flag = false;

3.javascript中的算術運算符和數學庫函數

javascript使用標準的算術運算符

+ 加 , - 減 , * 乘 , / 除 , % 去餘


javascript同時擁有一個數學庫,用來完成一些高級運算,好比平方根,絕對值和三角函數。算術運算符遵循標準的運算順序,能夠用括號來改變運算順序。

    var x = 3;
    var y = 1.1;
    console.log(x + y)
    console.log(x * y)
    console.log((x + y) * (x - y));
    var z = 9;
    console.log(Math.sqrt(z));
    console.log(Math.abs(y/x))

若是計算精度沒必要像上面那樣精確,能夠將數字轉換爲固定精度

    var x = 3;
    var y = 1.1;
    var z = x * y;
    console.log(z.toFixed(2));

2.判斷結構

根據布爾表達式的值,判斷結構讓程序能夠執行到那句能夠執行的程序的語句。經常使用的有if和switch語句

if有以下三種形式

  • 簡單的if語句
  • if-else語句
  • if-else-if語句

下面演示簡單的if語句

    var mid = 25;
    var height = 50;
    var low = 1;
    var current = 13;
    var found = -1;
    if (current < mid) {
        mid = (current - low) / 2
    }


下面演示if - else語句

    var mid = 25;
    var height = 50;
    var low = 1;
    var current = 13;
    var found = -1;
    if (current < mid) {
        mid = (current - low) / 2;
    } else {
        mid = (current + height) / 2;
    }

下面演示if - else -if

    var mid = 25;
    var height = 50;
    var low = 1;
    var current = 13;
    var found = -1;
    if (current < mid) {
        mid = (current - low) / 2;
    } else if (current > mid) {
        mid = (current + height) /2
    } else {
        found = current
    }

另一個判斷結構是switch語句,在有多個簡單選擇時,使用該語句的代碼更加清晰。

 3.循環結構

經常使用的兩種循環結構:while循環和for循環

若是但願條件爲真時,只執行一組語句,就選擇while循環,下面展現了while循環的工做原理

while循環

    var number = 1;
    var sum = 0;
    while (number < 11) {
        sum += number;
        ++number
    }
    console.log(sum) //55

若是但願按執行次數執行一組語句,就選擇for循環。下面是for循環求1到10的整數累加和

    var number = 1;
    var sum = 0;
    for (var number =1 ; number < 11; number++){
        sum += number;
    }
    console.log(sum)

訪問數組時,常用到for循環,以下:

    var number = [3,7,12,22,100];
    var sum = 0;
    for (var i = 0; i < number.length; ++i) {
        sum += number[i];
    }
    console.log(sum)//144

4.函數
javascript提供了兩種自定義函數的方式,一種有返回值,一種沒有返回值(這種函數有時叫作子程或者void函數)

下面展現瞭如何自定義一個有返回值的函數如何在javascript調用該函數。

    function factorial (number) {
        var product = 11;
        for (var i = number; i >= 1; --i) {
            product *= 1
        }
        return product;
    }
    console.log(factorial(4))

下面的例子展現如何定義一個沒有返回值的函數,使用該函數並非爲了獲得它的返回值,而是爲了執行函數中定義的操做。

javascript中的子程或者void函數

    function curve(arr, amount) {
        for (var i = 0; i < arr.length; ++i) {
            arr[i] += amount;
        }
    }

    var grades = [77,73,74,81.90];
    curve(grades,5);
    console.log(grades)

javascript中,函數的參數傳遞方式都是按值傳遞,沒有按引用傳遞的參數。可是javascript有保存引用的對象,好比數組,如上例,它們是按引用傳遞的


5.變量做用域

變量的做用域是指一個變量在程序中那些對象能夠訪問。javascript中的變量做用域被定義爲函數做用域
這是指變量的值在定義該變量的函數內是可見的。而且定義在該函數內的嵌套函數也能夠訪問該變量。

在主程序中,若是在函數的外部定義一個變量,那麼該變量擁有全局做用域,這是指能夠在包括函數體內的程序的任何部分訪問該變量。下面用一段簡短的程序展現做用域的工做原理。

    function showScope(){
        return scope;
    }
    var scope = "global";
    console.log(scope); //global
    console.log(showScope()); //global

showScope()函數內定義的變量scope擁有局部做用域,而在主程序中定義變量scope是一個全局變量。儘管兩個變量名字相同,但它們的做用域不一樣,在定義他們的地方訪問時獲得的值也不同。

這些行爲都是正常且符合預期的,可是若是在定義變量時省略了關鍵字var,那麼一切都變了。javascript容許在定義變量時不使用關鍵字var,可是這樣作的後果是定義的變量自動擁有了全局做用域,即便你在一個函數內定義該變量,它也是全局變量。

下面的例子是濫用全局變量的惡果。

    function showScope(){
        scope = "local";
        return scope;
    }

    scope = "global";
    console.log(scope); //global
    console.log(showScope());  //local
    console.log(scope) //local

上面的例子中,因爲showScope()函數內定義變量scope時省略了關鍵字var。因此在將字符串"local"賦值給該變量時,其實是改變了主程序中scope變量的值。所以,在定義變量時,應該老是var開始,避免發生相似錯誤。


前面咱們提到,javascript擁有的是函數做用域,其含義是javascript沒有塊級做用域,這一點有別於其它不少現代的編程語言。使用塊級做用域,能夠在一段代碼中定義變量,該變量只在塊內可見,離開這段代碼塊就不見了。在C++或者java的for循環中,咱們常常看到這樣的例子

    for (int i = 1; i <= 10; ++i) {
        count << "hello world!" << end;
    }

雖然javascript沒有塊級做用域,但在咱們寫for循環時,咱們假設它有:

    for (var i = 1; i <=10; ++i) {
        console.log("hello,world!")
    }

這樣作的緣由是,不但願本身養成壞編程習慣的幫手!

6.遞歸

javascript中容許函數遞歸調用,前面定義過的factorial()函數也可使用遞歸方式定義

    function factorial(number) {
        if (number == 1) {
            return number
        } else {
            return number * factorial(number - 1)
        }
    }

    console.log(factorial(5)) //120

當一個函數遞歸調用時,當遞歸沒有完成時,函數的計算結果暫時會被掛起。爲了說明這個過程,這裏用一副圖展現以5做爲參數,調用factorial()函數時函數執行的過程。

5 * factorial(4)
5 * 4 * factorial(3)
5 * 4 * 3 * factorial(2)
5 * 4 * 3 * 2 * factorial(1)
5 * 4 * 3 * 2 * 1
5 * 4 * 3 * 2 
5 * 4 * 6
5 * 24
120


對於大多數狀況,javascript有能力處理層次較深的遞歸調用;但保不齊有些算法須要的遞歸深度超出了javascript的處理能力,這時候,咱們就須要尋求改算法的一種迭代方式的解決方案了。任何能夠被遞歸定義的函數,均可以被改寫爲迭代的程序,這點要牢記於心

對象和麪向對象編程

數據結構都被要實現爲對象。javascript提供了不少種方式來建立和使用對象。下面作展現,建立對象,並用於建立和使用對象中的方法和屬性。

對象經過以下方式建立:定義包含屬性和方法聲明的構造函數,並在構造函數後緊跟方法的定義。下面是一個檢查銀行帳戶對象的構造函數:

    function Checking(amount){
        this.balance = amount; //屬性
        this.deposit = deposit; //方法
        this.withdraw = withdraw;//方法
        this.toString = toString;//方法
    }

this關鍵字用來將方法和屬性綁定到一個對象的實例上,下面看看咱們對上面聲明過的方法是如何定義的

    function deposit(amount) {
        this.balance += amount;
    }

    function withdraw(amount) {
        if (amount <= this.balance) {
            this.balance -= amount;
        }
        if (amount > this.balance) {
            console.log("Insufficient funds");
        }
    }

    function toString(){
        return "Balance:" + this.balance;
    }

這裏,咱們又一次的使用this關鍵字和balance屬性,以便讓javascript解釋器指定咱們引用的是那個對象的balance屬性

下面給出checking對象的完整定義和測試

    function Checking(amount){
        this.balance = amount; //屬性
        this.deposit = deposit; //方法
        this.withdraw = withdraw;//方法
        this.toString = toString;//方法
    }

    function deposit(amount) {
        this.balance += amount;
    }

    function withdraw(amount) {
        if (amount <= this.balance) {
            this.balance -= amount;
        }
        if (amount > this.balance) {
            console.log("餘額不足");
        }
    }

    function toString(){
        return "餘額:" + this.balance;
    }

    var account = new Checking(500);
    account.deposit(1000);
    console.log(account.toString());//餘額:1500

    account.withdraw(750);
    console.log(account.toString());//餘額:750

    account.withdraw(800); //餘額不足
    console.log(account.toString());//餘額:750

ps:編寫出讓人容易閱讀的代碼和編寫出讓計算機能正確執行的代碼同等重要,做爲負責任的程序員,必須將這一點牢記於心。

相關文章
相關標籤/搜索