JavaScript
(參考了《JavaScript高級程序設計》和部分知乎答案)
作用域
指可訪問變量、對象、函數的集合。
局部作用域:在函數內聲明,只能在函數內部訪問
全局作用域:在函數外定義,所有腳本和函數均可使用(函數內賦值但未聲明,仍為全局變量)
變量生命周期:全局變量在頁面關閉后銷毀,局部變量在函數執行完畢后銷毀
ps:在HTML中所有數據屬于window對象
數據類型
原始數據類型:棧,占據空間小,大小固定
String
Number
Boolean
Object
Function
不含值的數據類型:
Undefined:不存在的值,或變量被聲明了但未賦值
Null:一個對象被定義了,但是為空值(沒有任何屬性和方法)
引用對象類型:堆,占據空間大,大小不固定(在棧中儲存了指針,指向堆中的起始位置)
Object
Date
Array
boolean類型轉換
數據類型 | true | false |
---|---|---|
Boolean | true | false |
String | 任何非字符串 | ""(空) |
Number | 任何非零數字值 | 0和NaN |
Object | 任何對象 | Null |
Undefined | n/a(不適用) | underfined |
例子:
var message = "Hello world!";
if (message) {
alert("Value is true");
}
正則表達式
由一個字符串序列形成的搜索模式。
如:
var str = "Visit W3cSchool!";
var n = str.search(/W3cSchool/i);
//n=6 (不區分大小寫)
變量提升
函數聲明和變量聲明會被解釋器自動提升到方法體的最頂部
var x;
但是初始化的變量不會提升
var x = 5;
x = 5
為避免問題的出現,通常在作用域開始前聲明變量
注意:函數聲明也會自動提升,而傳遞給初始化變量的函數則不會
function fn () {} //提升
var fn = function () {} //不提升
函數調用的四種方法
1.方法調用模式
var myobject = {
value: 0,
inc: function() {
alert(this.value);
}
}
myobject.inc();
//this指向myobject
2.函數調用模式
var add = function(a,b) {
alert(this);
return a+b;
}
var sum = add(3,4);
arert sum;
3.構造器調用模式(摒棄)
4.apply調用
var arr = [10,20];
var sum = add.apply(myobject,arr);
aleat(sum);
原型鏈
當從一個對象那里讀取屬性或調用方法時,如果該對象不存在這樣的屬性或方法,就會去自己關聯的prototype對象那里尋找,直到找到或追溯過程結束為止。(即對象的屬性和方法追溯機制)
”讀“屬性會沿著原型鏈搜索,”新增“屬性時則不會去看原型鏈
(obj)
name:'obj' valueOf
__proto__ ——> toString
constructor
...
__proto__ ——> null
閉包
先看兩個典型例子
function foo () {
var local = 1;
function bar () {
local++;
return local;
}
return bar;
}
var func = foo();
func(); //2
func(); //3
bar函數調用了外層函數的local變量,函數運行完畢后local仍然保存在內存中,所以每次運行函數后local都會增加1
var add =(function () {
var counter = 0;
return function () {
return counter += 1;
}
})();
add(); //1
add(); //2
add(); //3
和上一個例子的原理是一樣的,內層函數調用了外層函數的全局變量count導致其始終存在于內存中,而內部函數的存在也依賴于外層函數,導致其也始終再內存中,不會在調用結束后被垃圾回收機制回收。
這就叫閉包,一句話概括:閉包就是函數和函數內部能訪問到的變量的總和。
或者說,如果存在有權訪問另一個函數作用域中變量的函數,那就構成了閉包。
它提供了一中間接的方式能夠訪問到函數內部的數據,有以下兩種情況:
1.函數作為返回值
2.函數作為參數傳遞
正確運用閉包由一個好處,就是讓一些數據更安全,只能通過特定的接口來訪問。
當然,有時候閉包也會導致一些問題,如下面的例子:
for (var i=0; i<10; i++) {
arr[i] = function() {
return i;
}
}
arr[0]; //10
arr[6]; //10
function內訪問了外部變量i,構成了一個閉包,我們先不寫內部的i,每次賦值的結果如下
arr[0] = function() { return i };
arr[1] = function() { return i };
...
arr[10] = function() { return i };
而i的作用域是整個for的花括號內,因此這些函數返回的是同一個i,遍歷完后產生了這11個結果,此時我們再調用函數的時候,i已經為10了,因此無論調用哪個函數,結果都是遍歷完后的i。
this
var obj = {
foo: function () {
console.log(this);
}
}
var bar = obj.foo;
obj.foo(); //obj
bar(); //window
要理解this,首先要知道函數的三種調用形式
func(p1,p2);
obj.child.method(p1,p2);
func.call(context,p1,p2); //正常形式
context就是this,
上面的例子中,bar()等價于func.call(undefined),這種情況下this為window,
而obj.foo()等價于obj.foo.call(obj),this為obj
有一個特例:
function fn() {
console.log(this);
}
var arr = [fn,fn2];
arr[0]; //這里的this指什么
我們把arr[0]假想為arr.0()
也就相當于arr.0.call(arr)
所以this為arr
對某些框架有其他方法來理解,
如在jQuery,this關鍵字指向的是當前正在執行事件的元素。
new操作
1.創建一個空對象,并且this變量引用該對象,繼承該對象的原型
var obj = {}
2.屬性和方法被加入到this引用的對象中
obj.__proto__ = Base.prototype;
3.新創建的對象又this所引用,并且隱式地返回this
Base.call(obj);
在以下例子中
var o = new myObject();
一旦沒有加上new,myObject()內部的this將指向全局對象
事件類型
UI事件
1.load事件:頁面完全加載后觸發
2.unload事件:頁面完全卸載后觸發
3.resize事件:當瀏覽器窗口被調整到一個新的高度或寬度時觸發
4.scroll事件:文檔被滾動期間觸發
焦點事件
1.focusout:在失去焦點的元素上觸發
2.focusin:在獲得焦點的元素上觸發
3.blur:在失去焦點的元素上觸發
4.DOMFocusOut:在失去焦點的元素上觸發
5.focus:在獲得焦點的元素上觸發
6.DOMFocusIn:在獲得焦點的元素上觸發
鼠標與滾輪事件
1.click:單擊鼠標或按下回車鍵時觸發
2.dbclick:雙擊鼠標按鈕時觸發
3.mousedown:按下任意鼠標按鈕時觸發
4.mouseup:用戶釋放鼠標按鈕時觸發
5.mousewheel:通過鼠標滾輪滾動頁面時觸發
6.mouseenter:鼠標光標首次從元素外部移動到元素范圍內時觸發(只限于被選元素)
7.mouseover:鼠標指針位于一個元素外部,用戶將其首次移入另一個元素邊界之內時觸發(任何元素,不限于被選元素)
8.mouseleave:位于元素上方的鼠標光標移動到元素范圍之外時觸發(只限于被選元素)
9.mouseout:鼠標指針位于一個元素的上方,移入到另一個元素時觸發(任何元素,不限于被選元素)
10.mousemove:鼠標指針在元素內部移動時重復地觸發
鍵盤與文本事件
1.keydown:按下鍵盤的任意鍵時觸發
2.keypress:按下鍵盤的字符鍵時觸發
3.keyup:釋放鍵盤上的鍵時觸發
除此之外,還有
變動事件
H5事件
觸摸與手勢事件
DOM事件處理程序
DOM0級事件處理程序
var btn = document.getElementById("myBtn");
btn.onclick = function () {
alert("clicked");
}
DOM2級事件處理程序
var btn = document.getElementById("myBtn");
btn,addElementListener("click",function(){
alert("clicked");
},false);
//true: 在捕獲階段調用事件處理程序
//false: 在冒泡階段調用事件處理程序
跨瀏覽器的事件處理程序(封裝)
var EventUtil = {
addHandler: function(element,type,hander) {
if (element.addEventListener) {
element.addEventListener(type,handler,false);
} else if (element.attachEvent) { //IE
element.attachEvent("on"+type,handler);
} else {
element["on"+type] = handler; //HTML事件
}
}
}
事件委托的原理以及優缺點
function delegateEvent (interfaceEle,selector,type,fn) {
if (interfaceEle.addEventListener) {
interfaceEle.addEventListener(type,eventfn);
} else { //IE
interfaceEle.attachEvent("on"+type,eventfn);
}
function eventfn (e) {
var e = e || window.event;
var target = e.target || e.srcElement; //兼容fireFox
if (matchSelector(target,selector)) {
if (fn) {
fn,call(target,e); //將fn內部的this指向target
}
}
}
}
var odiv = document.getElementById("iDiv");
delegateEvent(odiv,'click',function(){
alert("clicked");
});
優點:
1.大量減少內存占用,減少事件注冊
2.新增子對象無需再次對其綁定事件
原生Javascript實現事件代理
<ul id="parentList">
<li id="post-1">1</li>
<li id="post-2">2</li>
</ul>
<script>
function delegateEvent(interfaceEle,type,fn) {
if(interfaceEle.addEventListener) {
interfaceEle.addEventListener(type,fn);
} else { //IE
interfaceEle.attachEvent("on"+type,fn);
}
}
var parentList = document.getElementById("parentList");
delegateEvent(parentList,"click",function(e){
var e = e || window.event; //兼容IE
var target = e.target || e.srcElement; //兼容fireFox
alert(target.id);
});
</script>
這段代碼監聽了整個<ul>標簽并且啟用了事件代理,點擊<li>標簽的時候會返回具體的li標簽的id,對新增的對象元素也同樣有效
事件冒泡和事件捕獲
事件冒泡:事件開始時由最具體的元素接收,然后逐級向上,傳播到較為不具體的節點
事件捕獲:不太具體的節點更早地接收到事件,而最具體的節點最后接收到事件
DOM事件流:同時支持兩種事件模型:捕獲型事件和冒泡型事件,但是,捕獲型事件先發生。兩種事件流會觸及DOM中的所有對象,從document對象開始,也在document對象結束。
支持W3C標準的瀏覽器在添加事件時用addEventListener(event,fn,useCapture)方法,基中第3個參數useCapture是一個Boolean值,用來設置事件是在事件捕獲時執行,還是事件冒泡時執行。而不兼容W3C的瀏覽器(IE)用attachEvent()方法,此方法沒有相關設置,不過IE的事件模型默認是在事件冒泡時執行的,也就是在useCapture等于false的時候執行,所以把在處理事件時把useCapture設置為false是比較安全,也實現兼容瀏覽器的效果。
JSON
json的全稱為JavaScript Object Notation,即對象表示法,是一種約定的數據格式。
AJAX
ajax的全稱為Asynchronous JavaScript and XML,即異步的JavaScript和XML,
理解它的過程很簡單,就是用JS發起一個請求,并得到服務器返回的內容,原生JS寫法如下:
//第1步 聲明一個對象
var request;
//第2步 根據瀏覽器賦予對象http請求的方法
if (window.XMLHttpRequest) { //code for IE7+, Firefox, Chrome, Opera, Safari
request = new XMLHttpRequest();
} else { //code for IE6, IE5
request = new ActiveXObject("Microsoft.XMLHTTP");
}
//第3步 確定服務器返回的方法
request.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
console.log(request.responseText);
}
}
//第4步 在之前聲明的基礎上發起請求
request.open("GET","filename",true);
request.send();
注意以下要點:
IE6及以下版本用的是ActiveXObject方法,其余用XMLHttpRequest方法。
XMLHttpRequest對象三個重要的屬性:
onreadystagechange 存儲函數(或函數名),每當 readyState 屬性改變時,就會調用該函數。
readyState 存有 XMLHttpRequest 的狀態。從 0 到 4 發生變化。
0: 請求未初始化
1: 服務器連接已建立
2: 請求已接收
3: 請求處理中
4: 請求已完成,且響應已就緒
status
200:"ok"
404:未找到頁面
XMLHttpRequest對象的open()和send()方法:
open(method,url,async);
//規定請求的類型、URL 以及是否異步處理請求。
//method:請求的類型;GET 或 POST
//url:文件在服務器上的位置
//async:true(異步)或 false(同步)
send(string);
//將請求發送到服務器。
//string:僅用于 POST 請求
與 POST 相比,GET 更簡單也更快,并且在大部分情況下都能用。
然而,在以下情況中,請使用 POST 請求:
1.無法使用緩存文件(更新服務器上的文件或數據庫)
2.向服務器發送大量數據(POST 沒有數據量限制)
3.發送包含未知字符的用戶輸入時,POST 比 GET 更穩定也更可靠
jQuery封裝了AJAX的方法,只需要一行代碼:
$.get('filename').then(function(response){
//do something
});
原型繼承與擴展
Child.prototype = new Parent();
把父類對象賦值給子類構造函數的原型,這樣子類就可以訪問到父類以及父類的原型,這就叫原型繼承。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
alert ("hello, I'm" + this.name);
};
var BillGates = new Person("Bill Gates"); //建立對象
BillGates.sayHello(); //hello, I'm Bill Gates
Person.prototype.Retire = function() {
alert("poor"+this.name+"byebye!");
} //建立對象后再動態擴展
BillGates.Retire(); //poor BillGates byebye!
這種建立對象后再動態擴展的情況,就叫做原型擴展,新方法仍然可被之前建立的對象調用
js延遲加載
defer和async是動態創建dom的兩種方式,defer是延遲加載,async是異步下載腳本
在其他情況下,瀏覽器會按照script元素在頁面中出現的順序依次進行解析
封裝與模塊化開發
使代碼規范好用,使用簡單化,只需要知道參數和返回值,可以轉化編程思維角度
var modulel = (function(){
var _count = 0;
var m1 = function(){
//...
}
var m2 = function(){
//...
}
return {
m1:m1,
m2:m2
};
})();
跨域問題的解決方法
1.jsonp(jQuery的$.getScript方法就是利用jsonp跨域解決的)
2.iframe
3.window.name
4.window.postMessage
5.服務器上設置代理頁面
DOM操作
(1)創建新節點
createDocumentFragment() //創建一個DOM片段
createElement() //創建一個具體元素
createTextNode() //創建一個文本節點
(2)添加、移除、替換、插入
appendChild()
removeChild()
replaceChild()
insertBefore() //在已有子節點前插入新的節點
(3)查找
getElementsByTagName() //通過標簽名稱
getElementsByName() //通過元素Name屬性值
getElementById() //通過元素id,唯一,所以Element沒有s
前端模塊化開發
發展歷程
1.函數封裝
function f1 () {
statement
}
function f2 () {
statement
}
污染了全局變量,容易發生沖突,且模塊成員之間沒有聯系
2.對象的寫法
var myModule = {
var1: 1,
var2: 2,
fn1: function () { },
fn2: function () { }
}
調用myModule.fn2(); 避免了變量污染,只需要保證模塊名唯一即可
缺陷是外部可以隨意修改內部成員:
myModule.var1 = 100;
3.立即執行函數的寫法
var myModule = (function(){
var var1 = 1;
var var2 = 2;
function fn1 () { };
function fn2 () { };
return {
fn1: fn1,
fn2: fn2
};
}) ();
console.info(myModule.var1); //underfined
這樣就無法修改暴露出來的變量和函數,就是模塊化的基礎。
模塊化的規范 CMD和AMD
commonJS 通用模塊定義
1.定義模塊
每一個模塊都是一個單獨的作用域,無法被其他模塊讀取
2.模塊輸出
模塊只有一個出口,module.exports對象,把模塊希望輸出的全部內容放入該對象
3.加載模塊
加載模塊使用require方法,該方法讀取一個文件并執行返回文件內部的module.exports對象
例子
//模塊定義myModel.js
var name = 'Byron';
function printName () {
console.log(name);
}
function printFullName(firstName) {
console.log(firstName + name);
}
module.exports = {
printName: printName,
printFullName: printFullName
}
//加載模塊
var nameModule = require(./myModel.js);
nameModule.printName
AMD 異步模塊定義
綜合知識
關于緩存的三個關鍵字
cookie:是儲存在瀏覽器上的一小段數據,用來記錄某些當頁面關閉或刷新后仍然需要記錄的信息。
session:是一種讓服務器能夠識別某個用戶的機制。
localStorage:HTML5本地儲存web storage特性的API之一,用于將大量數據保存在瀏覽器中。
前端性能優化
1.減少http請求次數,JS、CSS源碼壓縮
2.前端模版,JS+數據,減少DOM操作次數,優化js性能
3.用innerHTML代替DOM操作,減少DOM操作次數,優化js性能
4.設置className,少用內聯style
5.少用全局變量,緩存DOM節點查找的結果
6.圖片壓縮和預加載,將樣式表放在頂部,腳本放在底部,加上時間戳
7.避免使用table,顯示比div+css布局慢
頁面加載過程
1.瀏覽器根據請求的URL交給DNS域名解析,找到真實IP,向服務器發起請求
2.服務器交給后臺處理完成后返回數據,瀏覽器接收文件(HTML、JS、CSS、圖片等)
3.瀏覽器對加載的資源進行語法解析,建立相應的內部數據結構
4.載入解析到的資源文件,渲染頁面,完成
瀏覽器渲染過程
1.瀏覽器解析html源碼,然后創建一個DOM樹,每一個標簽都有一個對應的節點,并且每一個文本也有一個對應的文本節點(DOM樹的根節點就是documentElement,對應html標簽)
2.瀏覽器解析CSS代碼,計算最終的樣式數據(對CSS中非法的語法會忽略掉,按優先級排列:瀏覽器默認設置>用戶設置>內聯樣式>外鏈樣式>html中的style)
3.構建渲染樹(忽略不需要渲染的元素)
4.根據渲染樹把頁面繪制到屏幕上
XHTML
可拓展超文本標記語言,將HTML作為XML的應用而重新定義的標準
其中script需要寫成以下形式才能被識別
<script>
//<![CDATA[
//內容
//]]>
</script>
CSS
css定義的權重
div:1
.class:10
#
id:100
內聯樣式表:1000
#
id div:100+1=101
.class div:10+1=11
.class1 .class2 div:10+10+1=21
權重相同時,最后定義的樣式會起作用
盒模型
標準模式:box-sizing:content-box;
怪異模式:box-sizing:border-box;
兩種模式的區別:
標準模式會被設置的padding撐開,而怪異模式則相當于將盒子的大小固定好,再將內容裝入盒子。盒子的大小并不會被padding所撐開。
.a a a a