JavaScript 知識點整理

JavaScript是按照ECMAScript標準設計和實現的,後文說的JavaScript語法實際上是ES5的標準的實現。
先說說有哪些基礎語法?面試

最基礎語法有哪些?

基礎語法幾乎全部的語言差別不大,無非數據類型、操做符、控制語句、函數等,簡單列舉下。正則表達式

5種基本數據類型 & 1種複雜的數據類型

JavaScript包含5種基本數據類型,分別是undefined / null /boolean / number / string,基本數據類型就這五種,沒有其餘的!
JavaScript包含1種複雜的數據類型,就是Object類型,Object類型是全部其餘對象的基類。
注意:JavaScript並不區分浮點數和整數,都是用Number來表示。算法

前面提到的5種基本數據類型,以及這兒的1種複雜數據類型,這就是數據類型的所有了!編程

基本操做符

這個是常識,知道怎麼回事就好。
經常使用的操做符包括:算術操做符、關係操做符、布爾操做符、賦值操做符等。數組

控制語句

這就是咱們常說的if-else之類的控制語句。
經常使用的並很少:if語句、switch語句、for語句、while語句、for-in語句。瀏覽器

函數

函數就是一小段邏輯的封裝,理論上邏輯越獨立越好。
JavaScript函數相對其餘語言來講有很大不一樣。JavaScript函數既能夠做爲參數,也能夠做爲返回值。
此外JavaScript函數能夠接受任意數量的參數,而且能夠經過arguments對象來訪問這些參數。閉包

任何一門語言的基礎語法都是相通的,除開一些細節差別,大體就是上面這些了:數據類型、操做符、控制語句、函數、模塊等等。app

接下來介紹稍微複雜的一些概念。函數

變量、做用域、內存問題

變量

JavaScript變量分爲兩種:基本類型和引用類型。其中基本類型就是前面提到的5種基本數據類型,引用類型就是前面提到的Object以及基於它的其餘複雜數據類型。
✦ 基本類型:在內存中佔據實際大小的空間,賦值的時候,會在內存中建立一份新的副本。保存在棧內存中。
✦ 引用類型:指向對象的指針而不是對象自己,賦值的時候,只是建立了一個新的指針指向對象。保存在堆內存中。學習


變量內存分配

一句話就是,基本類型在內存中是實際的值;而引用類型在內存中就是一個指針,指向一個對象,多個引用類型可能同時指向同一個對象。

那麼,如何肯定某個變量是哪一種數據類型呢?
肯定一個變量是哪一種基本類型用typeof操做符。
肯定一個變量是哪一種引用類型用instanceof操做符。
這個別忘了!

做用域

變量是在某個特定的做用域中聲明的,做用域決定了這些變量的生命週期,以及哪些代碼能夠訪問其中的變量。
JavaScript做用域只包括全局做用域和函數做用域,並不包含塊級做用域!

做用域是能夠嵌套的,從而造成做用域鏈。因爲做用域鏈的存在,可讓變量的查找向上追溯,即子函數能夠訪問父函數的做用域=>祖先函數的做用域=>直到全局做用域,這種函數咱們也稱爲閉包,後文會介紹。

var color = "blue";

function changeColor() {
    var anotherColor = "red";

    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
        // 這裏能夠訪問color、anotherColor、tempColor 
    }
    // 這裏能夠訪問color、anotherColor,但不能訪問tempColor
    swapColors();
}
// 這裏只能訪問color、changeColor();

以下圖所示,每一個做用域可以訪問到的變量以及嵌套的做用域可向上追溯。


做用域鏈

做用域的概念看着簡單,實際使用會有很多問題,遇到問題要細心分析。

內存問題

JavaScript引擎具備自動垃圾回收機制,不須要太關注內存分配和垃圾回收問題。這兒就不展開了!

引用類型

前面提過,Object是惟一的複雜數據類型,引用類型都是從Object類型上繼承而來。
✦ Array:數組類型
✦ Date:日期類型
✦ RegExp:正則表達式類型,這個多學學有好處!
✦ 等等...
那問題來了,咱們用的最多的函數是什麼數據類型呢?答案是Function類型!
誒,好像發現了點什麼東西?因爲Function是引用類型,而JavaScript又能夠往引用類型上加屬性和方法。那麼,函數也能夠!這也是JavaScript函數強大和複雜的地方。也就是說:函數也能夠擁有自定義方法和屬性!

此外,JavaScript對前面提到的5種基本類型的其中3種也作了引用類型封裝,分別是Boolean、Number、String,但其實使用很少,瞭解就行。

對了,在全部代碼執行以前,做用域就內置了兩個對象,分別是Global和Math,其中瀏覽器的Global就是window啦!

到此爲止,JavaScript中基礎的概念都差很少介紹了,其中函數和做用域相對來講複雜一些,其餘的都比較淺顯。
接下來,我會介紹介紹JavaScript中一些稍微複雜一些的概念:面向對象。

面向對象編程

JavaScript自己並無類和接口的概念了,面向對象都是基於原型實現的。
爲了簡單,咱們只分析面向對象的兩個問題:
✦ 如何定義一個類?
✦ 如何實現類的繼承

定義一個類

不扯其餘的,直接告訴你。咱們使用構造函數+原型的方式來定義一個類。

使用構造函數建立自定義類型,而後使用new操做符來建立類的實例,可是構造函數上的方法和屬性在每一個示例上都存在,不能共享,因而咱們引入原型來實現方法和屬性的共享。


原型

最後,咱們將須要共享的方法和屬性定義在原型上,把專屬於實例的方法和屬性放到構造函數中。到這兒,咱們就經過構造函數+原型的方式定義了一個類。

// 構造函數
function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];

}
// 原型
Person.prototype = {
    constructor: Person,
    sayName: function() {
        return this.name;
    }
}
// 實例化
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.friends.push("Van");
alert(person1.friends);                     //輸出"Shelby,Count,Van"
alert(person2.friends);                     //輸出"Shelby,Count"
alert(person1.friends === person2.friends);        //輸出false
alert(person1.sayName === person2.sayName);        //輸出true
實現繼承

前文講了如何定義一個類,那麼咱們定義一個父類,一個子類。
如何讓子類繼承父類呢?不扯別的,直接告訴你。JavaScript經過原型鏈來實現繼承!
如何構建原型鏈呢?將子類實例賦值給父類構造函數的原型便可。好繞,可是千萬得記住了!


原型鏈繼承

構建原型鏈以後,子類就能夠訪問父類的全部屬性和方法!

// 父類
function SuperType() {
    this.property = true;
}
SuperType.prototype.getSuperValue = function() {
    return this.property;
};

// 子類
function SubType() {
    this.subproperty = false;
}

//子類繼承父類
SubType.prototype = new SuperType();

//給子類添加新方法
SubType.prototype.getSubValue = function() {
    return this.subproperty;
};
//重寫父類的方法
SubType.prototype.getSuperValue = function() {
    return false;
};

// 實例化
var instance = new SubType();
console.log(instance.getSuperValue()); //輸出false

面向對象的知識能夠用一本書來寫,這兒只是簡單的介紹下最基礎最經常使用的概念。

函數表達式

JavaScript中有兩種定義函數的方式:函數聲明和函數表達式。
使用函數表達式無須對函數命名,從而實現動態編程,也即匿名函數。有了匿名函數,JavaScript函數有了更強大的用處。

遞歸

遞歸是一種很常見的算法,經典例子就是斐波拉契數列。也不扯其餘的,直接說遞歸的最佳實踐,上代碼:

// 最佳實踐,函數表達式
var factorial = (function f(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * f(num - 1);
    }
});

// 缺點:
// factorial存在被修改的可能
// 致使 return num * factorial(num - 1) 報錯
function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * factorial(num - 1);
    }
}

// 缺點:
// arguments.callee,規範已經不推薦使用
function factorial(num) {
    if (num <= 1) {
        return 1;
    } else {
        return num * arguments.callee(num - 1);
    }
}

遞歸就是這樣,好多人還在使用arguments.callee的方式,改回函數表達式的方式吧,這纔是最佳實踐。

囉嗦一句,好多人以爲遞歸難寫,其實你將其分爲兩個步驟就會清晰不少了。
✦ 邊界條件,一般是if-else。
✦ 遞歸調用。
按這個模式,找幾個經典的遞歸練練手,就熟悉了。

閉包

不少人常常以爲閉包很複雜,很容易掉到坑裏,其實否則。

那麼閉包是什麼呢?若是一個函數能夠訪問另外一個函數做用域中的變量,那麼前者就是閉包。因爲JavaScript函數能夠返回函數,天然,建立閉包的經常使用方式就是在一個函數內部建立另外一個函數!
這並無什麼神奇的,在父函數中定義子函數就能夠建立閉包,而子函數能夠訪問父函數的做用域。
咱們一般是由於被閉包坑了,纔會被閉包嚇到,尤爲是面試題裏一堆閉包。

閉包的定義前面提了,如何建立閉包也說了,那麼咱們說說閉包的缺陷以及如何解決?

/* 咱們經過subFuncs返回函數數組,而後分別調用執行 */

// 返回函數的數組subFuncs,而這些函數對superFunc的變量有引用
// 這就是一個典型的閉包
// 那麼有什麼問題呢?
// 當咱們回頭執行subFuncs中的函數的時候,咱們獲得的i其實一直都是10,爲何?
// 由於當咱們返回subFuncs以後,superFunc中的i=10
// 因此當執行subFuncs中的函數的時候,輸出i都爲10。
// 
// 以上,就是閉包最大的坑,一句話理解就是:
// 子函數對父函數變量的引用,是父函數運行結束以後的變量的狀態
function superFunc() {
    var subFuncs = new Array();
    for (var i = 0; i < 10; i++) {
        subFuncs[i] = function() {
            return i;
        };
    }

    return subFuncs;
}

// 那麼,如何解決上訴的閉包坑呢?
// 其實原理很簡單,既然閉包坑的本質是:子函數對父函數變量的引用,是父函數運行結束以後的變量的狀態
// 那麼咱們解決這個問題的方式就是:子函數對父函數變量的引用,使用運行時的狀態
// 如何作呢?
// 在函數表達式的基礎上,加上自執行便可。
function superFunc() {
    var subFuncs = new Array();
    for (var i = 0; i < 10; i++) {
        subFuncs[i] = function(num) {
            return function() {
                return num;
            };
        }(i);
    }
    return subFuncs;
}

綜上,閉包自己不是什麼複雜的機制,就是子函數能夠訪問父函數的做用域。
而因爲JavaScript函數的特殊性,咱們能夠返回函數,若是咱們將做爲閉包的函數返回,那麼該函數引用的父函數變量是父函數運行結束以後的狀態,而不是運行時的狀態,這即是閉包最大的坑。而爲了解決這個坑,咱們經常使用的方式就是讓函數表達式自執行。
此外,因爲閉包引用了祖先函數的做用域,因此濫用閉包會有內存問題。

好像把閉包說得一無可取,那麼閉包有什麼用處呢?
主要是封裝吧...

封裝

閉包能夠封裝私有變量或者封裝塊級做用域。
➙ 封裝塊級做用域
JavaScript並無塊級做用域的概念,只有全局做用域和函數做用域,那麼若是想要建立塊級做用域的話,咱們能夠經過閉包來模擬。
建立並當即調用一個函數,就能夠封裝一個塊級做用域。該函數能夠當即執行其中的代碼,內部變量執行結束就會被當即銷燬。

function outputNumbers(count) {
    // 在函數做用域下,利用閉包封裝塊級做用域
    // 這樣的話,i在外部不可用,便有了相似塊級做用域
    (function() {
        for (var i = 0; i < count; i++) {
            alert(i);
        }
    })();

    alert(i); //致使一個錯誤! 
}

// 在全局做用域下,利用閉包封裝塊級做用域
// 這樣的話,代碼塊不會對全局做用域形成污染
(function() {
    var now = new Date();

    if (now.getMonth() == 0 && now.getDate() == 1) {
        alert("Happy new year!");
    }
})();

// 是的,封裝塊級做用域的核心就是這個:函數表達式 + 自執行!
(function() {
    //這裏是塊級做用域
})();

➙ 封裝私有變量
JavaScript也沒有私有變量的概念,咱們也可使用閉包來實現公有方法,經過隱藏變量暴露方法的方式來實現封裝私有變量。

(function() {
    //私有變量和私有函數
    var privateVariable = 10;

    function privateFunction() {
        return false;
    }

    //構造函數
    MyObject = function() {};
    //公有/特權方法
    MyObject.prototype.publicMethod = function() {
        privateVariable++;

        return privateFunction();
    };
})();

總結說點啥?

這差很少就是JavaScript的一些基礎語法和稍微高級一些的用法,其實所謂的高級,都是JavaScript「不太成熟」的表現,尤爲是面向對象,出於工程化的須要可是JavaScript自己並不完美支持。好在ES6最新標準解決了不少問題,結合Babel用起來也不用太考慮兼容性問題,若是你是新手的話,建議你直接去擼ES6+Babel吧。

✦ JavaScript的基礎主要包括:5中基本數據類型、1種複雜的數據類型、操做符、控制語句、函數等。
✦ 瞭解基本的語法後,你還須要學習學習JavaScript的變量、做用域、做用域鏈。
✦ 常見的引用類型能夠邊查邊用。做爲過來人,建議多學學正則,對你的代碼功底會有較大的提高。
✦ 面向對象編程的部分外面有不少種方式,你只須要記住使用構造函數+原型去定義一個類,使用原型鏈去實現繼承便可。更多的擴展,去翻翻書吧。
✦ 函數表達式引出了幾個比較好玩的東西:遞歸、閉包、封裝。記住遞歸的最佳實踐、閉包的定義及缺陷、閉包的適用場景。

JavaScript做爲一門動態語言,和其餘語言有較大的差別,這也形成不少人學習JavaScript時會以爲難學。但你如今看看前文,雖然是一個簡略的總結,但JavaScript主要的內容就這些了,因此不要被本身嚇到了。
再補一句,若是你是新手的話,建議你直接去擼ES6+Babel吧。



文/齊修_qixiuss(簡書做者) 原文連接:http://www.jianshu.com/p/66f3aef3e131# 著做權歸做者全部,轉載請聯繫做者得到受權,並標註「簡書做者」。
相關文章
相關標籤/搜索