/** * 提供浏览器检测的模块 * @unfile * @module browser */ var browser = (function () { var agent = navigator.userAgent.toLowerCase(), opera = window.opera, browser = { /** * @property {boolean} ie 检测当前浏览器是否为IE * @example * ```javascript * if ( browser.ie ) { * console.log( '当前浏览器是IE' ); * } * ``` */ ie: /(msie\s|trident.*rv:)([\w.]+)/i.test(agent), /** * @property {boolean} opera 检测当前浏览器是否为Opera * @example * ```javascript * if ( browser.opera ) { * console.log( '当前浏览器是Opera' ); * } * ``` */ opera: !!opera && opera.version, /** * @property {boolean} webkit 检测当前浏览器是否是webkit内核的浏览器 * @example * ```javascript * if ( browser.webkit ) { * console.log( '当前浏览器是webkit内核浏览器' ); * } * ``` */ webkit: agent.indexOf(" applewebkit/") > -1, /** * @property {boolean} mac 检测当前浏览器是否是运行在mac平台下 * @example * ```javascript * if ( browser.mac ) { * console.log( '当前浏览器运行在mac平台下' ); * } * ``` */ mac: agent.indexOf("macintosh") > -1, /** * @property {boolean} quirks 检测当前浏览器是否处于“怪异模式”下 * @example * ```javascript * if ( browser.quirks ) { * console.log( '当前浏览器运行处于“怪异模式”' ); * } * ``` */ quirks: document.compatMode == "BackCompat" }; /** * @property {boolean} gecko 检测当前浏览器内核是否是gecko内核 * @example * ```javascript * if ( browser.gecko ) { * console.log( '当前浏览器内核是gecko内核' ); * } * ``` */ browser.gecko = navigator.product == "Gecko" && !browser.webkit && !browser.opera && !browser.ie; var version = 0; // Internet Explorer 6.0+ if (browser.ie) { var v1 = agent.match(/(?:msie\s([\w.]+))/); var v2 = agent.match(/(?:trident.*rv:([\w.]+))/); if (v1 && v2 && v1[1] && v2[1]) { version = Math.max(v1[1] * 1, v2[1] * 1); } else if (v1 && v1[1]) { version = v1[1] * 1; } else if (v2 && v2[1]) { version = v2[1] * 1; } else { version = 0; } browser.ie11Compat = document.documentMode == 11; /** * @property { boolean } ie9Compat 检测浏览器模式是否为 IE9 兼容模式 * @warning 如果浏览器不是IE, 则该值为undefined * @example * ```javascript * if ( browser.ie9Compat ) { * console.log( '当前浏览器运行在IE9兼容模式下' ); * } * ``` */ browser.ie9Compat = document.documentMode == 9; /** * @property { boolean } ie8 检测浏览器是否是IE8浏览器 * @warning 如果浏览器不是IE, 则该值为undefined * @example * ```javascript * if ( browser.ie8 ) { * console.log( '当前浏览器是IE8浏览器' ); * } * ``` */ browser.ie8 = !!document.documentMode; /** * @property { boolean } ie8Compat 检测浏览器模式是否为 IE8 兼容模式 * @warning 如果浏览器不是IE, 则该值为undefined * @example * ```javascript * if ( browser.ie8Compat ) { * console.log( '当前浏览器运行在IE8兼容模式下' ); * } * ``` */ browser.ie8Compat = document.documentMode == 8; /** * @property { boolean } ie7Compat 检测浏览器模式是否为 IE7 兼容模式 * @warning 如果浏览器不是IE, 则该值为undefined * @example * ```javascript * if ( browser.ie7Compat ) { * console.log( '当前浏览器运行在IE7兼容模式下' ); * } * ``` */ browser.ie7Compat = (version == 7 && !document.documentMode) || document.documentMode == 7; /** * @property { boolean } ie6Compat 检测浏览器模式是否为 IE6 模式 或者怪异模式 * @warning 如果浏览器不是IE, 则该值为undefined * @example * ```javascript * if ( browser.ie6Compat ) { * console.log( '当前浏览器运行在IE6模式或者怪异模式下' ); * } * ``` */ browser.ie6Compat = version < 7 || browser.quirks; browser.ie9above = version > 8; browser.ie9below = version < 9; browser.ie11above = version > 10; browser.ie11below = version < 11; } // Gecko. if (browser.gecko) { var geckoRelease = agent.match(/rv:([\d\.]+)/); if (geckoRelease) { geckoRelease = geckoRelease[1].split("."); version = geckoRelease[0] * 10000 + (geckoRelease[1] || 0) * 100 + (geckoRelease[2] || 0) * 1; } } /** * @property { Number } chrome 检测当前浏览器是否为Chrome, 如果是,则返回Chrome的大版本号 * @warning 如果浏览器不是chrome, 则该值为undefined * @example * ```javascript * if ( browser.chrome ) { * console.log( '当前浏览器是Chrome' ); * } * ``` */ if (/chrome\/(\d+\.\d)/i.test(agent)) { browser.chrome = +RegExp["\x241"]; } /** * @property { Number } safari 检测当前浏览器是否为Safari, 如果是,则返回Safari的大版本号 * @warning 如果浏览器不是safari, 则该值为undefined * @example * ```javascript * if ( browser.safari ) { * console.log( '当前浏览器是Safari' ); * } * ``` */ if ( /(\d+\.\d)?(?:\.\d)?\s+safari\/?(\d+\.\d+)?/i.test(agent) && !/chrome/i.test(agent) ) { browser.safari = +(RegExp["\x241"] || RegExp["\x242"]); } // Opera 9.50+ if (browser.opera) version = parseFloat(opera.version()); // WebKit 522+ (Safari 3+) if (browser.webkit) version = parseFloat(agent.match(/ applewebkit\/(\d+)/)[1]); /** * @property { Number } version 检测当前浏览器版本号 * @remind * <ul> * <li>IE系列返回值为5,6,7,8,9,10等</li> * <li>gecko系列会返回10900,158900等</li> * <li>webkit系列会返回其build号 (如 522等)</li> * </ul> * @example * ```javascript * console.log( '当前浏览器版本号是: ' + browser.version ); * ``` */ browser.version = version; /** * @property { boolean } isCompatible 检测当前浏览器是否能够与UEditor良好兼容 * @example * ```javascript * if ( browser.isCompatible ) { * console.log( '浏览器与UEditor能够良好兼容' ); * } * ``` */ browser.isCompatible = !browser.mobile && ((browser.ie && version >= 6) || (browser.gecko && version >= 10801) || (browser.opera && version >= 9.5) || (browser.air && version >= 1) || (browser.webkit && version >= 522) || false); return browser; })(); //快捷方式 var ie = browser.ie, webkit = browser.webkit, gecko = browser.gecko, opera = browser.opera; var utils = { /** * 用给定的迭代器遍历对象 * @method each * @param { Object } obj 需要遍历的对象 * @param { Function } iterator 迭代器, 该方法接受两个参数, 第一个参数是当前所处理的value, 第二个参数是当前遍历对象的key * @example * ```javascript * var demoObj = { * key1: 1, * key2: 2 * }; * * //output: key1: 1, key2: 2 * .each( demoObj, funciton ( value, key ) { * * console.log( key + ":" + value ); * * } ); * ``` */ /** * 用给定的迭代器遍历数组或类数组对象 * @method each * @param { Array } array 需要遍历的数组或者类数组 * @param { Function } iterator 迭代器, 该方法接受两个参数, 第一个参数是当前所处理的value, 第二个参数是当前遍历对象的key * @example * ```javascript * var divs = document.getElmentByTagNames( "div" ); * * //output: 0: DIV, 1: DIV ... * .each( divs, funciton ( value, key ) { * * console.log( key + ":" + valtagName ); * * } ); * ``` */ each: function (obj, iterator, context) { if (obj == null) return; if (obj.length === +obj.length) { for (var i = 0, l = obj.length; i < l; i++) { if (iterator.call(context, obj[i], i, obj) === false) return false; } } else { for (var key in obj) { if (obj.hasOwnProperty(key)) { if (iterator.call(context, obj[key], key, obj) === false) return false; } } } }, /** * 以给定对象作为原型创建一个新对象 * @method makeInstance * @param { Object } protoObject 该对象将作为新创建对象的原型 * @return { Object } 新的对象, 该对象的原型是给定的protoObject对象 * @example * ```javascript * * var protoObject = { sayHello: function () { console.log('Hello UEditor!'); } }; * * var newObject = .makeInstance( protoObject ); * //output: Hello UEditor! * newObject.sayHello(); * ``` */ makeInstance: function (obj) { var noop = new Function(); noop.prototype = obj; obj = new noop(); noop.prototype = null; return obj; }, isObject: function (item) { return (item && typeof item === 'object' && !Array.isArray(item)); }, merge: function (target, source) { var output = Object.assign({}, target); if (this.isObject(target) && this.isObject(source)) { Object.keys(source).forEach(key => { if (this.isObject(source[key])) { if (!(key in target)) { Object.assign(output, {[key]: source[key]}); } else { output[key] = this.merge(target[key], source[key]); } } else { Object.assign(output, {[key]: source[key]}); } }); } return output; }, /** * 将source对象中的属性扩展到target对象上, 根据指定的isKeepTarget值决定是否保留目标对象中与 * 源对象属性名相同的属性值。 * @method extend * @param { Object } target 目标对象, 新的属性将附加到该对象上 * @param { Object } source 源对象, 该对象的属性会被附加到target对象上 * @param { Boolean } isKeepTarget 是否保留目标对象中与源对象中属性名相同的属性 * @return { Object } 返回target对象 * @example * ```javascript * * var target = { name: 'target', sex: 1 }, * source = { name: 'source', age: 17 }; * * .extend( target, source, true ); * * //output: { name: 'target', sex: 1, age: 17 } * console.log( target ); * * ``` */ extend: function (t, s, b) { if (s) { for (var k in s) { if (!b || !t.hasOwnProperty(k)) { t[k] = s[k]; } } } return t; }, /** * 将给定的多个对象的属性复制到目标对象target上 * @method extend2 * @remind 该方法将强制把源对象上的属性复制到target对象上 * @remind 该方法支持两个及以上的参数, 从第二个参数开始, 其属性都会被复制到第一个参数上。 如果遇到同名的属性, * 将会覆盖掉之前的值。 * @param { Object } target 目标对象, 新的属性将附加到该对象上 * @param { Object... } source 源对象, 支持多个对象, 该对象的属性会被附加到target对象上 * @return { Object } 返回target对象 * @example * ```javascript * * var target = {}, * source1 = { name: 'source', age: 17 }, * source2 = { title: 'dev' }; * * .extend2( target, source1, source2 ); * * //output: { name: 'source', age: 17, title: 'dev' } * console.log( target ); * * ``` */ extend2: function (t) { var a = arguments; for (var i = 1; i < a.length; i++) { var x = a[i]; for (var k in x) { if (!t.hasOwnProperty(k)) { t[k] = x[k]; } } } return t; }, /** * 模拟继承机制, 使得subClass继承自superClass * @method inherits * @param { Object } subClass 子类对象 * @param { Object } superClass 超类对象 * @warning 该方法只能让subClass继承超类的原型, subClass对象自身的属性和方法不会被继承 * @return { Object } 继承superClass后的子类对象 * @example * ```javascript * function SuperClass(){ * this.name = "小李"; * } * * SuperClass.prototype = { * hello:function(str){ * console.log(this.name + str); * } * } * * function SubClass(){ * this.name = "小张"; * } * * .inherits(SubClass,SuperClass); * * var sub = new SubClass(); * //output: '小张早上好! * sub.hello("早上好!"); * ``` */ inherits: function (subClass, superClass) { var oldP = subClass.prototype, newP = utils.makeInstance(superClass.prototype); utils.extend(newP, oldP, true); subClass.prototype = newP; return (newP.constructor = subClass); }, /** * 用指定的context对象作为函数fn的上下文 * @method bind * @param { Function } fn 需要绑定上下文的函数对象 * @param { Object } content 函数fn新的上下文对象 * @return { Function } 一个新的函数, 该函数作为原始函数fn的代理, 将完成fn的上下文调换工作。 * @example * ```javascript * * var name = 'window', * newTest = null; * * function test () { * console.log( this.name ); * } * * newTest = .bind( test, { name: 'object' } ); * * //output: object * newTest(); * * //output: window * test(); * * ``` */ bind: function (fn, context) { return function () { return fn.apply(context, arguments); }; }, /** * 创建延迟指定时间后执行的函数fn * @method defer * @param { Function } fn 需要延迟执行的函数对象 * @param { int } delay 延迟的时间, 单位是毫秒 * @warning 该方法的时间控制是不精确的,仅仅只能保证函数的执行是在给定的时间之后, * 而不能保证刚好到达延迟时间时执行。 * @return { Function } 目标函数fn的代理函数, 只有执行该函数才能起到延时效果 * @example * ```javascript * var start = 0; * * function test(){ * console.log( new Date() - start ); * } * * var testDefer = .defer( test, 1000 ); * // * start = new Date(); * //output: (大约在1000毫秒之后输出) 1000 * testDefer(); * ``` */ /** * 创建延迟指定时间后执行的函数fn, 如果在延迟时间内再次执行该方法, 将会根据指定的exclusion的值, * 决定是否取消前一次函数的执行, 如果exclusion的值为true, 则取消执行,反之,将继续执行前一个方法。 * @method defer * @param { Function } fn 需要延迟执行的函数对象 * @param { int } delay 延迟的时间, 单位是毫秒 * @param { Boolean } exclusion 如果在延迟时间内再次执行该函数,该值将决定是否取消执行前一次函数的执行, * 值为true表示取消执行, 反之则将在执行前一次函数之后才执行本次函数调用。 * @warning 该方法的时间控制是不精确的,仅仅只能保证函数的执行是在给定的时间之后, * 而不能保证刚好到达延迟时间时执行。 * @return { Function } 目标函数fn的代理函数, 只有执行该函数才能起到延时效果 * @example * ```javascript * * function test(){ * console.log(1); * } * * var testDefer = .defer( test, 1000, true ); * * //output: (两次调用仅有一次输出) 1 * testDefer(); * testDefer(); * ``` */ defer: function (fn, delay, exclusion) { var timerID; return function () { if (exclusion) { clearTimeout(timerID); } timerID = setTimeout(fn, delay); }; }, /** * 获取元素item在数组array中首次出现的位置, 如果未找到item, 则返回-1 * @method indexOf * @remind 该方法的匹配过程使用的是恒等“===” * @param { Array } array 需要查找的数组对象 * @param { * } item 需要在目标数组中查找的值 * @return { int } 返回item在目标数组array中首次出现的位置, 如果在数组中未找到item, 则返回-1 * @example * ```javascript * var item = 1, * arr = [ 3, 4, 6, 8, 1, 1, 2 ]; * * //output: 4 * console.log( .indexOf( arr, item ) ); * ``` */ /** * 获取元素item数组array中首次出现的位置, 如果未找到item, 则返回-1。通过start的值可以指定搜索的起始位置。 * @method indexOf * @remind 该方法的匹配过程使用的是恒等“===” * @param { Array } array 需要查找的数组对象 * @param { * } item 需要在目标数组中查找的值 * @param { int } start 搜索的起始位置 * @return { int } 返回item在目标数组array中的start位置之后首次出现的位置, 如果在数组中未找到item, 则返回-1 * @example * ```javascript * var item = 1, * arr = [ 3, 4, 6, 8, 1, 2, 8, 3, 2, 1, 1, 4 ]; * * //output: 9 * console.log( .indexOf( arr, item, 5 ) ); * ``` */ indexOf: function (array, item, start) { var index = -1; start = this.isNumber(start) ? start : 0; this.each(array, function (v, i) { if (i >= start && v === item) { index = i; return false; } }); return index; }, /** * 移除数组array中所有的元素item * @method removeItem * @param { Array } array 要移除元素的目标数组 * @param { * } item 将要被移除的元素 * @remind 该方法的匹配过程使用的是恒等“===” * @example * ```javascript * var arr = [ 4, 5, 7, 1, 3, 4, 6 ]; * * .removeItem( arr, 4 ); * //output: [ 5, 7, 1, 3, 6 ] * console.log( arr ); * * ``` */ removeItem: function (array, item) { for (var i = 0, l = array.length; i < l; i++) { if (array[i] === item) { array.splice(i, 1); i--; } } }, /** * 删除字符串str的首尾空格 * @method trim * @param { String } str 需要删除首尾空格的字符串 * @return { String } 删除了首尾的空格后的字符串 * @example * ```javascript * * var str = " UEdtior "; * * //output: 9 * console.log( str.length ); * * //output: 7 * console.log( .trim( " UEdtior " ).length ); * * //output: 9 * console.log( str.length ); * * ``` */ trim: function (str) { return str.replace(/(^[ \t\n\r]+)|([ \t\n\r]+$)/g, ""); }, /** * 将字符串str以','分隔成数组后,将该数组转换成哈希对象, 其生成的hash对象的key为数组中的元素, value为1 * @method listToMap * @warning 该方法在生成的hash对象中,会为每一个key同时生成一个另一个全大写的key。 * @param { String } str 该字符串将被以','分割为数组, 然后进行转化 * @return { Object } 转化之后的hash对象 * @example * ```javascript * * //output: Object {UEdtior: 1, UEDTIOR: 1, Hello: 1, HELLO: 1} * console.log( .listToMap( 'UEdtior,Hello' ) ); * * ``` */ /** * 将字符串数组转换成哈希对象, 其生成的hash对象的key为数组中的元素, value为1 * @method listToMap * @warning 该方法在生成的hash对象中,会为每一个key同时生成一个另一个全大写的key。 * @param { Array } arr 字符串数组 * @return { Object } 转化之后的hash对象 * @example * ```javascript * * //output: Object {UEdtior: 1, UEDTIOR: 1, Hello: 1, HELLO: 1} * console.log( .listToMap( [ 'UEdtior', 'Hello' ] ) ); * * ``` */ listToMap: function (list) { if (!list) return {}; list = utils.isArray(list) ? list : list.split(","); for (var i = 0, ci, obj = {}; (ci = list[i++]);) { obj[ci.toUpperCase()] = obj[ci] = 1; } return obj; }, /** * 将str中的html符号转义,将转义“',&,<,",>,”,“”七个字符 * @method unhtml * @param { String } str 需要转义的字符串 * @return { String } 转义后的字符串 * @example * ```javascript * var html = '<body>&</body>'; * * //output: <body>&</body> * console.log( .unhtml( html ) ); * * ``` */ unhtml: function (str, reg) { return str ? str.replace( reg || /[&<">'](?:(amp|lt|ldquo|rdquo|quot|gt|#39|nbsp|#\d+);)?/g, function (a, b) { if (b) { return a; } else { return { "<": "<", "&": "&", '"': """, "“": "“", "”": "”", ">": ">", "'": "'" }[a]; } } ) : ""; }, /** * 将str中的转义字符还原成html字符 * @see .unhtml(String); * @method html * @param { String } str 需要逆转义的字符串 * @return { String } 逆转义后的字符串 * @example * ```javascript * * var str = '<body>&</body>'; * * //output: <body>&</body> * console.log( .html( str ) ); * * ``` */ html: function (str) { return str ? str.replace(/&((g|l|quo|ldquo|rdquo)t|amp|#39|nbsp);/g, function (m) { return { "<": "<", "&": "&", """: '"', "“": "“", "”": "”", ">": ">", "'": "'", " ": " " }[m]; }) : ""; }, /** * 将css样式转换为驼峰的形式 * @method cssStyleToDomStyle * @param { String } cssName 需要转换的css样式名 * @return { String } 转换成驼峰形式后的css样式名 * @example * ```javascript * * var str = 'border-top'; * * //output: borderTop * console.log( .cssStyleToDomStyle( str ) ); * * ``` */ cssStyleToDomStyle: (function () { var test = document.createElement("div").style, cache = { float: test.cssFloat !== undefined ? "cssFloat" : test.styleFloat !== undefined ? "styleFloat" : "float" }; return function (cssName) { return ( cache[cssName] || (cache[cssName] = cssName.toLowerCase().replace(/-./g, function (match) { return match.charAt(1).toUpperCase(); })) ); }; })(), /** * 动态
版权声明:本文为原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
关注微信公众号:"cq_xifan";