前端知識體系整理(不斷更新)
——人腦不是機器,記憶都會退化,我們需要文檔輔助作知識沉淀
javascript
基本功
語言特性
-
數據類型:
-
Undefined
,Null
,Bollean
,Number
,String
Object
、Array
Date
、RegExp
-
-
typeof輸出(以下六個值之一):
undefined
var x; typeof(x); // "undefined"
boolean
var x = false; typeof x; // "boolean"
string
var x = ''; typeof x; // "string"
number
var x = NaN; typeof x; // "number"
object
var x = {}; var y = []; var z = null; typeof x; // "object" typeof y; // "object" typeof z; // "object"
function
var x = function() {}; typeof x; // "function"
類型轉換:
- 簡單類型 -> 簡單類型:
'1'-0; // 0, equal to Number(1)
- 簡單類型 -> 對象(使用基本函數的構造函數:Number(), String(), Boolean()等生成):
var n = 5;
console.log(n.toString(2)); // 快速進制轉換
-
對象 -> 簡單類型(參考JavaScript 類型轉換)
-
隱式轉換:除Date外,統統是先
valueOf
、再toString
(Date
在
+
和
==
時優先轉化為字串):
[] + 1; // 1
- 顯式Number(對象):先
valueOf
,再toString()
,都不存在則返回NaN
:
Number({}); // NaN
- 顯式String(對象):先取
valueOf()
,再取valueOf()
,都不存在則拋異常:
String({}); // [object Object]
-
DOM操作(增、刪、查、改、移、數據交換)
-
createElement
,createTextNode
,createDocumentFragment
,appendChild
-
removeChild
,removeNode
-
getElementById
,getElementsByTagName
,getElementsByClassName
,querySelector
,querySelectorAll
,parentNode
,firstChild
,lastChild
,nextSibling
,previousSibling
,childNodes
-
replaceChild
,insertBefore
-
getAttribute
,setAttribute
,data-x
,jQuery.attr()
,jQuery().prop()
,jQuery().data()
,classList
,innerHTML
,innerText
,textContent
事件機制(IE vs W3C)
-
事件綁定與解綁:
addEventListener(type, handler, flag)
,attechEvent('on' + type, handler)
,removeEventListener(type, handler)
,detechEvent('on' + type, handler)
-
事件流:
- 事件捕獲流:沿著文檔樹由外到內
- 事件對象
function handler(e) { var e = e || window.event; var target = e.target || e.srcElement; // e.currentTarget 指的是綁定事件的元素,不一定和target是同一個 }
- 事件冒泡流:沿著文檔樹由內到外,load、unload、focus、blur、submit和change事件不支持冒
OOP(原型鏈、繼承。。。)
-
比較(參考
- 基于類
Class
的面向對象,對象由類Class
產生:如Java
、C#
- javascript:基于原型
prototype
的OOP,對象由構造器(構造函數)constructor
利用原型prototype
產生
- 基于類
-
生成js對象:
- 類JSON的對象字面量:簡單直觀,但不適用于復雜的對象(類)
var Preson = { name: 'xiaoming', age: 15 };
- 構造函數模式:存在內存浪費的問題,比如下面例子里的
this.city
,在內存里生成了多次
var Person = function(name, age) { // 全部標記為私有成員 this.name = name; this.age = age; this.city = 'shen zhen'; }; var xm = new Person('xiaoming', 15); var xl = new Person('xiaoli', 20);
- 原型
prototype
模式:每次實例化只增加私有的對象屬性(或方法)到實例中,所有實例的公有屬性(或方法)指向同一個內存地址
var Person = function(name, age) { // 對象的私有成員 this.name = name; this.age = age; }; Person.prototype.city = 'shen zhen';// 共有成員
-
對象的繼承
非構造函數的繼承:繼承可以簡單使用對象之間的深、淺拷貝
-
構造函數的繼承:大多是基于原型的繼承,但是閱讀性差,也不利于擴展
- 借調:依賴apply或者call實現
function A(name) { this.name = name; } function B(name, age) { A.apply(this, arguments); this.age = age; }
- 子類prototype引用父類的prototype
function A() {} A.prototype.propA = 'a'; A.prototype.propB = 'b'; function B() {} B.prototype = A.prototype; // 原型鏈引用,改成B.prototype = new A();可以解決引用的問題 B.prototype.propB = 'B'; // 函數重載 B.prototype.constructor = B; var b = new B();
A、B的prototype引用同一個地址,實時上A的prototype.constructor已經被改成了B
- 借用空函數的prototype,類似YUI的實現:
function extend(sub, sup) { var _f = function() {}; _f.prototype = sup.prototype; sub.prototype = new _f(); sub.prototype.constructor = sub; sub.super = sup.prototype;// 保存原構造函數 _f = null; } A.prototype.propA = 'a'; A.prototype.propB = 'b'; function B() {} extend(B, A);
構造函數的繼承,重要的是理解原型鏈
prototype chain
,繼承基本就是原型鏈的拷貝或者引用。理解原型鏈
prototype chain
:function A() {} function B() {} B.prototype = new A(); function C(x, y) {} C.prototype = new B(); var c = new C(); c.__proto__ === C.prototype;// true B.prototype.__proto__ === A.prototype;// true B.__proto__ === B.prototype;// true A.__proto__ === Function.prototype;// true A.prototype.__proto__ === Object.prototype;// true
_proto屬性_:對象的
__proto__
指向Object.prototype
,Function對象的__proto__
指向構造函數的prototype。 -
類式繼承:本質上還是使用構造函數的
prototype
,封裝成類,典型的例子是jQuery之父John Resig的Simple JavaScript Inheritance,其他類庫也有各自的實現- Simple Inheritance的用法
var Person = Class.extend({ init: function(gender) { this.gender = gender; } }); var Teacher = Person.extend({ init: funciton(gender, name) { this._super(gender); this.name = name; }, role: 'teacher', speek: function() { console.log('Hello, i am a %s.', this.role); } }); var Student = Person.extend({ init: funciton(gender, name) { this._super(gender); this.name = name; }, role: 'student', speek: function() { console.log('Hello, i am a %s.', this.role); } });
函數式編程、作用域、閉包、this
- 實參、形參
foo(1, 2);
function foo(a, b, c) {
console.log(arguments.length);//2 實際傳入的參數
console.log(foo.length);//3 期望傳入的參數
}
- 函數申明與函數表達式
function foo() {} // 函數申明
var foor = function foo() {};// 函數表達式
執行順序:解析器會率先讀取函數聲明,所以在任何代碼執行前函數申明可用
fn(2); // 4
function fn(n) {console.log(n);}
fn(2); // 4
function fn(n) {console.log(n*n);} //重載
fn(2); // 4
var fn = function(n) {console.log(++n);};// 函數表達式,按照申明的順序執行
fn(2); // 3
-
arguments, callee, caller, apply, call
-
arguments
,類數組,類似的還有NodeList、classList等對象 -
arguments.callee
,返回正在執行的Function
對象的一個引用
function foo(n) { console.log(arguments.callee.arguments.length); console.log(arguments.callee.length); } foo(1, 2, 3);// 分別打出3,1
-
arguments.caller
,返回調用這個Function
對象的Function
對象的引用 -
apply
和call
,傳參不同,功能相同,都是把Function
對象綁定到另外一個對象上去執行,其內的this
指向這個對象
-
-
作用域
- 函數的局部變量:函數形參、函數內部
var
聲明的變量 - 變量的查找(作用域鏈):查找函數內部變量 -> 查找嵌套的外部函數 ...-> 查找window對象 -> 未定義
- js中沒有塊級作用域,可以用匿名函數模擬
- 未用關鍵字
var
申明的變量,會自動升級為全局變量掛到window上 - 頂級作用域內使用
var
申明的變量是window對象的一個屬性
- 函數的局部變量:函數形參、函數內部
-
閉包
- 由于作用域的限制,函數外部不能訪問函數內部的局部變量
- 閉包就是能夠讀取其他函數內部變量的函數引自學習Javascript閉包
function foo() { var x = 1; return function fn() { // closure return x; } } var bar = foo(); console.log(bar()); // get the local variables in foo
- 閉包的另一個作用是在內存中保存函數的局部變量,這有可能導致內存泄露
this:函數中的
this
始終指向函數的調用者
function foo(x) {
this.x = x;
}
foo(1); // 調用者是window,也可以window.foo()
console.log(window.x); // 1
var o = {};
o.foo = foo;
o.foo(2); // 調用者是o
console.log(o.x); // 2
console.log(window.x); // 1
這里有一篇詳細的例子
Ajax(XMLHttpRequest vs ActiveXObject)
-
請求過程
- 建立到服務器的新請求:
xhr.open()
- 向服務器發送請求:
xhr.send()
- 退出當前請求:
xhr.abort()
- 查詢當前
HTML
的就緒狀態:xhr.readyState
- 服務器返回的請求響應文本:
xhr.responseText
- 建立到服務器的新請求:
-
RUST API:
POST
,GET
,PUT
,DELETE
- GET:更多的用于讀操作,參數暴露到url,(服務器端可能對)url長度有限制
- POST:更多的用于寫操作
HTTP狀態碼
XHR2
跨域問題
- 跨域的形成(同源限制):主域、子域、ip和域名、協議不同、端口不同
- 常用解決方案
-
iframe+document.domain:適用于垮子域的情況
缺點是如果一個域名下存在安全問題,另一個域名下可能也會有問題,還有就是創建iframe的開銷 -
動態引入js腳本:適合所有的跨域場景
引入的腳本會立刻執行,存在安全風險
要與遠端溝通約定變量,增加了開發和維護成本 -
iframe+location.hash:適合各種場景下跨域
iframe嵌套引用,開銷更大
會產生歷史記錄,url中暴露傳遞的內容 -
iframe+window.name:使用iframe的window.name從外域傳遞數據到本地域,適合各種場景下跨域且數據安全
缺點是數據有大小限制 -
postMessage
跨域通訊
-
iframe+document.domain:適用于垮子域的情況
jQuery
可閱讀yuanyan同學的jQuery編程實踐
安全問題
- XSS
- CSRF
- SQL注入
- 敏感信息采用安全傳輸(SSL/HTTPS)
- 上傳限制(大小、mime類型、可執行文件)
- (服務器端)嚴格的路徑限制,比如杜絕路徑跳轉
css
- css盒子模型
- css的繼承規則
- IE低版本的hacks
- 瀏覽器的怪異模式與標準模式
性能優化(最佳實踐)
HTML優化
- 語意化
html
結構:SEO友好,利于維護 - 精簡
html
結構:嵌套過復雜的結構會導致瀏覽器構建DOM樹緩慢 -
html
最小化:html大小直接關系到下載速度,移除內聯的css,javascript,甚至模板片,有條件的話盡可能壓縮html,去除注釋、空行等無用文本 - 總是設置文檔字符集:如果不設置,瀏覽器在渲染頁面前會做一些查找,先搜索可進行解析的字符
- 顯式設置圖片的寬高:減少頁面重繪(參考【高性能前端1】高性能HTML)
- 去除空鏈接屬性(
img
、link
、script
、iframe
元素的src
或href
屬性被設置了,但是屬性卻為空):部分瀏覽器依然會去請求空地址 - 正確的閉合標簽:瀏覽器不一定會將它們修復成正確的格式
- 避免
@import
引入樣式表:IE低版本瀏覽器會再頁面構建好之后再去加載import的樣式表,會導致白屏 - 樣式表放
head
里,腳本延后引入 - 未完待續。。。
CSS優化
避免css表達式:css表達式會不斷的重復計算,導致頁面性能下降
避免AlphaImageLoader濾鏡:這個濾鏡的問題在于瀏覽器加載圖片時它會終止內容的呈現并且凍結瀏覽器(引自【高性能前端1】高性能CSS)
合并圖片(css sprites)
盡量避免通配符選擇器:CSS選擇器是從右到左進行規則匹配,基于這個機制,瀏覽器會查找所有同類節點然后逐級往上查找,知道根節點,這樣效率很低
-
盡量避免屬性選擇器(
\*=
,|=
,^=
,$=
,~=
):正則表達式匹配比基于類別的匹配慢 移除無匹配的規則:縮減文檔體積;瀏覽器會把所有的樣式規則都解析后索引起來,即使是當前頁面無匹配的規則
合并多條可合并的規則,使用簡寫:
.box {margin-top: 10px; margin-left: 5px; margin-bottom: 15px;} /* bad */
.box {margin: 10px 0 15px 5px;} /* better */
- 對IE瀏覽器單獨使用hack:代碼清晰易讀,同時也減小樣式體積
- 模塊化css,最好能夠組件化:查找、維護方便,同時也利于代碼復用
- 完善注釋
- 未完待列。。
JavaScript優化
-
盡量減少或最少化對DOM的操作(脫離文檔流對DOM進行修改)
- 隱藏元素,對其進行修改之后再顯示
- 使用文檔片段
DocumentFragement
批量修改,最后再插入文檔 - 將元素拷貝一份,修改完之后再替換原有元素
-
謹慎操作節點集合NodeList(
images
,links
,forms
,document.getElementsByTagName
):
緩存NodeList以及NodeList.length的引用 -
盡量操作元素節點(DOM節點如
childNodes
,firstChild
不區分元素節點和其他類型節點,但大部分情況下只需要訪問元素節點引自《高性能JavaScript》):-
children
代替childNodes
-
childElementCount
代替childNodes.length
-
firstElementChild
代替firstChild
- ...
-
讀寫分離,減少layout:
x = box.offsetLeft; // read
box.offsetLeft = '100px'; // write
y = box.offsetTop; // read
box.offsetTop = '100px'; // write
這個過程造成了兩次的layout,可做如下改造:
x = box.offsetLeft; // read
y = box.offsetTop; // read
box.offsetLeft = '100px'; // write
box.offsetTop = '100px'; // write
- 最小化重排(
repeat
):
box.style.width = '100px';
box.style.heihgt = '50px;';
box.style.left = '200px';
三個操作都會重新計算元素的幾何結構,在部分瀏覽器可能會導致3次重排,可做如下改寫:
var css = 'width: 100px; height: 50px; left: 200px;';
box.style.cssText += css;
- 使用事件委托:充分利用冒泡機制,減少事件綁定
- 無阻塞加載:腳本延后加載,合并加載,并行加載
- 函數內部的變量盡可能使用局部變量,縮短變量作用域的查找時間
- 緩存對象引用:
var a = $('#box .a');
var b = $('#box .b');
可以緩存$('#box')
到臨時變量:
var box = $('#box');
var a = box.find('.a');
var b = box.find('.b');
- 減少多級引用:
var $P = Jx().UI.Pager.create();// 同樣可以先緩存結果
- 緩存Ajax:
緩存Ajax數據,利用本地存儲或者臨時變量,存儲不需要實時更新的數據
-
設置HTTP
Expires
信息
- 復雜的計算考慮使用
Web Worker
jQuery性能優化
合理使用選擇器
- id和標簽選擇器最快,因為是直接調用原生API
$('#box'); // document.getElementById | document.querySelector
$('div'); // document.getElementsByTagName
- 類選擇器在低版本瀏覽器較慢,偽元素、屬性選擇器在不支持
querySelector
的瀏覽器很慢 - 盡可能優先使用符合CSS語法規范的CSS選擇器表達式,以此來避免使用jQuery自定義的選擇器表達式,因為當jQuery遇到單個id, 標簽名,類名,選擇器就會快速調用瀏覽器支持的DOM方法查詢
$('input[checked="checked"]'); // 比較快
$('input:checked'); // 較慢
- 優先選擇
$.fn.find
查找子元素,因為find
之前的選擇器并沒有使用 jQuery 自帶的 Sizzle 選擇器引擎,而是使用原生API查找元素
$('#parent').find('.child'); // 最快
$('.child', $('#parent')); // 較快,內部會轉換成第一條語句的形式,性能有一定損耗
$('#parent .child'); // 不如上一個語句塊
- 使用組合選擇器時,盡可能讓右端更明確,因為Sizzle引擎是從右到左進行匹配的
$('div.foo .bar'); // slow
$('.foo div.bar'); // faster
- 避免過度具體,簡潔的 DOM 結構也有助于提升選擇器的性能
$('.foo .bar .baz');
$('.foo div.baz'); // better
- 盡量避免使用通配符選擇器
盡可能的少創建jQuery對象
- 如
document.getElementById('el')
比$('#el')
塊 - 如獲取元素id:
$('div').click(function(e) {
// 生成了個jQuery對象
var id = $(this).attr('id');
// 這樣更直接
var id = this.id;
});
- 使用鏈式調用緩存jQuery對象
<div id="user" class="none">
<p class="name"></p>
<p class="city"></p>
</div>
$('#user')
.find('.name').html('zhangsan').end()
.find('.city').html('shenzhen').end()
.removeClass('none');
- 做好jQuery對象緩存
var box = $('.box');
box.find('> .cls1');
box.find('> .cls2');
避免頻繁操作DOM
- 復雜操作把元素從DOM中移除再操作
var $el = $('.box').detach();
var $p = $el.parent();
// do some stuff with $el...
$p.append($el);
- 在循環外執行DOM修改
// 性能差
$.each(arr, function(i, el) {
$('.box').prepend($(el));
});
// 較好的做法
var frag = document.createDocumentFragment();
$.each(arr, function(i, el) {
flag.appendChild(el);
});
$('.box')[0].appendChild(flag);
使用事件代理
$('ul li').on('click', fn);
// better
$('ul').on('click', 'li', fn);
使用事件代理(委托),當有新元素添加進來的時候,不需要再為它綁定事件,這里有demo可以查看效果。
整體優化
- 雅虎34條:合并壓縮文件和圖片、gzip/deflate、CDN、HTTP頭設置Cache-Control/Expires和Last-Modified/ETag、并行下載與DNS查詢的平衡等
- 緩存靜態文件,盡可能采用CDN策略,并采用不帶cookie的獨立域名存放,并開啟
keep-alive
- 動態與靜態結合,服務器端拼湊頁面片,最快展現給用戶,縮短白屏時間和頁面可用時間,非首屏數據懶加載
- 內容分開存放,比如圖片和ajax分別采用不用的服務器(域名下)
- 保證單個html的http請求數最少
- 確保網站有favicon.ico文件(瀏覽器會自動請求favicon.ico,如果不存在則會出現大量的404消耗帶寬)
- 未完待續。。。