一、概述
JavaScript是世界上最流行的腳本語言,是一種運行在瀏覽器中的解釋型的編程語言,能夠實現跨平台、跨瀏覽器。雖然只是十多天時間的產物,並且有很多的缺陷和陷阱,但也造就了JavaScript的靈活和強大。隨着Node.js的興起,JavaScript已經從單純實現前端互動,發展到可以全棧實現整個應用。尤其是在移動互聯網蓬勃發展以及應用追求極致用戶體驗的今天,JavaScript更是必須要重視和掌握的。
二、變量
1、變量的聲明
使用var關鍵字進行變量的聲明,聲明的同時也可以進行賦值。變量的聲明只能有一次,但賦值可以有多次。如果只聲明變量,但是變量沒有被賦值,此時變量的值為undefined。
var s = "Hello";
由於JavaScript屬於動態語言,所以在定義變量時,不需要指定變量的類型。可以把任意數據類型賦值給變量,同一個變量可以使用不同的數據類型反復賦值。
2、變量的提升
根據JavaScript的規則,變量的聲明會被提升,但是變量的初始化並不會被提升。函數外聲明的變量,會被提升到文檔頂部。函數內聲明的變量,會被提升到函數頂部。
function f() {
var x = 1 + y;
alert(x);
var y = 2;
}
alert(x);
如果變量在函數內沒有聲明,該變量會被提升為全局變量。
function f() {
x = 1;
}
alert(x);
3、變量的作用域
如果變量在函數內聲明,則該變量的作用域為整個函數體,在函數外不可訪問,屬於局部變量。
function f() {
var x = 1;
}
alert(x);
函數可以嵌套,內部函數可以訪問外部函數定義的變量,反之則不可。
function outer() {
var x = 1;
function inner() {
var y = x + 1;
alert(y);
}
var z = y + 1;
alert(z);
}
如果內部函數與外部函數定義了相同的變量,根據就近原則,內部函數的變量會覆蓋外部函數的變量。
function outer() {
var x = 1;
function inner() {
var x = 2;
alert(x);
}
}
三、函數
1、函數的定義
定義函數有兩種方式:聲明和表達式。
1.1 函數聲明
function function_name(parameters) {
......
}
函數聲明后不會馬上執行,只有被調用的時候才會執行。
1.2 函數表達式
var f = function (parameters) {
......
};
f(parameters);
將函數存儲在變量中,之后可以通過變量名來調用,而不需要函數名稱。
2、函數的參數
函數有個內置的對象arguments,包含了函數調用的所有參數。
參數有兩種傳遞方式:值傳遞和引用傳遞。
2.1 值傳遞
函數只是獲取參數值,如果修改參數的值,不會改變參數的原始值,在函數外是不可見的。
2.2 引用傳遞
函數獲得的是對象的引用,在函數內部修改對象屬性時,會改變屬性的原始值,在函數外可見。
3、函數的調用
函數可以通過四種方式進行調用。
3.1 自我調用
函數表達式,如果后面緊跟(),則會自動自我調用。聲明的函數不能自我調用。
(function () {
alert("Hello");
})();
3.2 作為函數被調用
當函數沒有被其他對象調用時,函數默認是window全局對象的函數。此時this的值是window全局對象。
function f() {
alert("Hello");
return this;
};
f();
3.3 作為方法被調用
可以將函數定義為對象的方法,該對象是函數的所有者。當使用對象調用方法時,此時的this值指向調用函數的對象。
var obj = {
x : 1,
y : 2,
f : function () { return this.x + this.y;}
};
obj.f();
3.4 作為構造函數被調用
如果使用new關鍵字,實際是創建了一個新的對象。新對象會繼承構造函數的屬性和方法。此時this值指向新創建的對象。為了區分普通函數和構造函數,約定構造函數的首字母為大寫。
function F(x, y) {
this.x = x;
this.y = y;
};
var obj = new F(1, 2);
3.5 call和apply
函數是一個對象,可以調用call或apply方法。此時this值指向方法傳遞的對象。
function f(x, y) {
return x+y;
};
arr = [1, 2];
f.call(obj, 1, 2);
f.apply(obj, arr);
call和apply方法的唯一區別就是前者傳遞整個參數列表,而后者傳遞一個參數數組。
4、閉包
內嵌函數可以訪問上一層函數的變量。閉包是可以訪問上一層函數作用域里變量的函數。如果上一層函數返回閉包函數,那么就可以在上一層函數的外部訪問該函數的私有變量。利用閉包,可以實現調用公共方法,訪問私有變量。
此時返回的函數並沒有立刻執行,只有在調用返回函數時才真正執行。因為返回函數不會馬上執行,所以返回函數不要引用循環變量或后續會發生變化的變量。否則,在真正執行的時候,引用的變量可能已經發生改變。
如果必須引用的話,可以再創建一個函數,用該函數的參數綁定循環變量的值。這樣,循環變量的值雖然發生了改變,但綁定到參數的值不會隨之改變。
四、對象
1、對象的定義
對象的定義有兩種方式。
由若干鍵值對組成,並將其賦值給一個變量。
var person = {
name : "sean",
age : "20",
sayHello : function() {
alert("Hello " + name);
}
};
person.sayHello();
使用函數定義對象,然后創建新的對象實例。
function person(name, age) {
this.name = name;
this.age = age;
};
var p = new person("sean", 20);
2、屬性和方法
由於JavaScript的對象是動態類型,所以可以給一個對象添加或刪除屬性。當訪問的屬性不存在時,會返回undefined。如果要判斷一個屬性是否屬於某對象,可以使用in操作符。這里要注意的是,該屬性可能是對象繼承得來的。如果要判斷一個屬性是否屬於某對象自身,可以使用hasOwnProperty方法。
3、原型
雖然JavaScript可以面向對象編程,但所有的對象都是實例,不會區分類和實例的概念。要想實現類的概念,就需要借助原型。
首先創建一個相當於類概念的對象實例。
var person = {
name : "",
age : "",
sayHello : function() {
alert("Hello " + this.name);
}
};
然后創建一個新的對象實例,使其依賴這個原型。
var sean = {
name : "sean"
};
sean.__prototype__ = person;
sean.sayHello();
也可以使用Object.create()方法,傳入一個原型對象,並創建一個基於該原型的新對象。
var sean = Object.create(person);
sean.name = "sean";
sean.sayHello();
4、創建對象
JavaScript對每個創建的對象都會設置一個原型,指向該對象的原型對象。除了直接創建一個對象外,還可以使用構造函數來創建對象。使用構造函數創建的對象,會從原型獲得一個constructor屬性,該屬性指向構造函數本身。
5、原型鏈
當訪問一個對象的屬性時,首先在當前對象上查找該屬性。如果沒有找到,就到其原型對象上查找。如果還沒有找到,就一直上溯到Object的原型對象。如果還沒有找到,就返回undefined。這樣就形成了一條原型鏈。
因為函數也是對象,所以函數也擁有原型鏈。首先是函數本身,然后是Function.prototype,最后是Object.prototype。
6、繼承
繼承不僅僅是對象的屬性和方法的繼承,原型鏈也要體現出繼承關系。
首先,定義新的構造函數,並在內部調用希望繼承的構造函數,實現屬性和方法的繼承。
其次,借助中間函數,將新的構造函數的原型指向繼承的構造函數的原型,實現原型鏈繼承。
最后,將新的構造函數的constructor屬性重新指向新的構造函數。
7、瀏覽器對象
window對象不但是全局對象,還表示瀏覽器窗口。
屬性innerWidth和innerHeight分別表示瀏覽器窗口的內部寬度和高度,即用於顯示網頁的凈寬高。屬性outerWidth和outerHeight分別表示瀏覽器窗口的整個寬度和高度。
navigator對象表示瀏覽器的信息。屬性appName表示瀏覽器名稱。屬性appVersion表示瀏覽器版本。屬性language表示瀏覽器設置的語言。屬性platform表示操作系統類型。屬性userAgent表示瀏覽器設定的User-Agent字符串。
screen對象表示屏幕的信息。屬性width表示屏幕寬度,單位是像素。屬性height表示屏幕高度,單位是像素。屬性colorDepth表示顏色位數。
location對象表示當前頁面的URL信息。屬性href表示整個URL。屬性protocal表示使用的協議。屬性host表示主機名。屬性port表示端口號。屬性pathname表示訪問的路徑。屬性search表示查詢參數。方法assign加載一個新頁面。方法reload重載當前頁面。
document對象表示當前頁面。屬性title表示瀏覽器窗口的標題。屬性cookie獲取當前頁面的Cookie。方法getElementById按照元素的id獲取一個DOM節點。方法getElementByTabName按照元素的標簽名稱獲取一組DOM節點。
history對象保存瀏覽器的歷史記錄。
五、DOM
當頁面被加載時,瀏覽器解析HTML文檔,並會創建頁面的文檔對象模型DOM。DOM是一個樹形結構,通過操作DOM節點,可以訪問或改變文檔的所有元素。針對DOM的操作主要有四種:查找、更新、插入、刪除。
1、查找
要想操作DOM節點,首先需要找到要操作的節點。
-
通過id查找
調用document.getElementById方法查找指定id的元素。
-
通過標簽名查找
調用document.getElementByTagName方法查找指定標簽名的所有元素。
-
通過類名查找
調用document.getElementByClassName方法查找指定class的所有元素。
Tips:要精確地定位DOM,可以先定位父節點,再逐步縮小范圍。
2、更新
定位好節點之后,就可以進行更新操作。
-
修改innerHTML屬性
不但可以修改一個DOM節點的文本內容,還可以通過HTML片段修改DOM節點內部的子樹。
-
修改innerText或textContent屬性
可以對字符串進行HTML編碼,保證無法設置任何HTML標簽。這兩個屬性的區別是前者不返回隱藏元素的文本,而后者則返回所有文本。
-
修改style屬性
可以用來設置CSS樣式。如果是無效的屬性名,可以在JavaScript中使用駝峰式命名屬性。
3、插入
可以插入新的節點,從而改變DOM結構。
-
appendChild方法
把一個子節點添加到父節點的最后一個子節點。如果子節點已經存在,會先從原來的位置刪除,然后再插入到新的位置。通常會新創建一個節點,然后插入到指定位置,實現動態添加節點。
-
insertBefore方法
父節點會將新建的子節點插入到參照節點之前。
4、刪除
可以刪除節點,從而改變DOM結構。
-
removeChild方法
首先獲得要刪除的節點和它的父節點,然后調用父節點的方法將其刪除。遍歷父節點的子節點並進行刪除操作時,父節點的children屬性會在子節點發生變化時實時更新,需要特別注意。
六、異步處理
JavaScript語言是單線程的,一次只能完成一個任務。好處是實現簡單,壞處是如果有任務耗時很長,其他的任務就必須得排隊等待,直到該任務完成。導致的結果就是造成瀏覽器無響應,其他任務無法執行,體驗極差。
因此,JavaScript語言分成兩種任務執行模式:同步和異步。所謂同步,就是指程序的執行順序與任務的排列順序是一致的、同步的。所謂異步,是指每個任務有一個或多個回調函數,后一個任務無需等待前一個任務完成就可執行,當前一個任務完成后,執行回調函數。這樣,程序的執行順序與任務的排列順序是不一致的、異步的。
1、回調函數
這是異步處理最常用的的方法。
兩個同步執行的函數:
f1();
f2();
兩個異步執行的函數:
function f1(callback){
setTimeout(function () {
// f1的任務代碼
callback();
}, 1000);
}
f1(f2);
回調函數的優點是簡單、容易理解。缺點是不利於閱讀和維護、高耦合、流程混亂,每個任務只能指定一個回調函數。
2、事件監聽
采用事件驅動模式,由事件來決定要進行的處理,而不是代碼的順序。
為f1綁定一個事件,當f1發生done事件,就執行f2:
f1.on('done', f2);
執行完成后,立即觸發done事件,從而開始執行f2:
function f1(){
setTimeout(function () {
// f1的任務代碼
f1.trigger('done');
}, 1000);
}
事件監聽的優點是容易理解,可以綁定多個事件,每個事件可以指定多個回調函數,低耦合。缺點是流程不夠清晰。
3、發布/訂閱
與事件監聽類似。某個任務執行完成之后,會發布一個信號,其他任務可以訂閱這個信號,從而知道什么時候可以開始執行。又稱為觀察者模式。
訂閱信號:
jQuery.subscribe("done", f2);
發布信號:
function f1(){
setTimeout(function () {
// f1的任務代碼
jQuery.publish("done");
}, 1000);
}
取消訂閱:
jQuery.unsubscribe("done", f2);
可以了解存在多少信號、每個信號有多少訂閱者,從而監聽程序的運行。
4、Promises對象
該對象是CommonJS提出的一種規范,旨在為異步編程提供統一接口。
每一個異步任務返回一個Promises對象:
function f1(){
var dfd = $.Deferred();
setTimeout(function () {
// f1的任務代碼
dfd.resolve();
}, 500);
return dfd.promise;
}
該對象有一個then方法,可以指定回調函數:
f1().then(f2);
優點是回調函數可以使用鏈式寫法,流程清晰,並且如果一個任務已經完成,再添加回調函數,該回調函數會立刻執行。缺點是編寫和理解比較困難。
參考資料: