JavaScript_框架

框架与库:

库:解决某个问题而拼凑出来的一大堆函数与类的集合。每个函数(方法)之间都没什么关联。
框架:一个半成品的应用,直接给出一个骨架。

种子模块

对象扩展数组化类型判定简单的事件绑定与卸载无冲突处理模块加载domReady

命名空间

jQuery对命名空间的控制:
把命名空间保存到一个临时变量中,后面通过noConflict放回去.

var _jQuery = window.jQuery, _$ = window.$ // 先把可能存在的同名变量保存起来

jQuery.extend({
    noConflict (deep) {
        window.$ = _$ // 这时再放回去
        if (deep) {
            window.jQuery = _jQuery
            return jQuery
        }
    }
})

对象扩展

浅拷贝和深拷贝

在JavaScript中一般约定俗成为:extendmixin

// 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 方法

作用:当依赖列表都加载王弼执行用户回调。

  1. 取得依赖列表的第一个ID,转换为URL。无论是通过basePath + ID +'.js',还是以映射的方式直接得到。

  2. 检测此模块有没有加载过,或正在被加载。因此,需要一个对象来保持所有模块的加载情况。当用户从来没有加载过此节点,就进入加载流程。

  3. 创建script节点,绑定onerror,onload,onreadychange等事件判定加载成功与否,然后添加href并插入DOM树,开始加载。

  4. 将模块的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来表示消息订阅器,Depdependence的缩写,中文是依赖.

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。

  • 与标签有关的实现,都是对原字符串添加一对标签:anchorbigblinkboldfixedfontcolor,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(/]*>([\S\s]*?)/img, '')
}

escapeHTML
将字符串经过html转义得到合适在页面中显示的内容。比如:<替换<

function escapeHTML (target) {
    return target.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
过滤数组中的nullundefined,但不影响原数组

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对象的回调中执行它们。查看XMLHttpRequesetstatus(状态码),即便是成功返回还是服务器错误,决定调用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方法。但它们不是立即执行,而是异步的,它需要等待用户nexterrot绑定好所需要的回调.

Promise/A 与 mmDeferred

Promise/A属于Promise规范,而Promise规范则又隶属于CommonJS

Promise/A一个带有then方法的对象,它拥有3个状态。pending,fulfilled,rejected
一开始是pending,执行then方法后,当前回调被执行,会进入fulfiledrejected状态

then方法可传入两个函数,一个是成功执行时执行,第一个失败时执行,分别叫做onFulfill,onRejectthen还有第3个参数叫做onNotify,它不会改变对象的状态。
then方法在添加onFufillonReject会返回一个新的Promise对象,这样一来,就能形成一个Promise

为了防止Promise链还没有形成就被用户触发回调,强烈要求使用setTimeoutsetInmmediate,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)
}

你可能感兴趣的:(javascript)