一、在局部做用域中,使用var操做符定義的變量將成爲定義該變量的做用域中的局部變量,省略var的會建立全局變量;在全局做用域中,無論是否使用var操做符定義的變量都會建立一個全局變量。可是,在全局做用域中使用var建立的全局變量是不能被delete刪除的,而未使用var建立的變量和局部做用域中未使用var操做符建立的全局變量是能夠刪除的。(與其說省略var會建立全局變量,倒不如說省略var會直接給全局對象添加一個新的屬性,由於ES中的變量只能經過var關鍵字才能建立);正則表達式
var a = 1; // 全局做用域使用var建立全局變量 b = 2; // 全局做用域未使用var建立全局變量 function fn() { c = 3; // 局部做用域未使用var,建立全局變量 } fn(); console.log(a); //1 console.log(b); //2 console.log(c); //3 delete a; //false delete b; //true delete c; //true console.log(typeof a); //number console.log(typeof b); //undefined console.log(typeof c); //undefined
二、瀏覽器中,Window對象做爲全局對象,全局做用域中聲明的變量、函數都會變成window對象的屬性和方法。然而,定義全局變量和在window對象上直接定義屬性是有差異的: 全局變量不能經過delete操做符刪除,而直接定義在window對象上的屬性能夠。緣由是經過var語句添加的window屬性的[[Configurable]]特性被設置爲false。 例如:express
var a = 10; window.b = 20; //IE < 9 時拋出錯誤,其餘瀏覽器中返回false delete window.a; //IE < 9 時拋出錯誤,其餘瀏覽器中返回true delete window.b; alert(window.a); //10 alert(window.b); //undefined
三、undefined不是關鍵字,也不是保留字。在某些低版本的瀏覽器(例如IE八、IE7)中值是能夠被修改的(在ECMAScript3中,undefined是可讀/寫的變量,能夠給它賦任意值,這個錯誤在ECMAScript5中作了修正),在其餘瀏覽器中是能夠被賦值的,可是值不會改變。另外eval、arguments之類的也不是關鍵字或者保留字,而且能夠更改它們的值。數組
undefined = 1; console.log(undefined); //undefined //IE8中 undefined = 1; console.log(undefined); //1 eval = 1; console.log(eval); //1
四、若是在文檔開始沒發現文檔聲明,則瀏覽器會默認開啓混雜模式。瀏覽器
五、typeof null 的值爲"object",含義:null值表示空對象指針。然而:app
typeof null //"object" null instanceof Object //false
六、Array類型的原型是數組ide
Array.isArray(Array.prototype ); //true Array.isArray(String.prototype ) //false
七、浮點數中必須包含一個小數點,而且小數點後面必須至少有一位數字,若是小數點後面沒有任何數字,那麼這個數字會做爲整數值保存;一樣,若是這個數小數點後面只有0(自己就是一個整數),那麼它也會做爲整數值保存。這麼作的緣由:保存浮點數值須要的內存空間是保存整數值的兩倍,爲了節省內存。函數
1..toString().length //1 (1.+"").length //1 1.0.toString().length //1 (1.0+"").length //1 (1.0000000000+"").length //1 1.0000000000.toString().length //1 (1.00000000001+"").length //13 1.00000000001.toString().length //13
八、若是整數後面跟着一個句點(.),那麼JavaScript引擎會將它理解爲前面整數的小數點測試
1.toString() //SyntaxError: identifier starts immediately after numeric literal JavaScript引擎但願看到的數字而不是toString() (1).toString() // "1" 1..toString() // "1" 1...toString() //SyntaxError: missing name after . operator
九、JavaScript中浮點數值的最高精度爲17位小數,浮點數值計算會產生舍入偏差。因此儘可能避免測試某個特定的浮點數的值。this
0.1 + 0.2 == 0.3 //false 0.1 + 0.2 == 0.30000000000000004 //true 0.8 - 0.2 //0.6000000000000001 0.8 - 0.6 //0.20000000000000007
十、NaN表示"不是數字類型",可是typeof NaN的值爲"number"。NaN不等於任何值,包括它本身。spa
typeof NaN //"number" NaN == NaN //false
十一、對未初始化和未聲明的變量執行typeof操做符都會返回undefined值。可是未初始化的變量咱們能夠進行加減乘除等操做,而未聲明的操做只能進行typeof操做,除此以外都會報錯。
b + "" // Uncaught ReferenceError: b is not defined typeof b //"undefined" var x; typeof x //"undefined" x + "abc" //"undefinedabc"
十二、ES3中引入undefined值,爲了區分空對象指針和未初始化的變量。null == undefined的結果爲true。
13、typeof正則表達式結果爲"object",可是在Chrome7及以前版本和Safari 5及以前版本的結果爲「function」。正則表達式的valueOf方法返回正則表達式自己。
typeof /123/ // "object" /123/.valueOf() // /123/
1四、數組的長度能夠修改,而且會影響數組元素的值。例如:
var arr = [1,2,3,4,5]; arr.length = 1; alert(arr[2]); //undefined
1五、函數的length屬性表示函數但願接受的命名參數的個數。
(function fn(a,b,c){}).length //3
1六、每當讀取基本類型(boolean、string、number)值的時候,後臺會建立一個對應的基本包裝類型的對象,從而讓咱們可以調用一些方法來操做這些數據。
var s1 = "abc"; s1.color = "red"; alert(s1.color); //undefined
爲何基本數據類型會有屬性?其實在第二句的時候會建立臨時的對應基本包裝類型對象並調用指定的方法,以後會被當即銷燬。
1七、命名函數表達式在建立的時候,會在當前做用域最前段添加一個新的對象{func_name:refer_function_expression},而後,將做用域鏈添加到函數表達式的[[scope]]中,接着再刪除該對象。命名函數表達式的名字只在當前函數中起做用。例如:
var x=1; if( function f(){} ){ x+=typeof f; } console.log(x); //'1undefined'
1八、函數做用域鏈 Scope = 被調用函數函數的活動對象 + [[scope]]; [[scope]]屬性在函數建立時被存儲,永遠不變,直到函數銷燬。函數能夠不被調用,但這個屬性一直存在。與做用域鏈相比,做用域鏈是執行環境的一個屬性,而[[scope]]是函數的屬性。
1九、[[scope]]屬性存儲着全部父環境的活動對象和變量對象。然而,用Function構造函數定義的函數例外,它的[[scope]]僅僅包含全局對象。
var name = "global"; function outer() { var name = "inner"; var getName = new Function('alert(name)'); getName(); } outer(); //global
20、 setTimeout(function(){},1000)並不表示在1s以後調用對應函數,它表示1s以後把對應的任務添加到任務隊列中。若是隊列是空的,那麼添加的任務會當即執行;不然就要等待前面的代碼先執行。由於JavaScript是單線程語言。一樣的原理也存在於setInterval方法上,這可能會致使這樣一種現象發生:setInterval添加的第一個任務在任務隊列中等待前面代碼的執行,第二個任務也被添加到了任務隊列中。這樣就可能會致使沒必要要的錯誤發生。所以,咱們應該儘可能不要使用setInterval間歇調用,而是用setTimeout超時調用來模擬間歇調用。
function test() { //... } setInterval(test,1000); //改成 function test() { setTimeout(test,1000); } test();
2一、call 方法可將一個函數的對象上下文從初始的上下文改變爲指定的新對象。 然而若是call方法的第一個參數爲null或者undefined,那麼call方法將把全局對象(瀏覽器中爲window)做爲新的對象上下文。
var x = "window_x"; var obj = { x: "obj_x", getX: function() { console.log(this.x); } }; obj.getX(); //obj_x obj.getX.call(null); //window_x obj.getX.call(undefined); //window_x obj.getX.call(window); //window_x obj.getX.call(document); //undefined
2二、arguments對象中除了包含傳遞到函數中的參數外,還包含了length(參數個數)、callee(當前函數的引用,嚴格模式下不能訪問)等屬性。其中,arguments中保存的參數的值與對應的命名參數的值是保持同步的,但這並不代表它們共享同一個內存地址。它們的內存空間是獨立的,其值經過JavaScript引擎來保持同步。注意,上面提到的同步有一個前提:對應索引值要小於傳入到函數中的參數個數。即若是你傳入函數2個參數,那麼arguments[2]與命名參數是互不影響的。另外,arguments的length屬性是由傳入函數的參數決定的,就算手動爲arguments添加元素,其length屬性的值是不變的,除非你手動修改length的值。例如:
function fn(a,b,x) { arguments[1] = 10; console.log(b); } fn(1,2); //10 function fn(a,b,x) { arguments[2] = 10; console.log(x); } fn(1,2); //undefined function fn(a,b,x) { x = 10; console.log(arguments[2]); } fn(1,2); //undefined function fn(a,b,x) { arguments[2] = 10; arguments[3] = 20; console.log(arguments.length); } fn(1,2); //2 function fn(a,b,x) { arguments.length = 10; console.log(arguments.length); } fn(1,2); //10
2三、arguments雖然有length屬性,並能夠經過[index]訪問對應的值,但它並非數組,然而咱們能夠經過Array.prototype.slice方法將它轉換成數組。
function fn (a,b) { console.log(Array.isArray(arguments)); //false arguments = Array.prototype.slice.call(arguments,0); console.log(Array.isArray(arguments)); //true } fn(1,2);
24、document.write()、document.writeln()能夠在頁面呈現的過程當中直接向頁面中輸出內容,但若是頁面加載結束後再調用document.write()、document.writeln(),那麼輸出的內容將會重寫整個頁面。
<body> <div> Hello <script> document.write(" world!"); </script> </div> <script> window.onload = function() { setTimeout(function() { document.write("Be Matched for Marriage!!!"); //輸出的內容將會重寫頁面 },1000); } </script> </body>
2五、在Html中每一個擁有id屬性的元素,在JavaScript中有一個與之對應的全局變量,變量名爲id的值。
<div id="myDiv"> Hello World! </div> <script> window.onload = function() { console.log(myDiv.innerHTML); // Hello World! }; </script>
26、 JavaScript語句優先原則:當{}既能夠被理解爲複合語句塊也能夠被理解爲對象直接量或函數聲明的時候,JavaScript將會將其理解成爲複合語句塊。
{a:10} //返回10,而不是對象 : 表示標籤 var x = { a:10 } // {a:10}做爲右值出現,不能是語句塊,只能理解爲對象直接量
2七、JavaScript中的加法操做其實很簡單,只能把數字和數字相加或者字符串和字符串相加,其它的全部類型都被轉換成這兩種類型相加形式。v1 + v2,轉換過程以下:
1) 若是v1或者v2是對象,則轉換爲基本數據類型;
2) 若是v1或v2爲字符串,或者本來爲對象轉換成基本數據類型後爲字符串,則把另外一個也轉換成字符串,而後執行字符串連接操做獲得結果;
3) 不然,轉換爲數字類型,返回它們的和
2八、JavaScript中的加法操做中,若是操做數爲對象,JavaScript引擎經過內部的抽象操做ToPrimitive()將其轉換爲基本數據類型:
ToPrimitive(input [, PreferredType]) PreferredType能夠是Number或者String,若是爲Number,則轉換過程以下:
1) 調用valueOf()方法,若是valueOf()返回的結果是基本數據類型,則返回結果爲轉換的結果
2) 不然,調用toString()方法,若是返回結果爲基本數據類型,則返回結果爲轉換結果
3) 不然,拋出TypeError異常
若是PreferredType爲String,則轉換操做的第一步和第二步的順序會調換。
若是省略PreferredType這個參數,則PreferredType的值會按照這樣的規則來自動設置:Date類型的對象會被設置爲String,其它類型的值會被設置爲Number。
var obj = { valueOf: function () { console.log("valueOf"); return {}; // 沒有返回基本數據類型 }, toString: function () { console.log("toString"); return {}; // 沒有返回基本數據類型 } } obj + "" // valueOf toString Uncaught TypeError: Cannot convert object to primitive value(…)
[] + [] // "" "" + "" {} + [] // 0 {}; +[] => {}; +"" 語句優先 [] + {} //"[object Object]" "" + "[object Object]" {} + {} // NaN {}; +{} => NaN 語句優先 ({}+{}) //"[object Object][object Object]" "[object Object]" + "[object Object]" ({}+[]) //"[object Object]" "[object Object]" + ""
2九、+、-具備二義性,既能夠表示一元加減操做符,又能夠表示加法(減法)操做符,其中一元加減操做符爲從右向左結合,加法(減法)操做符爲從左向右結合。一元加減操做符的優先級高於加法(減法)操做符。++、--後置遞增(遞減)優先級高於一元加減操做符,++、--前置遞增(遞減)優先級等於一元加減操做符。
var a = 1, b = 2, c; c = a++ + - + - - - + + ++b; console.log(c); //4 console.log(a); //2 console.log(b); //3
30、JavaScript中的hoisting(懸置/置頂解析/預解析):你能夠在函數的任何位置聲明多個var語句,而且它們就好像是在函數頂部聲明同樣發揮做用。
var a = 1; function fn() { console.log(a); //undefined var a = 10; console.log(a); //10 } fn(); //至關於 function fn() { var a; console.log(a); //undefined a = 10; console.log(a); //10 } fn();
3一、與hoisting相似,函數聲明也有一個重要的特徵——函數聲明提高:執行代碼以前會先讀取函數聲明。
fn(); function fn() { console.log(a); //undefined var a = 10; console.log(a); //10 }
所以下面這種寫法就不能達到想要的效果了:
if(condition) { function fn() { alert("1"); } } else { function fn() { alert("2"); } }
大多數瀏覽器會返回第二個聲明,忽略condition(這個問題在新版本的瀏覽器中作了改變);Firefox會在condition爲true時返回第一個聲明。可是咱們可使用函數表達式來達到咱們的目的:
var fn; if(condition) { fn = function() { alert("1"); } } else { fn = function() { alert("2"); } }
3二、 對於左大括號的位置每一個人有着不一樣的偏好:同一行或者另起一行。其實放在哪裏無可非議,可是因爲JavaScript中分號插入機制(semicolon insertion mechanism)的存在,某些狀況下,大括號的位置會產生沒必要要的麻煩。例如:
function fn() { return // 下面代碼不會執行 { name : "Marco" } } var out = fn(); console.log(out); //至關於 function fn() { return ; { name: "Marco" } }
3三、JavaScript中的ASI(自動分號插入機制)不是說在JavaScript引擎解析過程當中將分號添加到代碼中,而是指解析器除了分號還會以換行爲基礎按必定的規則做爲短劇的依據,來保證解析的正確性。
產生ASI的規則:
1) 新行併入當前行,構成非法語句
2) ++、--後綴表達式做爲新行開始
3) continue,break,return,throw以後
4) 代碼塊的最後一個語句
var name = "Marco" alert(name) //至關於 var name = "Marco"; alert(name) var a = 1,b = 2 a ++ b console.log(a); //1 console.log(b); //3 //至關於 a; ++b function fn() { return // 下面代碼不會執行 { name : "Marco" } } var out = fn(); console.log(out); //undefined //至關於 function fn() { return ; { name: "Marco" } } function fn() { var x = 1, y = 2 } //至關於 function fn() { var x = 1, y = 2; }
3四、不會產生ASI的規則 (承接上一點)
1)新行以 [ 開始
2)新行以 ( 開始
3)新行以 / 開始
4)新行以 . 或者 , 開始
5)新行以 + 、- 、* 、% 開始
var a = ['a1', 'a2'] var b = a [0,1].slice(1) console.log(b) //2 //至關於 var a = ['a1', 'a2'] var b = a[0,1].slice(1) //[0,1]不能解析成數組字面量,因此0,1被解析成逗號表達式,其值爲1,因此至關於a[1].slice(1) console.log(b) var x = 1 var y = x (x+y).toString() // 至關於 var x = 1 var y = x(x+y).toString() //至關於將x+y的值做爲參數調用函數並調用返回值的toString函數,由於x不是函數,因此會報錯 Uncaught TypeError: x is not a function var m = 100 var n = m /10/.test(n) // 至關於 var m = 100 var n = m/10/.test(n) // m/10會被理解爲除法運算,而第二個 / 後面但願跟的是數字繼續進行除法運算 因此會報錯 Uncaught SyntaxError: Unexpected token . var a = 1 var b = a .toString() // 至關於 var a = 1 var b = a.toString() //所以不會報錯 var a = 1 var b = a + 1 //至關於 var a = 1 var b = a + 1 //b的結果爲2
3五、使用元素的contenteditable屬性可使用戶能夠編輯該元素。同時咱們能夠經過JavaScript來開啓或者關閉元素的可編輯模式。
<div id="content" contentEditable></div> <button id="on">開啓編輯</button> <button id="off">關閉編輯</button> <script> window.onload = function() { on.onclick = function() { content.contentEditable = "true"; } off.onclick = function() { content.contentEditable = "false"; } } </script>
3六、 執行上下文的代碼被分紅兩個階段來處理:
1) 進入執行上下文 2) 執行代碼
在進入上下文時,變量對象中將會包含如下屬性:
函數全部形參(若是是在函數上下文中);全部函數聲明;全部變量聲明
function fn(a, b) { var c = 1; function d() {} var e = function _e() {}; (function f() {}); } fn(10); // call
進入上下文,代碼執行以前,活動對象表現以下:
AO(test) = { a: 10, b: undefined, c: undefined, d: <reference to FunctionDeclaration "d"> e: undefined };
3七、在執行上下文中變量、函數、形參的重名問題
1) 形參重名:形參的初始值爲最後一個同名形參所對應的實參的值,若是不存在,則爲undefined
function fn(a,a,a) { console.log(a); } fn(1,2,3); //3 fn(1,2); //undefined fn(1); //undefined
2) 變量重名:因爲hoisting,聲明被提高到開始,因此沒有影響
function fn() { console.log(a); //undefined var a = 10; console.log(a); //10 var a; console.log(a); //10 } fn();
3) 函數重名:函數聲明被提高,初始值爲最後一次聲明的函數
function fn() { console.log(1); } function fn() { console.log(2); } fn(); //2
4) 函數與形參重名:初始值爲函數
function fn(a) { console.log(a); function a() { alert("Marco"); } } fn(2); //function a() { alert("Marco");}
5)變量與函數重名:初始值爲函數
function fn() { console.log(a); var a = 10; function a() { alert("Marco"); } } fn(); //function a() { alert("Marco");}
6) 變量與形參重名:初始值爲形參
function fn(a) { console.log(typeof a); var a = 10; } fn("Marco"); //string
7) 一個形參爲arguments:arguments的初始值爲對應的實參
function fn(arguments) { console.log(arguments); } fn(1,2,3); //1
8) 一個變量聲明爲arguments: arguments的初始值爲原始值,而不是新定義的變量
function fn() { console.log(typeof arguments); var arguments = 1; } fn(1,2,3); //object
9)多個變量重名問題
在進入上下文的時候,函數聲明和變量聲明被提高到了做用域的開始,當他們出現重名的時候其初始值優先級爲:函數聲明 > 形參 > arguments > 變量聲明
3八、函數的形參是不可刪除的
function fn(a) { console.log(a); //1 delete a; console.log(a); //1 } fn(1);
3九、JavaScript中的this不是變量對象的屬性,而是執行上下文環境的屬性。當在代碼中使用this,this的值直接從執行上下文中獲取,而不會從做用域鏈中搜尋。
40、如何肯定this的值: this的值在進入上下文時肯定,而且在上下文運行期間不會改變,this的值由函數的調用方式決定。
1) 直接調用函數,this的值爲undefined(嚴格模式)或者window(非嚴格模式)
2) 做爲對象方法調用,this的值爲該對象
3) 做爲構造函數調用(new),this的值爲新建立的對象
4) call、apply顯式指定this的值
var name = "baby"; var fn; var obj = { name: 'Marco', getname: function() { console.log(this.name); } } obj.getname(); //Marco (fn = obj.getname)(); //baby 賦值表達式的結果爲所賦的值
var name = "baby"; var fn; var obj = { name: 'Marco', getname: function() { return function() { console.log(this.name); } } } obj.getname()(); //baby
4一、在定時器(setTimeout和setInterval)和requestAnimationFrame的回調函數中,不管是否使用嚴格模式,this的值都是全局對象。
var name = "baby"; var obj = { name: 'Marco', getname: function() { setTimeout(function(){ console.log(this.name); },1000); } } obj.getname(); //baby
4二、當使用new 操做函數時,該函數就成爲構造函數。new操做的過程以下:
1) 建立一個空對象obj{},this指向obj;
2) 將空對象的__proto__指向構造函數的prototype
3) 在obj的執行環境調用構造函數,若是構造函數返回值爲對象,那麼返回該對象;若是構造函數沒有顯式返回值或者返回值爲基本數據類型,那麼忽略原來的返回值,並將obj做爲新的對象返回。
舉個栗子:
function People(name) { this.name = name; } People.prototype.getName = function() { console.log("I am "+this.name); } var p = new People('Marco'); p.getName(); //I am Marco console.log(p.name); //Marco
new的模擬操做過程以下 :
var p = new People('Marco'); new People('Marco') = { var obj = {}; obj.__proto__ = People.prototype; var result = People.call(obj,"Marco"); return typeof result === 'obj' ? result : obj; }
最終的原型鏈爲: p.__proto__->People.prototype->Object.prototype->null
再來個例子:
function f(){ return f; } new f() instanceof f;//false function f(){ return this; } new f() instanceof f;//true function f(){ return 123; } new f() instanceof f;//true
因此,咱們在使用構造函數的時候儘可能避免返回值,由於若是返回值爲對象的話,就得不到咱們想要的對象實例了。
4三、全等運算符比較準則:
1) 若是數據類型不一樣,返回false
2) 若是兩個被比較的值類型相同,值也相同,而且都不是 number 類型,返回true
3) 若是兩個值都是 number 類型,當兩個都不是 NaN,而且數值相同,或是兩個值分別爲 +0 和 -0 時,兩個值被認爲是全等
4) NaN === NaN 返回false
NaN === NaN //false +0 === -0 //true
4四、相等運算符比較準則:
1) null == undefined //true
2) 若是數據類型相同,則按全等運算符的比較準則進行比較
3) 不然,若是一方爲對象類型,則將其轉換爲原始值(ToPrimitive())
4) 將被比較的值轉換爲數字,再按全等運算符的比較準則進行比較
[] == [] //false '2' == true //false '1' == true //true var a = /123/, b = /123/; a == b //fasle a === b //false
4五、in操做符的用法
1) 對於通常的對象判斷某個屬性是否屬於對象
2) 對於數組能夠指定數字形式的索引值來判斷該位置是否存在數組元素
3) 使用delete刪除屬性以後,使用in檢查該屬性返回false
4) 將屬性值設置爲undefined,使用in檢查返回true
var obj = { name: 'Marco', age: 24 } console.log('name' in obj); //true delete obj.name; console.log('name' in obj); //false obj.age = undefined; console.log('age' in obj); //true var arr = [1,2,3]; console.log(0 in arr); //true console.log(3 in arr); //false
4六、如何建立稀疏數組
1) 使用構造函數Array()
2) 簡單地指定數組索引值大於當前數組的長度
3) 使用delete操做符
4) 省略數組直接量中的值
5) 指定length屬性
var arr1 = new Array(3); var arr2 = [1,2,3]; arr2[100] = 4; var arr3 = [1,2,3]; delete arr3[0]; var arr4 = [,,,,]; var arr5 = [1,,2,]; var arr6 = new Array(); arr6.length = 3;
4七、 判斷稀疏數組
1) index in array
2)forEach
var arr = [1,,2,]; console.log(0 in arr); //true console.log(1 in arr); //false console.log(3 in arr); //false console.log(arr.length); //3 arr.forEach(function(x,i) { console.log(i + " => "+x ); //0 => 1 2 => 2 });
注意:數組直接量的語法容許有可選的結尾的逗號,因此arr只有3個元素
4八、連續大於號或者小於號的比較問題,從左向右依次進行,而且須要注意隱式轉換
3 < 2 < 1 // true 3 > 2 > 1 //false 4 > 3 < 2 > 1 == 0 //true
4九、 比較運算符
1) 若是操做數爲對象,則轉換爲原始值(ToPrimitive())
2) 轉換爲原始值後若是兩個操做數都爲字符串,則按字典順序比較
3) 若是至少一個不是字符串,則兩個操做數都轉換爲數字進行數值比較,Infinity比除本身以外的其它值都大,-Infinity比除本身以外的其它值都小,-0和+0相等。若是其中一個爲NaN,那麼返回false
var a = [1, 2, 3], b = [1, 2, 4]; console.log( a < b); //true Array.prototype.toString = function(){ return 1; } console.log( a < b ); //false
50、函數有一個非標準的屬性——name,經過它咱們能夠訪問該函數的名字,該屬性是隻讀的。(IE不支持)
function foo() { } var oldName = foo.name; foo.name = "bar"; console.log([oldName, foo.name] ); //["foo", "foo"] IE下返回 [undefined, "bar"]
原創文章:轉載請註明文章出處 http://www.cnblogs.com/MarcoHan
不少問題沒有展開講(展開講的話,估計只能講一點了),可能講的不是很詳細,你們能夠先看代碼若是瞭解能夠直接跳過解釋。若是有問題或者錯誤,歡迎指正。
以上