自測連接>> 在js25題、js21題或者js85題測驗你的知識掌握。javascript
js25題筆記html
1. 使用typeof bar === "object"
可能遇到的陷阱和解決方法java
在 JavaScript 裏使用 typeof 來判斷數據類型,只能區分基本類型,即 「number」,」string」,」undefined」,」boolean」,」object」 五種。jquery
陷阱:若是bar是null,js會視之爲object類型。數組
解決:判斷bar是object但非null: 安全
console.log((bar !== null) && (typeof bar === "object"));
擴展:判斷bar是數組: typeof只能區分基本類型,它將function,array,object都視爲object類型,因此要區分這三類對象就不能用typeof方法了,能夠用toString.call()的返回值來判斷:閉包
console.log((bar !== null) && (typeof bar === "object") && (! $.isArray(bar)));
或者用jq的isArray() isFunction()來判斷:app
onsole.log((bar !== null) && (typeof bar === "object") && (toString.call(bar) !== "[object Array]"));
ECMA 5.1 中關於Object.prototype.toString.call() 的描述:函數
When the toString method is called, the following steps are taken:this
If the this value is undefined, return 「[object Undefined]「.
If the this value is null, return 「[object Null]「.
Let O be the result of calling ToObject passing the this value as the argument.
Let class be the value of the [[Class]] internal property of O.
Return the String value that is the result of concatenating the three Strings 「[object ", class, and "]「.
對全部值類型應用 Object.prototype.toString.call() 方法結果以下:
console.log(Object.prototype.toString.call(123)) //[object Number] console.log(Object.prototype.toString.call('123')) //[object String] console.log(Object.prototype.toString.call(undefined)) //[object Undefined] console.log(Object.prototype.toString.call(true)) //[object Boolean] console.log(Object.prototype.toString.call({})) //[object Object] console.log(Object.prototype.toString.call([])) //[object Array] console.log(Object.prototype.toString.call(function(){})) //[object Function]
2.不要寫容易引發誤會的代碼
(function(){ var a = b = 3; })(); console.log("a defined? " + (typeof a !== 'undefined')); console.log("b defined? " + (typeof b !== 'undefined'));
陷阱: 誤覺得函數內至關於
var b = 3; var a = b;
事實上是
b = 3; var a = b;
所以若是不是在strict mode下,b被當成全局變量。因此應該是輸出b有定義,a無定義。
3.在內聯函數中,ECMA5以前this指向window,而在ECMA5 內聯函數的this變成undefined。
4.閉包的應用
閉包的例子:
function f1(){ var privateA = 1; function f2(){//至關於getter方法 return privateA; }; f3 = function(n){//至關於setter方法,注意,這裏的f3是一個全局變量 privateA = n; }; return f2; } var f4 = f1(); console.log(f4());//1 f3(2); console.log(f4());//2
閉包的特色和做用:
1.保護函數內的變量安全,限定訪問接口,實現JS私有屬性和私有方法。f1的變量只有f2能訪問,f2至關於f1私有變量的getter方法。
2.在內存中維持一個變量。f4持有f2,f2引用着f1,所以f4存在的時候,f2和f1也會駐留在內存中(GC不會回收,所以使用閉包時也要留意內存泄露的問題)
5.jQ爲了處理多庫共存,使用noConflict()來轉移$使用權,而當即調用表達式能夠在加載時就初始化,造成一個單例模式的效果,故而使用當即調用表達式能夠解決js變量污染問題。若是jq的$使用權轉移以後還想用jq,結合當即調用表達式的做用,下面這個方法使咱們仍然能使用jq:
(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);
示例:
<!DOCTYPE html> <html> <head> <title></title> </head> <body> <div id="foo"> </div> </body> <script type="text/javascript" src="jquery.min.js"></script> <script type="text/javascript"> $.noConflict(); ;(function ($) { $("#foo").html("我仍是能使用jq!") })(jQuery); $("#foo").html("這裏就用不了jq了!")//報錯,$找不到 </script> </html>
6.use strict 使用嚴格模式有什麼好處
7.return的陷阱
當return單獨處於一行時,javascript會自動補全爲return;
<!DOCTYPE html> <html> <head> <title></title> </head> <body> <script type="text/javascript"> function foo1() { return { bar: "hello" }; } function foo2() { return { bar: "hello" }; } console.log("foo1 returns:"); console.log(foo1());//Object {bar: "hello"} console.log("foo2 returns:"); console.log(foo2()) //!!foo2的返回值爲 undefined </script> </body> </html>
8.NaN的陷阱
NaN表示一個非數字類型的值。與任何值比較都返回false,包括它本身。用數學函數操做都會返回NaN。
var a = NaN; console.log(a);//NaN console.log(a===a);//false console.log(a>0);//false console.log(Math.max(a));//NaN
可是使用typeof比較時卻會判斷它爲數值類型。
console.log(typeof NaN === "number"); // "true"
判斷NaN的方法能夠用isNaN(),雖然這個方法存在不足, 有一個更好的方法是使用Number.isNaN()
Number.isNaN(NaN); // true Number.isNaN(Number.NaN); // true Number.isNaN(0 / 0) // true // 使用全局的isNaN(),下面會返回true Number.isNaN("NaN"); // false Number.isNaN(undefined); // false Number.isNaN({}); // false Number.isNaN("blabla"); // false isNaN("NaN"); // true isNaN(undefined); // true isNaN({}); // true isNaN("blabla"); // true // 使用Number.isNaN()和全局isNaN()都返回false Number.isNaN(true); Number.isNaN(null); Number.isNaN(37); Number.isNaN("37"); Number.isNaN("37.37"); Number.isNaN(""); Number.isNaN(" ");
9.for循環中變量做用域的問題
for (var i = 0; i < 5; i++) { var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); btn.addEventListener('click', (function(i) { return function() { console.log(i); }; })(i)); document.body.appendChild(btn); }
學ECMAScript 6的時候遇過這個問題,點擊任意按鈕都是輸出5,由於在onclick觸發前,for循環已經結束了,點擊按鈕時輸出的i是for循環結束後的i,因此都是5.爲了輸出0,1,2,3這樣的值,咱們能夠從i的做做用域和onclick的執行時機下手。如下有三個方法,其中前兩個方法是基於i的做用域處理,後一個則是限制onclick的執行時機(用了當即調用方式)
for (let i = 0; i < 5; i++) { //let讓i的做用域在每次執行循環時都獨立出來 var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); btn.addEventListener('click', (function(i) { return function() { console.log(i); }; })(i)); document.body.appendChild(btn); }
['a', 'b', 'c', 'd', 'e'].forEach(function (value, i) { //['a', 'b', 'c', 'd', 'e']一樣是將每次循環的i分隔開來 var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); btn.addEventListener('click', function() { console.log(i); }); document.body.appendChild(btn); });
for (var i = 0; i < 5; i++) { var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); (function (i) { btn.addEventListener('click', function() { console.log(i); }); })(i);//當即調用表達式使btn.addEventListener在循環中完成初始化並加載 document.body.appendChild(btn); }
10.reverse,push,concat的使用
var arr1 = "john".split(''); var arr2 = arr1.reverse(); var arr3 = "jones".split(''); arr2.push(arr3); console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1)); console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1)); //輸出: //"array 1: length=5 last=j,o,n,e,s" //"array 2: length=5 last=j,o,n,e,s"
經過上面這段代碼來鞏固reverse,push和concat的使用。
首先,reverse會改變數組自己(arr1)而且返回一個數組引用(不是返回數組arr1,是返回arr1的引用),所以arr2 = arr1.reverse() 使得arr2指向arr1,從而對arr2的操做也會映射到arr1上。
此時arr1與arr2指向同一個對象,值爲[ 'n' , 'h' , 'o' , 'j' ]。
其次,push在插入一個數組時,會把數組當成一個元素插進去,並且直接改變數組。所以arr2.push(arr3)的結果是[ 'n' , 'h' , 'o' , 'j' ,['j' ,'o', 'n' ,'e' ,'s']]。從中咱們知道push和concat的區別就是concat是將數組中的元素一個一個拼接到前一個數組中,並且不直接改變對象,要將拼接後的對象從新返回給原對象(使用時 arr = arr.concat(xxx); <== > arr.push(xxx);)。而push是把元素整個放入對象結尾,因此遇到數組時push是往數組中放入array對象,concat是拼接數組。
所以在上面這段代碼中,輸出arr1最後一個元素時便輸出了arr3這個數組(push時arr3被看成最後一個元素加入arr1指向的數組對象),而arr1和arr2指向的對象相同,因此輸出二者內容都是同樣的。
11.利用事件隊列解決堆棧溢出
下面這份代碼在list特別大的時候會致使堆棧溢出
var list = readHugeList(); var nextListItem = function() { var item = list.pop(); if (item) { // process the list item... nextListItem(); } };
能夠巧妙地用事件隊列解決這個問題
var list = readHugeList(); var nextListItem = function() { var item = list.pop(); if (item) { // process the list item... setTimeout( nextListItem, 0); } };
經過上面的方法,用事件隊列處理遞歸,而不是調用堆棧。當運行nextListItem時,若是item不爲空(未到列表最後一個),這個計時方法(nextListItem)會被加入事件隊列,同時該方法也就退出了調用堆棧,這樣nextListItem方法會被一直加入到事件隊列,知道item爲空時開始逐個執行事件隊列裏的方法。與此同時,調用堆棧一直爲空。
12. || 與 &&
0 || 1 = 1 //0爲false,繼續計算下一個操做數1,最終獲得1
1 || 2 = 1 //1爲true,忽略下一個操做數2,最終獲得1
0 && 1 = 0 //0爲false,忽略下一個操做數1,最終獲得0
1 && 2 = 2 //1爲true,繼續計算下一個操做數1,最終獲得2
13. == 與 ===
==判斷值相等,===判斷值與類型均相同。
14.js在設置對象屬性時,會自動吧參數字符串化。
var a={}, b={key:'b'}, c={key:'c'}; a[b]=123; a[c]=456; console.log(a[b]); //456
緣由:b和c都是object,放入a時字符串化獲得[object object] 。所以a[b],a[c]其實都是a["[object object]"],輸出的值天然就是最後賦值的那一個了。
15.bind的用法(IE678不支持)
var hero = { _name: 'John Doe', getSecretIdentity: function (){ return this._name; } }; var stoleSecretIdentity = hero.getSecretIdentity;//undefined console.log(stoleSecretIdentity());//John Doe var stoleSecretIdentity2 = hero.getSecretIdentity.bind(hero);//John Doe