Javascript函数返回值及定时器基础
概念
函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。
js
支持两种函数:一类是语言内部的函数(如eval()
),另一类是自己创建的。
在 JavaScript
函数内部声明的变量(使用 var)是局部变量,所以只能在函数内部访问它。(该变量的作用域是局部的)。
您可以在不同的函数中使用名称相同的局部变量,因为只有声明过该变量的函数才能识别出该变量。
函数调用
有如下四种调用js
函数的方式:
- 作为函数
- 作为方法
- 作为构造函数
- 通过
call()
和apply()
返回函数的函数
1. 当函数无明确返回值时,返回的值就是`undefined`。
2. 当函数有返回值时,返回值是什么就返回什么。
我们可以通过使用 return
语句实现将函数返回调用它的地方。
在使用 return
语句时,函数会停止执行,并返回指定的值。
函数通常会返回一个唯一值,那么这个值也可能是另一个函数:
<script type="text/javascript">
//函数表达式
var box = function(){
var a=1;
return function(){
alert(a++)
}
alert(a);//永远不会执行
}
alert(box());//弹出"function(){alert(a++)}"
</script>
在这里,我们只需将返回值赋值给某个变量,然后就可以像使用一般函数那样调用它了:
<script type="text/javascript">
var box = function(){
var a=1;
return function(){
alert(++a)
}
}
var newFunc = box();
newFunc();//2
</script>
如果想让返回的函数立即执行,亦可以使用box()()
来执行这段代码。
ECMAScript
所有函数的参数都是按值传递的,言下之意就是参数不会按引用传递。
PS:如果存在按引用传递的话,那么函数里的那个变量将会是全局变量,在外部也可以访问。
(1)值类型:数值、布尔值、null、undefined。
(2)引用类型:对象、数组、函数。
引用类型值
:指的是那些保存在堆内存中的对象,意思是,变量中保存的实际上只是一个指针,这个指针执行内存中的另一个位置,由该位置保存对象;
创建匿名函数
function(){
return ‘hi’; //单独的匿名函数是无法运行的,就算能运行也无法调用,因为没有名字
}
这种匿名函数的用法在JQuery
中非常多。直接声明一个匿名函数,立即使用。用匿名函数的好处就是省得定义一个用一次就不用的函数,而且免了命名冲突的问题,js
中没有命名空间的概念,因此很容易函数名字冲突,一旦命名冲突以最后声明的为准。
在javascript
语言里任何匿名函数都是属于window
对象。在定义匿名函数时候它会返回自己的内存地址,如果此时有个变量接收了这个内存地址,那么匿名函数就能在程序里被使用了,因为匿名函数也是在全局执行环境构造时候定义和赋值,所以匿名函数的this
指向也是window
对象
(function(){
console.log(this === window);//true
})();
通过自我执行来执行匿名函数:
//通过自我执行来执行匿名函数
<script type="text/javascript">
(function (){ // (匿名函数)();第一圆括号放匿名函数,第二个圆括号执行
alert('Lee');
})();
</script>
把匿名函数自我执行的返回值赋给变量:
//把匿名函数自我执行的返回值赋给变量
<script type="text/javascript">
var box = (function (){
alert('Lee');
})(); //弹出”Lee”;
alert(box); //弹出 undefined
</script>
var box= (function () {
return 'hi';
})();
console.log(box);//hi
自我执行匿名函数的传参:
//自我执行匿名函数的传参
<script type="text/javascript">
(function (age){
alert(age);
})(100); //弹出100
</script>
自执行函数的三种写法
var result = function (){
alert(2);
}();
另一种语法也可得到同样结果:
var result = (function () {
console.log(2);
})();
将函数返回值分配给变量:
var result = (function () {
return 2;
}());
js
创建动态函数:
js
支持创建动态函数,动态函数必须用Function
对象来定义(Function是js
中的一个对象,是固定不变的,规定Function
对象的"F"必须大写,当是function
的时候,我们知道是定义函数的时候所使用的一个关键字:function funName(x,y)
,当是Function
的时候(F大写的时候),我们知道是js
中的对象)
创建动态函数的基本格式:var 变量名 = new Function("参数1","参数2","参数n","执行语句");
看下面的一段代码:
<script type="text/javascript">
var square = new Function ("x","y","var sum ; sum = x+y;return sum;");
alert("square(2,3)的结果是:"+square(2,3)); //square(2,3)的结果是:5
</script>
square
是动态创建的函数,在Function
对象后面的括号里的每一部分内容都必须是字符串形式的,也就是说都必须用引号(""或者是'')括起来
这段代码:
var square = new Function ("x","y","var sum ; sum = x+y;return sum;");
和下面这段代码:
//函数声明
function square (x,y){
var sum;
sum = x+y;
return sum;
}
是一摸一样的,只不过一个是动态函数
,一个是静态函数
。
我们为什么要把代码分成一小段一小段的代码呢?,把一个字符串分成了若干个独立的字符串的优点就在于我们可以通过修改其中的某些字符串来随时改变函数的作用。
回调函数
回调就是一个函数的调用过程。那么就从理解这个调用过程开始吧。函数a有一个参数,这个参数是个函数b,当函数a执行完以后执行函数b。那么这个过程就叫回调。
其实中文也很好理解:回调,回调,就是回头调用的意思。函数a的事先干完,回头再调用函数b。
这里必须清楚一点:函数b是你以参数形式传给函数a的,那么函数b就叫回调函数。
在jquery里的绝大多数效果函数都涉及到callback函数。jquery效果函数
例如:
<script type="text/javascript">
$("div").show(1000,function(){
//callback function
});
</script>
这里的callback function
换成实例可以是:
<script type="text/javascript">
$("div").show(1000,function(){
console.log("hello world")
});
</script>
Callback
实际上是,当一个函数执行完后,现执行的那个函数就是所谓的callback
函数。怎么样?很好理解吧……
方法和函数的区别
var arr = [1,2,3,4,5]
var a =12; // 变量:自由的
arr.a= 5; //属性:属于一个对象
function show() //函数:自由的
{
alert(‘a’);
}
arr.fn = function() //方法:属于一个对象
{
alert(‘b’);
}
其实方法就是函数,只不过方法是有所属的对象。
我们所熟知的,将函数绑定到 click
事件
语法:
$(selector).click(function)
参数 | 描述 |
---|---|
function | 可选。规定当发生 click 事件时运行的函数. |
这种形式在jquery
中经常见到。它是将function
当做该方法的参数,向该方法添加一个事件处理函数。
js全局函数
全局函数与内置对象的属性或方法不是一个概念。全局函数它不属于任何一个内置对象。
JavaScript 中包含以下 7 个全局函数,用于完成一些常用的功能:
escape( )、eval( )、isFinite( )、isNaN( )、parseFloat( )、
parseInt( )、unescape( )。
函数的几个作用
作为一个类构造器使用
function Class(){}
Class.prototype={};
var item=new Class();
作为闭包使用
(function(){
//独立作用域
})();
作为构造函数调用
所谓构造函数,就是通过这个函数生成一个新对象(object)。
<script type="text/javascript">
function Test(){//大写,以区分普通函数
this.x = 10;
}
var obj = new Test();
alert(obj.x); //弹出 10;
</script>
可以使用 new
运算符结合像 Object()
、Date()
和 Function()
这样的预定义的构造函数来创建对象并对其初始化。面向对象的编程其强有力的特征是定义自定义构造函数以创建脚本中使用的自定义对象的能力。创建了自定义的构造函数,这样就可以创建具有已定义属性的对象。下面是自定义函数的示例(注意 this
关键字的使用)。
function Circle (xPoint, yPoint, radius) {
this.x = xPoint; // 圆心的 x 坐标。
this.y = yPoint; // 圆心的 y 坐标。
this.r = radius; // 圆的半径。
}
调用 Circle 构造函数时,给出圆心点的值和圆的半径(所有这些元素是完全定义一个独特的圆对象所必需的)。结束时 Circle 对象包含三个属性。下面是如何例示 Circle
对象。
var aCircle = new Circle(5, 11, 99);
使用构造器函数的优点是,它可以根据参数来构造不同的对象。 缺点是构造时每个实例对象都会生成重复调用对象的方法,造成了内存的浪费。
<script type="text/javascript">
function Test(name){
this.occupation = "coder";
this.name = name;
this.whoAreYou = function(){
return "I'm " + this.name + "and I'm a " + this.occupation;
}
}
var obj = new Test('trigkit4');//利用同一个构造器创建不同的对象
var obj2 = new Test('student');
obj.whoAreYou();//"I'm trigkit4 and I'm a corder"
obj2.whoAreYou();//"I'm student and I'm a corder"
</script>
依照惯例,我们应该将构造器函数的首字母大写,以便显著地区别于一般的函数。
以下两种形式的定义函数方式是等价的。
<script type="text/javascript">
var test = function(){
alert("Hello World");
}
alert(typeof(test));//output function
</script>
这里明确定义了一个变量test
,他的初始值被赋予了一个function
实体
<br/>
<script type="text/javascript">
function test(){
alert("Hello World");
}
alert(typeof(test));//output function
</script>
<br/>
看看下面这种定义式函数形式:
<script type="text/javascript">
function test(){
alert("Hello World");
};
test();//居然输出Hello,很奇怪不是吗?
function test(){
alert("Hello");
};
test();//正常滴输出了Hello
</script>
很显然,第一个函数并没有起到作用,很奇怪不是吗?我们知道,javascript
解析引擎并不是一行一行地执行代码,而是一段一段地执行代码。在同一段程序的分析执行中,定义式的函数语句会被优先执行,所以第一个定义的代码逻辑已经被第二个覆盖了,所以两次调用相同函数,只会执行第二个。
作为值的函数
函数在js
中不仅是一种语法,也是一个值。也就是说可以将函数赋值给变量,存储在对象的属性或数组的元素中,作为参数传入另一个函数中。
函数的名字实际是看不见的,它仅仅是变量的名字,这个变量指代函数对象
<script type="text/javascript">
function square(x,y){
return x*y;
}
var s = square; //s和square指代同一个函数
square(2,3);//6
s(2,4);//8
</script>
除了可以将函数赋值给变量,同样可以将函数赋值给对象的属性,当函数作为对象的属性调用时,函数就称为方法
<script type="text/javascript">
var obj = {square:function(x,y){ //对象直接量
return x*y;
}};
var ect = obj.square(2,3);
</script>
prototype属性
每一个函数都包含prototype属性,这个属性指向一个对象的引用,这个对象称为原型对象。
详见:javascript学习总结(五)原型和原型链
call()和apply()
apply()
函数有两个参数:第一个参数是上下文,第二个参数是参数组成的数组。如果上下文是null,则使用全局对象代替。例如:
function.apply(this,[1,2,3])
call()
的第一个参数是上下文,后续是实例传入的参数序列,例如:
function.call(this,1,2,3);
高阶函数
这里的高阶函数可不是高数里的那个高阶函数,所谓高阶函数就是操作函数的函数,它接收一个或多个函数作为参数,并返回新函数
参数arguments
当函数被调用时,会得到一个免费奉送的参数数组,那就是arguments
数组。通过它,函数可以访问所有它被调用时传递给他的参数列表。这使得编写一个无需指定参数个数的函数成为可能。
<script type="text/javascript">
var sum = function(){
var i ,sum =0;
for(i = 0;i<arguments.length;i+=1){
sum+=arguments[i];
}
return sum;
};
document.writeln(sum(4,5,23,13,35,46,-10));//116
</script>
在ECMAScript
中的参数在内部是用一个数组来表示的,函数接收到的始终都是这个数组,而不关心数组中包含哪些参数
function add(num1,num2){
num = num1 + num2;
return num;
}
var result = 12,count = 20;
alert(add(result,count));//32;命名的参数只提供便利,解析器不会验证命名参数
实际上,arguments
并不是一个真正的数组,它只是一个类数组的对象,它拥有一个length
属性,但他缺少所有数组的方法。另外,arguments
对象的长度是由传入的参数个数决定的,而不是由定义函数时的命名参数的个数决定的
函数在定义或者声明的时候,所有的参数都是形参,因此,我们可以根据实际情况来命名参数,函数也只有在被调用时才会传入实参。而每个函数在被调用时都会自动取得两个特殊变量:this 和 arguments
函数的递归
函数的递归,即一个函数在通过名字调用自身的情况下构成的:
通过使用argument.callee
代替函数名:
//arguments.callee是一个指向正在执行的函数的指针
<script>
function factorial(num){
if(num<=1){
return 1;
}else{
return num*arguments.callee(num-1);
}
}
</script>
定时器
setInterval
俗称:间歇性定时器;功能创建一个定时器,可按照指定的周期(以毫秒计)来调用函数或计算表达式;setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。
由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数;注意这种定时器只要创建了就等于启动。
语法: setInterval(函数名,时间)
功能: 每隔参数2的时间内,就调用一次参数1的函数。注意:时间以毫秒来计算的!
如果要关闭setInterval设置的定时器,首先就要获取setInterval返回的ID值,然后通过clearInterval(返回的ID值)来关闭特定的setInterval设置的定时器。
实例:
var timer = window.setInterval(func,5000);
function func(){
console.log("Hello World");
}
这里就创建了一个每隔5000毫秒就执行函数名为func的定时器,在函数中执行打印“Hello World”。当然,上面也可以换成为这样来写:
var timer = window.clearInterval(function(){
console.log("Hello World");
},5000);
两者的功能是一毛一样的,只不过后面的是匿名函数而已!
如果要清除上面的定时器该肿么办呢?我们可以这样来做。
clearInterval(timer);
这里就通过创建定时器时存储在timer中的ID值,进行销毁特定ID的定时器。
关于返回的ID值,我们该怎么看呢?这就相当于在创建一个定时器的同时,我们也赋予了这个定时器一个特定的身份了,就像我们伟大的祖国给予每个人特定的身份证一样,是独一无二的,也代表了我们每个人也是独一无二的,在这个世界就只有一个你这样。你的名字!!!
下面我们利用下面的代码一起来看看定时器的身份证到底是什么鬼!咳咳
<div id="div1" style="position: absolute;left: 20px;top: 20px;width: 50px;height: 50px;background-color: red"></div>
<script type="text/javascript">
var timer;
var div1 = document.getElementById("div1");
div1.onclick = function(){
timer = setInterval(function(){
console.log(timer);
},1000);
}
</script>
这里通过JS获取到id为“div1”的div,给其增加点击事件后,只要点击div后,就会打印当前定时器的id。下面就是div的样式和不断的点击之后打印的结果。
打印结果可以看出,当我们不断的点击div后,timer的值也在不断的改变,说明了定时器在不断的制造出来,以至于在一定时间内打印的次数在不断的增加,timer的值也被不断新建的定时器赋值。也说明了timer就是定时器的返回值。
当然,我们在实际中遇到的问题远不止如此简单,这就需要我们开动我们灵活的大脑了,誓要将定时器玩弄于手掌之中!成大事者,人也。
setTimeout
相比于上面的间歇式定时器,此种定时器俗称延时定时器,也就是用于在指定的毫秒数后调用函数或计算表达式。就相当于设置了一个定时炸弹,在你设定的特定时间之后,它就会被激活,自行爆炸,爆炸之后它也不会再次爆炸!setTimeout定时器也是这样,其只执行一次。如果要多次调用,请使用 setInterval() 或者自身再次调用 setTimeout()。
** 语法:**setTimeout(函数名,时间);
功能:参数2时间后调用参数1函数
返回值:也是返回定时器的id,可以通过该id关闭定时器
如果要关闭setTimeout设置的定时器,方法原理同setInterval类似,不同的也就是方法名的不同。用的是clearTimeout(参数),后面的参数也就是setTimeout要返回的id值。
实例:
<button onclick="closeTimeout()">关闭延时定时器</button>
<script type="text/javascript">
var timer = window.setTimeout(func,5000);
function func(){
console.log("Hello World");
}
function closeTimeout(){
window.clearTimeout(timer); // 关闭延时定时器
}
</script>
这里设置了5秒后就会只打印一次的延时定时器,然后给按钮增加了清除定时器的方法,一旦按下button,延时定时器就会被销毁了;不过,需要注意的是如果你在延时定时器销毁之后点击是没有任何效果的,因为延时定时器执行了一次之后它就自动销毁了!(对着setInterval说:看看人家多自觉! setInterval弱弱地说:可是它功能没我强大啊!我默默不说话。。。)
其实,在现阶段的开发阶段,setInterval用的还是比setTimeout频繁的。因为每隔一段时间就执行相同的动作,也就是重复相同的动作,在我们的现实生活中还是常常遇见的。
最后简单谈谈定时器的回调:
其1:定时器的回调函数并不是相当于在时间到了就执行,而是有一个主js执行进程,这个进程是页面刚加载的时候页面按照加载顺序执行的js代码,此外还有一个需要在进程空闲的时候执行的代码队列,而我们所说的定时器的回调就是相当于setInterval在特定时间之后把定时器回调放入到空闲队列中(注意,空闲队列有可能还有其它的代码,比如点击事件,因此定时器回调放入的位置不一定是空闲队列的开始位置!)
其2:setInterval有个很烦的地方就是当js主程序空闲时候,执行代码队列里面的代码的时候,如果此时候我们有一个问题,定时器是等到回调执行完,才开始计时进行下次循环呢?还是只要一次计时完毕,插入回调之后不管回调执不执行就开始计时呢?答案显然是后者,这也就是我说setInterval坑比的原因啊,因为这会出现一种情况,当我们插入回调的时候前队列有别的代码在执行,这时候回调肯定是不会执行的,因此如果这个时候限定时间到了会再次插入回调,这个时候如果发现队列中的第一次回调没有执行,那么再次插入的回调浏览器就默认取消(这是以防出现回调连续执行多次的情况)但是这又引发了新的情况就是有些回调是不能取消掉的?