框架与库:
库:解决某个问题而拼凑出来的一大堆函数与类的集合。每个函数(方法)之间都没什么关联。
框架:一个半成品的应用,直接给出一个骨架。
种子模块
对象扩展
,数组化
,类型判定
,简单的事件绑定与卸载
,无冲突处理
,模块加载
与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-z\d]([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(/