论坛首页 Web前端技术论坛

【JS优化系列】从一个计时器的写法探讨js多种实现方式的优劣

浏览 15705 次
精华帖 (7) :: 良好帖 (5) :: 新手帖 (3) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-08-15   最后修改:2009-08-21

      前几天帮erricwang写一个js计时器,发现可以用有很多种方式实现相同的功能。现将它写下来,和大家一起探讨分析比较。

 

实现一:

<body>
<div id="time1">time1</div>
<div id="time2">time2</div>
</body>
<script>
function Timer(id){
	this.id = id;
	this.begin = function(count){
		this.show(this.id, count)();
		setInterval(this.show(this.id, count-1),1000);
	}
	this.show = function(id, count){
		return function(){
			document.getElementById(id).innerHTML = count<0 ? "over" :count;
			count--;
		}
	}
} 

t1 = new Timer("time1");
t1.begin(50);

t2 = new Timer("time2");
t2.begin(10);
</script>

 

   且看第一种实现,显然这里有一个很不好的地方,计时完成之后,setInterval没有被clear,严重影响了性能。

 

实现二:

function Timer(id){
	this.id = id;
	this.timer = null;
	this.count = 0;
	this.begin = function(count){
		this.count = count;
		this.show(this)();
		this.timer = setInterval(this.show(this),1000);
	}
	this.show = function(obj){	
		return function(){
			if(obj.count < 0){
				document.getElementById(obj.id).innerHTML = "over";
				clearInterval(obj.timer);
				return ;
			}
			document.getElementById(obj.id).innerHTML = obj.count;
			obj.count--;
		}
	}
} 

       实现二解决了实现一的setInterval未被clear的问题,看上去也很“完美”,且看下面:

alert(t1.show == t2.show) //结果false
alert(t1.begin == t2.begin) //结果false

      很明显这里Timer每个实例都复制了一份show和begin,二不是共享同一份的,所以t1.showt和2.show所指向的并非同一份实现,这样对性能多少会有些影响。

 

实现三:

function Timer(id){
	this.id = id;
	this.timer = null;
	this.count = 0;
	this.begin = function(count){
		this.count = count;
		Timer.show(this)();
		this.timer = setInterval(Timer.show(this),1000);
	}
	Timer.show = function(obj){	
		return function(){
			if(obj.count < 0){
				document.getElementById(obj.id).innerHTML = "over";
				clearInterval(obj.timer);
				return ;
			}
			document.getElementById(obj.id).innerHTML = obj.count;
			obj.count--;
		}
	}
} 

      这里实现了让所有实例共享一份show函数:

alert(t1.begin == t2.begin) //结果true 

 

实现四:

 

function Timer(id){
	this.id = id;
	this.timer = null;
	this.count = 0;
	this.begin = function(count){
		this.count = count;
		this.show(this)();//注意和实现三的区别:这里不是Timer.show(this)();
		this.timer = setInterval(this.show(this),1000);//注意和实现三的区别:这里不是Timer.show(this)();
	}
} 
Timer.prototype.show = function(obj){	
	return function(){
		if(obj.count < 0){
			document.getElementById(obj.id).innerHTML = "over";
			clearInterval(obj.timer);
			return ;
		}
		document.getElementById(obj.id).innerHTML = obj.count;
		obj.count--;
	}
}

      实现三和实现四很有意思:二者都实现了让所有实例共享一份show方法。

      区别是:实现三show作为Timer对象的一个属性,这里有点类似与java的静态方法,show方法是属于Timer对象本身的,而不是其实例(t1、t2...)的,所以需要用Timer.show(..)来调用;而实现四采用了“原型”prototype方式来实现,这里show方法不是Timer对象本身,而是其实例(t1、t2...)的,所以使用this.show(..)或者t1.show(...)来调用,这里很有意思“原型”创建的属性是属于其实例的,而且所有实例共享同一份实现(不知道我这里的理解是否正确,欢迎大家拍砖)。这多少有些让学java等面向对象语言的人难以理解,而这也正式js有意思的地方。

      那么这两种实现方式那种更优呢?欢迎各位讨论。

 

实现五:

 

function Timer(id){
	this.id = id;
	this.timer = null;
	this.count = 0;

}
Timer.prototype.begin = function(count){
	this.count = count;
	this.show(this)();//注意这里不是Timer.show(this)();
	this.timer = setInterval(this.show(this),1000);//注意这里不是Timer.show(this)();
}
Timer.prototype.show = function(obj){	
	return function(){
		if(obj.count < 0){
			document.getElementById(obj.id).innerHTML = "over";
			clearInterval(obj.timer);
			return ;
		}
		document.getElementById(obj.id).innerHTML = obj.count;
		obj.count--;
	}
} 

      这里将begin也使用原型实现,可能这种写法让你看起来不太舒服,那我们换一种。

 

实现六:

 

function Timer(id){
	this.id = id;
	this.timer = null;
	this.count = 0;
}
Timer.prototype = {
	begin : function(count){
		this.count = count;
		this.show(this)();//注意这里不是Timer.show(this)();
		this.timer = setInterval(this.show(this),1000);//注意这里不是Timer.show(this)();
	},
	show : function(obj){	
		return function(){
			if(obj.count < 0){
				document.getElementById(obj.id).innerHTML = "over";
				clearInterval(obj.timer);
				return ;
			}
			document.getElementById(obj.id).innerHTML = obj.count;
			obj.count--;
		}
	} 
}

      或者,再换一种。

 

实现七:

 

function Timer(id){
	this.id = id;
	this.timer = null;
	this.count = 0;
	Timer.prototype.begin = function(count){
		this.count = count;
		this.show(this)();//主要这里不是Timer.show(this)();
		this.timer = setInterval(this.show(this),1000);//主要这里不是Timer.show(this)();
	}
	Timer.prototype.show = function(obj){	
		return function(){
			if(obj.count < 0){
				document.getElementById(obj.id).innerHTML = "over";
				clearInterval(obj.timer);
				return ;
			}
			document.getElementById(obj.id).innerHTML = obj.count;
			obj.count--;
		}
	} 
}

       这方式,看起来是不是更优雅一些呢。以上都采用面向对象的方式来实现的。那我们是否还可以采用其他方式实现呢? 

 

实现八:

var Timer = {
	begin : function(id,count){
		var obj = {};
		obj["id"] = id;
		obj["count"] = count;
		Timer.show(obj)();
		obj["timer"] = setInterval(Timer.show(obj),1000);//注意这里不是Timer.show(this)();
	},
	show : function(obj){	
		return function(){
			if(obj["count"] < 0){
				document.getElementById(obj["id"]).innerHTML = "over";
				clearInterval(obj["timer"]);
				return ;
			}
			document.getElementById(obj["id"]).innerHTML = obj["count"] ;
			obj["count"]--;
		}
	}
}

Timer.begin("time1", 30);
Timer.begin("time2", 60);

       这里采用了对象字面量的方式来实现的。对象字面量其实是一种单例模式,用在这里其实很变扭,本文这里引入只是引入一种js实现方式而已,仅供大家探讨。下一例也一样。

 

实现九:

 

var Timer = (function(){
	var items = {};
	function begin(id,count){
		var obj = {};
		obj["id"] = id;
		obj["count"] = count;
		Timer.show(obj)();
		obj["timer"] = setInterval(Timer.show(obj),1000);//注意这里不是Timer.show(this)();
		Timer.items[id] = obj;
	};
	function show(obj){	
		return function(){
			if(obj["count"] < 0){
				document.getElementById(obj["id"]).innerHTML = "over";
				clearInterval(obj["timer"]);
				return ;
			}
			document.getElementById(obj["id"]).innerHTML = obj["count"] ;
			obj["count"]--;
		}
	}
	return {
		items : items,
		begin : begin,
		show : show
	}
})()

Timer.begin("time1", 30);
Timer.items["time1"]["count"] = 80;//重新从80开始计时
Timer.begin("time2", 60);

       这里其实也是采用的对象字面量的方式来实现的,只是采用了闭包而已,应用闭包可以真正的实现属性私有化(不过这里没有体现出来)。这里还加了items属性,让其所有实例保存起来,以便后面还可以调用。

 

      平常用的比较多的还有最后一种闭包结合对象字面量的方式(不过在这个场景个人觉得用面向对象方式来实现更好一些)。对象字面量的方式其实也是js的单例模式,在js里应用很广泛,不过在这里应用好像不太合适,看这里的最后两个实现都觉得很牵强。

      从一个小小的计时器,可以衍生出如此多的方式来实现,真让人惊叹于js表现形式之丰富。如此众多实现方式,都有其优点和缺点,在不同的场合也有不同的应用。这里只是表达了一下个人的理解和观点,欢迎大家一起讨论js各种实现方式的优缺点及其适用场合。也欢迎拍砖,或者提供更好的实现方法。

 

 

【本人发帖抛砖引玉,希望能够引出更多的“玉”来,希望所写的每一段代码都能够得到一种最“优雅”的实现方式。以后本人会抛出更多的“砖”,希望能引来更多的“玉”,以供大家一起学习进步】

 

-------------------------------------------------------2009.08.17------------------------------------------------------------

 

到目前为止,个人觉得3楼zbm2001 的实现最优,让我学到了不少,希望能有更多的牛人来提供更优的实现。

function Timer(id){
	this.element = document.getElementById(id);
	this.timer = null;
	this.count = 0;
}
Timer.prototype = {
	begin : function(count){
		this.count = count;
		this.show();
		var _this = this;
		this.timer = setInterval(function(){_this.show();}, 1000);
	}
	,
	show : function(){
		this.element.innerHTML = this.count < 0 ? clearInterval(this.timer) || "over" : this.count--;
	} 
}

 

 

-------------------------------------------------------2009.08.21------------------------------------------------------------

继续收集评论中的实现方式,优劣请读者自评

janpoem

var Timer = function(id) {
	
	var _step = 500, _count = 0, _ticktark = 0, _element = document.getElementById(id);
	
	function __clear() {
		if (_ticktark != null) clearInterval(_ticktark);
	}
	
	return {
		
		begin: function(count, step) {
			if (_element && count > 0) {
				// 看看起始值多少,主要看看有没有被污染
				console.log('on start:', 'count:', _count, 'step:', _step);
				__clear();
				_step = step;
				_count = count;
				// 再看看
				console.log('on set:', 'count:', _count, 'step:', _step);
				_ticktark = setInterval(this.show, _step)
			}
			return this;
		},
		
		show: function() {
			_element && (_element.innerHTML = _count > 0 ? _count-- : 'over');
			if (_count <= 0) {
				console.log(_count);
				__clear();
			}
			return this;
		}
	}
}	

Timer('time1').begin(20, 100);
Timer('time2').begin(30, 200);

 

lifesinger

function Timer(id) {
    this.container = document.getElementById(id);
}

Timer.prototype = {

    constructor: Timer,

    begin: function(count) {
        var container = this.container;
        setTimeout(function() {
            container.innerHTML = count > 0 ? count-- : "over";
            if(count + 1) {
                setTimeout(arguments.callee, 1000);
            }
        }, 1000);
    }
    
};

new Timer("time1").begin(10);
 

 

 

   发表时间:2009-08-15  
第七种方法

Timer.prototype = {  
6.        begin : function(count){  
7.            this.count = count;  
8.            this.show(this)();//注意这里不是Timer.show(this)();  
9.            this.timer = setInterval(this.show(this),1000);//注意这里不是Timer.show(this)();  
10.        }  
11.        show : function(obj){     
12.            return function(){  
13.                if(obj.count < 0){  
14.                    document.getElementById(obj.id).innerHTML = "over";  
15.                    clearInterval(obj.timer);  
16.                    return ;  
17.                }  
18.                document.getElementById(obj.id).innerHTML = obj.count;  
19.                obj.count--;  
20.            }  
21.        }  
22.    }  


这个超大的对象怎么能写到构造函数里啊,虽然没产生多余的副本,但是被构造2次也是非常不爽的.
0 请登录后投票
   发表时间:2009-08-15   最后修改:2009-08-16
ls说的很有道理,一针见血,好像确实每次实例化都会被构造一次,带来比较差的性能,欢迎继续拍砖。
0 请登录后投票
   发表时间:2009-08-16   最后修改:2009-08-16
function Timer(id){
	this.element = document.getElementById(id);
	this.timer = null;
	this.count = 0;
}
Timer.prototype = {
	begin : function(count){
		this.count = count;
		this.show();
		var _this = this;
		this.timer = setInterval(function(){_this.show();}, 1000);
	}
	,
	show : function(){
		this.element.innerHTML = this.count < 0 ? clearInterval(this.timer) || "over" : this.count--;
	} 
}
0 请登录后投票
   发表时间:2009-08-16  
zbm2001 写道
function Timer(id){
	this.element = document.getElementById(id);
	this.timer = null;
	this.count = 0;
}
Timer.prototype = {
	begin : function(count){
		this.count = count;
		this.show();
		var _this = this;
		this.timer = setInterval(function(){_this.show();}, 1000);
	}
	,
	show : function(){
		this.element.innerHTML = this.count < 0 ? clearInterval(this.timer) || "over" : this.count--;
	} 
}

这里setInterval没有被clear
0 请登录后投票
   发表时间:2009-08-16   最后修改:2009-08-16
谁告诉你的?当然你也可以清空一下定时器
this.timer = null;

设计模式的问题,二楼已给出提示,你也注意到了,不再赘述。

我给的一些修改,只是顺带提醒一下javascript编程中的需要注意的地方:

1.节约使用DOM获取元素,尽可能的避免重复获取;
将DOM元素保存在实例对象的hash中。

2.巧用javascript表达式/运算符和内置的类型转换,为程序提速,同时也使得代码内敛;
this.element.innerHTML = this.count < 0 ? clearInterval(this.timer) || "over" : this.count--;
三元表达式 + 短路运算

3.合理利用闭包。
var _this = this; 
        this.timer = setInterval(function(){_this.show();}, 1000);
每次执行只需获取一个实例的环境变量this,而不是每次通过返回一个新的函数来获取变量(实例对象)
0 请登录后投票
   发表时间:2009-08-16   最后修改:2009-08-16
另外,如果你偏爱传统的面向对象的设计模式,习惯将方法打包在类中,可以初始化定义一下:

function Timer(id){ 
    this.id = id; 
    this.timer = null; 
    this.count = 0; 
if(Timer.prototype.initialization !== true){
Timer.prototype.initialization = true;
    Timer.prototype.begin = function(count){ 
        this.count = count; 
        this.show(this)();//主要这里不是Timer.show(this)(); 
        this.timer = setInterval(this.show(this),1000);//主要这里不是Timer.show(this)(); 
    } 
    Timer.prototype.show = function(obj){    
        return function(){ 
            if(obj.count < 0){ 
                document.getElementById(obj.id).innerHTML = "over"; 
                clearInterval(obj.timer); 
                return ; 
            } 
            document.getElementById(obj.id).innerHTML = obj.count; 
            obj.count--; 
        } 
    }
}  

0 请登录后投票
   发表时间:2009-08-16  
zbm2001 写道
谁告诉你的?当然你也可以清空一下定时器
this.timer = null;

设计模式的问题,二楼已给出提示,你也注意到了,不再赘述。

我给的一些修改,只是顺带提醒一下javascript编程中的需要注意的地方:

1.节约使用DOM获取元素,尽可能的避免重复获取;
2.巧用javascript表达式/运算符和内置的类型转换,为程序提速,同时也使得代码内敛;
3.合理利用闭包。


代码很到位,欢迎继续拍砖!javaeye果然人才出没,欢迎继续提供更优雅的实现方式!
0 请登录后投票
   发表时间:2009-08-16  
ls说的也很中肯。在此引入闭包,只是引入js的一种常用实现方式而已,如果用在本例确实牵强。闭包更多的是实现真正的私有的作用,而闭包可能带来的内存泄露等问题确实值得注意。
0 请登录后投票
   发表时间:2009-08-16  
楼上2为对js真是研究透彻啊~!
0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics