摘要:框架與庫(kù)庫(kù)解決某個(gè)問(wèn)題而拼湊出來(lái)的一大堆函數(shù)與類的集合。框架一個(gè)半成品的應(yīng)用,直接給出一個(gè)骨架。鎖定,意味著無(wú)法擴(kuò)展。雙鏈?zhǔn)侵杆鼉?nèi)部把回調(diào)分成兩種,一種叫成功回調(diào),用于正常的執(zhí)行,一種叫錯(cuò)誤回調(diào),用于出錯(cuò)時(shí)執(zhí)行。
框架與庫(kù):
庫(kù):解決某個(gè)問(wèn)題而拼湊出來(lái)的一大堆函數(shù)與類的集合。每個(gè)函數(shù)(方法)之間都沒(méi)什么關(guān)聯(lián)。
框架:一個(gè)半成品的應(yīng)用,直接給出一個(gè)骨架。
對(duì)象擴(kuò)展,數(shù)組化,類型判定,簡(jiǎn)單的事件綁定與卸載,無(wú)沖突處理,模塊加載與domReady
命名空間
jQuery對(duì)命名空間的控制:
把命名空間保存到一個(gè)臨時(shí)變量中,后面通過(guò)noConflict放回去.
var _jQuery = window.jQuery, _$ = window.$ // 先把可能存在的同名變量保存起來(lái) jQuery.extend({ noConflict (deep) { window.$ = _$ // 這時(shí)再放回去 if (deep) { window.jQuery = _jQuery return jQuery } } })
對(duì)象擴(kuò)展
淺拷貝和深拷貝
在JavaScript中一般約定俗成為:extend或mixin
// mass Framework的mix方法 function mix(target, source) { var args = [].slice.call(arguments), i = 1, key, ride = typeof args[args.length - 1] === "boolean" ? args.pop() : true if (args.length === 1) { // 處理$.mix(hash)的情形 target = !this.window ? this : {} i = 0 } while ((srouce = args[i++])) { for (key in source) { // 允許對(duì)象糅雜,用戶保證都是對(duì)象 if (ride || !(key in target)) { target[key] = srouce[key] } } } return target }
數(shù)組化
瀏覽器下存在許多類數(shù)組對(duì)象:
function內(nèi)的arguments
document.forms
form.elements
document.links
select.options
document.getElementsByName
document.getElementsByTagName
childNodes
children (獲取節(jié)點(diǎn)集合HTMLCollection, NodeList)
轉(zhuǎn)換方法:
Array.form() [].slice.call() Array.prototype.slice.call()
// jQuery的makeArray var makeArray = function makeArray(array) { var ret = [] if (array !== null) { var i = array.length if (i === null || typeof array === "string" || jQuery.isFunction(array) || array.setInterval) { ret[0] = array } else { while (i) { ret[--i] = array[i] } } return ret } }
類型判定
JavaScript存在兩套類型系統(tǒng)
基本類型
對(duì)象類型系統(tǒng)
基本類型:undefined,null,string,boolean,number,Set,Map
對(duì)象類型:function,object
基本數(shù)據(jù)類型通過(guò)typeof來(lái)檢測(cè)
對(duì)象類型通過(guò)instanceof來(lái)檢測(cè)
一般通用的檢測(cè)方法:
Object.prototpe.toString.call(arg)
isPlainObject判斷是否為純凈的JavaScript對(duì)象
function isPlainObject (obj) { return obj && typeof obj === "object" && Object.getProtypeOf(obj) === Object.prootype }
domReady
作用:滿足用戶提前綁定事件
方法:
window.onload
mass的DomReady處理方式:
var readyList = [] mass.ready = function (fn) { if (readyList) { readyList.push(fn) } else { fn() } } var readyFn, ready = W3C ? "DOMContentLoaded" : "readystatechange" function fireReady () { for (var i = 0, fn; fn = readyList[i++]; ) { fn() } readyList = null fireReady = $.noop // 惰性函數(shù),防止IE9二次調(diào)用 _changeDeps } function deScrollCheck () { try { // IE下通過(guò)doScollCheck檢測(cè)DOM樹(shù)是否建完 html.doScroll("left") fireReady() } catch (e) { setTimeout(doScrollCheck) } } // 在FF3.6之前,不存在readyState屬性 if (!DOC.readyState) { var readyState = DOC.readyState = DOC.boyd ? "complete" : "loading" } if (DOC.readyState === "complete") { fireReady() // 如果在domReady之外加載 } else { $.bind(DOC, ready, readyFn = function () { if (W3C || DOC.readyState === "complete") { fireReady() if (readyState) { // IE下不能改寫(xiě)DOC.readyState DOC.readyState = "complete" } } }) if (html.doScroll) { try { if (self.eval ===parent.eval) { deScrollCheck() } } catch (e) { deScrollCheck() } } }
無(wú)沖突處理
jQuery的無(wú)沖突處理:
var window = this, undefined, _jQuery = window.jQuery, _$ = window.$, // 把window存入閉包中的同名變量,方便內(nèi)部函數(shù)在調(diào)用window時(shí)不要難過(guò)費(fèi)大力氣查找它 // _jQuery 與 _$用于以后重寫(xiě) jQuery = window.jQuery = window.$ = function (selector, context) { // 用于返回一個(gè)jQuery對(duì)象 return new jQuery.fn.init(selector, context) } jQuery.exnted({ noConflict (deep) { // 引入jQuery類庫(kù)后,閉包外面的window.$與window.jQuery都存儲(chǔ)著一個(gè)函數(shù),它是用來(lái)生成jQuery對(duì)象或domReady后執(zhí)行里面的函數(shù)的 // 在還沒(méi)有把function賦給它們時(shí),_jQuery與_$已經(jīng)被賦值了,因此它們倆的值必然是undefined,因此放棄控制權(quán),就是用undefined把window.$里面的jQuery系的函數(shù)清除掉。 window.$ = _$ // 相當(dāng)于window.$ = undefined // 需要為noConflict添加一個(gè)布爾值,為true if (deep) { // 必須用一個(gè)東西接納jQuery對(duì)象與jQuery的入口函數(shù),閉包里面的東西除非被window等宿主對(duì)象引用,否則就是不可見(jiàn)的,因此把比較里面的jQuery return出去,外面用一個(gè)變量接納就可以 window.jQuery = _jQuery // 相當(dāng)于window.jQuery = undefined } return jQuery } })模塊加載系統(tǒng)
AMD規(guī)范
AMD是Asynchronous Module Definition是異步模塊定義
異步:有效避免了采用同步加載方式中,導(dǎo)致的頁(yè)面假死現(xiàn)象
模塊定義:每個(gè)模塊必須按照一定的個(gè)是編寫(xiě)
加載器所在路徑
要加載一個(gè)模塊,需要一個(gè)URL作為加載地址,一個(gè)script作為加載媒介。但用戶在require時(shí)都用ID,需要一個(gè)將ID轉(zhuǎn)換為URL的方法。約定URL的合成規(guī)則:
basePath + 模塊ID + ".js"
在DOM樹(shù)中最后加入script
function getBasePath () { var nodes = document.getElementsByTagName("script") var node = nodes[nodes.length - 1] var src = document.querySelector ? node.src : node.getAttribute("src", 4) return src }
獲取當(dāng)前Script標(biāo)簽
function getCurrentScript (base) { // 為true時(shí)相當(dāng)于getBasePath var stack try { // FF 可以直接 var e = new Error("test"),但其它瀏覽器只會(huì)生成一個(gè)空Error a.b.c() // 強(qiáng)制報(bào)錯(cuò),以便獲取e.stack } catch (e) { // Safari的錯(cuò)誤對(duì)象只有l(wèi)ine,sourceId,sourceURL stack = e.stack if (!stack && window.opera) { // opera 9 沒(méi)有e.stack,但有e.Backtrace,不能直接獲取,需要對(duì)e對(duì)象轉(zhuǎn)字符串進(jìn)行抽取 stack = (`${e}`.match(/of inlked script S+/g) || []).join(" ") } } if (stack) { stack = stack.split(/[@]/g).pop() // 取得最后一行,最后一個(gè)空間或@之后的部分 stack = stack[0] === "(" ? stck.slice(1, -1) : stack.replace(/s/, "") // 去掉換行符 return stack.replace(/(:d+)?:d+$/i, "") // 去掉行號(hào)與或許存在的出錯(cuò)字符串起始位置 // 在動(dòng)態(tài)加載模塊時(shí),節(jié)點(diǎn)偶插入head中,因此只在head標(biāo)簽中尋找 var nodes = (base ? document : head).getElementByTagName("scirpt") for (var i = nodes.length, node; node = nodes[--i]) { if ((base || node.className) && node.readyState === "interactive") { // 如果此模塊 return node.className = node.src } } } }
require 方法
作用:當(dāng)依賴列表都加載王弼執(zhí)行用戶回調(diào)。
取得依賴列表的第一個(gè)ID,轉(zhuǎn)換為URL。無(wú)論是通過(guò)basePath + ID +".js",還是以映射的方式直接得到。
檢測(cè)此模塊有沒(méi)有加載過(guò),或正在被加載。因此,需要一個(gè)對(duì)象來(lái)保持所有模塊的加載情況。當(dāng)用戶從來(lái)沒(méi)有加載過(guò)此節(jié)點(diǎn),就進(jìn)入加載流程。
創(chuàng)建script節(jié)點(diǎn),綁定onerror,onload,onreadychange等事件判定加載成功與否,然后添加href并插入DOM樹(shù),開(kāi)始加載。
將模塊的URL,依賴列表等構(gòu)建成一個(gè)對(duì)象,放到檢測(cè)隊(duì)列中,在onerror,onload,onreadychange等事件觸發(fā)時(shí)進(jìn)行檢測(cè)。
require的實(shí)現(xiàn):
window.require = $.require = function require(list, factory, parent) { var deps = {} // 檢測(cè)它的依賴是否都未2 var args = [] // 保存依賴模塊的返回值 var dn = 0 // 需要安裝的模塊數(shù) var cn = 0 // 已安裝的模塊數(shù) var id = parent || `callback${setTimeout("1")}` var parent = parent || basepath // basepath為加載器的路徑 `${list}`.replace($.rowd, (el) => { var url = loadJSCSS(el, parent) if (url) { dn++ if (module[url] && module[url].state === 2) { cn++ } if (!deps[url]) { args.push(url) deps[url] = "alogy" // 去重 } } }) modules[id] = { // 創(chuàng)建一個(gè)對(duì)象,記錄模塊的加載情況與其他信息 id: id, factory: factory, deps, deps, args: args, state: 1 } if (dn === cn) { // 如果需要安裝的等于已安裝好的 fireFactory(id, args, factory) // 安裝到框架中 } else { // 放入到檢測(cè)對(duì)象中,等待 checkDeps處理 loadings.unshift(id) } checkDeps() }
大多數(shù)情況下喜歡使用Dep來(lái)表示消息訂閱器,Dep是dependence的縮寫(xiě),中文是依賴.
每require一次,相當(dāng)于把當(dāng)前的用戶回調(diào)當(dāng)成一個(gè)不用加載的匿名模塊,ID是隨機(jī)生成,回調(diào)是否執(zhí)行,要待deps對(duì)象里面所有值都為2
require方法中重要方法:
loadJSCSS,作用:用于轉(zhuǎn)換ID為URL
fireFactory,執(zhí)行用戶回調(diào)
checkDeps,檢測(cè)依賴是否都安裝好,安裝好就執(zhí)行fireFactory
function loadJSCSS(url, parent, ret, shim) { // 1. 特別處理mass | ready標(biāo)識(shí)符 if (/^(mass|ready)$/.test(url)) { return url } // 2. 轉(zhuǎn)換為完成路勁 if ($.config.alias[url]) { // 別名 ret = $.config.alias[url] if (typeof ret === "object") { shim = ret ret = ret.src } } else { if (/^(w+)(d)?:.*/.test(url)) { // 如果本來(lái)就是完整路徑 ret = url } else { parent = parent.substr(0, parent.lastIndexOf("/")) var tmp = url.charAt(0) if (tmp !== "." && tmp !== "/") { // 相對(duì)于更路徑 ret = basepath + url } else if (url.slice(0, 2) === "./") { // 相對(duì)于兄弟路徑 ret = parent + url.slice(1) } lse if (url.slice(0, 2) === "..") { // 相對(duì)于父路徑 var arr = parent.replace(//$/, "").split("/") tmp = url.replace(/..//g, () => { arr.pop() return "" }) ret = `${arr.join("/")}/${tmp}` } else if (tmp === "/") { ret = `${parent}${url}` // 相對(duì)于兄弟路徑 } else { $.error(`不符合模塊標(biāo)識(shí)符規(guī)則:${url}`) } } } var src = ret.replace(/[?#].*/, "") var ext if (/.(css|js)$/.test(src)) { ext = RegExp.$1 } if (!ext) { // 如果沒(méi)有后綴名,加上后綴名 src += ".js" ext = "js" } // 開(kāi)始加載JS 或 CSS if (ext === "js") { if (!modules[src]) { // 如果之前沒(méi)有加載過(guò) modules[src] = { id: src, parent: parent, exports: {} } if (shim) { // shim機(jī)制 require(shim.deps || "", () => { loadJS(src, () => { modules[src].state = 2 modules[src].exports = type shim.exports === "function" ? shim.exports() : window[shim.exports] checkDeps() }) }) } else { loadJS(src) } } return src } else { loadCSS(src) } } function loadJS(url, callback) { // 通過(guò)script節(jié)點(diǎn)加載目標(biāo)文件 var node = DOC.createElement("script") node.className = moduleClass // 讓getCurrentScript只處理類名為moduleClass的script節(jié)點(diǎn) node[W3C ? "onload" : "onreadystatechange"] = function () { if (W3C || /loaded|complete/i.test(node.readyState)) { // facotrys里面裝著define方法的工廠函數(shù)(define(id?, deps?, factory)) var factory = factorys.pop() factory && factory.delay(node.src) if (callback) { callback() } if (checkFail(node, false, !W3C)) { $.log(`已成功加載${node.src}`, 7) } } } node.onerror = function () { checkFail(node, true) } // 插入到head的第一個(gè)節(jié)點(diǎn)前,防止IE6下標(biāo)簽沒(méi)閉合前使用appendChild拋錯(cuò) node.src = url head.insertBefore(node, head.firstChild) } function checkDeps () { loop: for (var i = loadings.length, id; id = loadings[--i];) { for (var key in deps) { if (hasOwn.call(deps, key) && modules[key].state !== 2) { continue loop } } // 如果deps是空對(duì)象或者其依賴的模塊的狀態(tài)都是2 if (obj.state !== 2) { loading.splice(i, 1) // 必須先移除再安裝,防止在IE下DOM樹(shù)建完成后手動(dòng)刷新頁(yè)面,會(huì)多次執(zhí)行它 fireFactory(obj.id, obj.args, obj.factory) checkDeps() // 如果成功,則再執(zhí)行一次,以防止有些模塊就差本模塊沒(méi)有安裝好 } } } function fireFactory (id, deps, factory) { // 從 modules中手機(jī)各模塊的返回值執(zhí)行factory for (var i = 0, array = [], d; d = deps[i++];) { array.push(modules[d].exports) } var module = Object(modules[id]) var ret = factory.apply(global, array) modules.state = 2 if (ret !== void 0) { modules[id].exports = ret } return ret }
define方法
define需要考慮循環(huán)依賴的問(wèn)題。比如:加載A,要依賴B與C,加載B,要依賴A與C。這時(shí)A與B就循環(huán)依賴了。A與B在判定各自的deps中的鍵值都是2時(shí)才執(zhí)行,結(jié)果都無(wú)法執(zhí)行了。
window.define = $.define = function define (id, deps, factory) { var args = $.slice(arguments) if (typeof id === "string") { var _id = args.shift() } if (typeof args[0] === "boolean") { // 用于文件合并,在標(biāo)準(zhǔn)瀏覽器中跳過(guò)不定模塊 if (args[0]) { return } args.shift() } if (typeof args[0] === "function") { // 上線合并后能直接的到模塊ID,否則尋找當(dāng)前正在解析中的script節(jié)點(diǎn)的src作為模塊ID args.unshift([]) } // 除了Safari外,都能直接通過(guò)getCurrentScript一步到位得到當(dāng)前執(zhí)行的script節(jié)點(diǎn),Safari可通過(guò)onload+delay閉包組合解決 id = modules[id] && modules[id].state >= 1 ? _id : getCurrentScript() factory = args[1] factory.id = _id // 用于調(diào)試 factory.delay = function (id) { args.push(id) var isCycle = true try { isCycle = checkCycle(modules[id].deps, id) } catch (e) {} if (isCycle) { $.error(`${id}模塊與之前的某些模塊存在循環(huán)依賴`) } delete factory.delay // 釋放內(nèi)存 require.apply(null, args) } if (id) { factory.delay(id, args) } else { factorys.push(factory) } }
checkCycle方法:
function checkCycle (deps, nick) { // 檢測(cè)是否存在循環(huán)依賴 for (var id in deps) { if (deps[id] === "alogy" && modules[id].state !== 2 && (id === nick || checkCycle(modules[id].deps, nick))) { return true } } }語(yǔ)言模塊
字符串
類型:
與標(biāo)簽無(wú)關(guān)的實(shí)現(xiàn):carAt.charCodeAt,concat,indexOf,lastIndexof,localCompare,match,replace,serach,slice,split,substring,toLocaleLowerCase,toLocaleUpperCase,toLowerCase,toUpperCase以及從Object繼承回來(lái)的方法,如toString,valueOf。
與標(biāo)簽有關(guān)的實(shí)現(xiàn),都是對(duì)原字符串添加一對(duì)標(biāo)簽:anchor,big,blink,bold,fixed,fontcolor,italics,small,strike,sub,sup
后來(lái)添加或?yàn)闃?biāo)準(zhǔn)化的瀏覽器方法: trim,quote,toSource,trimLeft,trimRight
truncate
用于對(duì)字符進(jìn)行階段處理,當(dāng)超過(guò)限定長(zhǎng)度,默認(rèn)添加三個(gè)點(diǎn)號(hào)或其它。
function truncate (target, length = 30, truncation) { truncation = truncation === void(0) ? "..." : truncation return traget.length > length ? `${target.slice(0, length - target.length)}${truncation}` : `${target}` }
cameliae
轉(zhuǎn)換為駝峰分格
function camelize (target) { if (target.indexOf("-") < 0 && target.indexOf("_") < 0) { return target } return target.replace(/[-_][^-_]/g, (match) => { return match.charAt(1).toUpperCase() }) }
dasherize
轉(zhuǎn)為連字符風(fēng)格,亦即CSS變量的風(fēng)格
function underscored (target) { return target.replace(/([a-zd]([A-Z]))/g, "$1_$2").replace(/-/g, "_").toLowerCase() } function dasherize (target) { return underscored(target).replace(/_/g, "-") }
capitalize
首字母大寫(xiě)
function capitalize (target) { return target.charAt(0).toUpperCase() + target.substring(1).toLowerCase() }
stripTags
移除字符串中 html標(biāo)簽
function stripTags (target) { return String(target || "").replace(/<[^>]+>/g, "") }
stripScripts
移除字符串中所有的script標(biāo)簽
function stripScripts (target) { return Sting(target || "").replace(/