理解做用域鏈是Js編程中一個必需要具有的,做用域決定了變量和函數有權力訪問哪些數據。在Web瀏覽器中,全局執行環境是window對象,這也意味着全部的全局變量或者方法都是window對象的屬性或方法。當一個函數在被調用的時候都會建立本身的執行環境,而這個函數中所寫的代碼就開始進入這個函數的執行環境,因而由變量對象構建起了一個做用域鏈。css
var wow = '魔獸世界'; var message = function(){ var _wow = '123'; }
在這個例子中全局環境中包含了兩個對象(全局環境的變量對象不算),window.wow和window.message,而這個message函數中又包含了兩個對象,它本身的變量對象(其中定義了arguments對象)和全局環境的變量對象。當這個函數開始執行時,message本身的變量對象中定義了_wow,而它的全局環境的變量對象有wow,假設在message中alert一下wow,其實是message中包含的全局環境的變量對象.wow,因而能夠訪問。html
var wow = '123'; var message = function(){ var wow = '456'; }
若是執行message函數alert一下wow,它的做用域是這樣開始搜索的,先搜索message本身的變量對象中是否存在wow,若是有就訪問而且立馬中止搜索,若是沒有則繼續往上訪問它,有wow,則訪問而且立馬中止搜索,以此類推一直搜索到全局環境上的變量對象,若是這裏都沒,恭喜你,這裏要拋錯了。python
var c = '123'; var message = function(){ var g = '123'; var a = function(){ var d = '123'; } }
在這個例子中包含有三個執行環境,全局環境,message的環境,a的環境。從這裏能夠看出message自身包含兩個對象,本身的變量對象和全局環境中的變量對象,而函數a則包含了三個,自身的變量對象,message的變量對象和全局變量對象。css3
當開始執行這個函數時,在函數a中能夠訪問到變量g,那是由於函數a包含了message的變量對象,因而在自身沒有開始搜索上一級的變量對象時發現了,因而能夠訪問。那麼訪問c的原理也是如此,當自身和上一級的message的變量對象都沒有,可是全局變量對象中存在,因而訪問成功。web
瞭解這個做用域,對於Js編程是相當重要的,否則可能會出現,明明想要的預期結果是123,可是變成了456,爲何?那就是由於一級一級的搜索,可能會存在覆蓋,或者搜索到別的地方就當即中止搜索了。正則表達式
引用類型雖然看起來和類很類似,可是它們倒是不一樣的概念,引用類型的值,也就是對象是引用類型的一個實例。在Js中引用類型主要有Object,Array,Date,正則,Function等。chrome
Object和Function在後面詳細複述。編程
Array跨域
在Js中數組能夠存儲任意的數據,並且它的大小是能夠動態調整的相似於OC中的NSMutableArray。建立數組可使用構造函數的方式也可使用字面量的形式,另外可使用concat從一個數組中複製一個副本出來。數組自己提供了不少方法讓開發者使用來操做數組。數組
Date
時間對象也是使用很是多的玩意,它是使用GMT時間來描述,並且時間對象是能夠直接比對大小的。
var date1 = new Date(2015,1,2); var date2 = new Date(2015,1,10); date1 < date2
經常使用的方法
上面看起來都是獲取,固然也有設置,只是相應的get置換成set便可。
正則表達式
在Js里正則表達式是用RegExp類型來支持的,關於正則能夠看看以前寫的一篇文章,用python來描述的如何讀懂正則。
Js也支持三種模式,gim,表示全局,不區分大小寫,多行。
通常來講不多有人這麼使用var xxx = new RegExp(),而是用字面量的方式,好比var xx = /[bc]/gi;像用的比較多的方法有exec用於捕獲包含第一個匹配項的數組,沒有則返回null。test,用於判斷,若是匹配返回true,不匹配返回false。
處理字符串
在Js中還有一種叫作包裝類型的玩意,正由於此因此處理一些基本數據類型,好比字符串時,有不少方法可使用。
提及來Js的核心是什麼?那就是函數了。對於函數主要是理解它的幾個概念。
匿名函數又叫拉姆達函數,主要是在把函數當值傳遞的時候用,或者是把函數當返回值,好比:
function d(callback){ callback(); } d(function(){ alert('123') }); //或者 function b(){ return function(){ alert('123'); } } var g = b(); g();
其實第二種方式跟閉包的意義同樣了,所謂的閉包書面的解釋是能夠訪問另外一個函數做用域內變量的函數,稍微改寫一下可能會更明顯。
function b(){ var name = '123'; return function(){ alert(name); } } var g = b(); g();
從這裏能夠看出來return的函數能夠訪問到name,而外部卻不行,這個返回值的函數就能夠理解爲閉包。理解閉包還能夠看一個經典的求值的例子。
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當代碼在執行的時候,會建立變量對象而且構建一個做用域鏈,而這個對象保存着當前函數能夠訪問的對象。
window ->save_i ->this|argument ->a ->i ->看不見的a[0]-a[10] ->a[0]function(){} ->i ->c
上述的i和a[0]裏的i是同一個i,那麼結果就是10。
進一步處理
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]()); }
接着按上面的節奏來分析
window ->save_i ->this|argument ->a ->i ->看不見的a[0]-a[10] ->a[0]function(){} ->k ->function(){} ->k ->c
什麼是傳參?按值傳遞,至關因而在那個當即執行的函數中建立了一個新的地址和空間,雖然值是同樣的,可是每個k又是不一樣的,因此獲得的結果正好知足了咱們的預期。
原本正常狀況下save_i執行完畢後就要銷燬,可是內部的閉包被包含在這個做用域內了,因此save_i無法銷燬,從這裏能夠看的出來閉包會帶來內存的問題,由於用完以後無法銷燬,若是不注意的話。
那麼用完以後只能設置爲null來解除引用,等着自動銷燬把內存回收。
JavaScript的全部對象都衍生於Object對象,全部對象都繼承了Object.prototype上的方法和屬性,雖然它們可能會被覆蓋,熟悉它對於編程能起到很大的做用,也能比較深入的瞭解JavaScript這門語言。
建立一個對象可使用new,也可使用快速建立的方式:
var _object = {};
_object對象中就可使用Object.prototype中全部的方法和屬性,雖然看起來它是空的。說到這裏在編程中經常有一個很是有用的需求,如何判斷一個對象是空對象。
這是zepto中的判斷一個對象是不是空對象,經常使用:
$.isEmptyObject = function(obj) { var name for (name in obj) return false return true }
也順便看了下jQuery原理是如出一轍的:
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方法了,目前不能用對象直接量或者構造函數定義出來。
var obj = {name: 'jack', age:23} Object.defineProperty(obj, 'id', {value : '123', enumerable : false });
關於拷貝的問題,主要分爲深拷貝和淺拷貝,可是若是從空間分配上來講JavaScript的拷貝不該該算是深拷貝,好比:
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等都是引用類型。
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數字卻沒有發生變化。那麼從這裏,能夠有一個總結,所謂了深拷貝,淺拷貝的問題,應該針對的是有多個嵌套發生的狀況。否則假設是這樣的狀況,還能叫淺拷貝麼?
var object = {"de":123}; var o = copy(object); o.de = 456; console.log(object) //{"de":123}
明顯對象o中的de屬性修改並無影響到原始對象,一個對象中的屬性是一個字符串,若是從內存空間的角度上來講,這裏明顯是開闢了新的空間,還能說是淺拷貝麼?那麼針對另一種狀況。
var object = { "de":{ "d":123 } } var o = deepCopy(object); o.de.d = "asd";
若是一個對象中的第一層屬性,不是值類型,只單層循環,這樣來看的話確實是一個淺拷貝,由於在Js中引用類型用=賦值,其實是引用,這樣說的通。因此,深拷貝,還須要作一些處理,把object,array等引用類型識別出來,深層遞歸到最後一層,一個一個的拷貝。
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; }
思路是如此,這個例子只考慮了兩種狀況,對象和數組,爲了驗證這樣的思路,最後的結果與預期是同樣的。
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有一個特性:一切皆是對象。
聰明的開發者經過這些特性進行摸索,因而迂迴發明了一些程序設計,以便更好的組織代碼結構。
主要是用來解決有多個相同屬性和方法的對象的問題,能夠用函數來封裝特定的接口來實現
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出一個對象,這樣就成爲了自定義的構造函數,也能夠爲他們添加自定義的屬性和方法。
可是這樣的構造函數有一個缺陷,就是每一個方法都會在每一個實例上建立一次,由於每次建立都須要分配內存空間,可是有時候這樣的特性仍是有用的,主要是要控制它們,在不使用的時候釋放內存。
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類型數據,而後給指針變量賦值。
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(){}這樣的代碼在運行時分配的空間。那麼可見,使用原型的好處是空間只分配一次,你們都是共享的,由於它是指針。
先看一個例子
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。
apple.hasOwnProperty('name') apple.hasOwnProperty('showMessage')
使用hasOwnProperty來檢測究竟是對象屬性仍是原型屬性,使用this建立的屬性是一個對象屬性。
從上面能夠看出來原型鏈的好處,可是它也不是萬能的,正由於指針的存在,對於某些引用類型來講這個就很是很差了,我須要保持原對象屬性值是每個對象特有的,而不是共享的,因而把以前的構造函數與原型組合起來,也就解決了這樣的問題。
var Computer = function(name){ this.name = name; } Computer.prototype.showMessage = function(){ alert(this.name); } var apple = new Computer('apple'); apple.showMessage();
這樣的結果是在對象中都會建立一份屬於本身的屬性,而方法則是共享的。
動態原型模式
有時候遇到某些問題須要動態添加原型,可是實例中是不能添加的,因此繞來一下,在初始化構造函數中添加。
var Computer = function(){ if(typeof this.showMessage !== 'function'){ Computer.prototype.showMessage = function(){ } } }
只要初始化了一次,之後就不用修改了。
這種模式的原理就是在一個函數中封裝須要建立對象的代碼,而後返回它。
var test = function(name){ return { 'name':name } } var g = new test('apple'); var f = de('dell');
看起來它跟工廠模式仍是很像的,
這種模式主要是在解決須要安全的環境中使用,通常來講一個類若是不提供getter,setter方法,是不容許直接訪問和修改的。
var computer = function(name){ var _name = name; return { 'getter':function(){ return _name; }, 'setter':function(name){ _name = name; } } }
這樣的方式能夠保證屬性或者說是數據的安全性,不容許直接隨便修改,若是不提供setter方法的話,壓根就不容許。
談到面向對象,那麼就不能不談談繼承的問題了,而在Js中主要是將原型做爲實現繼承的主要思路。
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中全部的方法,可是這裏會存在幾個問題。
解決問題一如何傳入參數
咱們知道Js中有兩個方法能夠改變函數的上下文,apply和call,實際上類就是函數,這裏既借屬性也借prototype,不就能夠解決這樣的問題了麼。
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。
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上的屬性或方法,這裏的初衷是想借原型,沒辦法這個是實例,因此該借的不應借的都借來了。那麼要避免這樣的問題,就要解決繼承屬性的繼承屬性,繼承原型的繼承原型,也不亂借。
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的指針,因而悲劇了。
var element = document.getElementById('doc'); var my = {}; my.element = element; element.my = my;
你們都引用,因而,可想而知。要避免這種問題,必定要在不使用的時候my.element = null,把它斷開。
那麼,其餘瀏覽器呢?仍是標記清理的機制,好比一個函數的變量,在進入環境時標記上「進入環境」,執行完以後標記上「離開環境」,而後等待系統來釋放。
IE有一個手動釋放的方法,window.CollectGarbage,調用它就立馬釋放已經標記離開環境的變量,不過不少文章都不建議這樣作。
那麼通常都這樣作,引用類型的釋放
var my = {}; //使用完畢以後 my = null;
讓my脫離執行環境,標記上已經離開環境,而後等待系統執行垃圾回收,釋放內存。
註明: IE8已上,支持現代XMLHttpRequest
客戶端Js與服務器進行網絡交互必備的一個玩意,它不支持跨域,若要跨域還須要進行一些額外的處理。
var xhr = new XMLHttpRequest();
在使用xhr對象時,要調用的第一個方法是open(),它接受三個參數[發送請求的類型,請求的URL,描述是否同步仍是異步的布爾值]false同步,true異步。
關於Ajax同步異步的我的理解:
結束時須要調用xhr.send(),若是沒有發送數據的主體,必需要null,作爲發送參數。另外在接收到響應以前還能夠調用abort()來取消異步請求(不建議調用它)
當收到響應後會自動填充xhr對象,它有幾個比較重要的狀態,咱們必需要了解清楚與處理。
正常狀況下須要檢測status === 200 readyState === 4 這就表示responseText或者responseXML中已經填充了所有的數據能夠提供給客戶端使用了。
1 開頭的用於描述請求已經發送,須要請求者繼續操做才能繼續的狀態 2 開頭的用於描述請求已經成功 3 開頭的用於描述成功,可是還須要繼續操做 4 開頭的用於描述客戶端發送了什麼數據致使服務器錯誤 5 開頭的用於描述服務器錯誤(常見的如,服務端代碼拋錯了)
readyState狀態
0 未初始化,尚未調用open方法 1 已經調用open方法,尚未調用send方法 2 已經調用send方法,可是尚未接收到響應 3 已經接收了部分數據 4 已經接收了所有的數據
每個請求和響應都會帶有相應的HTTP頭信息,其中對開發者是頗有用的,而xhr對象提供了一個setRequestHeader方法來設置頭信息,它必須在調用open方法以後而且在send方法以前。
既然有設置,那麼必須得有獲取,xhr對象也提供了兩個方法分別來獲取,getResponseHeader傳入一個頭部字段名來獲取,getAllResponseHeaders來獲取所有的頭信息。
而接收數據則須要處理onreadystatechange事件,每次刷新狀態時,系統都會從新調用此事件。
客戶端Js出於安全的考慮,不容許跨域調用其餘頁面的對象,正是由於這樣纔給Ajax帶來了不少不方便的地方。跨域最簡單的理解就是由於Js同源策略的存在,好比a.com域名下的Js不能訪問b.com下的Js對象。
對於主域相同而子域名不一樣的狀況,能夠經過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
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
document.domain = '163.com';
使用這樣的方式來實現的跨域是有限制的
偶爾可使用一下
稍微有些繞,可是數據量比較大,也比較安全
在data.html頁面中
window.name = 123;
app.html頁面中建立一個隱藏的iframe,它的scr指向data.html,在onload事件中,把當前iframe的contentWindow.loaction修改爲empty.html,當再次onload時就能夠經過contentWindow.name來獲取到123了。
偶爾使用
利用這種方式,說實話(不建議),比較繞,並且數據量小,直接暴露在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值,這樣就達到了跨域的目的。
不建議使用,坑爹的思路
這種方式是目前開發時最經常使用的一種方式,利用動態建立script標籤來實現跨域的目的,雖然瀏覽器有顯示Js對象的訪問,可是它沒有限制Js文件的加載,任何域名下的Js文件均可以加載。
對客戶端而言,文件的加載其實就是發送一次GET請求,在服務端實現時,也就是處理此次的GET請求,而且響應,參數能夠經過?來帶走,俗稱一波流。
在客戶端上對於script文件加載是否已經完畢的判斷,IE是判斷script標籤的readystatechange屬性,而其餘瀏覽器是onload事件。
忽然感受作移動端不考慮IE的兼容,果真是槓槓的,建議使用
主要是利用window.postMessage來發送消息,監聽window.message來獲取消息,判斷origin能夠判斷消息來源,data獲取消息內容,soucre來引用發送方的window對象引用。
www.b.com/b.html發送消息給www.a.com/a.html
window.postMessage('hello','www.a.com/a.html')
window.addEventLister('message',function(event){ if(event.origin === 'http://b.com'){ //處理 } })
iframe的發送方式
contentWindow.postMessage('data','b.com')
話很少說,移動端這種跨域方式也很經常使用(建議推薦使用)
若是是本身產品,又是作移動端可使用,比上述任何方式都要方便,須要服務端支持響應時也要設置跨域頭。
若是服務器響應此頭,瀏覽器會檢查此頭,它的值表示請求內容所容許的域名,也就是若是是*號,表示全部域均可以訪問,若是這裏是a.com,表示除了同源外,只容許來自a.com域的訪問。
Access-Control-Allow-Origin:*
若是須要讀取cookie則須要設置它
Access-Control-Allow-Credentials:true
設置容許跨域的請求類型
Access-Control-Allow-Methods:POST
兼容性問題,某些版本的瀏覽器須要在open以後,設置xhr.withCredentials = true;話很少說,建議推薦使用
BOM提供了不少對象,它的核心是window,表示它是瀏覽器的一個實例,在ECMAScript中又是Global對象。它提供了不少訪問瀏覽器的功能,這些功能與網頁無關,因此缺乏事實標準的BOM既有意思又有些坑。複習它,主要是複習幾個比較有用的對象,其餘能夠了解一二。
算起來它是我用的最多的一個對象
它提供了當前窗口加載的頁面有關的信息,也對URL進行了片斷分解,既是window的屬性,也是document的屬性。
上述的屬性基本上均可以直接使用,search除外,它返回的是一個完整的查詢字符串,沒有辦法訪問其中的每一個查詢字符串參數,還須要額外的進行處理。
通常來講根據它的特色,?開頭&拼接,key=value的形式來展示,最好是key和value都要decodeURIComponent一下。
在location中除了上述的屬性外,還有一些比較有用的方法和技巧,主要是用來控制頁面跳轉的問題。
open如今估計沒人會用了
若是頁面中包含框架,則每一個框架都有本身的window對象,可使用frames來獲取,好比frames[0]或者frames['name']。這裏還要了解的是top,parent,對於這些只要理解的層級關係,每個指向都是會很是清楚的。
在作某些動畫效果的時候,主要是針對PC端,可能會使用到窗口位置,窗口大小的屬性來進行計算,好比innerWidth,innerHeight,outerWidth,outerHeight,獲取到這些尺寸,通常會與當前div的高寬進行減法來獲取精準的位置。
setTimeout和setInterval是進行時間調度的函數,咱們知道Js是單線程的,可是可使用這個在特定的時間範圍內執行代碼,前面一個setTimeout是在指定的時間內執行(只執行一次),後面的setInterval則是以指定的時間重複執行(N次)
用這個通常是在統計用戶瀏覽器版本,操做系統等場景下才用的上,偶爾有幾個會比較實用。
在Js中有幾個對象在編程裏真用不上,這個就是其中之一。它主要是用來代表客戶端的能力,好比顯示器的信息,像素,高,寬等。
history對象保存着用戶上網的歷史紀錄,可是這個也是很是不經常使用。主要是用go方法,back方法,forward方法。
說實話,後面三個navigator,screen,history基本上很廢材,HTML5中的history對象pushState很是有用外。
DOM是針對HTML和XML文檔的一個API,主要是使用JavaScript來進行編程操做HTML和XML文檔。其餘語言若是實現了DOM標準,理論上也是可使用這個API的,這裏僅僅討論JavaScript的應用。
理解層級結構與關係
在瀏覽器中好比HTML頁面是由不少有層次結構的標籤組成的,而爲這些標籤提供查詢,添加,刪除等等方法主要就是DOM在提供支持。
(頁面又稱爲文檔)文檔中全部的節點之間都存在這樣或那樣的關係,好比下面一個經典的HTML:
<html> <head></head> <body></body> </html>
一個標籤又能夠稱爲一個元素,head和body那就是兄弟關係,它們都來自一個父系html,又能夠說html的子元素是head和body,可能這樣描述還不太明顯,這樣就用原生Js操做DOM來的方式來看看層級結構。
var html = document.getElementsByTagName('html')[0];
先經過getElementsByTagName獲取html根元素的節點,每個元素都有一個childNodes集合和一個parentNode分別表明子節點集合和父節點,若是不存在,則都是null,若是是集合不存在,則是一個[]。
html的childNodes //[head,body] html的parentNode // document
每個元素也都有一個firstChild和lastChild來分別表明第一個子元素和最後一個子元素
每個元素也都有一個nextSibling和previousSibling分別表明前面一個元素和後面一個元素,以當前本身爲參照物。
從這樣能夠看出來,它就像族譜同樣對元素的關係進行了定義,經過理解這些層級關係,利用DOM提供的API能夠很順利的進行操做。
常見的獲取方式
後面兩個屬於HTML5提供的新API,在移動端會用的比較多,前者是獲取單個,後者獲取集合。
常見添加,刪除
appendChild主要是向childNodes集合的末尾添加一條元素,insterBefore能夠用來插入特定位置,兩個參數,要插入的節點和做爲參照的節點,更新成功後插入的節點會在參照節點以前,也就是參照節點的previousSibling。replaceChild和insterBefore有些相似,兩個參數,要插入的節點和參照節點,更新成功後,要插入的節點會替換參照節點,removeChild就比較好理解了,刪除一個節點,這四個方法都有返回值。
常見元素屬性
通常來講,若是var doc = document.getElementById('doc');doc.id = 'xx';這樣的方式也是能夠更新或者獲取到元素的屬性的,不過不推薦這麼使用,要獲取元素的屬性,DOM API也提供了三個方法來使用。
getAttribute能夠獲取元素的屬性,setAttribute能夠對元素的屬性進行設置,若是屬性名不存在,則建立該屬性。removeAttribute則是徹底刪除此屬性。
還有一個屬性attributes,主要是獲取元素屬性集合,這個不是很經常使用,主要是在遍歷元素屬性時會使用到,它是一個集合。
常見建立元素或文本
通常狀況下建立元素都會使用字符串的形式,innerHTML進去。不過,某些狀況下,會用到createElement來建立一個元素,若是用到它,那麼建立的文本也必須使用createTextNode了。
對於文本節點,註釋節點等開發真的不多用,能夠當一個子類大概瞭解便可。
關於模式的討論,主要能夠用document.compatMode來判斷,若是是CSS1Compat就是標準模式,移動端不會出現這樣的狀況,IE上可能有別的模式,模式主要是影響到CSS佈局上,Js影響很是少。
在移動端上滾動是一個比較要處理的問題,通常來講會使用scrollIntoView,scrollIntoViewIfNeeded,scrollByLines,scrollByPages,這四個方法safari chrome都有實現,意味着在iOS和安卓平臺都是良好的。
一些小技巧
每個元素都存在一個contains方法,用來檢測傳入的節點是否是當前節點的子節點,火狐對於的方法名叫compareDocumentPosition。
若是要獲取一個文本節點可使用innerText(純文本)來獲取字符串,若是要獲取全部的包括標籤的字符串可使用innerHTML。它們還有一種outer系列對應的方法,主要的區別是前者(outerText)會替換節點,後者(outerHTML)會修改調用它的元素,通常基本沒人使用。它們能夠獲取,也能夠經過賦值來設置新的節點。
對於這兩級在DOM中基本上IE沒啥支持,或者說支持的很是少,像style對象,CSS的一些對象外。
這裏最大的變化是增長了對XML命名空間的支持,元素樣式的訪問,節點的遍歷以及range。固然目前來看,節點的遍歷,range,XML命名空間在開發中使用的很是少,能夠當資料來閱讀,瞭解有這麼回事,用到的時候再查詢。而元素樣式的訪問,這個在開發中廣泛使用的較多,由於在無法使用css3動畫的瀏覽器中,能夠經過改變樣式來到達動畫的目的。
var doc = document.getElementById('doc'); doc.style.width = '100px';
對於iframe的訪問這裏增長了一個contentDocument對象來進行引用,還有節點的比較,isSameNode和isEqualNode,這兩個的區別在於,前者是否引用的同一個節點對象,後者是指兩個節點是不是相同的類型。不過,它們使用的也很少,瞭解就好。
元素的大小
這個部分須要理解,由於關乎到元素在瀏覽器上的位置顯示,跟動畫有關係,四個屬性。
滾動大小
這個在視察滾動或者處理滾動條的時候用的上,也是四個屬性
下面這些IE所有不支持,range支持一種叫作文本範圍的東西
元素遍歷
關於遍歷其實有兩個方法可用createNodeIterator和createTreeWalker,不過這些在開發中幾乎不會使用到,誰沒事去遍歷節點完呢。
關於range
這個也是很是少會使用到,除非是作那種編輯器應用或者在線編輯器等等,不過使用它能夠更精準的控制的DOM,主要是使用createRange方法。
IE瀏覽器的事件不是重點
事件是JavaScript與HTML進行交互的一個紐帶,理解事件能夠更好的處理Web應用程序,如今的瀏覽器中主要支持兩種事件流:
事件冒泡則是指事件開始時由具體的元素接收,而後逐級向上傳播。好比:
<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,對於對代碼有強迫症的同窗,基本上不會使用到它。
那麼,咱們該怎麼給一個元素添加上事件處理程序呢?
全部的DOM節點都具有這兩個方法,它接收三個參數:
通常狀況下第三個參數都填false
IE瀏覽器對應的兩個方法,attachEvent,detachEvent,它們只有冒泡,事件名要加上on。
在註冊完事件處理程序後,事件的一個比較重要的對象必需要理解,event事件對象。
通常來講,這個對象中包含着全部與當前元素所監聽的事件有關的信息,好比元素監聽的事件類型,元素自己等等。
比較重要的屬性和方法(只讀)
比較重要的屬性和方法(讀寫)
PC端主要是針對鼠標,移動端則是觸摸,手勢相關的處理
若是在PC端上發生一次click事件,實際上它是發生了三次事件,mousedown當鼠標按下的時候,mouseup當用戶放開的時候,click兩個加起來就發生了一次click事件。相對於移動,PC上的鼠標事件很是的豐富,例如mouseover當鼠標首次移入一個元素邊界時觸發,mouseout當鼠標移出元素時觸發,這個移出,到子元素上也會觸發這個事件,mousemove當鼠標在元素內移動時重複觸發。
整體來講對於文檔加載,表單控件,窗口大小改變等事件,好比獲取焦點,在失去或者獲取焦點是值改變等移動上都是同樣的,focus(得到焦點)blur(失去焦點)。
在作一些視差滾動的效果時scroll事件是很是好用,移動上在css中提供了一個相似的屬性。
惟一的區別是移動端上沒有鍵盤事件。
它們都是冒泡的,也能夠取消
三個跟蹤觸摸事件的屬性
移動event事件對象
PC上存在的,在移動上也存在,描述上有差別,好比
一些手勢
移動手勢乾貨三部曲