這是很久很久之前想寫的東西,拖了五六個月,沒有動筆,現今補齊,內容有些多,對初學者有用,錯誤之處,望指出。
理解作用域
理解作用域鏈是Js程式設計中一個必須要具備的,作用域決定了變數和函式有權力訪問哪些資料。在Web瀏覽器中,全域性執行環境是window物件,這也意味著所有的全域性變數或者方法都是window物件的屬性或方法。當一個函式在被呼叫的時候都會建立自己的執行環境,而這個函式中所寫的程式碼就開始進入這個函式的執行環境,於是由變數物件構建起了一個作用域鏈。
1 2 3 4 |
var wow = '魔獸世界'; var message = function(){ var _wow = '123'; } |
在這個例子中全域性環境中包含了兩個物件(全域性環境的變數物件不算),window.wow和window.message,而這個message函式中又包含了兩個物件,它自己的變數物件(其中定義了arguments物件)和全域性環境的變數物件。當這個函式開始執行時,message自己的變數物件中定義了_wow,而它的全域性環境的變數物件有wow,假設在message中alert一下wow,實際上是message中包含的全域性環境的變數物件.wow,於是可以訪問。
1 2 3 4 |
var wow = '123'; var message = function(){ var wow = '456'; } |
如果執行message函式alert一下wow,它的作用域是這樣開始搜尋的,先搜尋message自己的變數物件中是否存在wow,如果有就訪問並且立馬停止搜尋,如果沒有則繼續往上訪問它,有wow,則訪問並且立馬停止搜尋,以此類推一直搜尋到全域性環境上的變數物件,如果這裡都沒,恭喜你,這裡要拋錯了。
1 2 3 4 5 6 7 |
var c = '123'; var message = function(){ var g = '123'; var a = function(){ var d = '123'; } } |
在這個例子中包含有三個執行環境,全域性環境,message的環境,a的環境。從這裡可以看出message自身包含兩個物件,自己的變數物件和全域性環境中的變數物件,而函式a則包含了三個,自身的變數物件,message的變數物件和全域性變數物件。
當開始執行這個函式時,在函式a中可以訪問到變數g,那是因為函式a包含了message的變數物件,於是在自身沒有開始搜尋上一級的變數物件時發現了,於是可以訪問。那麼訪問c的原理也是如此,當自身和上一級的message的變數物件都沒有,但是全域性變數物件中存在,於是訪問成功。
瞭解這個作用域,對於Js程式設計是至關重要的,不然可能會出現,明明想要的預期結果是123,但是變成了456,為什麼?那就是因為一級一級的搜尋,可能會存在覆蓋,或者搜尋到別的地方就立即停止搜尋了。
理解引用型別
引用型別雖然看起來和類很相似,但是它們卻是不同的概念,引用型別的值,也就是物件是引用型別的一個例項。在Js中引用型別主要有Object,Array,Date,正則,Function等。
Object和Function在後面詳細複述。
Array
在Js中陣列可以儲存任意的資料,而且它的大小是可以動態調整的類似於OC中的NSMutableArray。建立陣列可以使用建構函式的方式也可以使用字面量的形式,另外可以使用concat從一個陣列中複製一個副本出來。陣列本身提供了很多方法讓開發者使用來運算元組。
- length 陣列的長度
- toString 可以返回一個以,拼接的字串,相當於是呼叫了下join(‘,’)
- join 可以用一個分割符來拼接成一個字串
- push 新增一個資料到陣列的末端
- pop 刪除陣列中的最後一項,有返回值
- shift 刪除陣列的第一項,有返回值
- unshift 新增一個資料到陣列的首端
- reverse 倒序
- sort 可以傳入一個排序的函式
- slice 可以基於當前陣列返回一個新的陣列,接收兩個引數,返回項的起始位置和結束位置
- splice 可以傳入N個引數,第一個參數列示要刪除,插入或則替換的位置,第二個參數列示要刪除的項數,第三個到第N個表示要插入或則替換的資料
Date
時間物件也是使用非常多的玩意,它是使用GMT時間來描述,而且時間物件是可以直接比對大小的。
1 2 3 |
var date1 = new Date(2015,1,2); var date2 = new Date(2015,1,10); date1 < date2 |
常用的方法
- getTime 獲取時間物件的毫秒數
- setTime 設定時間物件的毫秒數,會改變日期
- getFullYear 獲取時間物件的年(2015)
- getMonth 獲取時間物件的月(需要加1)
- getDay 獲取日期的星期幾(0-6)星期天到星期六
- getDate 獲取日期的天數
- getHours 獲取當前日期的小時
- getMinutes 獲取當前日期的分鐘數
- getSeconds 獲取當然日期的秒數
上面看起來都是獲取,當然也有設定,只是相應的get置換成set即可。
正規表示式
在Js里正規表示式是用RegExp型別來支援的,關於正則可以看看之前寫的一篇文章,用python來描述的如何讀懂正則。
Js也支援三種模式,gim,表示全域性,不區分大小寫,多行。
一般來說很少有人這麼使用var xxx = new RegExp(),而是用字面量的方式,比如var xx = /[bc]/gi;像用的比較多的方法有exec用於捕獲包含第一個匹配項的陣列,沒有則返回null。test,用於判斷,如果匹配返回true,不匹配返回false。
處理字串
在Js中還有一種叫做包裝型別的玩意,正因為此所以處理一些基本資料型別,比如字串時,有很多方法可以使用。
- concat 可以將一個或者多個字串拼接起來,返回一個新的字串
- slice 接收兩個引數,起始位置和結束位置,返回一個新的字串
- substr和substring和slice一樣,唯一的不同是substr第二個引數是返回字串的個數
- indexOf 從頭開始查詢字串,存在會返回它所在的位置,沒有返回-1
- lastIndexOf 從最後開始查詢字串
- toUpperCase 轉大寫
- toLowerCase 轉小寫
- match 正規表示式使用跟exec一樣
- search 正規表示式使用,查詢到返回一個位置,沒有返回-1
- replace 替換,第一個引數可以是正規表示式也可以是字串,第二個引數是要替換的字串
- localeCompare比較字串,如果字串相等返回0,如果字串的字母排在引數字串之前,返回負數,如果是之後,返回正數。
函式
說起來Js的核心是什麼?那就是函式了。對於函式主要是理解它的幾個概念。
- 它可以當值來傳遞,沒有重栽。
- 宣告的時候,比如function a(){} var a = function(){} 執行時會有區別
- 函式內部的引數arguments包含了傳入的所有引數
- this,表示在這個函式內的作用域,以及prototype
理解匿名函式和閉包
匿名函式又叫拉姆達函式,主要是在把函式當值傳遞的時候用,或者是把函式當返回值,比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function d(callback){ callback(); } d(function(){ alert('123') }); //或者 function b(){ return function(){ alert('123'); } } var g = b(); g(); |
其實第二種方式跟閉包的意義一樣了,所謂的閉包書面的解釋是可以訪問另一個函式作用域內變數的函式,稍微改寫一下可能會更明顯。
1 2 3 4 5 6 7 8 |
function b(){ var name = '123'; return function(){ alert(name); } } var g = b(); g(); |
從這裡可以看出來return的函式可以訪問到name,而外部卻不行,這個返回值的函式就可以理解為閉包。理解閉包還可以看一個經典的求值的例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function save_i(){ var a = []; for(var i = 0;i<10;i++){ a[i] = function(){ return i; } } return a; } var c = save_i(); for(var i = 0;i<10;i++){ alert(c[i]()); } |
從這個例子上來看,我們想得到的結果是10次迴圈a[i]儲存著一個閉包,然後alert出從0到10,但是結果很出人意料,全部是10,為什麼?哪裡理解的不對呢?a[i]明明是內部函式,然後讓它訪問另外一個函式作用域內的變數i。
個人覺得可以這樣去分析問題,在客戶端執行Js時有一個全域性執行環境,指向的是window物件。而所謂的物件也就是引用型別,實際上在後臺執行環境中,它就是一個指標。
回到Js當程式碼在執行的時候,會建立變數物件並且構建一個作用域鏈,而這個物件儲存著當前函式可以訪問的物件。
1 2 3 4 5 6 7 8 9 |
window ->save_i ->this|argument ->a ->i ->看不見的a[0]-a[10] ->a[0]function(){} ->i ->c |
上述的i和a[0]裡的i是同一個i,那麼結果就是10。
進一步處理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function save_i(){ var a = []; for(var i = 0;i<10;i++){ a[i] = function(k){ return function(){ return k; }; }(i) } return a; } var c = save_i(); for(var i = 0;i<10;i++){ console.log(c[i]()); } |
接著按上面的節奏來分析
1 2 3 4 5 6 7 8 9 10 11 12 |
window ->save_i ->this|argument ->a ->i ->看不見的a[0]-a[10] ->a[0]function(){} ->k ->function(){} ->k ->c |
什麼是傳參?按值傳遞,相當於是在那個立即執行的函式中建立了一個新的地址和空間,雖然值是一樣的,但是每一個k又是不同的,所以得到的結果正好滿足了我們的預期。
本來正常情況下save_i執行完畢後就要銷燬,但是內部的閉包被包含在這個作用域內了,所以save_i沒法銷燬,從這裡可以看的出來閉包會帶來記憶體的問題,因為用完之後沒法銷燬,如果不注意的話。
那麼用完之後只能設定為null來解除引用,等著自動銷燬把記憶體回收。
Object
JavaScript的所有物件都衍生於Object物件,所有物件都繼承了Object.prototype上的方法和屬性,雖然它們可能會被覆蓋,熟悉它對於程式設計能起到很大的作用,也能比較深刻的瞭解JavaScript這門語言。
建立一個物件可以使用new,也可以使用快速建立的方式:
1 |
var _object = {}; |
_object物件中就可以使用Object.prototype中所有的方法和屬性,雖然看起來它是空的。說到這裡在程式設計中常常有一個非常有用的需求,如何判斷一個物件是空物件。
這是zepto中的判斷一個物件是否是空物件,常常使用:
1 2 3 4 5 |
$.isEmptyObject = function(obj) { var name for (name in obj) return false return true } |
也順便看了下jQuery原理是一模一樣的:
1 2 3 4 5 6 7 |
isEmptyObject: function( obj ) { var name; for ( name in obj ) { return false; } return true; } |
使用in操作符來實現,它不會遍歷到父原型鏈。
constructor返回一個指向建立了該物件的函式引用,這個東西主要是可以用來識別(類)到底是指向哪裡的。
defineProperty直接在一個物件上定義一個新屬性,非常適合用於動態構建,傳入三個引數[動態新增物件的目標物件,需要定義或被修改的屬性名,需要定義的物件],在第三個引數中可以有些屬性來表示是否繼承(proto),要不要定義get,set方法,enumerable是否可列舉。
defineProperties跟上述defineProperty一樣,但是它可以新增多個。
getOwnPropertyNames返回一個由指定物件的所有屬性組成的陣列
keys返回一個陣列包括物件所有的屬性(可列舉)
keys是經常會用到的一個屬性,它只能包可列舉的,如果想獲取一個物件的所有屬性包括不列舉的,那麼使用getOwnPropertyNames。
hasOwnProperty用於判斷某個物件是否包含有自身的屬性,這個方法常常用於檢測物件中的屬性是否存在,它只檢測自身,對於繼承過來的都是false,這一點是非常重要的理解。
isPrototypeOf 用於檢測一個物件是否在另一個物件的原型鏈上,比如有兩個物件是互相互動的,常常會使用它來進行檢測。
propertyIsEnumerable這個方法也比較重要,返回一個布林值,檢測一個物件的自身屬性是否可以列舉
可列舉的理解,也就是物件的屬性可列舉,它的屬性值不可以修改,但是在Js中它有自己的定義,引擎內部看不見的該屬性的[[Enumerable]]特性為true,那麼就是可列舉的。基本上把一個普通物件可以看做是一個列舉型別,比如var color = {‘red’:1},red是可以修改的,但是red是可列舉的,但是如果是繼承過來的屬性,propertyIsEnumerable是返回false的,它還有一個特點,就是自身。
如果要定義不可列舉的屬性,那就要使用defineProperty方法了,目前不能用物件直接量或者建構函式定義出來。
1 2 |
var obj = {name: 'jack', age:23} Object.defineProperty(obj, 'id', {value : '123', enumerable : false }); |
深拷貝與淺拷貝
關於拷貝的問題,主要分為深拷貝和淺拷貝,但是如果從空間分配上來說JavaScript的拷貝不應該算是深拷貝,比如:
1 2 3 4 5 |
var d = {}; for(k in a){ d[k] = a[k]; } return d; |
今天突然想到了這麼一個問題,在C語言中,所謂的拷貝,就是分兩種情況,一種是把指標地址拷貝給另外一個變數,雖然也開闢的了一個記憶體空間,在棧上也存在著一個地址,我對這個變數進行修改,同一個指標是會改變其值的,這種拷貝叫淺拷貝。另外一種情況,直接開闢一個新空間,把需要複製的值都複製在這個新的空間中,這種拷貝叫中深拷貝。
如果看到上述的一段Js程式碼,很多人說它是淺拷貝,假設傳入一個a物件,拷貝完成之後返回一個d,當我修改返回物件的值時並不能同時修改a物件,於是,在這裡我有一個很大的疑問,在Js中到底什麼是淺拷貝,什麼是深拷貝的問題?
這一點上感覺Js真的很奇葩,如果在開發iOS中,不可變物件copy一下,依然是不可變,所以是淺拷貝,拷貝了指標變數中儲存的地址值。如果是可變物件copy一下,到不可變,空間變化了,包括不可變mutableCopy到不可變,空間依然變化了,所以是深拷貝。但是JavaScript中對於這一點要考慮一種情況,值型別,和引用型別,這個基礎知識,我相信大家都非常清楚。數字,字串等都是值型別,object,array等都是引用型別。
1 2 3 4 5 6 7 8 9 10 11 |
var a = [1,2,3]; var b = a; b.push(4); console.log(a); //[1,2,3,4] var numb = 123; var _numb = numb; _numb = 567; console.log(numb); //123 |
從這個例子中可以看的出來,它們使用的都是=符號,而陣列a發生了變化,numb數字卻沒有發生變化。那麼從這裡,可以有一個總結,所謂了深拷貝,淺拷貝的問題,應該針對的是有多個巢狀發生的情況。不然假設是這樣的情況,還能叫淺拷貝麼?
1 2 3 4 |
var object = {"de":123}; var o = copy(object); o.de = 456; console.log(object) //{"de":123} |
明顯物件o中的de屬性修改並沒有影響到原始物件,一個物件中的屬性是一個字串,如果從記憶體空間的角度上來說,這裡明顯是開闢了新的空間,還能說是淺拷貝麼?那麼針對另外一種情況。
1 2 3 4 5 6 7 |
var object = { "de":{ "d":123 } } var o = deepCopy(object); o.de.d = "asd"; |
如果一個物件中的第一層屬性,不是值型別,只單層迴圈,這樣來看的話確實是一個淺拷貝,因為在Js中引用型別用=賦值,實際上是引用,這樣說的通。所以,深拷貝,還需要做一些處理,把object,array等引用型別識別出來,深層遞迴到最後一層,一個一個的拷貝。
1 2 3 4 5 6 7 8 9 10 |
var deepCopy = function(o){ var target = {}; if(typeof o !== 'object' && !Array.isArray(o)){ return o; } for(var k in o){ target[k] = deepCopy(o[k]); } return target; } |
思路是如此,這個例子只考慮了兩種情況,物件和陣列,為了驗證這樣的思路,最後的結果與預期是一樣的。
1 2 3 4 5 6 7 8 9 10 11 |
var _copy = { 'object':{ 'name':'wen' }, 'array':[1,2] } var h = deepCopy(_copy); h.object.name = 'lcepy'; h.array[1] = 8; console.log(h); console.log(_copy); |
物件導向
物件導向的語言有一個非常明顯的標誌:類,通過類來建立任意多個具有相同屬性和方法的物件,可惜的是Js裡沒有這樣的概念。
但是Js有一個特性:一切皆是物件。
聰明的開發者通過這些特性進行摸索,於是迂迴發明了一些程式設計,以便更好的組織程式碼結構。
工廠模式
主要是用來解決有多個相同屬性和方法的物件的問題,可以用函式來封裝特定的介面來實現
1 2 3 4 5 6 7 8 9 10 11 |
var computer = function(name,version){ return { 'name':name, 'version':version, 'showMessage':function(){ alert(this.name); } } } var test = computer('apple','11.1'); test.showMessage(); |
建構函式模式
我們知道像原生的建構函式,比如Object,Array等,它們是在執行時自動出現在執行環境中的。因此,為了模仿它,這裡也可以通過一個普通的函式,並且new出一個物件,這樣就成為了自定義的建構函式,也可以為他們新增自定義的屬性和方法。
但是這樣的建構函式有一個缺陷,就是每個方法都會在每個例項上建立一次,因為每次建立都需要分配記憶體空間,但是有時候這樣的特性還是有用的,主要是要控制它們,在不使用的時候釋放記憶體。
1 2 3 4 5 6 7 8 9 10 11 |
var Computer = function(name,version){ this.name = name; this.version = version; this.showMessage = function(){ alert(this.name); } } var apple = new Computer('apple',2014); var dell = new Computer('dell',2010); apple.showMessage(); dell.showMessage(); |
像apple,dell是通過Computer例項化出來的不同的物件,但是它們的constructor都是指向Computer的。這裡也可以使用instanceof來對(物件)進行檢測。
在書寫上建構函式跟其他函式是沒有什麼區別的,主要的區別還是在使用上,建構函式需要使用new操作符。
其實這樣的書寫,已經跟類沒有什麼區別了,表面上來看,而建構函式我個人更傾向於一個類的某個靜態方法。
原型模式
說到原型模式就不得不提一提關於指標的問題,因為每一個函式都有一個prototype屬性,而這個屬性是一個指標,指向一個物件。
C語言描述指標,這個在iOS開發中非常重要
比如我先定義一個int型別的指標變數和一個普通的int型別資料,然後給指標變數賦值。
1 2 3 4 5 |
int *p; int pp = 123; p = &pp; *p = 999; printf('%d',pp); |
*是一個特殊符號用於標明它是一個指標變數。
&是地址符
分析這個就要說到棧記憶體和堆記憶體了,比如*p在棧記憶體中分配了一個地址假設是ff22x0,它還沒有空間。而pp存在一個地址ff23x0,並且分配了一個空間儲存著123,這個地址是指向這個空間的。
p = &pp 這樣的賦值操作,也就是把ff23x0取出來,並且給p分配一個空間把ff23x0儲存進去,並且ff22x0指向這個空間。
*p = 999 從這裡就可以看出來p操作的是地址,而這個地址不就是ff23x0麼,於是pp成了999。
所謂的指標也就是儲存著地址的變數。
回到原型上,如果每一個函式中的 prototype屬性都是一個指標,實際上它只是一個地址引用著一個空間,而這個空間正是我們寫的xxx.prototype.xxx = function(){}這樣的程式碼在執行時分配的空間。那麼可見,使用原型的好處是空間只分配一次,大家都是共享的,因為它是指標。
先看一個例子
1 2 3 4 5 6 7 8 9 10 |
var Computer = function(name){ this.name = name; } Computer.prototype.showMessage = function(name){ alert(name); } var apple = new Computer('apple'); var dell = new Computer('dell'); Computer.prototype.isPrototypeOf(apple); |
在解釋這個原型鏈之前,還要明白Js的一個特性,就是如果自身不存在,它會沿著原型往上查詢。它的原理稍微有些繞,Computer自身的prototype是指向它自身的原型物件的,而每一個函式又有一個constructor指向它自身,prototype.constructor又指向它自身。於是Computer的兩個例項apple,dell內部有一個proto屬性是指向Computer.prototype的,最後的結果是它們可以使用showMessage方法。
當然它們還有一個搜尋原則,比如在呼叫showMessage的時候,引擎先問apple自身有showMessage嗎?“沒有”,繼續搜尋,apple的原型有嗎,“有”,呼叫。所以從這裡可以看出,this.showMessage是會覆蓋prototype.showMessage的。
另外還可以使用isPrototypeOf來檢測一個物件是否在另一個物件的原型鏈上,上述的程式碼返回的是true。
1 2 |
apple.hasOwnProperty('name') apple.hasOwnProperty('showMessage') |
使用hasOwnProperty來檢測到底是物件屬性還是原型屬性,使用this建立的屬性是一個物件屬性。
從上面可以看出來原型鏈的好處,但是它也不是萬能的,正因為指標的存在,對於某些引用型別來說這個就非常不好了,我需要保持原物件屬性值是每一個物件特有的,而不是共享的,於是把之前的建構函式與原型組合起來,也就解決了這樣的問題。
1 2 3 4 5 6 7 8 |
var Computer = function(name){ this.name = name; } Computer.prototype.showMessage = function(){ alert(this.name); } var apple = new Computer('apple'); apple.showMessage(); |
這樣的結果是在物件中都會建立一份屬於自己的屬性,而方法則是共享的。
動態原型模式
有時候遇到某些問題需要動態新增原型,但是例項中是不能新增的,所以繞來一下,在初始化建構函式中新增。
1 2 3 4 5 6 7 |
var Computer = function(){ if(typeof this.showMessage !== 'function'){ Computer.prototype.showMessage = function(){ } } } |
只要初始化了一次,以後就不用修改了。
寄生建構函式模式
這種模式的原理就是在一個函式中封裝需要建立物件的程式碼,然後返回它。
1 2 3 4 5 6 7 |
var test = function(name){ return { 'name':name } } var g = new test('apple'); var f = de('dell'); |
看起來它跟工廠模式還是很像的,
穩妥模式
這種模式主要是在解決需要安全的環境中使用,一般來說一個類如果不提供getter,setter方法,是不允許直接訪問和修改的。
1 2 3 4 5 6 7 8 9 10 11 |
var computer = function(name){ var _name = name; return { 'getter':function(){ return _name; }, 'setter':function(name){ _name = name; } } } |
這樣的方式可以保證屬性或者說是資料的安全性,不允許直接隨便修改,如果不提供setter方法的話,壓根就不允許。
繼承
談到物件導向,那麼就不能不談談繼承的問題了,而在Js中主要是將原型作為實現繼承的主要思路。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var Computer = function(name){ //this.name = name; } Computer.prototype.show = function(){ alert('computer') } var Apple = function(){ } Apple.prototype = new Computer(); Apple.prototype.hide = function(){} Apple.prototype.show = function(){ alert('apple') } var apple = new Apple(); apple.show(); alert(apple instanceof Computer); |
使用這樣的方式,實際上是從Computer的例項中先借它的prototype中所有的方法,但是這裡會存在幾個問題。
- 如果Computer中需要傳入引數,比如name,借的時候我根本不知道要傳入什麼引數。
- 在Apple中如果要繼續給原型新增方法,那麼就不能使用字面量的形式了,它會覆蓋掉
- 如果要重寫父類中的方法必須要在借prototype之後
- 那麼如何確定原型和例項的關係?貌似用instanceof和isPrototypeOf都會返回true
解決問題一如何傳入引數
我們知道Js中有兩個方法可以改變函式的上下文,apply和call,實際上類就是函式,這裡既借屬性也借prototype,不就可以解決這樣的問題了麼。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var Computer = function(name){ //this.name = name; } Computer.prototype.show = function(){ alert('computer') } var Apple = function(name){ Computer.call(this,name); } Apple.prototype = new Computer(); var apple = new Apple('apple'); alert(apple instanceof Apple); alert(apple instanceof Computer); |
在執行時先借prototype,然後再借子類的this,但是這個也有個問題,那就是會呼叫兩次父類。
繼承的技巧
還有一種繼承是生成一個臨時物件,然後臨時物件借需要繼承的父類的prototype。
1 2 3 4 5 6 7 8 9 10 11 12 |
var extend = function(o){ var F = function(){} F.prototype = o; return new F(); } var parent = { 'name':['lcepy'] } var game = extend(parent); game.name.push('wow'); var _game = extend(parent); _game.name.push('view'); |
使用這樣的方式有個很大的缺陷,那就是不要借屬性之類的資料,因為它們是共享的,這是一個淺拷貝,還是因為指標的原因。不過要是繼承方法,這種方式很方便。
還有一種方式跟上述類似,主要是封裝了一層函式,用來返回物件。
寄生組合繼承
這樣的方式主要解決的問題是呼叫兩次父類的問題,避免額外的借來的屬性或方法。想想看第一次Computer.call(this),借來了this上的屬性或方法,第二次Apple.prototype = new Computer(),又借來了this上的屬性或方法,這裡的初衷是想借原型,沒辦法這個是例項,所以該借的不該借的都借來了。那麼要避免這樣的問題,就要解決繼承屬性的繼承屬性,繼承原型的繼承原型,也不亂借。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var extendPrototype = function(sub,supers){ var F = function(){} F.prototype = supers.prototype; var _f = new F(); _f.constructor = sub; sub.prototype = _f; } var Computer = function(name){ this.name = name; } Computer.prototype.show = function(){ alert(this.name); } var Apple = function(name){ Computer.call(this,name); } extendPrototype(Apple,Computer); var apple = new Apple('apple'); apple.show(); |
第一步把supers的原型賦值給F,第二步建立F的例項,第三步把_f例項的constructor屬性修改成子類,第四步把_f例項賦值給子類的prototype。
這樣的話就是不該借的也不會繼承了
理解記憶體管理
一般來說記憶體管理主要有這麼幾種方式,引用計數和標記,而JavaScript採用的就是標記管理的方式。Js的記憶體管理是自動的,但是並不是說執行完後立馬銷燬,而是有時間週期性,相隔一段時間執行一下垃圾回收,把沒有引用的記憶體全部銷燬。
OC中採用的是引用計數來手動管理記憶體,這樣的方式比較好,可以讓開發者自己來管理。當然也有不好的地方,如果遺忘了釋放,很可能引起應用的崩潰。
總體來看在IE中因為COM元件的原因,可能會發生迴圈引用的問題,這個問題在引用計數的記憶體管理都會遇見。所謂的迴圈引用是指在物件A中包含了一個指向B的指標,然後再物件B中包含一個指向A的指標,於是悲劇了。
1 2 3 4 |
var element = document.getElementById('doc'); var my = {}; my.element = element; element.my = my; |
大家都引用,於是,可想而知。要避免這種問題,一定要在不使用的時候my.element = null,把它斷開。
那麼,其他瀏覽器呢?還是標記清理的機制,比如一個函式的變數,在進入環境時標記上“進入環境”,執行完之後標記上“離開環境”,然後等待系統來釋放。
IE有一個手動釋放的方法,window.CollectGarbage,呼叫它就立馬釋放已經標記離開環境的變數,不過很多文章都不建議這樣做。
那麼一般都這樣做,引用型別的釋放
1 2 3 |
var my = {}; //使用完畢之後 my = null; |
讓my脫離執行環境,標記上已經離開環境,然後等待系統執行垃圾回收,釋放記憶體。
XMLHttpRequest
註明: IE8已上,支援現代XMLHttpRequest
客戶端Js與伺服器進行網路互動必備的一個玩意,它不支援跨域,若要跨域還需要進行一些額外的處理。
1 |
var xhr = new XMLHttpRequest(); |
在使用xhr物件時,要呼叫的第一個方法是open(),它接受三個引數[傳送請求的型別,請求的URL,描述是否同步還是非同步的布林值]false同步,true非同步。
關於Ajax同步非同步的個人理解:
- 同步,是用資料塊的方式來傳輸的,在Js執行的表現上,當執行到這個Ajax請求時會等待它與伺服器互動成功之後才能執行下面一行的程式碼,也就是阻塞。
- 非同步,是用位元組來傳輸的,它不等待是否成功,會執行之後的程式碼
結束時需要呼叫xhr.send(),如果沒有傳送資料的主體,必須要null,做為傳送引數。另外在接收到響應之前還可以呼叫abort()來取消非同步請求(不建議呼叫它)
HTTP狀態驗證
當收到響應後會自動填充xhr物件,它有幾個比較重要的狀態,我們必須要了解清楚與處理。
- responseText:作為響應主體返回的文字
- responseXML:如果響應內容的型別是”text/xml”或者”application/xml”,這個屬性中儲存的就是XML的DOM文件
- status:響應的HTTP狀態
- statusText:HTTP狀態的說明
- readyState:用於描述請求傳送到完成的過程
正常情況下需要檢測status === 200 readyState === 4 這就表示responseText或者responseXML中已經填充了全部的資料可以提供給客戶端使用了。
1 2 3 4 5 |
1 開頭的用於描述請求已經傳送,需要請求者繼續操作才能繼續的狀態 2 開頭的用於描述請求已經成功 3 開頭的用於描述成功,但是還需要繼續操作 4 開頭的用於描述客戶端傳送了什麼資料導致伺服器錯誤 5 開頭的用於描述伺服器錯誤(常見的如,服務端程式碼拋錯了) |
readyState狀態
1 2 3 4 5 |
0 未初始化,還沒有呼叫open方法 1 已經呼叫open方法,還沒有呼叫send方法 2 已經呼叫send方法,但是還沒有接收到響應 3 已經接收了部分資料 4 已經接收了全部的資料 |
xhr物件其他方法或事件
每一個請求和響應都會帶有相應的HTTP頭資訊,其中對開發者是很有用的,而xhr物件提供了一個setRequestHeader方法來設定頭資訊,它必須在呼叫open方法之後並且在send方法之前。
既然有設定,那麼必須得有獲取,xhr物件也提供了兩個方法分別來獲取,getResponseHeader傳入一個頭部欄位名來獲取,getAllResponseHeaders來獲取全部的頭資訊。
而接收資料則需要處理onreadystatechange事件,每次重新整理狀態時,系統都會重新呼叫此事件。
跨域
客戶端Js出於安全的考慮,不允許跨域呼叫其他頁面的物件,正是因為這樣才給Ajax帶來了很多不方便的地方。跨域最簡單的理解就是因為Js同源策略的存在,比如a.com域名下的Js不能訪問b.com下的Js物件。
- 協議埠沒法跨,客戶端
- 在跨域上,域僅僅是通過首部來識別,window.location.protocol +window.location.host
利用document.domain和iframe來設定
對於主域相同而子域名不同的情況,可以通過document.domain來處理,比如www.163.com/index.html和wow.163.com/wower.html,在這兩個檔案中分別加入document.domain = “163.com”,然後在index.html頁面中建立一個iframe引入wower.html,獲取iframe的contentDocument,這樣這兩個js就可以互動了。
index.html
1 2 3 4 5 6 7 8 9 |
document.domain = '163.com'; var iframe = document.createElement('iframe'); iframe.src = 'http://wow.163.com/wower.html'; iframe.style.display = 'none'; document.body.appendChild(iframe); iframe.onload = function(){ var doc = iframe.contentDocument || iframe.contentWindow.document; //現在可以通過doc來操作wower.html中的js物件了 } |
wower.html
1 |
document.domain = '163.com'; |
使用這樣的方式來實現的跨域是有限制的
- 主域名必須是同一個
- 安全性引發的問題,比如第一個頁面出現了安全問題,在後面的頁面也會出現
- iframe引用過多的話,每一個iframe都必須設定document.domain,比較瑣碎
偶爾可以使用一下
利用window.name
稍微有些繞,但是資料量比較大,也比較安全
- wow.163.com/app.html 應用所在的頁面
- wow.163.com/empty.html 中間代理頁面,搞個空的即可,但是必須在主域名下
- www.qq.com/data.html 需要互動的資料頁面
在data.html頁面中
1 |
window.name = 123; |
app.html頁面中建立一個隱藏的iframe,它的scr指向data.html,在onload事件中,把當前iframe的contentWindow.loaction修改成empty.html,當再次onload時就可以通過contentWindow.name來獲取到123了。
偶爾使用
利用iframe和location.hash
利用這種方式,說實話(不建議),比較繞,而且資料量小,直接暴露在URL上。它的原理主要是這樣的,假設wow.163.com/index.html頁面,wow.163.com/empty.html(空的,什麼內容都沒有),需要交換資料的頁面在www.qq.com/a.html上。
在wow.163.com/index.html#(#號就是我們要傳遞的資料),建立一個隱藏的iframe,hash值可以當引數傳遞給www.qq.com/a.html#(),在www.qq.com/a.html中可以獲取到hash值,根據它進行處理,然後在www.qq.com/a.html頁面中建立一個隱藏iframe,把處理的結果當hash值進行傳遞,給wow.163.com/empty.html#()這樣,在同一個域名下,wow.163.com/empty.html中的js可以通過parent.parent.location.hash = self.location.hash來改變hash值,這樣就達到了跨域的目的。
不建議使用,坑爹的思路
JSONP
這種方式是目前開發時最常用的一種方式,利用動態建立script標籤來實現跨域的目的,雖然瀏覽器有顯示Js物件的訪問,但是它沒有限制Js檔案的載入,任何域名下的Js檔案都可以載入。
對客戶端而言,檔案的載入其實就是傳送一次GET請求,在服務端實現時,也就是處理這次的GET請求,並且響應,引數可以通過?來帶走,俗稱一波流。
在客戶端上對於script檔案載入是否已經完畢的判斷,IE是判斷script標籤的readystatechange屬性,而其他瀏覽器是onload事件。
突然感覺做移動端不考慮IE的相容,果然是槓槓的,建議使用
HTML5 postMessage
主要是利用window.postMessage來傳送訊息,監聽window.message來獲取訊息,判斷origin可以判斷訊息來源,data獲取訊息內容,soucre來引用傳送方的window物件引用。
www.b.com/b.html傳送訊息給www.a.com/a.html
1 |
window.postMessage('hello','www.a.com/a.html') |
1 2 3 4 5 |
window.addEventLister('message',function(event){ if(event.origin === 'http://b.com'){ //處理 } }) |
iframe的傳送方式
1 |
contentWindow.postMessage('data','b.com') |
話不多說,移動端這種跨域方式也很常用(建議推薦使用)
HTML5 跨域頭 XMLHttpRequest2
如果是自己產品,又是做移動端可以使用,比上述任何方式都要方便,需要服務端支援響應時也要設定跨域頭。
如果伺服器響應此頭,瀏覽器會檢查此頭,它的值表示請求內容所允許的域名,也就是如果是*號,表示所有域都可以訪問,如果這裡是a.com,表示除了同源外,只允許來自a.com域的訪問。
1 |
Access-Control-Allow-Origin:* |
如果需要讀取cookie則需要設定它
1 |
Access-Control-Allow-Credentials:true |
設定允許跨域的請求型別
1 |
Access-Control-Allow-Methods:POST |
相容性問題,某些版本的瀏覽器需要在open之後,設定xhr.withCredentials = true;話不多說,建議推薦使用
瀏覽器物件模型
BOM提供了很多物件,它的核心是window,表示它是瀏覽器的一個例項,在ECMAScript中又是Global物件。它提供了很多訪問瀏覽器的功能,這些功能與網頁無關,所以缺少事實標準的BOM既有意思又有些坑。複習它,主要是複習幾個比較有用的物件,其他可以瞭解一二。
location
算起來它是我用的最多的一個物件
它提供了當前視窗載入的頁面有關的資訊,也對URL進行了片段分解,既是window的屬性,也是document的屬性。
- hash 返回URL的雜湊(#號後面跟著的零個或多個值)
- host 返回伺服器名稱和埠號
- hostname 返回不帶埠號的伺服器名稱
- href 返回當前載入頁面的完整URL
- pathname 返回URL中的目錄或檔名
- port 返回URL中指定的埠號
- protocol 返回頁面使用的協議
- search 返回URL中的查詢字串,它以問好(?)開頭
上述的屬性基本上都可以直接使用,search除外,它返回的是一個完整的查詢字串,沒有辦法訪問其中的每個查詢字串引數,還需要額外的進行處理。
一般來說根據它的特點,?開頭&拼接,key=value的形式來展現,最好是key和value都要decodeURIComponent一下。
在location中除了上述的屬性外,還有一些比較有用的方法和技巧,主要是用來控制頁面跳轉的問題。
- assign方法接收一個引數,表示立即開啟一個新的頁面並在歷史紀錄中生成一條記錄,它的效果等同於window.location.href = ”或者location.href = ”
- 修改location物件的屬性比如href,hash,search等也可以來改變URL
- replace方法接收一個引數,既跳轉到新的URL上,並且不會在歷史紀錄中增加一條新的紀錄
- reload表示重新載入當前頁面
處理框架,設定時間,open,視窗位置,視窗大小
open現在估計沒人會用了
如果頁面中包含框架,則每個框架都有自己的window物件,可以使用frames來獲取,比如frames[0]或者frames[‘name’]。這裡還要了解的是top,parent,對於這些只要理解的層級關係,每一個指向都是會非常清楚的。
在做某些動畫效果的時候,主要是針對PC端,可能會使用到視窗位置,視窗大小的屬性來進行計算,比如innerWidth,innerHeight,outerWidth,outerHeight,獲取到這些尺寸,一般會與當前div的高寬進行減法來獲取精準的位置。
setTimeout和setInterval是進行時間排程的函式,我們知道Js是單執行緒的,但是可以使用這個在特定的時間範圍內執行程式碼,前面一個setTimeout是在指定的時間內執行(只執行一次),後面的setInterval則是以指定的時間重複執行(N次)
navigator
用這個一般是在統計使用者瀏覽器版本,作業系統等場景下才用的上,偶爾有幾個會比較實用。
- cookieEnabled 判斷cookie是否開啟
- userAgent 瀏覽器使用者代理字串
- plugins陣列 主要是用來檢測瀏覽器安裝的外掛
###screen
在Js中有幾個物件在程式設計裡真用不上,這個就是其中之一。它主要是用來表明客戶端的能力,比如顯示器的資訊,畫素,高,寬等。
history
history物件儲存著使用者上網的歷史紀錄,但是這個也是非常不常用。主要是用go方法,back方法,forward方法。
說實話,後面三個navigator,screen,history基本上很廢材,HTML5中的history物件pushState非常有用外。
文件物件模型
DOM是針對HTML和XML文件的一個API,主要是使用JavaScript來進行程式設計操作HTML和XML文件。其他語言如果實現了DOM標準,理論上也是可以使用這個API的,這裡僅僅討論JavaScript的應用。
理解層級結構與關係
在瀏覽器中比如HTML頁面是由很多有層次結構的標籤組成的,而為這些標籤提供查詢,新增,刪除等等方法主要就是DOM在提供支援。
(頁面又稱為文件)文件中所有的節點之間都存在這樣或那樣的關係,比如下面一個經典的HTML:
1 2 3 4 |
<html> <head></head> <body></body> </html> |
一個標籤又可以稱為一個元素,head和body那就是兄弟關係,它們都來自一個父系html,又可以說html的子元素是head和body,可能這樣描述還不太明顯,這樣就用原生Js操作DOM來的方式來看看層級結構。
1 |
var html = document.getElementsByTagName('html')[0]; |
先通過getElementsByTagName獲取html根元素的節點,每一個元素都有一個childNodes集合和一個parentNode分別代表子節點集合和父節點,如果不存在,則都是null,如果是集合不存在,則是一個[]。
html的childNodes //[head,body] html的parentNode // document
每一個元素也都有一個firstChild和lastChild來分別代表第一個子元素和最後一個子元素
每一個元素也都有一個nextSibling和previousSibling分別代表前面一個元素和後面一個元素,以當前自己為參照物。
從這樣可以看出來,它就像族譜一樣對元素的關係進行了定義,通過理解這些層級關係,利用DOM提供的API可以很順利的進行操作。
操作DOM
常見的獲取方式
document.getElementById (通過ID來獲取到節點)
document.getElementsByTagName (通過節點標籤來獲取)
document.querySelector
document.querySelectorAll
後面兩個屬於HTML5提供的新API,在移動端會用的比較多,前者是獲取單個,後者獲取集合。
常見新增,刪除
appendChild
insterBefore
replaceChild
removeChild
appendChild主要是向childNodes集合的末尾新增一條元素,insterBefore可以用來插入特定位置,兩個引數,要插入的節點和作為參照的節點,更新成功後插入的節點會在參照節點之前,也就是參照節點的previousSibling。replaceChild和insterBefore有些類似,兩個引數,要插入的節點和參照節點,更新成功後,要插入的節點會替換參照節點,removeChild就比較好理解了,刪除一個節點,這四個方法都有返回值。
常見元素屬性
一般來說,如果var doc = document.getElementById(‘doc’);doc.id = ‘xx’;這樣的方式也是可以更新或者獲取到元素的屬性的,不過不推薦這麼使用,要獲取元素的屬性,DOM API也提供了三個方法來使用。
getAttribute
setAttribute
removeAttribute
getAttribute可以獲取元素的屬性,setAttribute可以對元素的屬性進行設定,如果屬性名不存在,則建立該屬性。removeAttribute則是完全刪除此屬性。
還有一個屬性attributes,主要是獲取元素屬性集合,這個不是很常用,主要是在遍歷元素屬性時會使用到,它是一個集合。
常見建立元素或文字
一般情況下建立元素都會使用字串的形式,innerHTML進去。不過,某些情況下,會用到createElement來建立一個元素,如果用到它,那麼建立的文字也必須使用createTextNode了。
對於文字節點,註釋節點等開發真的很少用,可以當一個子類大概瞭解即可。
關於模式的討論,主要可以用document.compatMode來判斷,如果是CSS1Compat就是標準模式,移動端不會出現這樣的情況,IE上可能有別的模式,模式主要是影響到CSS佈局上,Js影響非常少。
在移動端上滾動是一個比較要處理的問題,一般來說會使用scrollIntoView,scrollIntoViewIfNeeded,scrollByLines,scrollByPages,這四個方法safari chrome都有實現,意味著在iOS和安卓平臺都是良好的。
scrollByPages 將元素的內容滾動到指定的頁面高度,具體的高度是由元素的高度來決定的。
scrollByLines 將元素的內容滾動到知道的行數高度,引數可正可負。
scrollIntoViewIfNeeded,當元素在視窗(viewport)不可見,會滾動容器元素或者瀏覽器視窗讓其可見。如果是可見的,這個方法不起任何作用。如果引數為true,可能是垂直居中的可見。
scrollIntoView 滾動容器元素或者瀏覽器視窗,讓元素可見。
一些小技巧
每一個元素都存在一個contains方法,用來檢測傳入的節點是不是當前節點的子節點,火狐對於的方法名叫compareDocumentPosition。
如果要獲取一個文字節點可以使用innerText(純文字)來獲取字串,如果要獲取所有的包括標籤的字串可以使用innerHTML。它們還有一種outer系列對應的方法,主要的區別是前者(outerText)會替換節點,後者(outerHTML)會修改呼叫它的元素,一般基本沒人使用。它們可以獲取,也可以通過賦值來設定新的節點。
DOM2和DOM3
對於這兩級在DOM中基本上IE沒啥支援,或者說支援的非常少,像style物件,CSS的一些物件外。
這裡最大的變化是增加了對XML名稱空間的支援,元素樣式的訪問,節點的遍歷以及range。當然目前來看,節點的遍歷,range,XML名稱空間在開發中使用的非常少,可以當資料來閱讀,瞭解有這麼回事,用到的時候再查詢。而元素樣式的訪問,這個在開發中普遍使用的較多,因為在沒法使用css3動畫的瀏覽器中,可以通過改變樣式來到達動畫的目的。
1 2 |
var doc = document.getElementById('doc'); doc.style.width = '100px'; |
對於iframe的訪問這裡增加了一個contentDocument物件來進行引用,還有節點的比較,isSameNode和isEqualNode,這兩個的區別在於,前者是否引用的同一個節點物件,後者是指兩個節點是否是相同的型別。不過,它們使用的也不多,瞭解就好。
元素的大小
這個部分需要理解,因為關乎到元素在瀏覽器上的位置顯示,跟動畫有關係,四個屬性。
- offsetWidth 元素在水平方向佔用的空間大小
- offsetHeight 元素在垂直方向佔用的空間大小
- offsetLeft 元素的左外邊框到內邊框的距離
- offsetTop 元素的上外邊框到內邊框的距離
滾動大小
這個在視察滾動或者處理滾動條的時候用的上,也是四個屬性
- scrollHeight 在沒有滾動的情況下,元素的總高度
- scrollWidth 在沒有滾動的情況下,元素的總寬度
- scrollLeft 被隱藏在內容區域左側的畫素度
- scrollTop 被隱藏在內容區域上側的畫素度
下面這些IE全部不支援,range支援一種叫做文字範圍的東西
元素遍歷
關於遍歷其實有兩個方法可用createNodeIterator和createTreeWalker,不過這些在開發中幾乎不會使用到,誰沒事去遍歷節點完呢。
關於range
這個也是非常少會使用到,除非是做那種編輯器應用或者線上編輯器等等,不過使用它可以更精準的控制的DOM,主要是使用createRange方法。
事件
IE瀏覽器的事件不是重點
事件是JavaScript與HTML進行互動的一個紐帶,理解事件可以更好的處理Web應用程式,現在的瀏覽器中主要支援兩種事件流:
- 事件冒泡
- 事件捕獲
- DOM事件流
事件冒泡則是指事件開始時由具體的元素接收,然後逐級向上傳播。比如:
1 2 3 4 5 6 7 8 |
<html> <head></head> <body> <div> <p></p> </div> </body> </html> |
給p標籤監聽一個事件,它的流向是p,div,body,html,document,其實細心看來這種流的走向會存在一個問題,給div也監聽一個事件,當使用者點選P的時候是會觸發兩次的,好在event物件中有可以阻止事件冒泡的方法。
事件捕獲則是指事件由最上級接收,逐級向下傳播到具體的元素上,瞭解了冒泡之後這個就非常好理解了,正是一個相反的步驟。
而DOM事件流又正好是冒泡與捕獲的結合體,它分為三個階段:事件捕獲,目標事件,事件冒泡,如果在紙上畫出來,它的走向就是一個圓形。
對於事件處理程式,寫在HTML標籤中的,另外一種是直接寫一個function的,比如doc.onclick = function(){},一般來說這些瀏覽器支援,但是基本上不會使用了。因為前者是跟HTML耦合的,不利程式碼維護,而且雖然HTML載入了但是Js檔案還未載入,使用者點選後,是直接報錯的。後者雖然也可以刪除,比如doc.onclick = null,對於對程式碼有強迫症的同學,基本上不會使用到它。
那麼,我們該怎麼給一個元素新增上事件處理程式呢?
DOM2級事件處理程式
- addEventLister
- removeEventLister
所有的DOM節點都具備這兩個方法,它接收三個引數:
- 要處理的事件名稱,比如click(這裡跟上述兩個以及IE註冊事件都不同,不需要on)
- 需要事件進行處理的函式
- 一個布林值,表示(true,在捕獲階段呼叫事件處理函式)(false,在冒泡階段呼叫事件處理函式)
一般情況下第三個引數都填false
IE瀏覽器對應的兩個方法,attachEvent,detachEvent,它們只有冒泡,事件名要加上on。
事件物件
在註冊完事件處理程式後,事件的一個比較重要的物件必須要理解,event事件物件。
一般來說,這個物件中包含著所有與當前元素所監聽的事件有關的資訊,比如元素監聽的事件型別,元素本身等等。
比較重要的屬性和方法(只讀)
- currentTarget 真正監聽事件的那個元素
- target 事件的目標元素
- type 事件的型別
- perventDefault() 取消事件的預設行為
- stopPropagation() 取消事件的捕獲或者冒泡
- bubbles 事件是否冒泡
- eventPhase 事件處理程式的三個階段,1捕獲2處於目標3冒泡
比較重要的屬性和方法(讀寫)
- clientX 滑鼠在視窗中的水平位置
- clientY 滑鼠在視窗中的垂直位置
事件型別
PC端主要是針對滑鼠,移動端則是觸控,手勢相關的處理
如果在PC端上發生一次click事件,實際上它是發生了三次事件,mousedown當滑鼠按下的時候,mouseup當使用者放開的時候,click兩個加起來就發生了一次click事件。相對於移動,PC上的滑鼠事件非常的豐富,例如mouseover當滑鼠首次移入一個元素邊界時觸發,mouseout當滑鼠移出元素時觸發,這個移出,到子元素上也會觸發這個事件,mousemove當滑鼠在元素內移動時重複觸發。
總體來說對於文件載入,表單控制元件,視窗大小改變等事件,比如獲取焦點,在失去或者獲取焦點是值改變等移動上都是一樣的,focus(獲得焦點)blur(失去焦點)。
在做一些視差滾動的效果時scroll事件是非常好用,移動上在css中提供了一個類似的屬性。
唯一的區別是移動端上沒有鍵盤事件。
移動事件
- touchstart 當手指觸控到螢幕時觸發
- touchmove 當手指在螢幕上連續滑動時觸發
- touchend 當手指從螢幕上移開時觸發
- touchcancel 當系統停止跟蹤觸控時觸發(這個事件沒有確定的觸發時間)
它們都是冒泡的,也可以取消
三個跟蹤觸控事件的屬性
- touches 當前跟蹤觸控操作的touch陣列,在touchend事件中為空
- targetTouchs 特定事件目標的touch陣列
- ChangedTouches 上次觸控時發生了什麼改變的touch陣列
移動event事件物件
PC上存在的,在移動上也存在,描述上有差異,比如
- target 觸控的DOM節點目標
- pageX 觸控目標在頁面中的X座標
- pageY 觸控目標在頁面中的Y座標
一些手勢
- gesturestart 當一個手指按在螢幕上另外一個手指又觸控螢幕時觸發
- gesturechange 依賴前者當其中的一個手指發生改變時觸發
- gestureend 當任何一個手指離開時觸發
移動手勢乾貨三部曲
結語
現在的前端開發瞭解JS還是僅僅不夠的,你需要多方面擴充套件。
訪問Front-End-Develop-Guide專案,資料已準備齊全。