前端面試題目彙總摘錄(JS 基礎篇)

溫故而知新,保持空杯心態javascript

JS 基礎

JavaScript 的 typeof 返回那些數據類型

object number function boolean undefined stringhtml

typeof null; // object
typeof isNaN; // function
typeof isNaN(123); //boolean
typeof []; // object
Array.isArray(); // false
toString.call([]); // [object Array]
var arr = [];
arr.constructor; // ƒ Array() { [native code] }
複製代碼

強制類型轉換和隱式類型轉換?

顯示轉換(強制類型轉換)

js 提供瞭如下幾種轉型函數:前端

轉換的類型 函數
數值類型 Number(mix),parseInt(string,radix),parseFloat(string);
字符串類型 toString(radix),String(mix)
布爾類型 Boolean(mix)
Number(mix) 函數,能夠將任意類型的參數 mix 轉換爲數值類型,規則爲
  1. 若是是布爾值,truefalse分別被轉換爲 1 和 0
  2. 若是是數字值,返回自己
  3. 若是是 null,返回 0
  4. 若是是 undefined,返回 NaN
  5. 若是是字符串,遵循如下規則:
    1. 若是字符串中只包含數字,則將其轉換爲十進制(忽略前導0,前面正負號有效)
    2. 若是字符串中包含有效的浮點格式,則將其轉換爲對應的浮點數值(忽略前導0,前面正負號有效)
    3. 若是字符串中包含有效的十六進制格式,則轉換爲相同大小的十進制整數值
    4. 若是字符串是空的(不包含任何字符),則將其轉換爲 0
    5. 若是字符串中包含上述格式以後的字符,則將其轉換爲 NaN
  6. 若是是對象,則調用對象的 valueOf() 方法,而後按照前面的規則進行轉換返回的值,若是是轉換結果是 NaN,則調用對象的 toString() 方法,而後再一次按照前面的規則進行返回的字符串值的轉換

下表是對象的 valueOf() 的返回值java

對象 返回值
Array 數組的元素被轉換爲字符串,這些字符串由逗號分隔,鏈接在一塊兒。其操做與 Array.toString 和 Array.join 方法相同。
Boolean Boolean 值。
Date 存儲的時間是從 1970 年 1 月 1 日午夜開始計的毫秒數 UTC。
Function 函數自己。
Number 數字值。
Object 對象自己。這是默認狀況。
String 字符串值。

因爲 Number()函數在轉換字符串時原理比較複雜,且不夠合理,所以在處理字符串時,更經常使用的是 parseInt() 函數node

parstInt(string,radix) 函數,將字符串轉換爲整數類型的數值,其規則爲
  1. 忽略前面字符串前面的空格,直至找到第一個非空格字符
  2. 若是第一個字符不是數字字符或者負號,就會返回 NaN(也就是遇到空字符會返回 NaN)
  3. 若是第一個字符是數字字符,會繼續解析第二個字符,知道解析完全部後續的字符或者是遇到一個非數字字符
  4. 若是字符串中第一個字符是數字字符,也可以識別各類進制
  5. 最好在 第二個參數指定轉換的基數(進制),就不會有所歧義。
parseFloat(string)函數,將字符串轉換爲浮點數類型的數值。

與parseInt()函數相似,parseFloat()也是從第一個字符(位置0)開始解析每一個字符。並且也是一直解析到字符串末尾,或者解析到碰見一個無效的浮點數字字符爲止。也就是說,字符串中的第一個小數點是有效的,而第二個小數點就是無效的了,所以它後面的字符串將被忽略。jquery

toString(radix)

undefinednull以外的全部類型的值都具備 toString() 方法,其做用是返回對象的字符串表示。web

多數狀況下,調用toString()方法沒必要傳遞參數。可是,在調用數值的toString()方法時,能夠傳遞一個參數:輸出數值的基數。默認狀況下,toString()方法以十進制格式返回數值的字符串表示。面試

對象 操做
Array 將 Array 的元素轉換爲字符串。結果字符串由逗號分隔,且鏈接起來。
Boolean 若是 Boolean 值是 true,則返回 「true」。不然,返回 「false」。
Date 返回日期的文字表示法。
Error 返回一個包含相關錯誤信息的字符串。
Function 返回以下格式的字符串,其中 functionname 是被調用 toString 方法函數的名稱:function functionname( ) { [native code] }
Number 返回數字的文字表示。
String 返回 String 對象的值。
默認 返回 「[object objectname]」,其中 objectname 是對象類型的名稱。

在不知道要轉換的值是否是null或undefined的狀況下,還可使用轉型函數String(),這個函數可以將任何類型的值轉換爲字符串。正則表達式

String(mix)函數,將任何類型的值轉換爲字符串,其規則爲:
  1. 若是有toString()方法,則調用該方法(不傳遞radix參數)並返回結果
  2. 若是是null,返回」null」
  3. 若是是undefined,返回」undefined」
Boolean(mix)函數,將任何類型的值轉換爲布爾值。

如下值會被轉換爲false:false、」"、0、NaN、null、undefined,其他任何值都會被轉換爲trueshell

隱式轉換(非強制轉換類型)

在某些狀況下,即便咱們不提供顯示轉換,Javascript也會進行自動類型轉換,主要狀況有:

用於檢測是否爲非數值的函數:isNaN(mix)

isNaN()函數,經測試發現,該函數會嘗試將參數值用 Number() 進行轉換,若是結果爲「非數值」則返回 true,不然返回 false

遞增遞減操做符(包括前置和後置)、一元正負符號操做符(通過對比發現,其規則與Number()規則基本相同)
  1. 若是是包含有效數字字符的字符串,先將其轉換爲數字值(轉換規則同 Number()),再執行加減1的操做,字符串變量變爲數值變量。
  2. 若是是不包含有效數字字符的字符串,將變量的值設置爲 NaN,字符串變量變成數值變量。
  3. 若是是布爾值 false,先將其轉換爲0再執行加減1的操做,布爾值變量編程數值變量。
  4. 若是是布爾值 true,先將其轉換爲1再執行加減1的操做,布爾值變量變成數值變量。
  5. 若是是浮點數值,執行加減1的操做。
  6. 若是是對象,先調用對象的 valueOf() 方法,而後對該返回值應用前面的規則。若是結果是 NaN,則調用 toString() 方法後再應用前面的規則。對象變量變成數值變量。
加法運算操做符

加號運算操做符在Javascript也用於字符串鏈接符,因此加號操做符的規則分兩種狀況:

若是兩個操做值都是數值,其規則爲:

  1. 若是一個操做數爲 NaN,則結果爲 NaN
  2. 若是是 Infinity+Infinity,結果是 Infinity
  3. 若是是 -Infinity+(-Infinity),結果是 -Infinity
  4. 若是是 Infinity+(-Infinity),結果是 NaN
  5. 若是是 +0+(+0),結果爲 +0
  6. 若是是 (-0)+(-0),結果爲 -0
  7. 若是是 (+0)+(-0),結果爲 +0

若是有一個操做值爲字符串,則:

  1. 若是兩個操做值都是字符串,則將它們拼接起來 若是隻有一個操做值爲字符串,則將另外操做值轉換爲字符串,而後拼接起來
  2. 若是一個操做數是對象、數值或者布爾值,則調用toString()方法取得字符串值,而後再應用前面的字符串規則。
  3. 對於undefined和null,分別調用String()顯式轉換爲字符串。

能夠看出,加法運算中,若是有一個操做值爲字符串類型,則將另外一個操做值轉換爲字符串,最後鏈接起來。

乘除、減號運算符、取模運算符

這些操做符針對的是運算,因此他們具備共同性:若是操做值之一不是數值,則被隱式調用Number() 函數進行轉換。具體每一種運算的詳細規則請參考ECMAScript中的定義。

邏輯操做符(!、&&、||)

邏輯非(!)操做符首先經過Boolean()函數將它的操做值轉換爲布爾值,而後求反。

邏輯與(&&)操做符,若是一個操做值不是布爾值時,遵循如下規則進行轉換:

  1. 若是第一個操做數經 Boolean() 轉換後爲 true,則返回第二個操做值,不然返回第一個值(不是 Boolean() 轉換後的值)
  2. 若是有一個操做值爲 null,返回 null
  3. 若是有一個操做值爲 NaN,返回 NaN
  4. 若是有一個操做值爲 undefined,返回 undefined

邏輯或(||)操做符,若是一個操做值不是布爾值,遵循如下規則

  1. 若是第一個操做值經 Boolean() 轉換後爲 false,則返回第二個操做值,不然返回第一個操做值(不是 Boolean() 轉換後的值)
  2. 對於 undefinednullNaN 的處理規則與邏輯與(&&)相同
關係操做符(<, >, <=, >=)

與上述操做符同樣,關係操做符的操做值也能夠是任意類型的,因此使用非數值類型參與比較時也須要系統進行隱式類型轉換:

  1. 若是兩個操做值都是數值,則進行數值比較
  2. 若是兩個操做值都是字符串,則比較字符串對應的字符編碼值
  3. 若是隻有一個操做值是數值,則將另外一個操做值轉換爲數值,進行數值比較
  4. 若是一個操做數是對象,則調用 valueOf() 方法(若是對象沒有 valueOf() 方法則調用 toString() 方法),獲得的結果按照前面的規則執行比較
  5. 若是一個操做值是布爾值,則將其轉換爲數值,再進行比較

注:NaN 是很是特殊的值,它不和任何類型的值相等,包括它本身,同時它與任何類型的值比較大小時都返回 false

相等操做符(==)

相等操做符會對操做值進行隱式轉換後進行比較:

  1. 若是一個操做值爲布爾值,則在比較以前先將其轉換爲數值
  2. 若是一個操做值爲字符串,另外一個操做值爲數值,則經過Number()函數將字符串轉換爲數值
  3. 若是一個操做值是對象,另外一個不是,則調用對象的 valueOf() 方法,獲得的結果按照前面的規則進行比較
  4. nullundefined 是相等的
  5. 若是一個操做值爲 NaN,則相等比較返回 false
  6. 若是兩個操做值都是對象,則比較它們是否是指向同一個對象

split()、join()的區別

前者是切割成數組的形式

後者是將數組轉換爲字符串

數組方法pop/push/unshift/shift

數組方法 描述
pop() 刪除原數組最後一項,並返回刪除元素的值;若是數組爲空則返回undefined
push() 將參數添加到原數組末尾,並返回數組的長度
unshift() 將參數添加到原數組開頭,並返回數組的長度
shift() 刪除原數組第一項,並返回刪除元素的值;若是數組爲空則返回undefined

事件綁定和普通事件有什麼區別

普通事件中的onclick是DOM0級事件只支持單個事件,會被其餘onclick事件覆蓋,而事件綁定中的addEventListener是DOM2級事件能夠添加多個事件而不用擔憂被覆蓋

普通添加事件的方法:

var btn = document.getElementById("hello");
btn.onclick = function(){
	alert(1);
}
btn.onclick = function(){
	alert(2);
}
複製代碼

執行上面的代碼只會alert 2

事件綁定方式添加事件:

var btn = document.getElementById("hello");
btn.addEventListener("click",function(){
	alert(1);
},false);
btn.addEventListener("click",function(){
	alert(2);
},false);
複製代碼

執行上面的代碼會先alert 1 再 alert 2

IE 和 DOM 事件流有什麼區別

事件

HTML元素事件是瀏覽器內在自動產生的,當有事件發生時html元素會向外界(這裏主要指元素事件的訂閱者)發出各類事件,如click,onmouseover,onmouseout等等。

DOM事件流

DOM(文檔對象模型)結構是一個樹型結構,當一個HTML元素產生一個事件時,該事件會在元素結點與根結點之間的路徑傳播,路徑所通過的結點都會收到該事件,這個傳播過程可稱爲DOM事件流。

冒泡型事件(Bubbling)

這是IE瀏覽器對事件模型的實現。冒泡,顧名思義,事件像個水中的氣泡同樣一直往上冒,直到頂端。從DOM樹型結構上理解,就是事件由葉子結點沿祖先結點一直向上傳遞直到根結點;從瀏覽器界面視圖HTML元素排列層次上理解就是事件由具備從屬關係的最肯定的目標元素一直傳遞到最不肯定的目標元素.

捕獲型事件(Capturing)

Netscape Navigator的實現,它與冒泡型恰好相反,由DOM樹最頂層元素一直到最精確的元素,直觀上的理解應該如同冒泡型,事件傳遞應該由最肯定的元素,即事件產生元素開始。

冒泡和捕獲

DOM標準事件模型

由於兩個不一樣的模型都有其優勢和解釋,DOM標準支持捕獲型與冒泡型,能夠說是它們二者的結合體。它能夠在一個DOM元素上綁定多個事件處理器,而且在處理函數內部,this關鍵字仍然指向被綁定的DOM元素,另外處理函數參數列表的第一個位置傳遞事件event對象。

首先是捕獲式傳遞事件,接着是冒泡式傳遞,因此,若是一個處理函數既註冊了捕獲型事件的監聽,又註冊冒泡型事件監聽,那麼在DOM事件模型中它就會被調用兩次。

實例

<body>
    <div>
        <button>點擊這裏</button>
    </div>
</body>
複製代碼

冒泡:button -> div -> body (IE 事件流)

捕獲:body -> div -> button (Netscape事件流)

DOM: body -> div -> button -> button -> div -> body(先捕獲後冒泡)

**事件偵聽函數的區別 **

// IE使用: 
[Object].attachEvent("name_of_event_handler", fnHandler); //綁定函數 
[Object].detachEvent("name_of_event_handler", fnHandler); //移除綁定 

// DOM使用: 
[Object].addEventListener("name_of_event", fnHandler, bCapture); //綁定函數 
[Object].removeEventListener("name_of_event", fnHandler, bCapture); //移除綁定 
複製代碼

如何取消瀏覽器事件的傳遞與事件傳遞後瀏覽器的默認處理

取消事件傳遞是指,中止捕獲型事件或冒泡型事件的進一步傳遞。

事件傳遞後的默認處理是指,一般瀏覽器在事件傳遞並處理完後會執行與該事件關聯的默認動做(若是存在這樣的動做)。例如,若是表單中input type 屬性是 「submit」,點擊後在事件傳播完瀏覽器就就自動提交表單。又例如,input 元素的 keydown 事件發生並處理後,瀏覽器默認會將用戶鍵入的字符自動追加到 input 元素的值中。

要取消瀏覽器的事件傳遞,IE與DOM標準又有所不一樣。

在IE下,經過設置 event 對象的 cancelBubbletrue 便可。

function someHandle() { 
   window.event.cancelBubble = true; 
}
複製代碼

DOM標準經過調用 event對象的 stopPropagation() 方法便可。

function someHandle(event) {
    event.stopPropagation(); 
}
複製代碼

因些,跨瀏覽器的中止事件傳遞的方法是:

function someHandle(event) { 
  event = event || window.event; 
  if(event.stopPropagation) 
     event.stopPropagation(); 
  else event.cancelBubble = true; 
}
複製代碼

取消事件傳遞後的默認處理,IE與DOM標準又不所不一樣。

在IE下,經過設置 event 對象的 returnValuefalse 便可。

function someHandle() { 
   window.event.returnValue = false; 
}
複製代碼

DOM標準經過調用 event 對象的 preventDefault() 方法便可。

function someHandle(event) { 
   event.preventDefault(); 
}
複製代碼

因些,跨瀏覽器的取消事件傳遞後的默認處理方法是:

function**` `someHandle(event) { 
event = event || window.event; 
if(event.preventDefault) 
     event.preventDefault(); 
  else event.returnValue = false; 
}
複製代碼

IE 和標準下有哪些兼容性的寫法

var ev = ev || window.event
document.documentElement.clinetWidth || document.body.clientWidth
var target = ev.srcElement || ev.target
複製代碼

call 和 apply 的區別

call 和 apply 相同點: 都是爲了用一個本不屬於一個對象的方法,讓這個對象去執行

基本使用

call()

function.call(obj[,arg1[, arg2[, [,.argN]]]]]) 複製代碼
  • 調用call的對象必須是個函數function
  • call的第一個參數將會是function改變上下文後指向的對象.若是不傳,將會默認是全局對象window
  • 第二個參數開始能夠接收任意個參數,這些參數將會做爲function的參數傳入function
  • 調用call的方法會當即執行

apply()

function.apply(obj[,argArray]) 複製代碼

call方法的使用基本一致,可是隻接收兩個參數,其中第二個參數必須是一個數組或者類數組,這也是這兩個方法很重要的一個區別

數組與類數組小科普

數組咱們都知道是什麼,它的特徵都有哪些呢?

  1. 能夠經過角標調用,如 array[0]
  2. 具備長度屬性length
  3. 能夠經過 for 循環和forEach方法進行遍歷

類數組顧名思義,具有的特徵應該與數組基本相同,那麼能夠知道,一個形以下面這個對象的對象就是一個類數組

var arrayLike = {
    0: 'item1',
    1: 'item2',
    2: 'item3',
    length: 3
}
複製代碼

類數組arrayLike能夠經過角標進行調用,具備length屬性,同時也能夠經過 for 循環進行遍歷

咱們常用的獲取dom節點的方法返回的就是一個類數組,在一個方法中使用 arguments關鍵字獲取到的該方法的全部參數也是一個類數組

可是類數組卻不能經過forEach進行遍歷,由於forEach是數組原型鏈上的方法,類數組畢竟不是數組,因此沒法使用

不一樣點

call方法從第二個參數開始能夠接收任意個參數,每一個參數會映射到相應位置的func的參數上,能夠經過參數名調用,可是若是將全部的參數做爲數組傳入,它們會做爲一個總體映射到func對應的第一個參數上,以後參數都爲空

function func (a,b,c) {}

func.call(obj, 1,2,3)
// function接收到的參數其實是 1,2,3

func.call(obj, [1,2,3])
// function接收到的參數其實是 [1,2,3],undefined,undefined
複製代碼

apply方法最多隻有兩個參數,第二個參數接收數組或者類數組,可是都會被轉換成類數組傳入func中,而且會被映射到func對應的參數上

func.apply(obj, [1,2,3])
// function接收到的參數其實是 1,2,3

func.apply(obj, {
    0: 1,
    1: 2,
    2: 3,
    length: 3
})
// function接收到的參數其實是 1,2,3
複製代碼

b 繼承 a 的方法

方法一:對象冒充

function Parent(username){
    this.username = username;
    this.hello = function(){
        console.log(this.username);
    }
}
function Child(username,password){
    this.method = Parent; // this.method 做爲一個臨時的屬性,而且指向了 Parent所指向的對象函數
    this.method(username); // 執行 this.method 方法,即執行了 Parent 所指向的對象函數
    delete this.method; // 銷燬 this.method 屬性,即此時 Child 就已經擁有了 Parent 的全部方法和屬性 
    this.password = password;
    this.world = function(){
        console.log(this.password);
    }
}
const parent = new Parent('hello parent');
const child = new Child('hello child','123456');
console.log(child);
parent.hello();
child.hello();
child.world(); 
複製代碼

**方法二:call() **

call 方法是 Function 類中的方法 call 方法的第一個參數的值賦值給類(即方法)中出現的 this call 方法的第二個參數開始依次賦值給類(即方法)所接受的參數

function Parent(username){
    this.username = username;
    this.hello = function(){
        console.log(this.username);
    }
}
function Child(username,password){
    Parent.call(this,username);
    this.password = password;
    this.world = function(){
        console.log(this.password);
    }
}
const parent = new Parent('hello parent');
const child = new Child('hello child','123456');
parent.hello();
child.hello();
child.world();
複製代碼

方法三:apply()

apply方法接受2個參數

第一個參數與call方法的第一個參數同樣,即賦值給類(即方法)中出現的this

第二個參數爲數組類型,這個數組中的每一個元素依次賦值給類(即方法)所接受的參數

function Parent(username){
    this.username = username;
    this.hello = function(){
        console.log(this.username);
    }
}
function Child(username,password){
    Parent.apply(this,new Array(username));
    this.password = password;
    this.world = function(){
        console.log(this.password);
    }
}
const parent = new Parent('hello parent');
const child = new Child('hello child','123456');
parent.hello();
child.hello();
child.world();
複製代碼

方法四:原型鏈

即子類經過 prototype 將全部在父類中經過 prototype 追加的屬性和方法都追加到 Child ,從而實現繼承

function Parent(){}
Parent.prototype.hello = "hello";
Parent.prototype.sayHello = function(){
    console.log(this.hello);
}
function Child(){}
Child.prototype = new Parent();// 將 Parent 中全部經過 prototype 追加的屬性和方法都追加到 Child 從而實現了繼承
Child.prototype.world = "world";
Child.prototype.sayWorld = function(){
    console.log(this.world);
}
const child = new Child();
child.sayHello();
child.sayWorld();
複製代碼

方法五:混合方式,call()+ 原型鏈

function Parent(hello){
    this.hello = hello;
}
Parent.prototype.sayHello = function(){
    console.log(this.hello);
}
function Child(hello,world){
    Parent.call(this,hello); // 將父類的屬性繼承過來
    this.world = world;
}
Child.prototype = new Parent(); //將父類的方法繼承過來
Child.prototype.sayWorld = function(){ // 新增方法
    console.log(this.world);
}
const child = new Child("hello","world");
child.sayHello();
child.sayWorld();
複製代碼

JavaScript this 指針、閉包、做用域

**js 中的this 指針 **

在函數執行時,this 老是指向調用該函數的對象。要判斷 this 的指向,其實就是判斷 this 所在的函數屬於誰。

在《javaScript語言精粹》這本書中,把 this 出現的場景分爲四類,簡單的說就是:

1)有對象就指向調用對象

var myObject = { value: 123 }
myObject.getValue = function(){
    console.log(this.value); // 123
    console.log(this); // {value: 123, getValue: ƒ}
}
myObject.getValue();
複製代碼

2)沒調用對象就指向全局對象

var myObject = { value: 123 }
myObject.getValue = function(){
    var foo = function(){
        console.log(this.value); // undefined
        console.log(this); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …} 

        // foo函數雖然定義在getValue 函數體內,可是不屬於 getValue也不屬於 myObject,因此調用的時候,它的 this 指針指向了全局對象
    }
    foo();
    return this.value;
}
console.log(myObject.getValue()); // 123
複製代碼

3) 用new構造就指向新對象

// js 中經過 new 關鍵詞來調用構造函數,此時 this 會綁定雜該新對象上
var someClass = function(){
    this.value = 123;
}
var myCreate = new someClass();
console.log(myCreate.value); // 123
複製代碼

4)經過 apply 或 call 或 bind 來改變 this 的指向

var myObject = { value: 123 };
var foo = function(){
    console.log(this);
}
foo(); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
foo.apply(myObject); // {value: 123}
foo.call(myObject); // {value: 123}
var newFoo = foo.bind(myObject);
newFoo(); // {value: 123}
複製代碼

閉包

閉包英文是 Closure ,簡而言之,閉包就是

  1. 函數的局部集合,只是這些局部變量在函數返回後會繼續存在
  2. 函數的「堆棧」在函數返回後並不釋放,能夠理解爲這些函數堆棧並不在棧上分配而是在堆上分配
  3. 當在一個函數內部定義另一個函數就會產生閉包

做爲局部變量均可以被函數內的代碼訪問,這個和靜態語言是沒有差異的,閉包的差異在於局部變量能夠在函數執行結束後仍然被函數外的代碼訪問,這意味着函數必須返回一個指向閉包的「引用」,或將這個「引用」賦值給某個外部變量,才能保證閉包中局部變量被外部代碼訪問,固然包含這個引用的實體應該是一個對象。可是ES並無提供相關的成員和方法來訪問包中的局部變量,可是在ES中,函數對象中定義的內部函數是能夠直接訪問外部函數的局部變量,經過這種機制,能夠用以下方式完成對閉包的訪問。

function greeting(name){
    var text = "Hello " + name; // 局部變量
    // 每次調用時,產生閉包,並返回內部函數對象給調用者
    return function(){
        console.log(text);
    }
}
var sayHello = greeting('Closure');
// 經過閉包訪問到了局部變量text
sayHello();  // 輸出Hello Closure 
複製代碼

在 ECMAscript 的腳本函數運行時,每一個函數關聯都有一個執行上下文場景(Exection Context),這個執行上下文包括三個部分

  • 文法環境(The LexicalEnvironment)
  • 變量環境(The VariableEnvironment)
  • this綁定

其中第三點this綁定與閉包無關,不在本文中討論。文法環境中用於解析函數執行過程使用到的變量標識符。咱們能夠將文法環境想象成一個對象,該對象包含了兩個重要組件,環境記錄(Enviroment Recode),和外部引用(指針)。環境記錄包含包含了函數內部聲明的局部變量和參數變量,外部引用指向了外部函數對象的上下文執行場景。全局的上下文場景中此引用值爲NULL。這樣的數據結構就構成了一個單向的鏈表,每一個引用都指向外層的上下文場景。

例如上面咱們例子的閉包模型應該是這樣,sayHello函數在最下層,上層是函數greeting,最外層是全局場景。以下圖:

closure

所以當sayHello被調用的時候,sayHello會經過上下文場景找到局部變量text的值,所以在屏幕的對話框中顯示出」Hello Closure」

針對一些例子來幫助你們更加深刻的理解閉包,下面共有5個樣例,例子來自於JavaScript Closures For Dummies(鏡像)

例子1:閉包中局部變量是引用而非拷貝

function say667(){
    var num = 666;
    var sayConsole = function(){
        console.log(num);
    }
    num++;
    return sayConsole;
}
var sayConsole = say667();
sayConsole(); // 667
複製代碼

例子2:多個函數綁定同一個閉包,由於他們定義在同一個函數內。

function setupSomeGlobals(){
    var num = 666;
    gConsoleNumber = function() { console.log(num); }
    gIncreaseNumber = function() { num++; }
    gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();
gConsoleNumber(); // 666
gIncreaseNumber();
gConsoleNumber(); // 667
gSetNumber(12);
gConsoleNumber(); // 12
複製代碼

例子3:當在一個循環中賦值函數時,這些函數將綁定一樣的閉包

function buildList(list){
    var result = [];
    for(var i = 0; i < list.length; i++){
        var item = 'item' + list[i];
        result.push(function(){
            console.log(item+' '+list[i]);
        })
    }
    return result;
}
function testList(){
    var fnList = buildList([1,2,3]);
    for(var j = 0; j < fnList.length; j++){
        fnList[j]();
    }
}
testList(); // 輸出3次 item3 undefined
複製代碼

testList的執行結果是彈出item3 undefined窗口三次,由於這三個函數綁定了同一個閉包,並且item的值爲最後計算的結果,可是當i跳出循環時i值爲4,因此list[4]的結果爲undefined.

例子4:外部函數全部局部變量都在閉包內,即便這個變量聲明在內部函數定義以後。

function sayAlice(){
    var sayConsole = function(){
        console.log(alice);
    }
    var alice = "Hello Alice";
    return sayConsole;
}
var helloAlice=sayAlice();
helloAlice();
複製代碼

執行結果輸出」Hello Alice」的窗口。即便局部變量聲明在函數sayAlert以後,局部變量仍然能夠被訪問到。

例子5:每次函數調用的時候建立一個新的閉包

function newClosure(someNum,someRef){
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x){
        num += x;
        anArray.push(num);
        console.log('num: ' + num +'\nanArray ' + anArray.toString() +'\nref.someVar ' + ref.someVar);
    }
}

closure1=newClosure(40,{someVar:'closure 1'});
closure2=newClosure(1000,{someVar:'closure 2'});

closure1(5); // num: 45 anArray 1,2,3,45 ref.someVar closure 1
closure2(-10); // num: 990 anArray 1,2,3,990 ref.someVar closure 2
複製代碼
閉包的缺點:

(1)因爲閉包會使得函數中的變量都被保存在內存中,內存消耗很大,因此不能濫用閉包,不然會形成網頁的性能問題,在IE中可能致使內存泄露。解決方法是,在退出函數以前,將不使用的局部變量所有刪除。

(2)閉包會在父函數外部,改變父函數內部變量的值。因此,若是你把父函數看成對象(object)使用,把閉包看成它的公用方法(Public Method),把內部變量看成它的私有屬性(private value),這時必定要當心,不要隨便改變父函數內部變量的值。

例子:

function Cars(){
  this.name = "Benz";
  this.color = ["white","black"];
}
Cars.prototype.sayColor = function(){
  var outer = this;
  return function(){
    return outer.color
  };
};
 
var instance = new Cars();
console.log(instance.sayColor()())
複製代碼

改造:

function Cars(){
  this.name = "Benz";
  this.color = ["white","black"];
}
Cars.prototype.sayColor = function(){
  var outerColor = this.color; //保存一個副本到變量中
  return function(){
    return outerColor; //應用這個副本
  };
  outColor = null; //釋放內存
};
 
var instance = new Cars();
console.log(instance.sayColor()())
複製代碼

做用域

在JS當中一個變量的做用域(scope)是程序中定義這個變量的區域。變量分爲兩類:全局(global)的和局部的。其中全局變量的做用域是全局性的,即在JavaScript代碼中,它到處都有定義。而在函數以內聲明的變量,就只在函數體內部有定義。它們是局部變量,做用域是局部性的。函數的參數也是局部變量,它們只在函數體內部有定義。

咱們能夠藉助JavaScript的做用域鏈(scope chain)更好地瞭解變量的做用域。每一個JavaScript執行環境都有一個和它關聯在一塊兒的做用域鏈。這個做用域鏈是一個對象列表或對象鏈。當JavaScript代碼須要查詢變量x的值時(這個過程叫作變量解析(variable name resolution)),它就開始查看該鏈的第一個對象。若是那個對象有一個名爲x的屬性,那麼就採用那個屬性的值。若是第一個對象沒有名爲x的屬性,JavaScript就會繼續查詢鏈中的第二個對象。若是第二個對象仍然沒有名爲x的屬性,那麼就繼續查詢下一個對象,以此類推。若是查詢到最後(指頂層代碼中)不存在這個屬性,那麼這個變量的值就是未定義的。

var a,b;
(function(){
    alert(a); // undefined 
    alert(b); // undefined 
    var a = b = 3;
    alert(a); // 3
    alert(b); // 3
})();
    alert(a); // undefined 
	alert(b); // 3
複製代碼

以上代碼至關於

var a,b;
(function(){
    alert(a);
    alert(b);
    var a = 3;
    b = 3;
    alert(a);
    alert(b);
})();
    alert(a);

複製代碼

事件委託是什麼?

概述

什麼叫作事件委託,別名叫事件代理,JavaScript 高級程序設計上講。事件委託就是利用事件冒泡,只指定一個事件處理程序,就能夠管理某一類型的全部事件。

實際例子:

有三個同事預計會在週一收到快遞,爲簽收快遞,有兩種方法:一是三我的在公司門口等快遞,二是委託給前臺的小姐代爲簽收。現實生活中,咱們大多采用委託的方案(公司也不會容忍那麼多人站在門口)。前臺小姐收到快遞後,會判斷收件人是誰,按照收件人的要求籤收,甚至是代付。這種方案還有一個好處就是,即便公司來了不少新員工(無論多少),前臺小姐也會在收到寄給新員工們的快遞後覈實代爲簽收。

這裏有2層意思: 第1、如今委託前臺的小姐是能夠代爲簽收的,即程序中的現有的 DOM 節點是有事件的。

第2、新員工也能夠被前臺小姐代爲簽收,即程序中新添加的 DOM 節點也是有事件的。

爲何要使用事件委託

通常來講,DOM 須要有事件處理程序,就會直接給它設處理程序,可是若是是不少 DOM 須要添加處理事件呢?例如咱們有100個 li,每一個 li 都有相同的 click 點擊事件,可能咱們會用到 for 循環,來遍歷全部 li ,而後給它們添加事件,那麼會存在什麼樣的問題?

在 JavsScript 中,添加到頁面上的事件處理程序數量將直接影響到總體運行性能,由於須要不斷地與 DOM 進行交互,訪問 DOM 的次數越多,引發瀏覽器重繪與重排的次數也就越多,就會延長整個頁面的交互就緒時間,這就是爲何性能優化的主要思想之一就是減小 DOM 操做的緣由、若是要用到事件委託,就會將全部的操做都放在 js 程序裏面,與 DOM 的操做就只須要交互一次,這樣就能夠大大減小與 DOM 的交互次數,提升性能。

每一個函數都是一個對象,是對象就會佔用內存,對象越多,內存佔用率就越大,天然性能就越差了(內存不夠用,是硬傷,哈哈),好比上面的100個li,就要佔用100個內存空間,若是是1000個,10000個呢,那隻能說呵呵了,若是用事件委託,那麼咱們就能夠只對它的父級(若是隻有一個父級)這一個對象進行操做,這樣咱們就須要一個內存空間就夠了,是否是省了不少,天然性能就會更好。

事件委託的原理

事件委託是利用事件的冒泡原理來實現的,何爲事件冒泡?就是事件從最深的節點開始執行,而後逐步向上傳播事件,例子:

頁面上有一個節點樹,div>ul>li>a,好比給最裏面的 a 加一個 click 點擊事件,那麼這個事件就會一層一層的往外執行,執行順序 a>li>ul>div,有這麼一個機制,那麼咱們給最外面的 div 加點擊事件,那麼裏面的 ul,li,a 作點擊事件的時候,都會冒泡到最外層的 div 上面,都會觸發,這就是事件委託,委託他們父級代爲執行事件。

事件委託怎麼實現

<ul id="ul">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>
複製代碼

實現功能是點擊li,彈出123:

window.onload = function(){
    var oUl = document.getElementById('ul');
    var aLi = oUl.getElementsByTagName('li');
    for(var i = 0; i < aLi.length; i++){
        aLi[i].onclick = function(){
            alert(123);
        }
    }
}
複製代碼

上面的代碼的意思很簡單,相信不少人都是這麼實現的,咱們看看有多少次的dom操做,首先要找到ul,而後遍歷li,而後點擊li的時候,又要找一次目標的li的位置,才能執行最後的操做,每次點擊都要找一次li;

那麼咱們用事件委託的方式作又會怎麼樣呢?

window.onload = function(){
    var oUl = document.getElementById('ul');
    oUl.onclick = function(){
        alert(123);
    }
}
複製代碼

這裏用父級ul作事件處理,當li被點擊時,因爲冒泡原理,事件就會冒泡到ul上,由於ul上有點擊事件,因此事件就會觸發,固然,這裏當點擊ul的時候,也是會觸發的,那麼問題就來了,若是我想讓事件代理的效果跟直接給節點的事件效果同樣怎麼辦,好比說只有點擊li纔會觸發,不怕,咱們有絕招:

Event對象提供了一個屬性叫target,能夠返回事件的目標節點,咱們成爲事件源,也就是說,target就能夠表示爲當前的事件操做的dom,可是不是真正操做dom,固然,這個是有兼容性的,標準瀏覽器用ev.target,IE瀏覽器用event.srcElement,此時只是獲取了當前節點的位置,並不知道是什麼節點名稱,這裏咱們用nodeName來獲取具體是什麼標籤名,這個返回的是一個大寫的,咱們須要轉成小寫再作比較(習慣問題):

window.onload = function(){
    var oUl = document.getElementById("ul");
    oUl.onclick = function(ev){
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if(target.nodeName.toLowerCase() == "li"){
            alert(target.innerHTML);
        }
    }
}
複製代碼

這樣改下就只有點擊li會觸發事件了,且每次只執行一次dom操做,若是li數量不少的話,將大大減小dom的操做,優化的性能可想而知!

上面的例子是說li操做的是一樣的效果,要是每一個li被點擊的效果都不同,那麼用事件委託還有用嗎?

var Add = document.getElementById("add");
var Remove = document.getElementById("remove");
var Move = document.getElementById("move");
var Select = document.getElementById("select");
Add.onclick = function(){
    alert('添加');
};
Remove.onclick = function(){
    alert('刪除');
};
Move.onclick = function(){
    alert('移動');
};
Select.onclick = function(){
    alert('選擇');
}
複製代碼

上面實現的效果我就很少說了,很簡單,4個按鈕,點擊每個作不一樣的操做,那麼至少須要4次dom操做,若是用事件委託,能進行優化嗎?

var oBox = document.getElementById("box");
oBox.onclick = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    if(target.nodeName.toLowerCase() == 'input'){
        switch(target.id) {
            case 'add':
            alert('添加');
            break;
            case 'remove':
            alert('刪除');
            break;
            case 'move':
            alert('移動');
            break;
            case 'select':
            alert('選擇');
            break;
            default :
            alert('業務錯誤');
            break;
        }
    }
}
複製代碼

用事件委託就能夠只用一次dom操做就能完成全部的效果,比上面的性能確定是要好一些的

如今講的都是document加載完成的現有dom節點下的操做,那麼若是是新增的節點,新增的節點會有事件嗎?也就是說,一個新員工來了,他能收到快遞嗎?

<input type="button" name="" id="btn" value="添加" />
<ul id="ul1">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>
複製代碼

如今是移入li,li變紅,移出li,li變白,這麼一個效果,而後點擊按鈕,能夠向ul中添加一個li子節點

window.onload = function(){
    var oBtn = document.getElementById("btn");
    var oUl = document.getElementById("ul");
    var aLi = oUl.getElementsByTagName("li");
    var num = 4;

    // 鼠標移入變紅,移出變白
    for(var i = 0; i < aLi.length; i++){
        aLi[i].onmouseover = function(){
            this.style.background = 'red';
        }
        aLi[i].onmouseout = function(){
            this.style.background = '#fff';
        }
    }

    // 新增節點
    oBtn.onclick = function(){
        num++;
        var oLi = document.createElement('li');
        oLi.innerHTML = 111 * num;
        oUl.appendChild(oLi);
    }
}
複製代碼

這是通常的作法,可是你會發現,新增的li是沒有事件的,說明添加子節點的時候,事件沒有一塊兒添加進去,這不是咱們想要的結果,那怎麼作呢?通常的解決方案會是這樣,將for循環用一個函數包起來,命名爲mHover,以下:

// 將鼠標移出移入包裝爲一個函數
window.onload = function(){
    var oBtn = document.getElementById("btn");
    var oUl = document.getElementById("ul");
    var aLi = oUl.getElementsByTagName("li");
    var num = 4;


    // 鼠標移入變紅,移出變白
    function mHover(){
        for(var i = 0; i < aLi.length; i++){
            aLi[i].onmouseover = function(){
                this.style.background = 'red';
            }
            aLi[i].onmouseout = function(){
                this.style.background = '#fff';
            }
        }
    }
    mHover();
    // 新增節點
    oBtn.onclick = function(){
        num++;
        var oLi = document.createElement('li');
        oLi.innerHTML = 111 * num;
        oUl.appendChild(oLi);
        mHover();
    }
}
複製代碼

雖然功能實現了,看着還挺好,但實際上無疑是又增長了一個dom操做,在優化性能方面是不可取的,那麼有事件委託的方式,能作到優化嗎?

window.onload = function(){
    var oBtn = document.getElementById("btn");
    var oUl = document.getElementById("ul");
    var aLi = oUl.getElementsByTagName("li");
    var num = 4;


    // 事件委託 鼠標移入變紅,移出變白
    // 添加的子元素也有事件
    oUl.onmouseover = function(){
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if(target.nodeName.toLowerCase() == 'li'){
            target.style.background = 'red';
        }
    }		
    oUl.onmouseout = function(){
        var ev = ev || window.event;
        var target = ev.target || ev.srcElement;
        if(target.nodeName.toLowerCase() == 'li'){
            target.style.background = '#fff';
        }
    }
    // 新增節點
    oBtn.onclick = function(){
        num++;
        var oLi = document.createElement('li');
        oLi.innerHTML = 111 * num;
        oUl.appendChild(oLi);
    }
}
複製代碼

另一個思考的問題

如今給一個場景 ul > li > div > p,div佔滿li,p佔滿div,仍是給ul綁定時間,須要判斷點擊的是否是li(假設li裏面的結構是不固定的),那麼e.target就多是p,也有多是div,這種狀況你會怎麼處理呢?

<ul id="test">
    <li>
        <p>11111111111</p>
    </li>
    <li>
        <div>
            22222222
        </div>
    </li>
    <li>
        <span>3333333333</span>
    </li>
    <li>4444444</li>
</ul>
複製代碼

如上列表,有4個li,裏面的內容各不相同,點擊li,event對象確定是當前點擊的對象,怎麼指定到li上,下面我直接給解決方案:

var oUl = document.getElementById('test');
oUl.addEventListener('click',function(ev){
    var target = ev.target;
    while (target !== oUl) {
        if(target.tagName.toLowerCase() == 'li'){
            alert(target.innerHTML);
            break;
        }
        target = target.parentNode;
    }
});
複製代碼

如何阻止事件冒泡和默認事件

在解答這個問題以前,先來看看事件的執行順序

<div>
    <ul>
        <li>冒泡/捕獲</li>
    </ul>
</div>
複製代碼

實例:

var html = document.documentElement;
var body = document.body;
var div = body.querySelector('div');
var ul = body.querySelector('ul');
var li = body.querySelector('li');


// 捕獲
ul.addEventListener('click',captureCallback,true);
li.addEventListener('click',captureCallback,true);
div.addEventListener('click',captureCallback,true);
body.addEventListener('click',captureCallback,true);
html.addEventListener('click',captureCallback,true);	

// 冒泡
ul.addEventListener('click',bubblingCallback,false);
li.addEventListener('click',bubblingCallback,false);
div.addEventListener('click',bubblingCallback,false);
body.addEventListener('click',bubblingCallback,false);
html.addEventListener('click',bubblingCallback,false);

function captureCallback(e){
    // e.stopPropagation();
    var target = e.currentTarget;
    console.log(target.tagName);
}	
function bubblingCallback(e){
    // e.stopPropagation();
    var target = e.currentTarget;
    console.log(target.tagName);
}
複製代碼

點擊 html 中的冒泡/捕獲,能夠獲得如下結果

capturing&bubbling phase.html:36 HTML
capturing&bubbling phase.html:36 BODY
capturing&bubbling phase.html:36 DIV
capturing&bubbling phase.html:36 UL
capturing&bubbling phase.html:36 LI
capturing&bubbling phase.html:40 LI
capturing&bubbling phase.html:40 UL
capturing&bubbling phase.html:40 DIV
capturing&bubbling phase.html:40 BODY
capturing&bubbling phase.html:40 HTML
複製代碼

總結就是先捕獲,後冒泡,捕獲是從上到下,冒泡是從下到上。(形象說法:捕獲像石頭沉入海底,冒泡像氣球冒出水面)

去掉 函數 bubblingCallback 中的 e.stopPropagation()的註釋,則只會進行捕獲,輸出

capturing&bubbling phase.html:40 HTML
capturing&bubbling phase.html:40 BODY
capturing&bubbling phase.html:40 DIV
capturing&bubbling phase.html:40 UL
capturing&bubbling phase.html:40 LI
capturing&bubbling phase.html:45 LI
複製代碼

而取消 函數 captureCallback中的 e.stopPropagation() 註釋,則只捕獲到 最上面的標籤

capturing&bubbling phase.html:40 HTML
複製代碼

經過上面,咱們能夠得知, event.stopPropagation(),能夠阻止 捕獲和冒泡階段當前事件的進一步傳播。

規則:

  1. 在冒泡事件和捕獲事件同時存在的狀況下,捕獲事件優先級高一點
  2. 在同一個元素的綁定事件中,冒泡和捕獲沒有次序之分,遵循Javascript的執行順序。
  3. 在元素上同時綁定捕獲事件和冒泡事件,若是經過此元素的子級元素觸發,則優先觸發捕獲事件,若不經過此元素的子級元素觸發,則按照Javascript執行順序觸發。

事件不一樣瀏覽器處理函數

  • element.addEventListener(type, listener[, useCapture]); // IE6~8不支持(捕獲和冒泡經過useCapture,默認false)
  • element.attachEvent(’on’ + type, listener); // IE6~10,IE11不支持(只執行冒泡事件)
  • element[’on’ + type] = function(){} // 全部瀏覽器(默認執行冒泡事件)

事件不一樣瀏覽器處理函數

W3C 中定義了 3個事件階段,依次是 捕獲階段、目標階段、冒泡階段。事件對象按照上圖的傳播路徑依次完成這些階段。若是某個階段不支持或事件對象的傳播被終止,那麼該階段就會被跳過。舉個例子,若是Event.bubbles屬性被設置爲false,那麼冒泡階段就會被跳過。若是Event.stopPropagation()在事件派發前被調用,那麼全部的階段都會被跳過。

  • 捕獲 階段:在事件對象到達事件目標以前,事件對象必須從 window 通過目標的祖先節點傳播到事件目標。這個階段被咱們稱爲捕獲階段,在這個階段註冊的事件監聽器在事件到達其目標前必須先處理事件。
  • 目標 階段:事件對象到達其事件目標,這個階段被咱們稱之爲目標階段。一旦事件對象達到事件目標,該階段的事件監聽器就要對它進行處理。若是一個事件對象類型被標誌爲不能冒泡。那麼對應的事件對象在到此階段時就會終止傳播。
  • 冒泡 階段:事件對象以一個與捕獲階段相反的方向從事件目標傳播通過其祖先節點傳播到 window ,這個階段被稱之爲冒泡階段,在此階段註冊的事件監聽器會對應的冒泡事件進行處理

在一個事件完成了全部階段的傳播路徑後,它的Event.currentTarget會被設置爲null而且Event.eventPhase會被設爲0。Event的全部其餘屬性都不會改變(包括指向事件目標的Event.target屬性)

跨瀏覽器的事件處理函數

var EventUtil = {
  addHandler: function(element, type, handler) {
    if (element.addEventListener) {  // DOM2
      element.addEventListener(type, handler, false);
    } else if (element.attachEvent) {  // IE
      element.attachEvent('on' + type, handler);
    } else {  // DOM0
      element['on' + type] = handler;
    }
  },

  removeHandler: function(element, type, handler) {
    if (element.removeEventListener) {
      element.removeEventListener(type, handler, false);
    } else if (element.detachEvent) {
      element.detachEvent('on' + type, handler);
    } else {
      element['on' + type] = null;
    }
  }
};
複製代碼

跨瀏覽器的事件對象:

var EventUtil = {
  getEvent: function(e) {
    return e ? e : window.e;
  },

  getTarget: function(e) {
    return e.target || e.srcElement;
  },

  preventDefault: function(e) {
    if (e.preventDefault) {
      e.preventDefault();
    } else {
      e.returnValue = false;
    }
  },

  stopPropagation: function(e) {
    if (e.stopPropagation) {
      e.stopPropagation()
    } else {
      e.cancelBubble = true;
    }
  }
}
複製代碼

js冒泡和捕獲是事件的兩種行爲,使用event.stopPropagation()起到阻止捕獲和冒泡階段中當前事件的進一步傳播。使用event.preventDefault()能夠取消默認事件。

防止冒泡和捕獲

w3c的方法是e.stopPropagation(),IE則是使用e.cancelBubble = true

topPropagation也是事件對象(Event)的一個方法,做用是阻止目標元素的冒泡事件,可是會不阻止默認行爲。什麼是冒泡事件?如在一個按鈕是綁定一個」click」事件,那麼」click」事件會依次在它的父級元素中被觸發 。stopPropagation就是阻止目標元素的事件冒泡到父級元素。如:

<div id='div' onclick='alert("div");'>
    <ul onclick='alert("ul");'>
    	<li onclick='alert("li");'>test</li>
    </ul>
</div>
複製代碼

單擊時,會依次觸發alert(「li」),alert(「ul」),alert(「div」),這就是事件冒泡。

阻止冒泡

window.event? window.event.cancelBubble = true : e.stopPropagation();
複製代碼

取消默認事件

w3c的方法是e.preventDefault(),IE則是使用e.returnValue = false

preventDefault它是事件對象(Event)的一個方法,做用是取消一個目標元素的默認行爲。既然是說默認行爲,固然是元素必須有默認行爲才能被取消,若是元素自己就沒有默認行爲,調用固然就無效了。什麼元素有默認行爲呢?如連接 <a>,提交按鈕 <input type="submit"> 等。當Event 對象的 cancelable爲false時,表示沒有默認行爲,這時即便有默認行爲,調用preventDefault也是不會起做用的。

咱們都知道,連接 <a> 的默認動做就是跳轉到指定頁面,下面就以它爲例,阻止它的跳轉:

//假定有連接<a href="http://laibh.top/" id="testA" >laibh.top</a>
var a = document.getElementById("test");
a.onclick =function(e){
if(e.preventDefault){
    	e.preventDefault();
    }else{
    	window.event.returnValue == false;
    }
}
複製代碼

return false

javascript的return false只會阻止默認行爲,而是用jQuery的話則既阻止默認行爲又防止對象冒泡

下面這個使用原生js,只會阻止默認行爲,不會中止冒泡

<div id='div' onclick='alert("div");'>
<ul onclick='alert("ul");'>
    <li id='ul-a' onclick='alert("li");'><a href="http://caibaojian.com/"id="testB">caibaojian.com</a></li>
</ul>
</div>
<script> var a = document.getElementById("testB"); a.onclick = function(){ return false; }; </script>
複製代碼

總結使用方法

當須要中止冒泡行爲時,可使用

function stopBubble(e) { 
//若是提供了事件對象,則這是一個非IE瀏覽器 
if ( e && e.stopPropagation ) 
    //所以它支持W3C的stopPropagation()方法 
    e.stopPropagation(); 
else 
    //不然,咱們須要使用IE的方式來取消事件冒泡 
    window.event.cancelBubble = true; 
}
複製代碼

當須要阻止默認行爲時,可使用

//阻止瀏覽器的默認行爲 
function stopDefault( e ) { 
    //阻止默認瀏覽器動做(W3C) 
    if ( e && e.preventDefault ) 
        e.preventDefault(); 
    //IE中阻止函數器默認動做的方式 
    else 
        window.event.returnValue = false; 
    return false; 
}
複製代碼

事件注意點

  • event表明事件的狀態,例如觸發event對象的元素、鼠標的位置及狀態、按下的鍵等等;
  • event對象只在事件發生的過程當中纔有效。

firefox裏的event跟IE裏的不一樣,IE裏的是全局變量,隨時可用;firefox裏的要用參數引導才能用,是運行時的臨時變量。

在IE/Opera中是window.event,在Firefox中是event;而事件的對象,在IE中是window.event.srcElement,在Firefox中是event.target,Opera中二者均可用。

function a(e){
    var e = (e) ? e : ((window.event) ? window.event : null); 
    var e = e || window.event; // firefox下window.event爲null, IE下event爲null
}
複製代碼

查找 添加 刪除 替換 插入到某個節點的方法

// 查找節點
document.getElementById('id'); // 經過id查找,返回惟一的節點,若是有多個將會返回第一個,在IE六、7中有個bug,會返回name值相同的元素,全部要作一個兼容
document.getElementsByClassName('class'); // 經過class查找,返回節點數組
document.getElementsByTagName('div'); // 經過標籤名

// 建立節點
document.createDocumentFragment(); // 建立內存文檔碎片
document.createElement(); // 建立元素
document.createTextNode(); // 建立文本節點

// 添加節點
var oDiv = document.createElement('div');

// 插入 Dom 節點
// 方法1:appendChild() 把節點插入到父節點的末尾
document.body.appendChild(oDiv) // 把 div 插入到 body 中,而且位於末尾
// 方法2:insertBefore() 把節點插入到父節點的某個兄弟節點的前面
var oP = createElement('p'); // 建立一個 p 節點
document.body.insertBefore(oP,oDiv); // 把 p 節點插入到 div 的前面

// 刪除節點
document.body.removeChild(oP); // 刪除 p 節點

// 替換 Dom 節點
var oSpan = document.createElement('span');
document.body.replaceChild(oSpan,oBox); // 用 span 標籤替換 div 標籤

複製代碼

javaScript 的本地對象,內置對象和宿主對象

內部對象

js中的內部對象包括Array、Boolean、Date、Function、Global、Math、Number、Object、RegExp、String以及各類錯誤類對象,包括Error、EvalError、RangeError、ReferenceError、SyntaxError和TypeError。

本地對象

Array、Boolean、Date、Function、Number、Object、RegExp、String、Error、EvalError、RangeError、ReferenceError、SyntaxError和TypeError 等 new 能夠實例化

內置對象

其中Global和Math這兩個對象又被稱爲「內置對象」,這兩個對象在腳本程序初始化時被建立,沒必要實例化這兩個對象。

宿主對象

宿主環境:通常宿主環境由外殼程序建立與維護,只要能提供js引擎執行的環境均可稱之爲外殼程序。如:web瀏覽器,一些桌面應用系統等。即由web瀏覽器或是這些桌面應用系統早就的環境即宿主環境。

那麼宿主就是瀏覽器自帶的 document , window 等

===== 的不一樣

前者會自動轉換類型,然後者不會。

比較過程:

雙等號==:

  1. 若是兩個值類型相同,再進行三個等號(===)的比較
  2. 若是兩個值類型不一樣,也有可能相等,需根據如下規則進行類型轉換再比較
    1. 若是一個是 null,一個是 undefined 那麼相等
    2. 若是一個是字符串,一個是數值,把字符串轉換成數值以後再進行比較

三等號 === :

  1. 若是類型不一樣,就必定不相等
  2. 若是兩個都是數值,而且是同一個值,那麼相等;若是其中至少一個是 NaN,那麼不相等(判斷一個值是不是 NaN,只能使用 isNaN() 來判斷)
  3. 若是兩個都是字符串,每一個位置的字符都同樣,那麼相等,不然不相等
  4. 若是兩個值都是 true,或是 false,那麼相等
  5. 若是兩個值都引用同一個對象或者是函數,那麼相等,不然不相等
  6. 若是兩個值都是 null,或者是 undefined 那麼相等。

javaScript 的同源策略

同源策略的含義:腳本只能讀取和所屬文檔來源相同的窗口和文檔的屬性。

同源指的是主機名、協議和端口號的組合

同源策略

同源策略帶來的限制

  1. cookie、LocalStorage 和 IndexDB沒法讀取

  2. DOM 沒法獲取

  3. AJAX請求沒法發送

主流跨域請求解決方案

一、JSONP 實現跨域

爲了便於客戶端使用數據,逐漸造成了一種非正式傳輸協議。人們把它稱做JSONP。該協議的一個要點就是容許用戶傳遞一個callback參數給服務端,而後服務端返回數據時會將這個callback參數做爲函數名來包裹住JSON數據,這樣客戶端就能夠隨意定製本身的函數來自動處理返回數據了。

jsonp的核心是動態添加 script標籤 來調用服務器提供的js腳本。

說說JSON和JSONP

JSONP實現原理?

  1. JS 跨域請求資源會被限制。可是在頁面中,script 標籤跨域時,倒是沒有限制的(frame,img同理)。
  2. 咱們經過,script的src屬性,請求服務器,並經過參數(如:?callback=foo,foo爲本地一個執行的方法)告訴服務器返回指定格式的JS腳本,並將數據封裝在此腳本中。
  3. 服務器再配合客戶端返回一段腳本 (如:* foo({「id」: 123, 「name」 : 張三, 「age」: 17});* ),其實返回的就是 一個客戶端本地的一個 可執行的方法的方法名, 並將要返回的 數據封裝在了參數 裏。
  4. 請求到資源後,本地就會執行此方法,經過對參數的處理,也就獲取到了咱們所要的數據。

JSONP的侷限性

JSONP 方式,當然方便強大。可是他的侷限性在於,它沒法完成POST請求。便是咱們將type改成post,在發送請求時,依然會是以Get的方式。

二、CORS跨域

CORS原理

CORS(Cross-Origin-Resource Sharing,跨院資源共享)是一種容許多種資源(圖片,Css文字,Javascript等)在一個Web頁面請求域以外的另外一個域的資源的機制。 跨域資源共享這種機制讓Web應用服務器支持跨站訪問控制,從而使得安全的進行跨站數據傳輸成爲了可能。

經過這種機制設置一系列的響應頭,這些響應頭容許瀏覽器與服務器進行交流,實現資源共享。

各語言設置響應頭的方法

CORS 解決方案相對於JSONP 更加靈活,並且支持POST請求,是跨域的根源性解決方案

三、代理層

JSONP 和CORS 是主流的 跨域問題 的解決方案。除了他們吶,還有一種解決方案,就是代理層。簡要說一下

JS 調用本源的後臺的方法(這樣就不存在跨域的問題),而經過後臺(任何具備網絡訪問功能的後臺語言,ASP.NET ,JAVA,PHP等)去跨域請求資源,然後將結果返回至前臺。

另外也能夠看看

同源策略

JavaScript的同源策略 Redirect 1

編寫一個數組去重的方法

var arr = [1,1,3,4,2,4,7] => [1,2,4,2,7]

// 方法1 雙重循環,逐個判斷數組中某個元素是否與其餘元素相同,相同則去掉,而且索引減1,重複執行直到雙重遍歷完成
function DuplicateRemoval(arr){
    for(var i = 0; i < arr.length-1; i++){
        for(var j = i + 1; j < arr.length; j++){
            if(arr[i] == arr[j]){
                arr.splice(j,1);
                j--;
            }
        }
    }
    return arr;
}
var arr=[1,1,3,4,2,4,7];
console.log(DuplicateRemoval(arr));

// 方法2 藉助 indexOf() 方法判斷此元素在該數組中首次出現位置下標與循環的下標是否相同
function DuplicateRemoval(arr){
    for(var i = 0; i < arr.length; i++){
        if(arr.indexOf(arr[i]) !== i){
            arr.splice(i,1);
            i--
        }
    }
    return arr;
}
var arr=[1,1,3,4,2,4,7];
console.log(DuplicateRemoval(arr));	

// 方法3 利用數組的方法 filter()
var arr=[1,1,3,4,2,4,7];
var result = arr.filter( (element, index, self) => self.indexOf(element) === index );
console.log(result);

// 方法4 利用新數組 經過indexOf判斷當前元素在數組中的索引若是與循環相同則添加到新數組中
var arr=[1,1,3,4,2,4,7];
function Add(arr){
    var result = [];
    for(var i = 0; i < arr.length; i++){
        if(arr.indexOf(arr[i]) === i){
            result.push(arr[i]);
        }
    }
    return result;
}
console.log(Add(arr));

// 方法5 ES6方法 Set() Set函數能夠接受一個數組(或相似數組的對象)做爲參數,用來初始化。
var arr=[1,1,3,4,2,4,7];
console.log([...new Set(arr)]);
複製代碼

JavaScript 的數據類型都有什麼

基本數據類型:String,Boolean,number,undefined,object,Null

引用數據類型:Object(Array,Date,RegExp,Function)

類型檢測

typeof

typeof 運算精度只能是基礎類型也就是 numberstringundefinedbooleanobject ,要注意的是 null 和數組使用 typeof 運算符獲得的也是 object

console.log(typeof 123); // number
console.log(typeof 'type'); // string
console.log(typeof null); // object
console.log(typeof undefined); // undefined
console.log(typeof true); // boolean
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof function(){}); // function
複製代碼
instanceof

用於判斷一個變量是否某個對象的實例,或用於判斷一個變量是否某個對象的實例

instanceof 運算符能夠精確到是哪種類型的引用,但 instanceof 也有一個缺陷就是對於直接賦值的數字,字符串,布爾值以及數組是不能將其識別爲NumberStringBooleanArray

console.log(123 instanceof Number); // false
console.log('type' instanceof String); // false
console.log(true instanceof Boolean); // false
console.log([] instanceof Array); // true
console.log({} instanceof Object); // true
console.log(function(){} instanceof Function); // true
複製代碼
toString.call()
console.log(toString.call(123) ); // [object Number]
console.log(toString.call('type')); // [object String]
console.log(toString.call(null)); // [object Null]
console.log(toString.call(undefined)); // [object Undefined]
console.log(toString.call(true)); // [object Boolean]
console.log(toString.call([])); // [object Array]
console.log(toString.call({})); // [object Object]
console.log(toString.call(function(){})); // [object Function] 
複製代碼
constructor

構造函數的屬性 用來判別對建立實例對象的函數的引用,另外 constructor 能夠被改寫

var number = 123,
string = 'type',
boolean = true,
arr = [],
obj = {},
fn = function(){};

console.log( number.constructor); // ƒ Number() { [native code] }
console.log( string.constructor); // ƒ String() { [native code] }
console.log( boolean.constructor); // ƒ Boolean() { [native code] }
console.log( arr.constructor); // ƒ Array() { [native code] }
console.log( obj.constructor); // ƒ Object() { [native code] }
console.log( fn.constructor); // ƒ Function() { [native code] }
複製代碼

也能夠用 constructor.name就會返回函數名

Object.prototype.toString.call()

這個方法其實跟上面 toString()是同樣的,只是上面那個方法有可能會被重寫。因此用這個比較穩妥,全部對象都包含有一個內部屬性[[Class]] ,這個屬性不能直接訪問,可是咱們能夠經過Object.prototype.toString.call()

console.log(Object.prototype.toString.call(123) ); // [object Number]
console.log(Object.prototype.toString.call('type')); // [object String]
console.log(Object.prototype.toString.call(null)); // [object Null]
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 Array]
console.log(Object.prototype.toString.call({})); // [object Object]
console.log(Object.prototype.toString.call(function(){})); // [object Function] 
複製代碼

須要注意的是IE6/7/8中 Object.prototype.toString.apply(null)返回「[object Object]」。

js 中 innerText/value/innerHTML三個屬性的區別

<div value = "3" id="target3">
    <input type="text" value="1" id="target1">
    <span id="target2" value="2">span</span>
</div>
複製代碼
var a = document.getElementById("target1");
var b = document.getElementById("target2");
var c = document.getElementById("target3");
console.log('----------');
console.log(a.value); // 1
console.log(a.innerHTML);
console.log(a.innerText);	
console.log('----------');
console.log(b.value); // undefined
console.log(b.innerHTML); // span
console.log(b.innerText); // span 
console.log('----------');
console.log(c.value); // undefined
console.log(c.innerHTML); 
// <input type="text" value="1" id="target1"><span id="target2" value="2">span</span>
console.log(c.innerText); // span
複製代碼

總結:

  1. innerText是標籤內的文本,輸出的是字符串,它能夠得到某個節點下面的全部標籤內的文本
  2. innerHtml 能夠得到某個DOM 節點內部的 HTML 代碼
  3. value是表單元素的屬性,得到是表單元素裏面的值

但願獲取到頁面中全部的 checkbox 怎麼作?( ( 不使用第三方框架) )

html:

<input type="checkbox">
<input type="checkbox">
<input type="checkbox">
複製代碼
window.onload = function(){
    var domList = document.getElementsByTagName('input');
    var checkBoxList = []; // 返回全部的 checkbox 
    var len = domList.length; // 緩存到局部變量
    while(len--){
        if(domList[len].type = 'checkbox'){
            checkBoxList.push(domList[len]);
        }
    }
    console.log(checkBoxList);	//  [input, input, input]
}
複製代碼

當一個 Dom節點被點擊時候,咱們但願可以執行一個函數,應該怎麼作?

<!-- 直接在 DOM 裏綁定事件:-->
<div onlick = "test()">
複製代碼
// 在js 裏面經過 onclick 綁定
xxx.onclick = test
// 在事件裏面添加綁定
addEventListener(xxx, 'click', test)
複製代碼

Javascript 的事件流模型都有什麼?

"事件冒泡":事件開始由最具體的元素接受,而後逐級向上傳播

「事件捕捉」:事件由最不具體的節點先接收,而後逐級向下,一直到最具體的

「DOM 事件流」:三個階段,事件捕捉,目標階段。事件冒泡。

已知有字符串 foo=」get-element-by-id」,寫一個 function 將其轉化成駝峯表示法」——getElementById」。

var foo = "get-element-by-id";
function combo(msg){
    var arr = msg.split('-');
    for(var i=1;i < arr.length; i++){
        arr[i] = arr[i].charAt(0).toUpperCase()+arr[i].substr(1,arr[i].length-1);
    }
    msg = arr.join('');
    return msg;
}
console.log(combo(foo)); // getElementById
複製代碼

輸出今天的日期,以 YYYY-MM-DD 的方式

var date = new Date();
var year = date.getFullYear();
var month = date.getMonth() + 1;
month = month < 10 ? '0' + month : month;
var day = date.getDate();
day = day < 10 ? '0' + day : day;
console.log([year,month,day].join('-')) // 2018-11-01
複製代碼

將字符串 」 <tr><td>{${$name}</td></tr>」 中的 {$id} 替換 成 10 , {$name} 替換成 Tony (使用正則表達式)

var str = '<tr><td>{$id}</td><td>{$name}</td></tr>';
str.replace(/{\$id}/g,10).replace(/{\$name}/,'Tony')
複製代碼

爲了保證頁面輸出安全,咱們常常須要對一些特殊的字符進行轉義,請寫一個函數 escapeHtml ,將 <, >, &, 進行轉義

function escapeHtml(str){
    return str.replace(/[<>」&]/g,function(match){
        switch(match) {
            case "<":
            return '&lt';
            break;
            case ">":
            return '&gt'
            break;
            case "\」":
            return '&quot'
            break;	
            case "&":
            return '&amp'
            break;
        }
    });
}
複製代碼

參考連接:

  1. JS類型轉換(強制和自動的規則)
  2. JavaScript 數據類型轉換(顯式與隱式)
  3. DOM標準與IE的html元素事件模型區別
  4. ie和dom事件流的區別
  5. 前端面試題——call與apply方法的異同
  6. JavaScript中B繼承A的方法Me丶微笑
  7. Javascript 中 做用域、閉包與 this 指針
  8. 理解JAVASCRIPT的閉包
  9. 深刻理解JS中的變量做用域
  10. 解決js函數閉包內存泄露問題的辦法
  11. js中的事件委託或是事件代理詳解
  12. JavaScript捕獲和冒泡探討
  13. JS阻止冒泡和取消默認事件(默認行爲)
  14. javascript 原生方法對dom節點的操做,建立、添加、刪除、替換、插入、複製、移動等操做
  15. JavaScript中本地對象、內置對象和宿主對象
  16. js中==和===區別
  17. 同源策略與JS跨域請求(圖文實例詳解)
  18. Js數組去重方法總結
  19. javascript 六種數據類型(一)
相關文章
相關標籤/搜索