摘要:框架与库库解决某个问题而拼凑出来的一大堆函数与类的集合。框架一个半成品的应用,直接给出一个骨架。锁定,意味着无法扩展。双链是指它内部把回调分成两种,一种叫成功回调,用于正常的执行,一种叫错误回调,用于出错时执行。
框架与库:
库:解决某个问题而拼凑出来的一大堆函数与类的集合。每个函数(方法)之间都没什么关联。
框架:一个半成品的应用,直接给出一个骨架。
对象扩展,数组化,类型判定,简单的事件绑定与卸载,无冲突处理,模块加载与domReady
命名空间
jQuery对命名空间的控制:
把命名空间保存到一个临时变量中,后面通过noConflict放回去.
var _jQuery = window.jQuery, _$ = window.$ // 先把可能存在的同名变量保存起来 jQuery.extend({ noConflict (deep) { window.$ = _$ // 这时再放回去 if (deep) { window.jQuery = _jQuery return jQuery } } })
对象扩展
浅拷贝和深拷贝
在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) { // 允许对象糅杂,用户保证都是对象 if (ride || !(key in target)) { target[key] = srouce[key] } } } return target }
数组化
浏览器下存在许多类数组对象:
function内的arguments
document.forms
form.elements
document.links
select.options
document.getElementsByName
document.getElementsByTagName
childNodes
children (获取节点集合HTMLCollection, NodeList)
转换方法:
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存在两套类型系统
基本类型
对象类型系统
基本类型:undefined,null,string,boolean,number,Set,Map
对象类型:function,object
基本数据类型通过typeof来检测
对象类型通过instanceof来检测
一般通用的检测方法:
Object.prototpe.toString.call(arg)
isPlainObject判断是否为纯净的JavaScript对象
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 // 惰性函数,防止IE9二次调用 _changeDeps } function deScrollCheck () { try { // IE下通过doScollCheck检测DOM树是否建完 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下不能改写DOC.readyState DOC.readyState = "complete" } } }) if (html.doScroll) { try { if (self.eval ===parent.eval) { deScrollCheck() } } catch (e) { deScrollCheck() } } }
无冲突处理
jQuery的无冲突处理:
var window = this, undefined, _jQuery = window.jQuery, _$ = window.$, // 把window存入闭包中的同名变量,方便内部函数在调用window时不要难过费大力气查找它 // _jQuery 与 _$用于以后重写 jQuery = window.jQuery = window.$ = function (selector, context) { // 用于返回一个jQuery对象 return new jQuery.fn.init(selector, context) } jQuery.exnted({ noConflict (deep) { // 引入jQuery类库后,闭包外面的window.$与window.jQuery都存储着一个函数,它是用来生成jQuery对象或domReady后执行里面的函数的 // 在还没有把function赋给它们时,_jQuery与_$已经被赋值了,因此它们俩的值必然是undefined,因此放弃控制权,就是用undefined把window.$里面的jQuery系的函数清除掉。 window.$ = _$ // 相当于window.$ = undefined // 需要为noConflict添加一个布尔值,为true if (deep) { // 必须用一个东西接纳jQuery对象与jQuery的入口函数,闭包里面的东西除非被window等宿主对象引用,否则就是不可见的,因此把比较里面的jQuery return出去,外面用一个变量接纳就可以 window.jQuery = _jQuery // 相当于window.jQuery = undefined } return jQuery } })模块加载系统
AMD规范
AMD是Asynchronous Module Definition是异步模块定义
异步:有效避免了采用同步加载方式中,导致的页面假死现象
模块定义:每个模块必须按照一定的个是编写
加载器所在路径
要加载一个模块,需要一个URL作为加载地址,一个script作为加载媒介。但用户在require时都用ID,需要一个将ID转换为URL的方法。约定URL的合成规则:
basePath + 模块ID + ".js"
在DOM树中最后加入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 }
获取当前Script标签
function getCurrentScript (base) { // 为true时相当于getBasePath var stack try { // FF 可以直接 var e = new Error("test"),但其它浏览器只会生成一个空Error a.b.c() // 强制报错,以便获取e.stack } catch (e) { // Safari的错误对象只有line,sourceId,sourceURL stack = e.stack if (!stack && window.opera) { // opera 9 没有e.stack,但有e.Backtrace,不能直接获取,需要对e对象转字符串进行抽取 stack = (`${e}`.match(/of inlked script S+/g) || []).join(" ") } } if (stack) { stack = stack.split(/[@]/g).pop() // 取得最后一行,最后一个空间或@之后的部分 stack = stack[0] === "(" ? stck.slice(1, -1) : stack.replace(/s/, "") // 去掉换行符 return stack.replace(/(:d+)?:d+$/i, "") // 去掉行号与或许存在的出错字符串起始位置 // 在动态加载模块时,节点偶插入head中,因此只在head标签中寻找 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 方法
作用:当依赖列表都加载王弼执行用户回调。
取得依赖列表的第一个ID,转换为URL。无论是通过basePath + ID +".js",还是以映射的方式直接得到。
检测此模块有没有加载过,或正在被加载。因此,需要一个对象来保持所有模块的加载情况。当用户从来没有加载过此节点,就进入加载流程。
创建script节点,绑定onerror,onload,onreadychange等事件判定加载成功与否,然后添加href并插入DOM树,开始加载。
将模块的URL,依赖列表等构建成一个对象,放到检测队列中,在onerror,onload,onreadychange等事件触发时进行检测。
require的实现:
window.require = $.require = function require(list, factory, parent) { var deps = {} // 检测它的依赖是否都未2 var args = [] // 保存依赖模块的返回值 var dn = 0 // 需要安装的模块数 var cn = 0 // 已安装的模块数 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] = { // 创建一个对象,记录模块的加载情况与其他信息 id: id, factory: factory, deps, deps, args: args, state: 1 } if (dn === cn) { // 如果需要安装的等于已安装好的 fireFactory(id, args, factory) // 安装到框架中 } else { // 放入到检测对象中,等待 checkDeps处理 loadings.unshift(id) } checkDeps() }
大多数情况下喜欢使用Dep来表示消息订阅器,Dep是dependence的缩写,中文是依赖.
每require一次,相当于把当前的用户回调当成一个不用加载的匿名模块,ID是随机生成,回调是否执行,要待deps对象里面所有值都为2
require方法中重要方法:
loadJSCSS,作用:用于转换ID为URL
fireFactory,执行用户回调
checkDeps,检测依赖是否都安装好,安装好就执行fireFactory
function loadJSCSS(url, parent, ret, shim) { // 1. 特别处理mass | ready标识符 if (/^(mass|ready)$/.test(url)) { return url } // 2. 转换为完成路劲 if ($.config.alias[url]) { // 别名 ret = $.config.alias[url] if (typeof ret === "object") { shim = ret ret = ret.src } } else { if (/^(w+)(d)?:.*/.test(url)) { // 如果本来就是完整路径 ret = url } else { parent = parent.substr(0, parent.lastIndexOf("/")) var tmp = url.charAt(0) if (tmp !== "." && tmp !== "/") { // 相对于更路径 ret = basepath + url } else if (url.slice(0, 2) === "./") { // 相对于兄弟路径 ret = parent + url.slice(1) } lse if (url.slice(0, 2) === "..") { // 相对于父路径 var arr = parent.replace(//$/, "").split("/") tmp = url.replace(/..//g, () => { arr.pop() return "" }) ret = `${arr.join("/")}/${tmp}` } else if (tmp === "/") { ret = `${parent}${url}` // 相对于兄弟路径 } else { $.error(`不符合模块标识符规则:${url}`) } } } var src = ret.replace(/[?#].*/, "") var ext if (/.(css|js)$/.test(src)) { ext = RegExp.$1 } if (!ext) { // 如果没有后缀名,加上后缀名 src += ".js" ext = "js" } // 开始加载JS 或 CSS if (ext === "js") { if (!modules[src]) { // 如果之前没有加载过 modules[src] = { id: src, parent: parent, exports: {} } if (shim) { // shim机制 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) { // 通过script节点加载目标文件 var node = DOC.createElement("script") node.className = moduleClass // 让getCurrentScript只处理类名为moduleClass的script节点 node[W3C ? "onload" : "onreadystatechange"] = function () { if (W3C || /loaded|complete/i.test(node.readyState)) { // facotrys里面装着define方法的工厂函数(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的第一个节点前,防止IE6下标签没闭合前使用appendChild抛错 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是空对象或者其依赖的模块的状态都是2 if (obj.state !== 2) { loading.splice(i, 1) // 必须先移除再安装,防止在IE下DOM树建完成后手动刷新页面,会多次执行它 fireFactory(obj.id, obj.args, obj.factory) checkDeps() // 如果成功,则再执行一次,以防止有些模块就差本模块没有安装好 } } } function fireFactory (id, deps, factory) { // 从 modules中手机各模块的返回值执行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需要考虑循环依赖的问题。比如:加载A,要依赖B与C,加载B,要依赖A与C。这时A与B就循环依赖了。A与B在判定各自的deps中的键值都是2时才执行,结果都无法执行了。
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") { // 用于文件合并,在标准浏览器中跳过不定模块 if (args[0]) { return } args.shift() } if (typeof args[0] === "function") { // 上线合并后能直接的到模块ID,否则寻找当前正在解析中的script节点的src作为模块ID args.unshift([]) } // 除了Safari外,都能直接通过getCurrentScript一步到位得到当前执行的script节点,Safari可通过onload+delay闭包组合解决 id = modules[id] && modules[id].state >= 1 ? _id : getCurrentScript() factory = args[1] factory.id = _id // 用于调试 factory.delay = function (id) { args.push(id) var isCycle = true try { isCycle = checkCycle(modules[id].deps, id) } catch (e) {} if (isCycle) { $.error(`${id}模块与之前的某些模块存在循环依赖`) } delete factory.delay // 释放内存 require.apply(null, args) } if (id) { factory.delay(id, args) } else { factorys.push(factory) } }
checkCycle方法:
function checkCycle (deps, nick) { // 检测是否存在循环依赖 for (var id in deps) { if (deps[id] === "alogy" && modules[id].state !== 2 && (id === nick || checkCycle(modules[id].deps, nick))) { return true } } }语言模块
字符串
类型:
与标签无关的实现:carAt.charCodeAt,concat,indexOf,lastIndexof,localCompare,match,replace,serach,slice,split,substring,toLocaleLowerCase,toLocaleUpperCase,toLowerCase,toUpperCase以及从Object继承回来的方法,如toString,valueOf。
与标签有关的实现,都是对原字符串添加一对标签:anchor,big,blink,bold,fixed,fontcolor,italics,small,strike,sub,sup
后来添加或为标准化的浏览器方法: trim,quote,toSource,trimLeft,trimRight
truncate
用于对字符进行阶段处理,当超过限定长度,默认添加三个点号或其它。
function truncate (target, length = 30, truncation) { truncation = truncation === void(0) ? "..." : truncation return traget.length > length ? `${target.slice(0, length - target.length)}${truncation}` : `${target}` }
cameliae
转换为驼峰分格
function camelize (target) { if (target.indexOf("-") < 0 && target.indexOf("_") < 0) { return target } return target.replace(/[-_][^-_]/g, (match) => { return match.charAt(1).toUpperCase() }) }
dasherize
转为连字符风格,亦即CSS变量的风格
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
首字母大写
function capitalize (target) { return target.charAt(0).toUpperCase() + target.substring(1).toLowerCase() }
stripTags
移除字符串中 html标签
function stripTags (target) { return String(target || "").replace(/<[^>]+>/g, "") }
stripScripts
移除字符串中所有的script标签
function stripScripts (target) { return Sting(target || "").replace(/