WEB開發面面談之(5)——寫JS時必須注意的的一些問題

更多詳情請看http://blog.zhangbing.club/%E...javascript

下面例舉了平常前段開發中遇到的場景,解決方案有不少,但從開發階段就進行規範,能夠很大程度避免不少後續的潛在和兼容問題。html

獲取body元素

非標準作法java

document.body

W3C規範方法c#

document.getElementsByTagName('body').item(0)

使用jQuery/Zepto數組

$('body');

獲取窗口標題

非標準作法瀏覽器

document.title

W3C規範方法緩存

document.getElementsByTagName('title').item(0).innerHTML

使用jQuery/Zepto安全

$('title').text()

監聽iframe的加載完成事件

  • 寫法1:
iframe.onload = function() {...}

問題:存在兼容性問題,IE六、7無效app

  • 寫法2:
iframe.onload = iframe.onreadystatechange = function(){...}

問題:邏輯複雜,事件綁定邏輯混亂,在某些瀏覽器上onload和onreadystatechange都會觸發,須要另外加標記位判斷,邏輯複雜。異步

  • 簡潔而徹底兼容的寫法:
var bindIframeOnloadEvent = function(el, onload) {
 if (el.attachEvent){
    el.attachEvent("onload", onload);
 } else {
   el.onload = onload;
 }
};
bindIframeOnloadEvent(iframe, function(){...});

如何操做iframe內部的window

  • 寫法1:
iframe.contentWindow

問題: 部分瀏覽器不兼容(IE67),獲取失敗

  • 寫法2:
document.frames[frameId]

問題: 非標準調用,兼容性是問題,強制必須爲iframe添加ID。

  • 簡潔而徹底兼容的寫法:
var getIframeWindow = function(el) {
return el.contentWindow || el.contentDocument.parentWindow;
};
var win = getIframeWindow(iframe);

設置iframe的邊框

  • 寫法1:
iframe.boder = 0;

問題: 非W3C標準,後面極可能廢棄,部分瀏覽器不必定支持

  • 寫法2:
iframe.style.boder = 'none';

問題: 徹底依賴CSS控制,但存在兼容性問題,IE繼續頭疼

  • 最終解決方案:
iframe.boder = 0;
iframe.style.boder = 'none';

如何在a標籤上綁定鼠標點擊事件

  • 寫法1:
<a href="javascript:func();">test</a>

問題:

  1. 不符合CSP規範
  2. 等價於全局eval。只能調用公開的全局方法,污染全局變量
  3. 鼠標懸停時,狀態欄會顯示要運行的代碼?!這對最終用戶不友好
  4. 運行代碼的上下文是window對象,和事件處理模型相違背
  • 寫法2:
<a href="#" onclick="func();">test</a>

問題:

  1. 不符合CSP規範
  2. onclick和href在部分瀏覽器(IE繼續躺槍)行文詭異,執行衝突異常
  3. 等價於全局eval。只能調用公開的全局方法,污染全局變量(緣由同寫法1)
  • 寫法3:
<a href="#" onclick="func();return false;">test</a>

問題:只解決了問題2,其他問題仍存在

  • 標準寫法:
<a id='aTest'>test</a>
<script>
document.getElementById('aTest').onclick = function() {
func();
};
</script>

使用jQuery/Zepto亦可,存在惟一的小問題是鼠標指針不是手形,是默認。可採用CSS樣式來解決cursor:pointer 。

script標籤的書寫方法深挖

要點

  1. script標籤的type屬性不是必須的,默認缺省就是text/javascript
  2. script標籤的language屬性徹底無用(asp時代微軟彷佛使用該屬性來標記服務端語言是vb仍是c#),不要多此一舉
  3. 動態建立的script標籤必需要指定type='text/javascript',不然JS不會執行
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = '###';
document.getElementsByTagName('body').item(0).appendChild(script);
  1. 動態建立的script追加動做是異步的,並不會馬上取得script運行結果,若是要等待加載完成須要監聽完成事件
  2. 使用非標準或者比較新的屬性須要格外注意,不要使代碼邏輯依賴於這些特性。如defer/async屬性
  3. 使用script.onerror來監聽腳本執行失敗的狀況(語法錯誤,初始化運行時錯誤等都會觸發)
  4. 監聽script的完成事件比較複雜。
varbindScriptOnloadEvent = function(script, onload) {
    var done = false;
    script.onload = script.onreadystatechange = function() {
    if (!done && (!this.readyState || this.readyState == 'loaded' || this.readyState == 'complete')) {
        done = true;
        script.onload = script.onreadystatechange = null;
        onload();
        }
    };
};
bindScriptOnloadEvent(script, function(){...});

須要考慮兼容性,因此代碼較多

substr函數不要使用

緣由:非標準,在部分瀏覽器報錯,甚至連個人Android4.0上的瀏覽器都不認該函數

替代方案:使用substring函數。

jQuery/Zepto選擇器的.text()和.html()方法

現狀:大多數開發同窗會混淆二者並亂用,不清楚什麼時候用哪一個

詳解:.text()方法用於獲取和設置文本內容,.html()方法用戶獲取和設置HTML內容,當要設置或獲取的內容僅僅爲文本時,二者行爲徹底相同,但要操做的文本內容是HTML時,行爲有着本質區別。

總結:

  1. 根據實際須要選擇使用哪一個方法,如能判定內容爲純文本請使用text()方法。僅當確實須要渲染HTML時才用html()方法
  2. 從安全角度,text()方法比html()方法更安全,無注入風險。
  3. 嚴格意義上,html()方法不符合CSP規範,直接將字符串解析爲DOM節點
  4. 業務須要確實要使用.html()方法渲染動態內容時,必須作安全檢查,避免惡意代碼注入
  5. .text()和.html()獲取值可能存在代碼縮進(空格和TAB),若有須要可使用$.trim()來剔除

數組與對象深挖

要點:

  • 數組對象僅有concat/reverse/slice/splice爲標準API,並且絕對徹底兼容
  • 數組對象請勿使用indexOf、lastIndexOf、map、every、forEach等非標準API,不只兼容性存在問題,並且效率不必定高,反而不如本身實現
  • 遍歷數組請將.length緩存到變量
for(vari=0,l=arr.length;i<l;i++){...}
  • 遍歷數組請勿使用此寫法
for(vari in arr){...}
  • 遍歷key-value型對象必須使用hasOwnProperty()來過濾遍歷結果。
for(var key in obj) {
if(!obj.hasOwnProperty(key) continue;
    //...
}
  • 不管是數組或對象,在遍歷操做時不要改變被遍歷的變量結構,如增刪元素,增刪key值等(雖然你能夠這麼作),對於元素自身及子成員的修改是絕對安全的。

關於Prototype的使用

要點:

  • 不要亂用Prototype。不要輕易在Object/Array/Function等對象上追加prototype(雖然咱們已有某些庫這麼作了)容易產生歧義衝突,在使用for~in遍歷時很容易引起問題。
  • 自定義的prototype成員會在for~in循環中出現,請根據實際狀況使用hasOwnProperty()來過濾遍歷結果。

不嚴謹的寫法:

function Test() {}
Test.prototype.a = 1;
Test.prototype.b = 2;
var o = new Test();
for(vari in o) {
console.log({key: i, value: o});
}
//{key:a, value:1}
//{key:b, value:2}
嚴謹的寫法:
var o = new Test();
for(vari in o) {
if(!o.hasOwnProperty(i)) continue;
console.log({key: i, value: o});
}
//無輸出
  • 對象的__proto成員,用途是獲取當前實例的原型對象。非標準實現,存在兼容性問題,請不要使用
  • 原則上不要輕易重寫已存在的prototype方法。但能夠在單個實例中覆寫該方法
  • prototype上定義靜態對象變量,會形成全部對象共用,而不是分別建立實例,請在構造方法中分配實例

錯誤寫法:

function Test() {}
Test.prototype.arr = [];
var a = new Test();
var b = new Test();
a.arr.push(1);
b.arr.push(2);
console.log(a.arr, b.arr);
//[1,2], [1,2]
正確寫法
function Test() {
this.arr = [];
}

var a = new Test();
var b = new Test();
a.arr.push(1);
b.arr.push(2);
console.log(a.arr, b.arr);
//[1], [2]

總結

JS是門靈活的語言,靈活到想怎麼寫均可以。但裏面坑仍是很多的。在有多種選擇時,多考慮下哪一種方法更好,而不是盲目選擇一種。

相關文章
相關標籤/搜索