setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式。setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。

近日在编写程序时发现调用setInterval传入的方法时,在程序间隔调用时无法获取正确的类方法,究其原因是由于JS灵活的this指针重绑定导致的。那么this指针为什么会重绑定,又重绑定到哪里去了呢?setInterval又做了些什么 工作呢?

大家都知道JS的类是通过function模拟出来的,并不是真正面向对象的类,那么就要从这里说起了。JS模拟类的方式大致分为3中形式:第一种是HW开发人员惯用的,原型扩展方式模拟类;第二种是匿名对象扩展模拟类;第三种就是通过Function动态方式创建类,这种方式可mapping到面向对象语言中的反射机制,有异曲同工之处,当然今天要讲的重点不是这个,回到我们的主题。要了解this指针,首先我们来看个例子:

function getName(){
return this.name;
}
function Person(){
this.name = "sparon";
}
Person.prototype.sayName = getName;
var people = new Person();
people.sayName();
result:
sparon

这个例子说明this指针是晚绑定的,而并不是程序初始化时绑定的。在《JAVASCRIPT高级程序设计》一书中"7.2.2关于this对象" 一节中指出:"this对象是在运行时给予函数的执行环境绑定的"。this作为全局唯一特殊指针存在,this作为类内部调用标志,当我们处于类外部调用this指针访问类内部成员时是无效的。

好了,在明确这点后我们在回过头来讲讲setInterval这个函数,其实准确的说setInterval是widnow对象的一个方法,window对象也是一个特殊的对象,以后有机会再慢慢讲来。

语法:setInterval(code,millisec[,"lang"])

  • code          :        必需。要调用的函数或要执行的代码串。
  • millisec:   必须。周期性执行或调用 code 之间的时间间隔,以毫秒计。

后面的参数基本用不到,这里就不作解释了。

在这里code可以是一个函数名,可以是函数名字符串,同时也可以是一段代码,或一个匿名函数,这就是JS语言的灵活之处。milisec当然就是调用间隔时间了,下面就来说说这个code参数。

先来看看我早起的调用,大家看看问题出在哪里,并想一下在gameStart 和gameLoop中的this指针分别是指向那个对象,是不是同一个对象,如果不是为什么。

function SPARONGame(){
this.name = "DotaAllStar";
this.player_Num = 10;
this.game_init = false;
this.game_start = false;
this.game_speed = 100;
}
SPARONGame.prototype.init = function() {
// Todo init game...
$("#start").click(this.gameStart);
this.game_init = true;
}
SPARONGame.prototype.gameStart = function() {
if (!this.game_init) { // 这里调用this.game_init会存在问题,大家想想问题出在哪里?这里this又指向何处?
return;
}
this.game_start = true;
setInterval(this.gameLoop, this.game_speed);
}
SPARONGame.prototype.gameLoop = function() {
if (!this.game_start){ // 这里调用this.game_start会存在问题,大家想想问题出在哪里?这里this又指向何处?
return;
}
}

在这段代码中最关键的问题在于作用域问题,要解决这个问题首先要了解this指针是指向何处?首先gameStart中的this是指向document对象的,因为当执行$("#start").click(this.gameStart);时其实click是将gameStart作为函数来处理了,所以当用户点击页面的start标签时,JS将gameStart函数绑定到了document对象下。而gameLoop中的this是指向window的,原理相同,至于为什么,这就要回顾到我前面的示例了,在之前讲过this指针是动态晚绑定的。

那么在知道这点后,我怎么来修正呢?这里就要用到JS的命名空间了,正是由于this是晚绑定的,而在gameStart和gameLoop中将对象方法传递给事件处理函数时,JS无法动态的去解析或者说变更this为指向的对象,从而导致了在后期调用该参数是,this被重绑定。这里有很多解决方案,之前在网上看到一篇文章是将this对象通过eval函数设置到window对象下,这样全局可访问,到调用方法内部再将其取回即可正常使用,还有一种方法很简单,就是利用变量存储指针引用,然后在匿名函数中通过变量调用方法,第三种就是我所讲到的解决方案,通过命名空间定位。

在JS中点(.)通常表示类.方法/字段/…,但可以从另一方面来理解,点操作符时间上是通过命名空间的方式模拟除了面向对象的功能,我们其实可以将点操作符作为命名空间的路径标志,这样,当我们传送一个具体实例类的时候JS解释器就能够方便的找到我们所要的方法,并且知道该方法在具体那个实例下,好了,下面我们来体会一下,我将上面的例子稍作变动即可成功。

var SPARONGame = {
name : "DotaAllStar",
player_Num : 10,
game_init : false,
game_start : false,
game_speed : 100,
init : function() {
// Todo init game...
$("#start").click(this.gameStart);
this.game_init = true;
},
gameStart : function(){
if (!SPARONGame.game_init) {
return;
}
SPARONGame.game_start = true;
setInterval("SPARONGame.gameLoop()", SPARONGame.game_speed);
},
gameLoop : function() {
if (!this.game_start){
return;
}
}
}

在这段代码中需要注意的是setInterval的第一个参数是字符串,而不是类名.方法名,否则最后达到的效果仍然是将其作为函数传入。

在解决以上问题后,最后再简单的提下setInterval中的两个小BUG,虽然很微妙,但是一定要知道,否则在编写程序处理边界问题时就会出现大问题了。1.某些间隔会被跳过;2.多个定时器的代码执行之间的间隔可能会比预期的小,关于这两个问题的详细解答可参考《JavaScript高级程序设计》的"18.2.1 重复的定时器"一节。

如何用setInterval调用类的方法的更多相关文章

  1. Java动态调用类中方法

    在Java中,调用类的方法有两种方式:对于静态方法可以直接使用类名调用,对于非静态方法必须使用类的对象调用.反射机制提供了比较另类的调用方式,可以根据需要指定要调用的方法,而不必在编程时确定.调用的方 ...

  2. C#通过反射调用类及方法

    反射有个典型的应用,就是菜单的动态加载,原理就是通过反射调用某个窗体(类).下面演示一下通过反射调用类及方法: 1.新建一个类,命名为:ReflectionHelper,代码如下: #region 创 ...

  3. Spring事务调用类自己方法失效解决办法和原因

    问题 正常情况下,我们都是在controller里调用service里的方法,这个方法如果需要加事务,就在方法上加上@Transactional,这样是没问题的,事务会生效. 可是如果像下面这样,绕以 ...

  4. C# 如何利用反射,将字符串转化为类名并调用类中方法

    首先,先随便创建一个测试类 <span style="font-family:Microsoft YaHei;font-size:18px;">public class ...

  5. PHP使用字符串名称调用类的方法

    <?php class Game { function Play($id) { echo "Playing game $id\n"; } } $game = new Game ...

  6. rutime中动态调用类的方法

    Dynamically invoke a class method in Objective C 代码 #import <Foundation/Foundation.h> #import ...

  7. C#反射实例化类并调用类的方法

    反射提高了程序的灵活性和扩展性,降低耦合性,提高自适应能力. 它允许程序创建和控制任何类的对象,无需提前硬编码目标类: SalBLL a = (SalBLL)Assembly.Load("B ...

  8. C#如何通过反射调用类下的方法

    首先模拟一个mvc的项目,创建一个业务类(HomeService),在类下创建3个方法 public class HomeService { /// <summary> /// 无参方法 ...

  9. python: 面向对象:类和对象调用类中的变量和方法

    一. 面向对象初识 我们在生活中做事都是面向过程的,前面实现一些基本逻辑功能代码也是用面向过程的语句实现的,后来学了函数,把这些功能又装到了函数里.但用面向过程的方法去写程序,只能实现一个功能,我们要 ...

随机推荐

  1. python基础之初识

    一. 计算机是什么 基本组成: 主板+cpu+内存 cpu: 主频, 核数(16) 内存:大小(8G, 16G, 32G) 型号: DDR3, DDR4, DDR5, 主频(海盗船,玩家国度) 显卡: ...

  2. 阿里云提醒 网站被WebShell木马后门的处理过程

    昨晚凌晨收到新客户的安全求助,说是阿里云短信提示,网站有webshell木马文件被植入,我们SINE安全公司立即成立,安全应急响应小组,客户提供了阿里云的账号密码,随即登陆阿里云进去查看到详情,登陆云 ...

  3. Kubernetes-Envoy(一种全新的Ingress实现方式)

    Ingress 在讲Envoy之前,先介绍一下Kubernetes中Service的表现形式为IP:Port,及工作在Ingress:TCP/IP层.而对于基于HTTP的服务来说,不同的URL地址经常 ...

  4. 13 ThreadLocal

    ThreadLocal 在多线程环境下,每个线程都有自己的数据.一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁. 1. 使用函数 ...

  5. Django信号的使用

    https://www.cnblogs.com/renpingsheng/p/7566647.html

  6. VS2010安装MVC3出错

             开始已经在电脑上安装了VS2010以及SP1,还装了MVC4的相关升级包.最后项目中又要用MVC3,然后又去安装MVC3的安装包,但是在安装的过程就出现了问题.一直安装不成功,最后在 ...

  7. iOS下原生与JS交互(总结)

    iOS开发免不了要与UIWebView打交道,然后就要涉及到JS与原生OC交互,今天总结一下JS与原生OC交互的两种方式. JS调用原生OC篇(我自己用的方式二,简单方便) 方式一 第一种方式是用JS ...

  8. gitk中文乱码问题处理

    执行了 git config --global gui.encoding utf- 查看 %USERPROFILE%\.gitconfig 文件中也有 [gui] encoding = utf-8 在 ...

  9. 设置socket接收和发送超时的一种方式

    Linux环境设置Socket接收和发送超时: 须如下定义:struct timeval timeout = {3,0};  //设置发送超时setsockopt(socket,SOL_SOCKET, ...

  10. XPATH之normalize-space(.)和normalize-space(text())区别

    normalize,字面意思就是正规化,加上space大概意思就是空格的处理了. 官方解释是这样的: 通过去掉前导和尾随空白并使用单个空格替换一系列空白字符,使空白标准化.如果省略了该参数,上下文节点 ...