JavaScript是一种基于对象的脚本语言,JavaScript语言中的函数可以独立存在,也可以作为一个类使用。函数本身既是一个对象,也是一个Function实例。
定义函数
JavaScript是弱类型语言,因此定义函数时,不需要声明函数的返回值类型,也不需要声明函数的参数类型。
有三种方式:
1.定义命名函数
示例:
<script type=text/javascript">
hello("hhh");
function hello(name){
alert(name);
}
</script>
- 函数可以有返回值,也可以没有返回值。返回值使用return语句返回,当遇到return语句时,函数就返回返回值,函数运行结束。
- 从上面程序可以看出,在同一个<script…/>元素中,JavaScript允许先调用函数,然后再定义该函数(“函数提升”特性)。但在不同的<script…/>元素中时,必须先定义函数,再调用该函数。后面的<script…/>元素中可以调用前面<script…/>元素里定义的函数,但前面的<script…/>元素不能调用后面<script…/>元素里定义的函数。
2.定义匿名函数(主要用)
示例:
<script type=text/javascript">
var hello = function(name){
alert(name);
};
hello("hhh");
</script>
- 这种方式无须指定函数名,注意在定义最后加上分号;。
- 对于这种方式定义,可读性好:程序使用function关键字定义一个函数对象(Function类的实例),然后把这个对象赋值给hello变量,以后程序即可通过hello来调用这个函数。
- 如果将有名字的函数赋值给某个变量,那么原来的名字将被忽略。
3.使用Function类匿名函数
示例:
<script type=text/javascript">
var hello = new Function('name', 'alert(name);' + 'document.writeln(name);');
hello('hhh');
</script>
- JavaScript提供了一个Function类,该类可以用于定义函数,Function类的构造器的参数个数不受限制,Function可以接受一系列的字符串参数,其中最后一个字符串参数是函数的执行体,执行体的各语句以分号隔开,而前面的各字符串参数则是函数的参数。
递归函数
在函数定义中调用函数自身。
递归一定要向已知点追溯,不向中止点追溯的递归是无穷递归。
<script type="text/javascript">
var factorial = function(n){
if(typeof(n) == 'number'){
if(n == 1 || n == 0){
return 1;
}else{
return n*factorial(n-1); //调用自身
}
}else{
alert("参数类型错误!");
}
}
alert(factorial(3));
</script>
局部变量和局部函数
- 局部变量 在函数里使用var定义的变量。只能在函数里访问。
- 全局变量 在函数外定义的变量或在函数内不使用var定义的变量。可以在所有的函数里访问。
局部函数 在函数里定义。
<script type="text/javascript">
function outer(){
function inner1(){
document.write("局部函数1<br>");
}
function inner2(){
document.write("局部函数2<br>");
}
document.write("开始测试局部函数...<br>");
inner1();
inner2();
document.write("结束测试局部函数...<br>");
}
document.write("调用outer之前...<br>");
outer();
inner1();
document.write("调用outer之后...<br>");
</script>
在外部函数里调用局部函数不能让局部函数执行。上述代码中在inner1()不被执行。
函数、方法、对象、变量和类
当使用JavaScript定义一个函数后,将得到以下:
- 函数
- 变量 同名变量。
- 类 同名类。
- 对象 定义一个函数时,系统也会创建一个对象,该对象是Function的实例。
- 方法 定义一个函数时,该函数通常会附加给某个对象,作为该对象的方法。
- 如果直接输出函数本身,将会输出函数的源代码。
- JavaScript的函数不仅是一个函数,更是一个类,在定义一个JavaScript函数的同时,也得到了一个与该函数同名的类,该函数也是该类唯一的构造器。
<script type="text/javascript">
function person(name, age){
this.name = name;
this.age = age;
//为函数分配info方法,使用匿名函数来定义方法
this.info = function(){
document.writeln("名字:" + this.name + "<br>");
document.writeln("年纪:" + this.age + "<br>");
};
}
//创建p对象
var p = new person('张三', 25);
//执行info方法
p.info();
</script>
- 被this关键字修饰的变量不再是局部变量,而是该函数的实例属性。
- 如果没有明确指定将函数“附加”哪个对象上,该函数将“附加”到window对象上,作为window对象的方法。
应该避免JavaScript脚本的变量名与函数名重名,否则会出现变量值覆盖函数。
<script type="text/javascript">
function hello(name){
document.write(name);
}
alert(hello);
hello = "张三";
hello("李四");//报错
</script>
若是未给变量赋值,函数优先级比变量高,因此函数不会被覆盖。
function hello(name){
document.write(name);
}
alert(hello);
var hello;
hello("李四");//正常输出
函数的实例属性和类属性
- 局部变量 在函数中以var声明的变量,只能在函数里访问。
- 实例属性 在函数中以this前缀修饰的变量,必须通过对象来访问。
- 类属性 在函数中以函数名前缀修饰的变量,必须通过类来访问。
原理:同一个类(函数)只占用一块内存;同一类每创建一个对象,系统将会为该对象的实例属性分配一块内存。
<script type="text/javascript">
function Person(national, age){
//this修饰的变量为实例属性
this.age = age;
//Person修饰的变量为类属性
Person.national = national;
//以var定义的变量为局部变量
var bb = 0;
}
//创建Person的第一个对象p1
var p1 = new Person('中国', 29);
document.writeln("创建第一个Person对象<br>");
//输出p1的年龄、国籍
document.writeln("p1的age属性为:" + p1.age + "<br>");
document.writeln("p1的national属性为:" + p1.national + "<br>");
document.writeln("通过Person访问静态national属性为:" + Person.national + "<br>");
document.writeln("p1的bb属性为:" + p1.bb + "<br>");
//创建Person的第二个对象p2
var p2 = new Person('美国', 32);
document.writeln("创建两个Person对象之后<br>");
//再次输出p1的年龄、国籍
document.writeln("p1的age属性为:" + p1.age + "<br>");
document.writeln("p1的national属性为:" + p1.national + "<br>");
//输出p2的年龄、国籍
document.writeln("p1的age属性为:" + p2.age + "<br>");
document.writeln("p1的national属性为:" + p2.national + "<br>");
//通过类名访问类属性名
document.writeln("通过Person访问静态national属性为:" + Person.national + "<br>");
</script>
JavaScript对象不能访问它所属类的类属性。
<script type="text/javascript">
function Student(grade, subject){
this.grade = grade;
Student.subject = subject;
}
s1 = new Student(5, 'Java');
with(document){
writeln('s1的grade属性:' + s1.grade + "<br>");//输出5
writeln('s1的subject属性:' + s1.subject + "<br>");//输出undefined
writeln('Student的subject属性:' + Student.subject + "<br>");//输出Java
}
//为实例s1增加一个subject属性
s1.subject = 'Ruby';
with(document){
writeln('<hr>为s1的subject属性赋值后:<br>');
writeln('s1的subject属性:' + s1.subject + "<br>");//输出Ruby
writeln('Student的subject属性:' + Student.subject + "<br>");//输出Java
}
s2 = new Student(6, 'Python');
with(document){
writeln('<hr>s2的grade属性:' + s2.grade + "<br>");
writeln('s2的subject属性:' + s2.subject + "<br>");
writeln('Sudent的subject属性:' + Student.subject + "<br>");
}
</script>
调用函数
1.直接调用函数
//调用window对象的alert方法
window.alert("测试");
//调用p对象的walk方法
p.walk();
当程序使用window对象来调用方法时,可以省略方法前面的window调用者。
2.使用new关键字
var obj = new test();
3.以call()方式调用函数
语法格式: 函数.call(调用者,参数1, 参数2,…)
等价于 调用者.函数(参数1,参数2,…)
4.以apply()方法调用函数
与call()方法类似,区别:
- 通过call()调用函数时,必须在括号中详细地列出每个参数。
- 通过apply()调用函数时,需要以数组形式一次性传去所有调用参数。
对应关系:
函数.call(调用者,参数1, 参数2,…)= 函数.apply(调用者,[参数1, 参数2,…])
<script type="text/javascript">
var myfun = function(a, b){
alert("a的值是:" + a + "\nb的值是:" + b);
}
//以call()方法动态调用函数
myfun.call(window, 12, 23);
//以apply()方法动态调用函数
myfun.apply(window, [12,13]);
var example = function(num1, num2){
//arguments代表调用该函数时传入的所有参数
myfun.apply(this, arguments);
}
example(20, 40);
</script>
函数独立性
函数永远不从属于其他类、对象。
<script type="text/javascript">
function Person(name){
this.name = name;
this.info = function(){
alert("我的name是:" + this.name);
}
}
var p = new Person("张三");
p.info();//输出张三
var name = "测试名称"; //window对象的属性
//以window对象作为调用者来调用p对象的info方法,因此info()方法中的this代表的是window对象
p.info.call(window); //输出测试名称
</script>
再次证明函数独立性:
function Dog(name, age, bark){
//定义Dog函数,等同于定义了Dog类
this.name = name;
this.age = age;
this.bark = bark;
//使用内嵌函数为Dog实例定义方法
this.info = function(){
return this.name + "的年龄为:" + this.age + ",它的叫声:" + this.bark;
}
}
//创建Dog实例
var dog = new Dog("旺财", 3, "汪汪,汪汪…");
//创建Cat函数,对应Cat类
function Cat(name, age){
this.name = name;
this.age = age;
}
//创建Cat实例
var cat = new Cat("kitty", 2);
//将dog实例的info方法分离出来,再通过call方法来调用info方法,此时cat为调用者
alert(dog.info.call(cat));
函数提升
JavaScript会将全局函数提升到<script…/>元素的顶部定义。局部函数会被提升到所在函数的顶部。
如果使用程序先定义匿名函数,然后将匿名函数赋值给变量。此时只提升被赋值的变量,函数定义本身不会被提升。
如果匿名函数被赋值的变量没有使用var声明,那么该变量就是一个全局变量,因此该匿名函数就是一个全局函数。
JavaScript编程时应该尽量避免变量名和函数名同名,否则会发生互相覆盖的情形。
- 定义变量时只使用var定义变量,不分配初始值,此时函数的优先级更高,函数会覆盖变量。
- 定义变量时为变量指定了初始值,此时变量的优先级更高,变量会覆盖函数。
<script type="text/javascript">
function a(){
}
var a;
console.log(a);
var b;
function b(){
}
console.log(b);
var c = 1;
function c(){
}
console.log(c);
function d(){
}
var d = 1;
console.log(d);
</script>
- 由于定义变量a、b时并未指定初始值,因此不管这些变量放在同名的函数之前还是之后,变量都会被函数覆盖。
- 由于定义变量c、d时指定了初始值,因此不管这些变量放在同名的函数之前还是之后,变量都会覆盖函数。
箭头函数
语法格式:(参数1,参数2,参数3,…) => { 执行体 }
相当于
function(参数1, 参数2,参数3,…){
执行体
}
- 如果箭头函数的执行体只有一条return语句,则允许省略执行体的花括号和return关键字。
- 如果箭头函数的参数列表只有一个参数,则允许省略参数列表的圆括号。
- 如果函数不需要形参,那么箭头函数的形参列表的圆括号不可以省略。
<script type="text/javascript">
var arr = ["张三","李四","王五","孙六"];
//使用函数作为map()方法的参数
var newArr1 = arr.map(function(ele){
return ele.length;
});
//使用箭头函数作为map()方法的参数
var newArr2 = arr.map((ele) => {
return ele.length;
});
//由于箭头函数只有一个形参,可以省略形参列表的圆括号
//箭头函数的执行体只有一条return语句,可以省略return关键字
var newArr3 = arr.map(ele => ele.length);
console.log(newArr3);
//使用函数作为foreach()方法的参数
arr.forEach(function(ele){
console.log(ele);
});
//使用箭头函数作为forEach()方法的参数
arr.forEach(ele => console.log(ele));
</script>
与普通函数不同的是,箭头函数并不拥有自己的this关键字。
对于普通函数而言:
- 如果程序通过new调用函数创建对象,那么该函数中的this代表所创建的对象。
- 如果直接调用函数,那么该函数中的this代表全局对象(window)。
<script type="text/javascript">
function Person(){
this.age = 0;
setInterval(function growUp(){
//对于普通函数来说,直接执行该函数时,this代表全局变量(window)
console.log(this === window);
this.age++;//对象window的age++
}, 1000);
}
var p = new Person();
setInterval(function(){
console.log(p.age);//对象p的age
}, 1000);
</script>
箭头函数中的this总是代表包含箭头函数的上下文,如下例:
<script type="text/javascript">
function Person(){
this.age = 0;
setInterval(() => {
//箭头函数中的this总是代表箭头函数的上下文,即对象p
console.log(this == window);
//此处的this,将完全等同于Person构造器中的this
this.age++;
}, 1000);
}
var p = new Person();
setInterval(function(){
console.log(p.age);
}, 1000);
</script>
如果直接在全局范围内定义箭头函数,那么箭头函数的上下文就是window本身,此时箭头函数中的this代表全局对象window。
箭头函数不绑定arguments,因此不能在箭头函数中通过arguments来访问调用箭头函数的参数。箭头函数中的argumens总是引用当前上下文的arguments。
<script type="text/javascript">
var arguments = "张三";
//箭头函数中的arguments引用当前上下文的arguments,即"张三"字符串
var arr = () => arguments;//普通函数将返回空
console.log(arr());
function foo(){
//箭头函数中的arguments引用当前上下文的arguments
//此时arguments代表调用foo函数的参数
var f = (i) => 'Hello,' + arguments[0];//普通函数将返回2
return f(2);
}
console.log(foo("张三","李四"));
</script>
由于箭头函数语法的特殊性,容易犯如下错误:
1.函数返回对象的错误
- 程序应该将JavaScript对象放在圆括号内
<script type="text/javascript">
var f = () => ({name:'hhh'});
</script>
- 箭头函数不允许在形参列表和箭头之间换行
- 箭头函数允许在箭头和函数执行体之间换行
2.解析顺序导致的错误
var func;
func = func || () => "hhh"; //报错
将箭头函数放在圆括号中。
var func;
func = func || ( () => "hhh");
函数的参数处理
基本类型的参数传递
采用值传递方式,在函数中修改参数值并不会对实参有任何影响。
复合类型的参数传递
类似于指针,不管修改传入函数里的副本所引用的JavaScript对象还是修改变量所引用的JavaScript对象,实际上修改的是同一个对象。
空参数
JavaScript会将没有传入实参的参数值自动设置为undefined值。
如果先后定义两个同名的函数,它们的形参列表不同,这也不是函数重载,而是后面定义的函数覆盖前面定义的函数。
参数类型
JavaScript函数声明的参数列表无须类型声明。(这就是JavaScript语言不如Java、C语言程序健壮的一个重要原因)
为解决弱类型语言所存在的问题,提出了 “鸭子类型” 的理论:
- 如果弱类型语言的函数需要接受参数,则应先判断参数类型,并判断参数是否包含了需要访问的属性、方法。只有当这些条件都满足时,程序才开始真正处理调用参数的属性、方法。
示例:
<script type="text/javascript">
function changeAge(person){
if(typeof person == 'object' && typeof person.age == 'number'){
document.write("执行前:" + person.age + "<br>");
person.age = 10;
document.write("执行中:" + person.age + "<br>");
}else{
document.writeln("参数类型不符合" + typeof person + "<br>");
}
}
changeAge();
changeAge('xxx');
changeAge(true);
p = {abc:34};
changeAge(p);
person = {age:25};
changeAge(person);
</script>