JavaScript语法(三)函数

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>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值