框架与库:
库:解决某个问题而拼凑出来的一大堆函数与类的集合。每个函数(方法)之间都没什么关联。
框架:一个半成品的应用,直接给出一个骨架。
种子模块
对象扩展
,数组化
,类型判定
,简单的事件绑定与卸载
,无冲突处理
,模块加载
与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(/<script[^>]*>([\S\s]*?)/img, '')
}
escapeHTML
将字符串经过html转义得到合适在页面中显示的内容。比如:<
替换<
function escapeHTML (target) {
return target.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''')
}
unescapeHTML
将字符串中的html实体字符还原为对应字符。
function unescapeHTML (target) {
return target.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/&/g, '&') // 处理转义的中文和实体和实体字符
.replace(/&#([\d]+);/g, function ($0, $1) {//
return String.fromCharCode(parseInt($1, 10))
})
}
escapeRegExp
将字符串安全格式化为正则表达式的源码
function escapeRegExp (target) {
return target.replace(/([-.*+?^{}()|[\]\/\\])/g, '\\$1')
}
format
function formate (str, object) {
// var array = Array.prototype.slice.call(arguments, 1)
var array = Array.of(arguments)
return str.replace(/\\?\{{([^{}]+)\}\}/gm, (match, name) => {
if (`${match.charAt(0)}` === '\\') {
return match.slice(1)
}
var index = Number(name)
if (index >= 0) {
return arry[index]
}
if (object && object[name] !== void 0) {
return object[name]
}
return ''
})
}
trim
去除前后端空格
function trim (str) {
str.replace(/^\s\s*/, '').replace(/\s\s*$/, '')
}
function trime (str) {
return str.replace(/^\s+|\s+/g, '')
}
function trim (str) {
var whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\n\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000'
for (var i = 0; i < str.length; i++) {
if (whitespace.indexOf(str.charAt(i)) === -1) {
str = str.substring(i)
break
}
}
for (i = str.length - 1; i >= 0; i--) {
if (whitespace.indexOf(str.charAt(i)) === -1) {
str = str.substring(0, i + 1)
break
}
}
return whitespace.indexOf(str.charAt(0)) === -1 ? str : ''
}
数组
removeAt
/**
* 移除数组中指定位置的元素
* @param {[type]} target [description]
* @param {[type]} index [description]
* @return {[type]} [description]
*/
function removeAt (target, index) {
return !!target.splice(index, 1).length
}
shffle
对数组进行洗牌
function shuffle (target) {
var j, x, i = target.length
for (; i > 0; j = parseInt(Math.random() * i), x = target[--i], target[i] = target[j], target[j] = x) {
return target
}
}
random
从数组中随机抽选一个元素出来
function random (target) {
return target[Math.floor(Math.random() * target.length)]
}
flatten
对数组进行平坦化处理
function flatten (target) {
var result = []
target.forEach((item) => {
if (Array.isArray(item)) {
result = result.concat(flatten[item])
} else {
result.push(item)
}
})
return result
}
unique
对数组进行去重操作。(一维数组)
function unique (target) {
return Array.form(new Set(target))
}
function unique (target) {
var result = []
loop: for (var i = 0, n = target.length; i < n; i++) {
for (var x = i + 1; x < n; x++) {
if (target[x] === target[i]) {
continue loop
}
}
result.push(target[i])
}
return result
}
compact
过滤数组中的null
和undefined
,但不影响原数组
function compact (target) {
return target.filter((el) => {
return el !== null
})
}
日期
isLeapYear
判断是否为闰年
function isLeapYear (date) {
return new Date(date.getFullYear(), 2, 0).getDate() === 29
}
getDaysInMonth
获取当前月份的天数
function getDaysInMonth (date) {
return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate()
}
类工厂
JS对类的支持
function Instance () {}
var instance = new Instance()
new
操作时发生:
创建一个空对象instance
instance.proto__ = instanceClass.prototype
将构造器函数里面的 this = instance
执行构造器里面的代码
判定有没有返回值,没有返回值默认 undefined,如果返回值为复合数据类型,则直接返回,否则返回this。
ES5属性描述符
新增加的API:
Object.keys // 用于收集当前对象的遍历属性(不包括原型链上的),以数组形式返回
Object.getOwnPropertyNames // 用于收集当前对象不可遍历属性与可遍历属性(不包括原型链上的),以数组形式返回
Object.getPrototypeOf
Object.defineProperty
Object.defineProperties
Object.getOwnPropertyDescriptor
Object.create
Object.scal // 不准删除已有的本地属性,内部实现就是遍历一下,把每个本地属性的configurabel改为false
Object.freeze // 原有本地属性不让修改,内部实现就是遍历一下,把每个本地属性的writeable也该为false
Object.perventExtensions // 阻止添加本地属性,如果本地属性被删除,也无法再加回来
Object.isSealed // 判断一个对象是否被锁定。锁定,意味着无法扩展。
Object.isFrozen
Object.isExtensible
模拟Object.defineProperty
if (typeof Object.defineProperty !== 'function') {
Object.defineProperty = function (obj, prop, desc) {
if ('value' in desc) {
obj[prop] = desc.value
}
if ('get' in desc) {
obj.__defineGetter__(prop, desc.get)
}
if ('set' in desc) {
obj.__defineSetter__(prop, desc.set)
}
return obj
}
}
模拟Object.defineProperties
if (typeof Object.defineProperties !== 'function') {
Object.defineProperties = functionb (obj, descs) {
for (var prop in descs) {
if (descs.hsaOwnProperty(prop)) {
Object.defineProperty(obj, prop, descs[prop])
}
}
}
}
模拟Object.create
作用:用于创建一个子类的原型
第一个参数为父类的原型,第二个是子类另外要添加的属性的配置对象
if (typeof Object.create !== 'funcion') {
Object.create = function (prototype, descs) {
fnctino F () {}
F.prototype = prototype
var obj = new F()
if (descs !== null) {
Object.defineProperties(obj, descs)
}
return obj
}
}
IE下模拟Object.create
var createEmpty
var supprotsProto = Object.prototype.__proto__ = null
if (supprotsProto || typeof document === 'undefined') {
createEmpty = function () {
return {
'__proto__': null
}
}
} else {
// 因为无法让一个对象继承自一个不存在的东西,它最后肯定要回溯到Object.prototype,
// 那么就从一个新执行环境中盗取一个Object.prototype,把它的所有原型属性都砍掉,这样它的实例就
// 既没有特殊属性,也没有什么原型属性。只剩下一个__proto__,值为null
createEmpty = (() => {
var iframe = document.createElement('iframe')
var parent = document.body || document.documentElement
iframe.style.display = 'none'
parent.appendChild(iframe)
iframe.src = 'javascript:'
var mepty = iframe.contentWindow.Object.prototype
parent.removeChild(iframe)
iframe = null
delete empty.constructor
delete empty.hasOwnProperty
delete empty.propertyIsEnumerable
delete empty.isPrototypeOf
delete empty.toLocaleString
delete empty.toString
delete empty.valueOf
empty.__proto__ = null
function Empty () {}
Empty.prototype = empty
return function () {
return new Empty()
}
})()
}
异步处理
对于JavaScript这样单线程的东西唯一的解耦方法就是提供异步API。
异步API,简单的说:不会立即执行的方法。
setTimeout 与setInterval
如果回调的执行时间大于间隔时间,那么浏览器会继续执行它们,导致真正的间隔时间比原来的大一点.
它们存在一个最小的时钟间隔,在IE6~IE8中为15.6ms,后来精准到10ms,IE10为4ms,其它浏览器相仿.
有关零秒延迟,此回调将会放到一个能立即执行的时段进行触发,JavaScript代码大体上自顶向下执行,但中间穿插着有关DOM渲染,事件回应等异步代码它们将组成一个队列,零秒延迟将会实现插队操作
不写第二个参数,浏览器自动配时间,在IE,FireFox中,第一次可能给个很大数字,100ms上下,往后会缩小到最小时间间隔,Safari,Chrome,Opera则多为10ms上下,FireFox中,setInterval不写第二个参数,会当作setTimeout处理,只执行一次。
支持额外参数,从第三个参数起,作为回调的传参传入
setTimeout方法的时间参数若为极端值(如负数,0,或者极大的正数),会立即执行。
算出最小间隔时间
function test (count, ms) {
var c = 1
var time = [new Date() * 1]
var id = setTimeout(() => {
time.push(new Date() * 1)
c += 1
if (c <= count) {
setTimeout(arguments.callee, ms)
} else {
clearTimeout(id)
var t1 = time.length
var av1 = 0
for (var i = 1; i < t1; i++) {
var n = time[i] - time[i - 1] // 收集每次与上一次相差的时间数
av1 += n
}
console.log(av1 / count) // 求平均值
}
}, ms)
}
window.onload = () => {
var id = setTimeout(() => {
test(100, 1)
clearTimeout(id)
}, 3000)
}
如果闲最短时间间隔太大,可以改造一下setTimeout。
方法:利用image死链时立即执行onerror回调的情况进行改造。
var orig_setTimeout = window.setTimeout
window.setTimeout = function (fun, wait) {
if (wait < 15) {
orig_setTimeout(fun, wait)
} else {
var img = new Image()
img.onload = image.onerror = function () {
fun()
}
img.src = "data:,foo"
}
}
Deferred
是一个双链参数加工的流水线模型。双链是指它内部把回调分成两种,一种叫成功回调,用于正常的执行,一种叫错误回调,用于出错时执行。各自组成两个队列
添加回调时是一组组添加的,每组回调的参数都是上一组回调的处理结果,当然只有第一组的参数是用户传入的。
流水线的解释,每个回调可能不是紧挨着执行,有时需要耗些时间,可能是异步API引起的,也可能是调用了如wait
这样的方法。假若出错,由后一组的错误回调捕获处理,没有问题尝试再转回成功队列。
var Deferred = function (canceller) {
this.chain = []
this.id = setTimeout('1')
this.fired = -1
this.paused = 0
this.canceller = canceller
this.silentlyCancelled = false
this.chained = false
}
function curry (fn, scope, args) {
return function () {
var argsv = [].concat.apply(args, arguments)
return fn.apply(scope, argsv)
}
}
Deferred.prototype = {
// 3种状态,未触发,触发成功,触发失败
state: function () {
if (this.fired === -1) { // 未触发
return 'unfired'
} else if (this.fired === 0) { // 触发成功
return 'sucess'
} else { // 触发失败
retur 'error'
}
},
// 取消触发,类似于ajax的abort
cancel: function (e) {
if (this.fired === -1) { // 只有未触发时才能cancel掉
if (this.canceller) {
this.canceller(this)
} else {
this.silentlyCancelled = ture
}
if (this.fired === -1) {
if (!(e instanceof Error)) {
e = new Error(e + '')
}
this.errback(e)
}
} else if ((this.fired === 0) && (this.results[0] instaceof Deferred)) {
this.results[0].cancel(e)
}
},
// 决定是用那个队列
_resback: function (res) {
this.fired = ((res instanceof Error) ? 1 : 0)
this.results[this.fired] = res
if (this.paused === 0) {
this._fire()
}
},
// 判断是否触发过
_check: function () {
if (this.fired != -1) {
if (!this.silentlyCancelled) {
throw new '此方法已经被调用过!'
}
this.silentlyCancelled = false
return
}
},
// 触发成功队列
callback: function (res) {
this._check()
if (res instanceof Deferred) {
throw new Error('Deferred instances can only be chained if they are the result of a callback')
}
this._resback(res)
},
// 触发错误队列
errback: function (res) {
this._check()
if (res instanceof Deferred) {
throw new Error('Deferred instances can only be chained if they are the result of a ballback')
}
if (!(res instanceof Error)) {
res = new Error(res + '')
}
this._resback(res)
},
// 同时添加成功与错误回调
addBoth: function (a, b) {
b = b || a
return this.addCallbacks(a, b)
},
// 添加成功回调
addCallback: function (fn) {
if (argynebts.length > 1) {
var args = [].slice.call(argumnets, 1)
fn = curry(fn, window, args)
}
return this.addCallbacks(fn, null)
},
// 添加错误回调
addErrback: function (fn) {
if (arguments.length > 1) {
var args = [].slice.call(arguments, 1)
fn = curry(fn, window,argus)
}
return this.addCallbacks(null, fn)
},
// 同时添加成功回调与错误回调,后来Promise的then方法就是参考它设计
addCallbacks: function (cb, eb) {
if (this.chained) {
throw new Error('Chained Deferreds can not be re-used')
}
if (this.finalized) {
throw new Error('Finalized Deferreds can not be re-used')
}
this.chain.push([cb, eb])
if (this.fired >= 0) {
this._fire()
}
return this
},
// 将队列的回调依次触发
_fire: function () {
var chain = this.chain
var fired = this.fired
var res = this.results(fired)
var self = this
var cb = null
while (chain.length > 0 && this.paused === 0) {
var pair = chain.shift()
var f = pair[fired]
if (f === null) {
continue
}
try {
res = f(res)
fired = ((res instanceof Eorror ? 1 : 0))
if (res instanceof Deferred) {
cb = function (res) {
self.paused--
self_resback(res)
}
this.paused++
}
} catch (err) {
fired = 1
if (!(err instanceof Error)) {
try {
err = new Error(err + '')
} catch (e) {
alert(e)
}
}
res = err
}
}
this.fired = fired
this.result[fired] = res
if (cb && this.paused) {
res.addBoth(cb)
res.chained = true
}
}
}
触发这些回调时通过callback
,与ertback
方法,通常放到XMLHttpRequres
对象的回调中执行它们。查看XMLHttpRequeset
的status
(状态码),即便是成功返回还是服务器错误,决定调用Deferred
对象的callback
还是ertback
,将返回值传入到它们里面
JS Deferred
JS Deferred
实现形态基本奠定了后来成为Promise/A
的范式
每一个回调都至少涉及一个Deferred
github源码:jsdeferred
function Deferred () {
return (this instanceof Deferred) ? this.init() : new Deferred()
}
Deferred.ok = function (x) {
return x
}
Deferred.ng = function (x) {
throw x
}
Deferred.wait = function (n) {
var d = new Deferred()
var t = new Date()
var id = setTImeout(function () {
d.call((new Date()).getTime() - t.getTime())
}, n * 1000)
d.canceller = function () {
clearTimeout(id)
}
return d
}
Deferred.register = function (name, fun) {
this.prototype[name] = function () {
var a = arguments
return this.next(fcuntion () {
return fun.apply(this, a)
})
}
}
Deferred.parallel = function (dl) {
var isArray = false // 它可以放一个数组或对象,或N个函数,或Deferred对象做参数
if (arguments.length > 1) {
dl = Array.prototype.slice.call(arguments)
isArray = true
} else if (Array.isArray && Array.isArray(dl) || typeof dl.length === 'number') {
isArray = true
}
// 并归用的Deferred
var ret = new Deferred()
// 收集结果
var value = {}
// 计数器
var num = 0
for (var i in dl) {
if (dl.hasOwnPrototpye(i)) {
(funcion (d, i) {
// 通通转成Deferred对象
if (typeof d === 'function') {
dl[i] = d = Deferred.next(d) // 转换为Deferred对象
}
d.next(function (v) { // 添加两个回调,next与error
values[i] = v
if (--num <= 0) {
if (isArray) {
values.length = dl.length
values = Array.prototype.slice.call(values, 0)
}
ret.call(values)
}
}).error(funciton (e) {
ret.fail(e)
})
num++
})(dl[i], i)
}
}
// 如果里面没有内容立即执行
if (!num) {
Deferred.next(function () {
ret.call()
})
ret.canceller = function () {
for (var i in dl)
if (dl.hasOwnProperty(i)) {
dl[i].cancel()
}
}
}
return ret
}
Deferred.register('wait', Deferred.wait)
// IE的提速
Deferred.next_faster_way_readystatechange = (typeof window === 'object') && (localtion.protocol == 'http:') && window.VBArray && funciton (fun) { // MSIE
var d = new Deferred()
var t = (new Date()).get()
// 浏览器的发请求数是有限的在IE6,IE7中为2-4。IE8,IE9为6
// 如果超出这数目,可能造成阻塞,必须待这几个处理完之后才继续处理
// 因此这里添加一个阀值,如果上次与这次间隔超过150还没处理完
// 那么久退化到原始的setTimeout方法
if (t - arguments.callee._perv_timeout_called < 150) {
var cancel = false
// 创建一个script节点,加载一个不存在的资源来引发onerror
// 由于旧版本IE不区分onerror与onload,都只会触发onredaystatechange
// 只要造成异步效果,让用户有足够时间绑定回调就行
var script = document.createElement('script')
script.type = 'text/javascript'
script.src = 'data:text/javascript'
script.onreaydstatechange = function () {
if (!cancel) {
d.canceller()
d.call()
}
}
// 清掉事件与移出DOM树
d.canceller = function () {
if (!cancel) {
cancel = true
script.onreaydstatechange = null
document.body.removeChild(scirpt)
}
}
// Deferred最好延迟到domReady或onload后执行
document.body.appendChild(scirpt)
} else {
arguments.callee._prev_timeout_called = t
var id = setTimeout(function () {
d.call()
}, 0)
d.canceller = function () {
clearTimeout(id)
}
}
if (fun) {
d.callback.on = fun
}
return d
}
// 标准浏览器的提速
Deferred.next_faster_way_Image = (tyupeof window === 'object') && (typeof(Image) !== 'undefined') && !window.opera && document.addEventListener && function (fun) {
// 用于opera外的标准浏览器
var d = new Deferred()
var img = new Image()
// 创建一个image加载一个不存在的图片(为了防止万分之一的图片存在的情况,onload也绑上了)
var handler = function () {
d.canceller()
d.call()
}
img.addEventListener('load', handler, false)
img.addEventListener('error', handler, false)
d.canceller = function () {
img.removeEventListener('load', handler, false)
img.removeEventListener('error', handler, false)
}
img.src = 'data:image/png,' + Math.random()
if (fun) {
d.callback.ok = fun
}
return d
}
Deferred.prototype = {
init: function () {
this._next = null
this.callback = {
ok: Deferred.ok
ng: Deferred.ng
}
return this
},
next: function (fun) {
return this._post('ok', fun)
},
error: function (fun) {
return this._post('ng', fun)
},
call: function (val) {
return this._fire('ok', val)
},
fail: function (err) {
return this._fire('ng', err)
},
cancel: function () {
(this.canceller || function () {})()
return this.init()
},
_post: function (okng, fun) {
this._next = new Deferred()
this._next.callback[okng] = fun
return this._next
},
_fire: function (okng, value) {
var next = 'ok' // 每次都尝试从成功队列执行
try { // 决定是ing成功的回调还是错误的回调
value = this.callback[okng].call(this, value)
} catch (e) {
next = 'ng'
value = e
}
if (value instanceof Deferred) {
value._next = this._next // 把`_next`对象进行转移,防止它在setTimeout中执行。
} else { // 执行链表中的下一个Deferred的_fire
if (this._next) {
this._next.fire(next, value)
}
}
return this
}
}
Deferred链的实现
它每绑定一个回调函数就需要一个全新的Deferred
对象,从而形成一个Deferred
链,两个Deferred
能连到一块,取决于两个东西:
当前
Deferred
实例在_fire
方法执行callback
时得到的属性_next
属性
JSDeferred的并归结果parallel
可以实现多个嵌套请求.
它里面存在一个数组,用于收集一个阶段N个并行执行的回调的结果,同时应该还有一个计数器,如果回调都成功执行了开始执行“回调的回调”,这个回调是框架提供的,然后处理所有收集到的结果,放进用户绑定的后续回调中。
JSDeferred的性能提速
JSDeferred的每一个对象都允许自动执行它绑定的回调,不需要手动调用call
,fail
方法。但它们不是立即执行,而是异步的,它需要等待用户next
,errot
绑定好所需要的回调.
Promise/A 与 mmDeferred
Promise/A
属于Promise
规范,而Promise
规范则又隶属于CommonJS
。
Promise/A
一个带有then
方法的对象,它拥有3个状态。pending
,fulfilled
,rejected
。
一开始是pending
,执行then
方法后,当前回调被执行,会进入fulfiled
或rejected
状态
then
方法可传入两个函数,一个是成功执行时执行,第一个失败时执行,分别叫做onFulfill
,onReject
。then
还有第3个参数叫做onNotify
,它不会改变对象的状态。then
方法在添加onFufill
或onReject
会返回一个新的Promise
对象,这样一来,就能形成一个Promise
链
为了防止Promise
链还没有形成就被用户触发回调,强烈要求使用setTimeout
,setInmmediate
,process.nextTick
等异步API来提供足够的构建时间。
此外,在实现Promise/A+
时,渐渐归纳成添加all
,any
等方法,来并归结果或处理竞态状态。
function Deferred () {
return (this instanceof Deferred) ? this.init() : new Deferred()
}
Deferred.ok = function (x) {
return x
}
Deferred.ng = function (x) {
throw x
}
Deferred.prototype = {
init () {
this.callback = {
resolve: Deferred.ok,
reject: Deferred.ng,
notify: Deferred.ok,
ensure: Deferred.ok
}
this.state = 'pending'
this.dirty = false
this.promise = {
then (onResolve, onReject, onNotify) {
return this._post(onResolve, onReject, onNotify)
},
otherwise (onReject) {
return this._post(null, onReject, null)
},
ensure (onEnsure) {
return this._post(0, 0, 0, onEnsure)
},
_next: null
}
return this
},
_post (fn0, fn1, fn2, fn3) {
var deferred
if (!this.dirty) {
deferred = this
} else {
deferred = this.promise._next = new Deferred()
}
var index = -1
var fns = argument
'resolve,reject,notify, ensure'.replace(/\w+/g, (method) => {
var fn = fns[++index]
if (typeof fn === 'function') {
deferred.callback[method] = fn
deferred.dirty = true
}
})
return deferred.promise
},
_fire (method, value) {
var next = 'resolve'
try {
if (this.state == 'pending' || method === 'notify') {
var fn = thi.callback[method]
value = fn.call(this, value)
if (method !== 'notify') {
this.state = method
} else {
next = 'notify'
}
}
} catch (e) {
next = 'reject'
value = e
}
var ensure = this.callback.encure
if (Deferred.ok !=== ensure) {
try {
encure.call(this)
} catch (e) {
next = 'reject'
value = e
}
}
if (Deferred.isPromise(value)) {
value._next = this.promise._next
} else {
if (this.promise._next) {
this.promise._next._fire(next, value)
}
}
return this
}
}
'resoluve,reject,notify'.replace(/\w+/g, (method) => {
Deferred.prototpe[method] = (val) => {
if (!this.dirty) {
setTimeout(() => {
this._fire(method, val)
}, 0)
} else {
return this._fire(method, val)
}
}
})
Deferred.isPromise = function (obj) {
return !!(obj && typeof obj.then === 'function')
}
function some (any, promises) {
var deferred = Deferred()
var n = 0
var result = []
var end
function loop (promise, index) {
promise.then((ret) => {
if (!end) {
result[index] = ret // 保证回调的顺序
n++
if (any || n >= promises.length) {
deferred.resolve(ang ? ret : result)
end = true
}
}
}, (e) => {
end = true
deferred.reject(e)
})
}
for (var i = 0, l = promises.length; i < l; i++) {
loop(promises[i], i)
}
return deferred.promise
}
Deferred.all = function () {
return some(false, arguments)
}
Deferred.any = function () {
return some(true, arguments)
}
Deferred.nextTick = function (fn) {
setTimeout(fn, 0)
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。