大海Online的博客

Looking for Interest

组件版本管理

组件版本管理

任何编程语言, 当组件有依赖关系+版本管理之后, 都会遇到同样的问题, 相依性地狱

常见的问题有:

  1. 循环依赖: A依赖于B, B依赖于C, C依赖于A, 相互循环依赖, 导致解析无法继续
  2. 版本不兼容: A依赖于B,C1, B依赖于C2, C1与C2不兼容时, 导致系统无法运行
  3. 依赖过多: 依赖的深度与广度都比较大, 导致系统过于复杂, 能性能调试带来诸多不便

比如:

  1. DLL, DLL初期缺少规化, 导致当覆盖或升级系统的DLL时, 经常出现冲突的情况
  2. JAR, 百家争鸣, 经常导致版本问题, 甚至象log4j,logback,jcf之间的纠缠
  3. js, 复杂的页面交互, 历史遗留问题, 经常导致loader后的几个js不兼容,而作者可能是同一人

同时也有一些版本库管理器

  1. APT, YUM linux上的软件包管理
  2. npm nodejs的软件包管理
  3. brew mac homebrew软件
  4. sublime 编辑器中的package control也算是
  5. maven jar包版本依赖管理
  6. eclipse-plugins 插件版本管理
  7. bundle ios.app, mac.app, 一种常用的app打包模式
  8. 还有很多很多

从已有的解决方案来思考下这个问题

  1. jar包以及java类都不会因循环依赖而导致问题, 因为依赖与运行解耦合了 考虑js.loader中的依赖, AMD因运行后才能得到依赖的module, 导致循环依赖不得不break 而CMD方案, 则提前将module设定为了{}, 少了点微不足道的功能, 但解耦合了, 从而可以将分析模块依赖与模块运行独立开来, 避免掉break
  2. 可以得出这样一个结论, 一个相互依赖+版本的组件库, 通常具备以下几个功能 a) 一个打包器, 通常由语言方制定好规范, 大家相互遵守 b) 一个版本管理工具, 通常会带有命令行, 可以查看树型依赖 c) 依赖的组件, 有远程仓库, 通过id, 可以通过http直接下载, 有中央仓库, 而大公司也喜欢制造自己软件仓库 d) 敲入安装命令后, 可以自行下载元信息, 分析依赖, 下载依赖, 执行初始化 e) 软件本身不参与自己的版本管理
  3. 版本不兼容的问题, 通常与实际情况有关, 很难有最佳实践, 向下兼容是比较靠谱的, 通常情况下, 一但不向下兼容, 通常都会在事后遇到依赖组件冲突问题, 除非完全统一升级 如果无法向下兼容, 那就另起炉灶吧, 换个名字, 换个命名空间, 换个全局变量
  4. 提前定义好接口规范, 小版本号bugfix, 中版本增加功能, 大版本号表示大幅重构或变更

因此:

  1. 在考虑是否采用版本号时, 首先要确定好, 是否有足够的精力去制造或者寻找一个合适的版本管理工具, 一个代码中央仓库, 一个有氛围的社区
  2. 如果没有, 就还是单版本吧, 一个稳定的核心, 每个组件都尽量只依赖于核心, 独立成单独组件, 不相互依赖, 采用简单代码冗余, 代替复杂版本管理

一句话: 要么简单, 要么复杂, 量力选择, 针对具体情况, 做出选择

insertBefore

insertBefore

经常看到这样的写法:

if (head.firstChild) {
	head.insertBefore(node, head.firstChild);
} else {
	head.appendChild(node);
}

其实可以改为:

head.insertBefore(node, head.firstChild);
  • insertBefore的第二参数为空时, 相当于appendChild
  • head.firstChild为空时, insertBefore等同于appendChild
  • jquery中的getScript时, 就是只有一句 insertBefore

另外:

  • IE6,IE7,IE8 下的head默认不是空的, 有一个文本空节点, head.childNodes.length === 1
  • IE9下面时, 如有<head></head>, 为1个文本节点, 内容为一个回车符
  • IE9下面时, 如没有head标签, 与chrome一致, 节点数为0
  • chrome时, <head></head>与没有head标签,均一致, 节点数为0

isFunction

[size=xx-large]isFunction

标准写法是:

 var isFunction = function(obj) {
       return Object.prototype.toString.call(obj) === '[object Function]'
 }

深层次:

  • 有博客说, 之前的chrome会识别 new Regexp()为function, 没有测试之前的chrome, 现在的不会了
  • 有博客说, document.write在IE下会识别为object, 其实新的写法, 依然还是object, 不信,jquery测试下, 同理getAttribute也是一样的
  • jquery的isFunction注释
    // See test/unit/core.js for details concerning isFunction.
    // Since version 1.3, DOM methods and functions like alert
    // aren't supported. They return false on IE (#2968).
    isFunction: function( obj ) {
        return jQuery.type(obj) === "function";
    }
  • jquery中的 type(obj), 即是 Object.prototype.toString.call(obj)
  • jquery已经不支持DOM方法,以及像alert这样的方法, 因为他们在IE都返回false,无法区分
  • #2968 见 http://bugs.jquery.com/ticket/2968

最简单的写法:

 var isFunction = function(obj) {
       return typeof obj === 'function'
 }
  • 这个写法, 在jquery的isFunction测试用例中, 全部通过 https://code.google.com/p/jqueryjs/source/browse/trunk/jquery/test/unit/core.js
  • 简单的就是最好的~~

测试用例 (修改自jquery的isFunction测试用例)

<body>
<ul id="result"></ul>

<script>
var jQuery = {
	isFunction: function(obj) {
		return typeof obj === 'function'
	}
}

function ok(bool, text) {
	document.getElementById('result').innerHTML += ('<li>' + (bool ? '√':'×') + ' &nbsp; ' + text);
}

function test() {
        // Make sure that false values return false
        ok( !jQuery.isFunction(), "No Value" );
        ok( !jQuery.isFunction( null ), "null Value" );
        ok( !jQuery.isFunction( undefined ), "undefined Value" );
        ok( !jQuery.isFunction( "" ), "Empty String Value" );
        ok( !jQuery.isFunction( 0 ), "0 Value" );

        // Check built-ins
        // Safari uses "(Internal Function)"
        ok( jQuery.isFunction(String), "String Function("+String+")" );
        ok( jQuery.isFunction(Array), "Array Function("+Array+")" );
        ok( jQuery.isFunction(Object), "Object Function("+Object+")" );
        ok( jQuery.isFunction(Function), "Function Function("+Function+")" );

        // When stringified, this could be misinterpreted
        var mystr = "function";
        ok( !jQuery.isFunction(mystr), "Function String" );

        // When stringified, this could be misinterpreted
        var myarr = [ "function" ];
        ok( !jQuery.isFunction(myarr), "Function Array" );

        // When stringified, this could be misinterpreted
        var myfunction = { "function": "test" };
        ok( !jQuery.isFunction(myfunction), "Function Object" );

        // Make sure normal functions still work
        var fn = function(){};
        ok( jQuery.isFunction(fn), "Normal Function" );

        var obj = document.createElement("object");

        // Firefox says this is a function
        ok( !jQuery.isFunction(obj), "Object Element" );

        // IE says this is an object
        // Since 1.3, this isn't supported (#2968)
        //ok( jQuery.isFunction(obj.getAttribute), "getAttribute Function" );

        var nodes = document.body.childNodes;

        // Safari says this is a function
        ok( !jQuery.isFunction(nodes), "childNodes Property" );

        var first = document.body.firstChild;

        // Normal elements are reported ok everywhere
        ok( !jQuery.isFunction(first), "A normal DOM Element" );

        var input = document.createElement("input");
        input.type = "text";
        document.body.appendChild( input );

        // IE says this is an object
        // Since 1.3, this isn't supported (#2968)
        //ok( jQuery.isFunction(input.focus), "A default function property" );

        document.body.removeChild( input );

        var a = document.createElement("a");
        a.href = "some-function";
        document.body.appendChild( a );

        // This serializes with the word 'function' in it
        ok( !jQuery.isFunction(a), "Anchor Element" );

        document.body.removeChild( a );

        // Recursive function calls have lengths and array-like properties
        function callme(callback){
                function fn(response){
                        callback(response);
                }

                ok( jQuery.isFunction(fn), "Recursive Function Call" );

                fn({ some: "data" });
        };

        callme(function(){
                callme(function(){});
        });
}

test();

</script>


</body>

function 工具集

function 工具集

经常会碰到这样的问题

  1. button.click 后,只调用一次方法
  2. window.scroll触发太频繁,导致滚动加载次数太多
  3. document.keyup次数太多,导致智能提示调用次数太多

你需要以下的方法来解决此类问题


$('a.load').on('click', $.once(function() {
     $('#tabs .first').load(url);
}));

$(window).on('scroll', $.throttle(function() {
     $('#rest').lazyload();    
}));

$(input).on('keyup', $.debounce(function() {
     $.suggest($(this).val())
}));

工具集源码如下:

$.bind = function(func, context) {
      var args = Array.prototype.slice.call(arguments, 2);
      return function() {
          return  func.apply(context, args);
      }
}

//只调用一次
$.once = function(func){
        var ran = false;
	return function() {
		if(ran) return;
		ran = true;
		func();
	}
}

//ms时间内只执行一次
$.throttle = function(func, ms) {
   ms = ms || 150;

   var last = +new Date();
   var timer ;
    return function() {
        var now = +new Date();
        if(now - last > ms) {
             timer = null;
             last = now;
             func();
        } else {
             timer = setTimeout(func, ms - (now-last));
        }
    }
}

//缓冲ms时间后才执行
$.debounce = function(func, ms) {
       ms = ms || 150;

       var timer;
       return function() {
           clearTimeout(timer);
           timer = setTimeout(func, ms);
      }
}

  • underscore, yui 中有throttle, debounce两个方法, jquery中没有发现

maven jetty 配置

jetty的maven插件配置(pom.xml)

  • scanIntervalSeconds 配置扫描时间时间, 如果有文件变动, 如pom.xml,web.xml或classes, 则jetty会重新加载应用, 相当方便
  • contextPath 配置ROOT, 即将web的上下文路径, 改为 /
  • 将jetty默认的uri编码, 改为GBK (jetty默认是utf8)
<plugin> 
    <groupId>org.mortbay.jetty</groupId> 
    <artifactId>maven-jetty-plugin</artifactId>
    <configuration>    
        <contextPath>/</contextPath>    
        <connectors>    
            <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
                <port>8080</port>    
            </connector>    
        </connectors>    
        <reload>automatic</reload>  
        <scanIntervalSeconds>3</scanIntervalSeconds>
        <systemProperties>
            <systemProperty>
	        <name>org.mortbay.util.URI.charset</name>
	        <value>GBK</value>
	    </systemProperty>
	 </systemProperties>
     </configuration> 
</plugin>

jetty的另一段配置(修改web.xml) 用于解决使用jetty后无法修改文件问题(错误描述:请求的操作无法在使用用户映射区域打开的文件上执行)

<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.mortbay.jetty.servlet.DefaultServlet</servlet-class>
    <init-param>
        <param-name>useFileMappedBuffer</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>0</load-on-startup>
</servlet>

修改mvn时的java运行时参数 (解决 PermGen space问题)

set MAVEN_OPTS=-XX:MaxPermSize=128M
mvn jetty:run

判断两个字符串的相似度(js)

问题来源于 http://weibo.com/1748374882/yavpCkc31

问题:有两个字符串 A 和 B,如何简单高效的判断 A 和 B 的相似度?A 和 B 完全一样,相似度为 100, A 和 B 完全不一样,相似度为 0. A 和 B 中有一半文字相同,相似度为 50. 依此类推。文字中包含中英文,空白字符也算。哪位用 JS 挑战下?性能第一,代码长短无所谓。

function compare(x, y) {
	var z = 0;
	var s = x.length + y.length;;

	x.sort();
	y.sort();
	var a = x.shift();
	var b = y.shift();

	while(a !== undefined && b !== undefined) {
		if (a === b) {
			z++;
			a = x.shift();
			b = y.shift();
		} else if (a < b) {
			a = x.shift();
		} else if (a > b) {
			b = y.shift();
		}
	}
	return z/s * 200;
}

console.log(compare(['123', '中文', 'hello'], ['123', '中文', 'hello']))
console.log(compare(['123', '中文', 'hello'], ['123', '中文', 'hello'].sort()))
console.log(compare(['123', '中文', 'hello'], ['123', '中文', 'hello'].reverse()))
console.log(compare(['123', '中文', 'hello','中2文'], ['12', '中2文', '123','中文3']))
console.log(compare(['123', '中文', 'hello'], ['中文', 'world', '456']))
console.log(compare(['123', '中3文', 'hello'], ['中文', 'world', '汉字']))

js的简单串行加载方案

写了一个超简单的串行加载工具, 不想引入框架时, 可以试用下

(function (all) {
	var callee = arguments.callee;
	all.shift()(function(data) {
		callee.call(null, all, data);
	});
})([
	function(next) {
		setTimeout(function() {
			document.body.innerHTML += '<li>first';

			next();
		}, 1000)
	}, function(next) {
		setTimeout(function() {
			document.body.innerHTML += '<li>second';

			next();
		}, 2000)
	}, function(next) {
		setTimeout(function() {
			document.body.innerHTML += '<li>third';

		}, 3000)
	}
]);


parseURL

我们知道, 在location对象中, 可以直接获取 hostname, search , hash 等参数 而链接的url中, 我们没有现成的API可以获取这些参数 下面是一个小方法, 可以得到链接中的各种参数

function parseURL(url) {
	var ret = {};
	ret.href = url;

	var match = url.match(/^([^\/:]+:)?(?:\/\/([^\/:]*)(?::([\d]+))?)?(\/?[^\?#]*)?(\?[^\#]*)?(#.*)?$/i);
	if (match) {
		ret.isValid = true;
		ret.protocol = match[1] || '';
		ret.hostname = match[2] || '';
		ret.port = match[3] || '';
		ret.host = ret.hostname + ret.port ? ':' : '' + ret.port;
		ret.pathname = match[4] || "/";
		ret.origin = ret.protocol + ret.host ? '//' : '' + ret.host;
		ret.search = match[5] || '';
		ret.hash = match[6] || '';
	} else {
		ret.isValid = false;
		ret.path = ret.url;
	}
	return ret;
}

硬盘里还能存点啥?

网速越来越快了, 需要存硬盘的东西越来越少了

  • 电影 没必要下载电影了, 随时随地观看在线电影,直播电视, 怀旧连续剧等 (pplive,youku)
  • 音乐 在线音乐,在线播放列表,在线mv,最新最热歌曲,各类频道 (ting, yinyuetai)
  • 电子书 不必把大量的电子书, 存放在硬盘里, 多是看一遍后, 就存下来了, 以后就很少翻了, 看着4.5个G的电子书, 删也不是, 不删也不是, 还是网络共享下, 大家共同学习. (w3school, open开源文档)
  • 书签 浏览器的书签,没必要备份在本地了, 也没有必要换电脑或重装系统时备份了, (chrome书签同步)
  • 安装程序 自己本地有个5G的TDDownload, 发现这些安装程序, 很少用, 且版本总是很旧, 或者多版本共存,该整理下了,换软件管理吧 (360软件管家)
  • 代码 自己的代码 or 别人的代码,还是用版本控制吧 (github,googlecode)
  • 工具包 java的jar包吗? 用maven吧,只记录地址 (maven)
  • 文档 自己的一些文档,压缩一下备份在网络上 (各类网盘)
  • 笔记 一些小碎文字,如cdkey,桌面上的小心得 (云笔记)
  • 游戏 小游戏,flash游戏,在线游戏等, 如果网络够快,将来的大型网游,也可随时下载
  • 驱动 驱动一般都有地方下载,如果用书签就更好了 (mydrivers 或 官网吧)
  • 其它 如各类backup, 等…

细数下来

  • 硬盘的功能, 越来越像一个cache了, 程序的cache,数据的cache,只是做个中转. ]* 硬盘大小不够时,可以随时删除,随时从服务端再获取
  • 将来我们的电脑,会不会就像ipad一样
    1. 在线商店里, 双击某个应用, 开始自动安装
    2. 自动将程序装在 D:\program files\
    3. 自动将数据装在 D:\Users\xx\Application Data , 并自动服务端同步
    4. 然后, 打开应用, 正常使用
    5. 使用过程中, 将配置,数据自动同步, 某些cache,找个大盘自动cache
    6. 不需要理会装在哪里,不需要理会数据备份问题,都由软件来解决吧
  • 这个就是传说中的app模式, ipad, 360桌面, Q+, win8, 似乎都有这个意向…

还有哪些会存在硬盘里呢:

  1. 个人私密文档,私密照片,不适宜共享 (包括私密电影?)
  2. 超大文件, 下载起来费事, 如游戏客户端,大型软件安装程序
  3. 其它

mini javascript loader

[size=medium] 自己写的一个mini 且 simple 的 javascript loader

功能特点

  • 异步并行载入js, 每个js只加载一次
  • 支持模块依赖, 未检测循环依赖
  • 支持别名
  • 兼容ie6+ , firefox, chrome …
  • API :
    define.alias[‘modName’] = ‘http://path/to/mods/root/dir’ define(deps, callback) 其中 callback(dep1, dep2, dep3…) require(deps, callback) 其中 callback(dep1, dep2, dep3…)

define = (function() {
	
	var modules = {};
	var modulesCallbacks = {};
	var alias = window.alias || {};

	function map(arr, callback) {
		var ret = [];
		for(var i = 0; i < arr.length; i++) {
			ret.push(callback(arr[i], i, arr));
		}
		return ret;
	}
	function isString(obj) {
		return typeof(obj) == 'string';
	}
	function isArray(obj) {
		return Object.prototype.toString.call(obj) == '[object Array]';
	}
	function isFunction(obj) {
		return typeof(obj) == 'function';
	}
	function mix(src, dst) {
		for(var key in dst) {
			src[key] = dst[key];
		}
	}
	function getScript(url, callback) {
		var node = document.createElement("script");
		node.async = true;
		if (document.addEventListener) {
			node.addEventListener('load', callback);
		} else {
			node.onreadystatechange = function () {
				if(node.readyState == 'loaded' || node.readyState == 'complete') {
					callback && callback();
				}
			}
		}
		node.src = url;

		var head = document.head || document.getElementsByTagName('head')[0] || document.documentElement;
		head.insertBefore(node, head.firstChild);
		return node;
	}

	function getCurrentScript() {
		
		//firefox4 and opera
		if (document.currentScript) {
			return document.currentScript;
		} else if (document.attachEvent) {
			//ie6-9 得到当前正在执行的script标签
			var scripts = document.getElementsByTagName('script');
			for (var i = scripts.length - 1; i > -1; i--) {
				if (scripts[i].readyState === 'interactive') {
					return scripts[i];
				}
			}
		} else {
			// 参考 https://github.com/samyk/jiagra/blob/master/jiagra.js
			// chrome and firefox4以前的版本
			var stack;
			try {
				makeReferenceError
			} catch (e) {
				stack = e.stack;
			}
			if (!stack)
				return undefined;
			// chrome uses at, ff uses @

			var e = stack.indexOf(' at ') !== -1 ? ' at ' : '@';
			while (stack.indexOf(e) !== -1) {
				stack = stack.substring(stack.indexOf(e) + e.length);
			}
			stack = stack.replace(/:\d+:\d+$/ig, "");

			var scripts = document.getElementsByTagName('script');
			for (var i = scripts.length - 1; i > -1; i--) {
				if (scripts[i].src === stack) {
					return scripts[i];
				}
			}
		}
	}

	function getScriptWithCache(url, callback) {
		var scripts = this._scripts = this._scripts || {};
		var cache = scripts;
		if (cache === true) {
			callback && callback();
			return ;
		} else if (isArray(cache)) {
			cache.push(callback);
			return ;
		} else {
			scripts = [callback];
		}

		getScript(url, function() {
			var callbacks = scripts || [];
			scripts = true;
			for(var i = 0; i < callbacks.length; i++) {
				callbacks[i] && callbacks[i]();
			}
		});		
	}

	function getModule(mod, callback) {
		if (!modules[mod]) {
			modulesCallbacks[mod] = modulesCallbacks[mod] || [];
			modulesCallbacks[mod].push(callback);

			getScriptWithCache(mod);
		} else {
			callback && callback(modules[mod]);
		}
	}

	function getModules(mods, callback) {
		if (mods == undefined || mods.length == 0) {
			callback && callback();
			return ;
		}
		var c = 0;

		var ret = [];
		map(mods, function(mod, i) {
			getModule(mod, function(obj) {
				ret[i] = obj;

				if (++c == mods.length) {
					callback && callback(ret);
				}
			});
		});
	}

	function define(deps, callback) {
		var container = this;

		if (!isString(deps) && !isArray(deps)) {
			callback = deps;
			deps = [];
		}
		var current = getCurrentScript();
		var url = current && current.src;

		deps = deps || [];
		if (isString(deps)) {
			deps = deps.split(',');
		}
		var allDeps = map(deps, tran);				//全部转换映射

		getModules(allDeps, function(mods) {
			var ret = isFunction(callback) ? callback.apply(undefined, mods||[]) : callback;
			modules = modules || {}
			mix(modules, ret);

			container != window && (mix(container, ret));

			if (modulesCallbacks) {
				for(var i = 0; i < modulesCallbacks.length; i++) {
					modulesCallbacks[i](ret);
				}
			}
		});

		return container;
	}

	function tran(dep) {
		for(var key in alias) {
			dep = dep.replace(new RegExp('^' + key), alias[key]);		//TODO
		}
		return dep + '.js';
	}

	define.alias = alias;
	return define;
})();

测试用例

//上述代码保存为 http://localhost/loader/loader.js


<!-- index.htm -->
<script src="loader.js"></script>

<script>
define.alias['ok'] = 'http://localhost/loader/';
define(['ok/mod1', 'ok/mod2'], function(Mod1, Mod2) {
	console.log('execute index ', Mod1, Mod2);
});
</script>


//   http://localhost/loader/mod1.js
require(['ok/mod2'], function(Mod2) {
	console.log('mod1 index ', Mod2);

	return {
		mod1: 'this is mod1'
	}
});


//   http://localhost/loader/mod2.js
define(function() {
	console.log('mod2 index ');

	return {
		'hello' : 'world'
	}
});

补: 其它用法

//mod3.js
//功能模块1
define({
    sayHello : function() {
       console.log('hello');
    }
});

//功能模块2, 模块2没支持!!!
define(function() {
    return {
        sayWorld : function() {
           console.log('world');
        }
    }
});




App = {define : define, require: define};
App.define.alias['ok'] = 'http://lhj/loader';
App.define({
      showMsg : function(msg) {
          console.log(msg);
      }
});

App.define(function(Mod3) {
      return {
          say : function() {
				App.require('ok/Mod3', function(Mod3) {
	                  return Mod3.sayHello() + ' ' + '';//Mod3.sayWorld();
				});
          }
      }
});

App.showMsg(123);
App.say();