如何用setInterval调用类的方法
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调用类的方法的更多相关文章
- Java动态调用类中方法
在Java中,调用类的方法有两种方式:对于静态方法可以直接使用类名调用,对于非静态方法必须使用类的对象调用.反射机制提供了比较另类的调用方式,可以根据需要指定要调用的方法,而不必在编程时确定.调用的方 ...
- C#通过反射调用类及方法
反射有个典型的应用,就是菜单的动态加载,原理就是通过反射调用某个窗体(类).下面演示一下通过反射调用类及方法: 1.新建一个类,命名为:ReflectionHelper,代码如下: #region 创 ...
- Spring事务调用类自己方法失效解决办法和原因
问题 正常情况下,我们都是在controller里调用service里的方法,这个方法如果需要加事务,就在方法上加上@Transactional,这样是没问题的,事务会生效. 可是如果像下面这样,绕以 ...
- C# 如何利用反射,将字符串转化为类名并调用类中方法
首先,先随便创建一个测试类 <span style="font-family:Microsoft YaHei;font-size:18px;">public class ...
- PHP使用字符串名称调用类的方法
<?php class Game { function Play($id) { echo "Playing game $id\n"; } } $game = new Game ...
- rutime中动态调用类的方法
Dynamically invoke a class method in Objective C 代码 #import <Foundation/Foundation.h> #import ...
- C#反射实例化类并调用类的方法
反射提高了程序的灵活性和扩展性,降低耦合性,提高自适应能力. 它允许程序创建和控制任何类的对象,无需提前硬编码目标类: SalBLL a = (SalBLL)Assembly.Load("B ...
- C#如何通过反射调用类下的方法
首先模拟一个mvc的项目,创建一个业务类(HomeService),在类下创建3个方法 public class HomeService { /// <summary> /// 无参方法 ...
- python: 面向对象:类和对象调用类中的变量和方法
一. 面向对象初识 我们在生活中做事都是面向过程的,前面实现一些基本逻辑功能代码也是用面向过程的语句实现的,后来学了函数,把这些功能又装到了函数里.但用面向过程的方法去写程序,只能实现一个功能,我们要 ...
随机推荐
- Hive优化之谓词下推
Hive优化之谓词下推 解释 Hive谓词下推(Predicate pushdown) 关系型数据库借鉴而来,关系型数据中谓词下推到外部数据库用以减少数据传输 基本思想:尽可能早的处理表达式 属于逻辑 ...
- CSS 转载
CSS介绍 CSS(Cascading Style Sheet,层叠样式表)定义如何显示HTML元素. 当浏览器读到一个样式表,它就会按照这个样式表来对文档进行格式化(渲染). CSS语法 CSS实例 ...
- Python的jieba模块简介
现如今,词云技术遍地都是,分词模块除了jieba也有很多,主要介绍一下jieba的基本使用 import jieba import jieba.posseg as psg from os import ...
- Nodejs模块初始化
模块初始化 一个模块中的JS代码仅在模块第一次被使用时执行一次,并在执行过程中初始化模块的导出对象.之后,缓存起来的导出对象被重复利用. 主模块 通过命令行参数传递给NodeJS以启动程序的模块被称为 ...
- java web项目使用ant编译将不同的功能代码打包成jar,进而分局点将项目打包成不同的tar.gz包进而部署
使用ant可以轻松的将一个项目分离代码,直接打包成不同需求的tar.gz包使用 1.build.properties (属性) version.num=1.0 #版本信息 2.build.xml (a ...
- 3,jieba gensim 最好别分家之最简单的相似度实现
简单的问答已经实现了,那么问题也跟着出现了,我不能确定问题一定是"你叫什么名字",也有可能是"你是谁","你叫啥"之类的,这就引出了人工智能 ...
- C++11中std::forward的使用
std::forward argument: Returns an rvalue reference to arg if arg is not an lvalue reference; If arg ...
- shell -- 大括号和小括号
在这里我想说的是几种shell里的小括号,大括号结构和有括号的变量,命令的用法,如下: 1.${var} 2.$(cmd) 3.()和{} 4.${var:-string},${var:+string ...
- cadence17.2的OrCAD启动找不到license的问题
1. cadence17.2的OrCAD每次启动都说找不到license 2. 提示是找不到licence,看下系统变量,licence的路径是在的 3. 估计是读取licence的路径的服务未开启, ...
- Java - 问题集 - 导出csv文件中文乱码
微软的excel文件需要通过文件头的bom来识别编码,所以写文件时,需要先写入bom头. FileOutputStream fos = new FileOutputStream(new File(&q ...