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. Hive优化之谓词下推

    Hive优化之谓词下推 解释 Hive谓词下推(Predicate pushdown) 关系型数据库借鉴而来,关系型数据中谓词下推到外部数据库用以减少数据传输 基本思想:尽可能早的处理表达式 属于逻辑 ...

  2. CSS 转载

    CSS介绍 CSS(Cascading Style Sheet,层叠样式表)定义如何显示HTML元素. 当浏览器读到一个样式表,它就会按照这个样式表来对文档进行格式化(渲染). CSS语法 CSS实例 ...

  3. Python的jieba模块简介

    现如今,词云技术遍地都是,分词模块除了jieba也有很多,主要介绍一下jieba的基本使用 import jieba import jieba.posseg as psg from os import ...

  4. Nodejs模块初始化

    模块初始化 一个模块中的JS代码仅在模块第一次被使用时执行一次,并在执行过程中初始化模块的导出对象.之后,缓存起来的导出对象被重复利用. 主模块 通过命令行参数传递给NodeJS以启动程序的模块被称为 ...

  5. java web项目使用ant编译将不同的功能代码打包成jar,进而分局点将项目打包成不同的tar.gz包进而部署

    使用ant可以轻松的将一个项目分离代码,直接打包成不同需求的tar.gz包使用 1.build.properties (属性) version.num=1.0 #版本信息 2.build.xml (a ...

  6. 3,jieba gensim 最好别分家之最简单的相似度实现

    简单的问答已经实现了,那么问题也跟着出现了,我不能确定问题一定是"你叫什么名字",也有可能是"你是谁","你叫啥"之类的,这就引出了人工智能 ...

  7. C++11中std::forward的使用

    std::forward argument: Returns an rvalue reference to arg if arg is not an lvalue reference; If arg ...

  8. shell -- 大括号和小括号

    在这里我想说的是几种shell里的小括号,大括号结构和有括号的变量,命令的用法,如下: 1.${var} 2.$(cmd) 3.()和{} 4.${var:-string},${var:+string ...

  9. cadence17.2的OrCAD启动找不到license的问题

    1. cadence17.2的OrCAD每次启动都说找不到license 2. 提示是找不到licence,看下系统变量,licence的路径是在的 3. 估计是读取licence的路径的服务未开启, ...

  10. Java - 问题集 - 导出csv文件中文乱码

    微软的excel文件需要通过文件头的bom来识别编码,所以写文件时,需要先写入bom头. FileOutputStream fos = new FileOutputStream(new File(&q ...