javascript框架設(shè)計(jì)讀書筆記之模塊加載系統(tǒng)
模塊加載,其實(shí)就是把js分成很多個(gè)模塊,便于開發(fā)和維護(hù)。因此加載很多js模塊的時(shí)候,需要?jiǎng)討B(tài)的加載,以便提高用戶體驗(yàn)。
在介紹模塊加載庫之前,先介紹一個(gè)方法。
動(dòng)態(tài)加載js方法:
function loadJs(url , callback){
var node = document.createElement("script");
node[window.addEventListener ? "onload":"onreadystatechange"] = function(){
if(window.addEventListener || /loaded|complete/i.test(node.readyState)){
callback();
node.onreadystatechange = null;
}
}
node.onerror = function(){};
node.src = url;
var head = document.getElementsByTagName("head")[0];
head.insertBefore(node,head.firstChild); //插入到head的第一個(gè)節(jié)點(diǎn)前,防止ie6下head標(biāo)簽沒閉合前,使用appendChild報(bào)錯(cuò)?!?br /> }
由于司徒正美使用了它寫的mass框架來介紹模塊加載,而業(yè)界用的最多的是require.js和sea.js。因此,我覺得他個(gè)性有點(diǎn)強(qiáng)。
我來講下sea.js的模塊加載過程吧:
頁面chaojidan.jsp,在head標(biāo)簽中,引入sea.js,這時(shí)就會(huì)得到seajs對象。
同時(shí)引入index.js。
index.js的代碼如下:
seajs.use(['./a','jquery'],function(a,$){
var num = a.a;
$('#J_A').text(num);
})
a.js :
define(function(require,exports,module){
var b = require('./b');
var a = function(){
return 1 + parseInt(b.b());
}
exports.a = a;
})
b.js :
define(function(require,exports,module){
var c = require('./c');
var b = function(){
return 2 + parseInt(c.c());
}
exports.b = b;
})
c.js :
define(function(require,exports,module){
var c = function(){
return 3;
}
exports.c = c;
})
由上可知,a模塊依賴b,b依賴c.
當(dāng)程序進(jìn)入到index.js,seajs將調(diào)用use方法。
seajs.use = function(ids, callback) {
globalModule._use(ids, callback)
}
說明: globalModule 為seajs初始化時(shí)(引入sea.js時(shí)),Module的實(shí)例 var globalModule = new Module(util.pageUri, STATUS.COMPILED)
此時(shí) ids -> ['./a','jquery'], callback -> function(a,$){var num = a.a;$('#J_A').text(num);}
接下來將調(diào)用 globalModule._use(ids, callback)
Module.prototype._use = function(ids, callback) {
var uris = resolve(ids, this.uri); //解析['./a','jquery']
this._load(uris, function() { //把解析出來的a,jquery模塊的地址[url1,url2],調(diào)用_load方法。
//util.map : 讓數(shù)據(jù)成員全部執(zhí)行一次一個(gè)指定的函數(shù),并返回一個(gè)新的數(shù)組,該數(shù)組為原數(shù)組成員執(zhí)行回調(diào)后的結(jié)果
var args = util.map(uris, function(uri) {
return uri ? cachedModules[uri]._compile() : null;//如果存在url,就調(diào)用_compile方法。
})
if (callback) { callback.apply(null, args) }
})
}
因?yàn)檎{(diào)用_load方法后,會(huì)出現(xiàn)兩個(gè)回調(diào)函數(shù),因此我們將function(a,$){var num = a.a;$('#J_A').text(num);}標(biāo)志為callback1,
把this._load(uris, function() { })回調(diào)方法標(biāo)志為callback2.
resolve方法就是解析模塊地址的,這里我就不細(xì)講了。
最終var uris = resolve(ids, this.uri)中 的uris被解析成了['http://localhost/test/SEAJS/a.js','http://localhost/test/SEAJS/lib/juqery/1.7.2/juqery-debug.js'],模塊路徑解析已經(jīng)完畢。
而接下來將執(zhí)行this._load
// _load()方法主要會(huì)先判斷哪些資源文件還沒有ready,如果全部資源文件都處于ready狀態(tài)就執(zhí)行callback2
// 在這其中還會(huì)做循環(huán)依賴的判斷,以及對沒有加載的js執(zhí)行加載
Module.prototype._load = function(uris, callback2) {
//util.filter : 讓數(shù)據(jù)成員全部執(zhí)行一次一個(gè)指定的函數(shù),并返回一個(gè)新的數(shù)組,該數(shù)組為原數(shù)組成員執(zhí)行回調(diào)后返回為true的成員
//unLoadedUris是那些沒有被編譯的模塊uri數(shù)組
var unLoadedUris = util.filter(uris, function(uri) {
//返回執(zhí)行函數(shù)布爾值為true的成員,在uri存在并且在內(nèi)部變量cacheModules中不存在或者它在存儲(chǔ)信息中status的值小于STATUS.READY時(shí)返回true
// STATUS.READY值為4,小于四則可能的情況是獲取中,下載中。
return uri && (!cachedModules[uri] ||
cachedModules[uri].status < STATUS.READY)
});
//如果uris中的模塊全部都ready好了,執(zhí)行回調(diào)并退出函數(shù)體(這時(shí)就會(huì)調(diào)用模塊的_compile方法了)。
var length = unLoadedUris.length
if (length === 0) { callback2() return }
//還未加載的模塊個(gè)數(shù)
var remain = length
//創(chuàng)建閉包,嘗試去加載那些沒有加載的模塊
for (var i = 0; i < length; i++) {
(function(uri) {
//判斷如果在內(nèi)部變量cachedModules里面并不存在該uri的存儲(chǔ)信息則實(shí)例化一個(gè)Module對象
var module = cachedModules[uri] ||
(cachedModules[uri] = new Module(uri, STATUS.FETCHING))
//如果模塊的狀態(tài)值大于等于2,也就意味著模塊已經(jīng)被下載好并已經(jīng)存在于本地了,這個(gè)時(shí)候執(zhí)行onFetched()
//否則則調(diào)用fetch(uri, onFetched) ,嘗試下載資源文件,資源文件下載后會(huì)觸發(fā)onload,onload中會(huì)執(zhí)行回調(diào)onFetched的方法。
module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched)
function onFetched() {
module = cachedModules[uri]
//當(dāng)模塊的狀態(tài)值為大于等于STATUS.SAVED的時(shí)候,也就意味著該模塊所有的依賴信息已經(jīng)被拿到
if (module.status >= STATUS.SAVED) {
//getPureDependencies:得到不存在循環(huán)依賴的依賴數(shù)組
var deps = getPureDependencies(module)
//如果依賴數(shù)組不為空
if (deps.length) {
//再次執(zhí)行_load()方法,直到全部依賴加載完成后執(zhí)行回調(diào)
Module.prototype._load(deps, function() {
cb(module)
})
}
//如果依賴數(shù)組為空的情況下,直接執(zhí)行cb(module)
else {
cb(module)
}
}
// 如果獲取失敗后,比如404或者不符合模塊化規(guī)范
//在這種情形下,module.status會(huì)維持在 FETCHING 或者 FETCHED
else {
cb()
}
}
})(unLoadedUris[i])
}
// cb 方法 - 加載完所有模塊執(zhí)行回調(diào)
function cb(module) {
// 如果module的存儲(chǔ)信息存在,那么修改它的module存儲(chǔ)信息中的status的值,修改為 STATUS.READY
module && (module.status = STATUS.READY)
// 只有當(dāng)所有模塊加載完畢后執(zhí)行回調(diào)。
--remain === 0 && callback2()
}
}
}
這里unLoadedUris的數(shù)組長度為2 ,['http://localhost/test/SEAJS/a.js','http://localhost/test/SEAJS/lib/juqery/1.7.2/juqery-debug.js'],所以 接下來會(huì)產(chǎn)生兩個(gè)以 js路徑為名稱的閉包。
以http://localhost/test/SEAJS/a.js為例
接下來 : 首先會(huì)創(chuàng)建一個(gè)Module:
cachedModules('http://localhost/test/SEAJS/a.js') = new Module('http://localhost/test/SEAJS/a.js',1)
module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched)
因?yàn)榇藭r(shí)a模塊并沒有加載 所以接下來將會(huì)執(zhí)行 fetch(uri, onFetched) 即fetch('http://localhost/test/SEAJS/a.js',onFetched)。
function fetch(uri, onFetched) {
// 根據(jù)map中的規(guī)則替換uri為新的請求地址
var requestUri = util.parseMap(uri)
// 首先在已獲取列表中查找是否含有requestUri記錄
if (fetchedList[requestUri]) {
// 這個(gè)時(shí)候?qū)⒃紆ri的module存儲(chǔ)信息刷新到通過map重定義的requestUri上
cachedModules[uri] = cachedModules[requestUri]
// 執(zhí)行onFetched 并返回,意味著模塊已經(jīng)獲取成功了
onFetched()
return
}
//在獲取列表中查詢 requestUri 的存儲(chǔ)信息
if (fetchingList[requestUri]) {
//在callbacklist中加入該uri對應(yīng)下的callback,并返回
callbackList[requestUri].push(onFetched) //如果正在獲取中,就把此模塊的onFetched回調(diào)方法push進(jìn)數(shù)組中,并返回。
return
}
// 如果嘗試獲取的模塊都未出現(xiàn)在fetchedList和fetchingList中,則分別在請求列表和回調(diào)列表中添加其信息
fetchingList[requestUri] = true
callbackList[requestUri] = [onFetched]
// Fetches it
Module._fetch(
requestUri,
function() {
fetchedList[requestUri] = true
// Updates module status
// 如果 module.status 等于 STATUS.FECTCHING ,則修改module狀態(tài)為FETCHED
var module = cachedModules[uri]
if (module.status === STATUS.FETCHING) {
module.status = STATUS.FETCHED
}
if (fetchingList[requestUri]) {
delete fetchingList[requestUri]
}
// Calls callbackList 統(tǒng)一執(zhí)行回調(diào)
if (callbackList[requestUri]) {
util.forEach(callbackList[requestUri], function(fn) {
fn() //fn就是模塊a對應(yīng)的onFeched方法。
})
delete callbackList[requestUri]
}
},
config.charset
)
}
接下來 將會(huì)執(zhí)行 Module._fetch(),這里的回調(diào)函數(shù)我們稱作為callback3.
此方法就是調(diào)用loadJs方法動(dòng)態(tài)下載a.js文件。(因?yàn)橛衋和jquery,所以會(huì)新建兩個(gè)script),這里有一個(gè)疑問,新建a的script,并添加到head中,就會(huì)下載js文件,但是在seajs中,并沒有下載,而是等jquery的script建立好,并添加到head中,才會(huì)下載(谷歌調(diào)試器設(shè)斷點(diǎn),一直顯示pending等待中)。這是為毛?
(推薦看這里:
下載成功后,就會(huì)解析執(zhí)行,執(zhí)行的是define方法。這里會(huì)先執(zhí)行a模塊的代碼。
define(id,deps,function(){})方法解析
//define 定義 ,id : 模塊id , deps : 模塊依賴 , factory
Module._define = function(id, deps, factory) {
//解析依賴關(guān)系 // 如果deps不是數(shù)組類型,同時(shí)factory是函數(shù)
if (!util.isArray(deps) && util.isFunction(factory)) { // 函數(shù)體內(nèi)正則匹配require字符串,并形成數(shù)組返回賦值給deps
deps = util.parseDependencies(factory.toString())
}
//設(shè)置元信息
var meta = { id: id, dependencies: deps, factory: factory }
if (document.attachEvent) {
// 得到當(dāng)前script的節(jié)點(diǎn)
var script = util.getCurrentScript()
// 如果script節(jié)點(diǎn)存在
if (script) {
// 得到原始uri地址
derivedUri = util.unParseMap(util.getScriptAbsoluteSrc(script)) }
if (!derivedUri) {
util.log('Failed to derive URI from interactive script for:', factory.toString(), 'warn')
}
}
.........
}
define首先會(huì)對factory執(zhí)行一個(gè)判斷 ,判斷它是否為一個(gè)函數(shù)(原因是因?yàn)閐efine內(nèi)也可以包括文件,對象)
如果是函數(shù) , 那么 就會(huì)通過factory.toString(),得到函數(shù),并通過正則匹配得 a.js的依賴,并把依賴保存在 deps 中
對于 a.js 而言, 它的依賴 是 b.js 所以 deps為 ['./b']
并對 a.js 的信息進(jìn)行保存 var meta = { id: id, dependencies: deps, factory: factory }
針對a.js meta = { id : undefined , dependencies : ['./b'] , factory : function(xxx){xxx}}
在 ie 6-9 瀏覽器中可以拿到當(dāng)前運(yùn)行js的路徑 但是在標(biāo)準(zhǔn)瀏覽器中 ,這不可行 ,所以暫時(shí)先把元信息賦值給anonymousModuleMeta = meta。
然后觸發(fā)onload,這時(shí)就會(huì)調(diào)用回調(diào)方法callback3,此回調(diào)方法就會(huì)修改當(dāng)前回調(diào)模塊(a.js)的狀態(tài)值,將其設(shè)置為 module.status = STATUS.FETCHED。
再接下來 ,將統(tǒng)一 執(zhí)行回調(diào)隊(duì)列 callbackList 中的 a.js所對應(yīng)的回調(diào),也就是onFetched。
onFetched方法會(huì)檢查a模塊是否有依賴模塊,因?yàn)閍依賴于b,所以對模塊a所依賴的b.js 執(zhí)行_load()。
會(huì)去下載b模塊,這時(shí)會(huì)先執(zhí)行jquery的define方法。因?yàn)閖query沒依賴模塊,所以onload回調(diào)后。onFetched調(diào)用cb方法。
當(dāng)b按照a一樣的過程實(shí)現(xiàn)后,就會(huì)下載c模塊。最終c,b,a模塊都下載執(zhí)行define,并onload結(jié)束后,也會(huì)調(diào)用cb方法,(先c,再b,后c)
所有模塊都為ready之后,就會(huì)調(diào)用callback2方法。
最終回調(diào)到callback2,執(zhí)行a和jquery模塊的_compile方法:
首先編譯a.js模塊,模塊a的function執(zhí)行,因?yàn)閍里面有require(b.js),因此會(huì)去執(zhí)行b模塊的function.
模塊 a 的function開始執(zhí)行
模塊 b 的function開始執(zhí)行
模塊 c 的function開始執(zhí)行
模塊 c 的function執(zhí)行完畢
模塊 b 的function執(zhí)行完畢
模塊 a 的function執(zhí)行完畢
最后執(zhí)行jquery的function。
編譯結(jié)束后,就執(zhí)行callback1,就可以使用a和jquery對象了。
PS:seajs版本已經(jīng)更新,現(xiàn)在沒有_compile方法了。(大家自行去看,我也要去看下)
接著講下seajs的模塊編譯_compile過程。
首先是a.js的編譯
Module.prototype._compile = function() {
126 var module = this
127 // 如果該模塊已經(jīng)編譯過,則直接返回module.exports
128 if (module.status === STATUS.COMPILED) {
129 return module.exports
130 }
133 // 1. the module file is 404.
134 // 2. the module file is not written with valid module format.
135 // 3. other error cases.
136 // 這里是處理一些異常情況,此時(shí)直接返回null
137 if (module.status < STATUS.SAVED && !hasModifiers(module)) {
138 return null
139 }
140 // 更改模塊狀態(tài)為COMPILING,表示模塊正在編譯
141 module.status = STATUS.COMPILING
142
143 // 模塊內(nèi)部使用,是一個(gè)方法,用來獲取其他模塊提供(稱之為子模塊)的接口,同步操作
144 function require(id) {
145 // 根據(jù)id解析模塊的路徑
146 var uri = resolve(id, module.uri)
147 // 從模塊緩存中獲取模塊(注意,其實(shí)這里子模塊作為主模塊的依賴項(xiàng)是已經(jīng)被下載下來的)
148 var child = cachedModules[uri]
149
150 // Just return null when uri is invalid.
151 // 如果child為空,只能表示參數(shù)填寫出錯(cuò)導(dǎo)致uri不正確,那么直接返回null
152 if (!child) {
153 return null
154 }
155
156 // Avoids circular calls.
157 // 如果子模塊的狀態(tài)為STATUS.COMPILING,直接返回child.exports,避免因?yàn)檠h(huán)依賴反復(fù)編譯模塊
158 if (child.status === STATUS.COMPILING) {
159 return child.exports
160 }
161 // 指向初始化時(shí)調(diào)用當(dāng)前模塊的模塊。根據(jù)該屬性,可以得到模塊初始化時(shí)的Call Stack.
162 child.parent = module
163 // 返回編譯過的child的module.exports
164 return child._compile()
165 }
166 // 模塊內(nèi)部使用,用來異步加載模塊,并在加載完成后執(zhí)行指定回調(diào)。
167 require.async = function(ids, callback) {
168 module._use(ids, callback)
169 }
170 // 使用模塊系統(tǒng)內(nèi)部的路徑解析機(jī)制來解析并返回模塊路徑。該函數(shù)不會(huì)加載模塊,只返回解析后的絕對路徑。
171 require.resolve = function(id) {
172 return resolve(id, module.uri)
173 }
174 // 通過該屬性,可以查看到模塊系統(tǒng)加載過的所有模塊。
175 // 在某些情況下,如果需要重新加載某個(gè)模塊,可以得到該模塊的 uri, 然后通過 delete require.cache[uri] 來將其信息刪除掉。這樣下次 使用時(shí),就會(huì)重新獲取。
176 require.cache = cachedModules
177
178 // require是一個(gè)方法,用來獲取其他模塊提供的接口。
179 module.require = require
180 // exports是一個(gè)對象,用來向外提供模塊接口。
181 module.exports = {}
182 var factory = module.factory
183
184 // factory 為函數(shù)時(shí),表示模塊的構(gòu)造方法。執(zhí)行該方法,可以得到模塊向外提供的接口。
185 if (util.isFunction(factory)) {
186 compileStack.push(module)
187 runInModuleContext(factory, module)
188 compileStack.pop()
189 }
190 // factory 為對象、字符串等非函數(shù)類型時(shí),表示模塊的接口就是該對象、字符串等值。
191 // 如:define({ "foo": "bar" });
192 // 如:define('I am a template. My name is {{name}}.');
193 else if (factory !== undefined) {
194 module.exports = factory
195 }
196
197 // 更改模塊狀態(tài)為COMPILED,表示模塊已編譯
198 module.status = STATUS.COMPILED
199 // 執(zhí)行模塊接口修改,通過seajs.modify()
200 execModifiers(module)
201 return module.exports
202 }
if (util.isFunction(factory)) {
186 compileStack.push(module)
187 runInModuleContext(factory, module)
188 compileStack.pop()
189 }
這里就是把module.export進(jìn)行初始化。runInModuleContext方法:
// 根據(jù)模塊上下文執(zhí)行模塊代碼
489 function runInModuleContext(fn, module) {
490 // 傳入與模塊相關(guān)的兩個(gè)參數(shù)以及模塊自身
491 // exports用來暴露接口
492 // require用來獲取依賴模塊(同步)(編譯)
493 var ret = fn(module.require, module.exports, module)
494 // 支持返回值暴露接口形式,如:
495 // return {
496 // fn1 : xx
497 // ,fn2 : xx
498 // ...
499 // }
500 if (ret !== undefined) {
501 module.exports = ret
502 }
503 }
執(zhí)行a.js中的function方法,這時(shí)會(huì)調(diào)用var b = require("b.js"),
require方法會(huì)返回b的compile方法的返回值,b模塊中又有var c = require('c.js')。
這時(shí)會(huì)調(diào)用c的compile方法,然后調(diào)用c的function,c中,如果要暴露對象,或者是return 對象c,則模塊c的exports = c?;蛘咧苯邮莔odule.export = c;總之最后會(huì)返回module c.export = c;所以var c = module c.export = c,模塊b中,就可以使用變量c調(diào)用模塊c中的c對象的方法和屬性。
以此類推,最終a模塊也能調(diào)用b模塊中b對象的屬性和方法。
不管什么模塊,只要使用了module.export = xx模塊,其他模塊就可以使用require("xx模塊"),調(diào)用xx模塊中的各種方法了。
最終模塊的狀態(tài)會(huì)變成module.status = STATUS.COMPILED。
Module.prototype._use = function(ids, callback) {
var uris = resolve(ids, this.uri); //解析['./a','jquery']
this._load(uris, function() { //把解析出來的a,jquery模塊的地址[url1,url2],調(diào)用_load方法。
//util.map : 讓數(shù)據(jù)成員全部執(zhí)行一次一個(gè)指定的函數(shù),并返回一個(gè)新的數(shù)組,該數(shù)組為原數(shù)組成員執(zhí)行回調(diào)后的結(jié)果
var args = util.map(uris, function(uri) {
return uri ? cachedModules[uri]._compile() : null;//如果存在url,就調(diào)用_compile方法。
})
if (callback) { callback.apply(null, args) }
})
}
這時(shí)args = [module a.export, module jquery.export];
seajs.use(['./a','jquery'],function(a,$){
var num = a.a;
$('#J_A').text(num);
})
這時(shí)function中的a和$就是module a.export和module jquery.export。
因?yàn)楸救爽F(xiàn)在在研究jquery源碼和jquery框架設(shè)計(jì),因此共享一些經(jīng)驗(yàn):
jquery源碼,我在網(wǎng)上看了很多解析,看著看著就看不下去了。意義不大,推薦妙味課堂的jquery源碼解析。
司徒正美的javascript框架設(shè)計(jì),個(gè)人覺得難度大,但是精讀后,你就是高級(jí)前端工程師了。
玉伯的sea.js,我建議去學(xué)習(xí),去用,畢竟是中國人自己做的。我們公司新的項(xiàng)目或者重構(gòu),都會(huì)使用seajs來做。
接下來就是模塊化handbars以及mvc的backbone或者mvvm的angular的源碼精讀。這里我希望有人給我提建議,看什么書,看什么網(wǎng)站,看什么視頻能夠快速的學(xué)習(xí)。
- 十大熱門的JavaScript框架和庫
- 深入解析JavaScript框架Backbone.js中的事件機(jī)制
- JavaScript框架是什么?怎樣才能叫做框架?
- 超贊的動(dòng)手創(chuàng)建JavaScript框架的詳細(xì)教程
- javascript框架設(shè)計(jì)之類工廠
- javascript框架設(shè)計(jì)之瀏覽器的嗅探和特征偵測
- javascript框架設(shè)計(jì)之種子模塊
- javascript框架設(shè)計(jì)之框架分類及主要功能
- 2014 年最熱門的21款JavaScript框架推薦
- javascript框架設(shè)計(jì)讀書筆記之?dāng)?shù)組的擴(kuò)展與修復(fù)
- javascript框架設(shè)計(jì)讀書筆記之字符串的擴(kuò)展和修復(fù)
- javascript框架設(shè)計(jì)讀書筆記之種子模塊
- JavaScript框架(iframe)操作總結(jié)
- 怎么選擇Javascript框架(Javascript Framework)
- 詳細(xì)介紹8款超實(shí)用JavaScript框架
- brook javascript框架介紹
- 16個(gè)最流行的JavaScript框架[推薦]
- 如何選擇適合你的JavaScript框架
相關(guān)文章
uniapp實(shí)現(xiàn)審批流程的具體操作步驟
這篇文章主要介紹了uniapp實(shí)現(xiàn)審批流程的具體操作方法,實(shí)現(xiàn)思路大概是需要要定義一個(gè)變量,記錄當(dāng)前激活的步驟,通過數(shù)組的長度來循環(huán)數(shù)據(jù),如果有就采用3元一次進(jìn)行選擇,具體實(shí)現(xiàn)步驟跟隨小編一起看看吧2024-03-03JavaScript原生對象之Number對象的屬性和方法詳解
這篇文章主要介紹了JavaScript原生對象之Number對象的屬性和方法詳解,本文講解了創(chuàng)建 Number 對象的語法、MAX_VALUE、MIN_VALUE、NaN等屬性或方法,需要的朋友可以參考下2015-03-03javascript實(shí)現(xiàn)鼠標(biāo)拖動(dòng)改變層大小的方法
這篇文章主要介紹了javascript實(shí)現(xiàn)鼠標(biāo)拖動(dòng)改變層大小的方法,涉及javascript操作鼠標(biāo)事件及樣式的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-04-04w3c編程挑戰(zhàn)_初級(jí)腳本算法實(shí)戰(zhàn)篇
下面小編就為大家?guī)硪黄獁3c編程挑戰(zhàn)_初級(jí)腳本算法實(shí)戰(zhàn)篇。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06JS獲取子、父、兄節(jié)點(diǎn)方法小結(jié)
這篇文章主要介紹了JS獲取子、父、兄節(jié)點(diǎn)方法總結(jié)及JS獲取兄弟節(jié)點(diǎn)的兩種方法,需要的朋友可以參考下2017-08-08