一.函数
Javascript是一门基于对象的脚本语言,代码复用的单位是函数,但它的函数比结构化程序设计语言的函数功能更丰富。JavaScript语言中的函数是“一等公民”,它可以独立存在;而且JavaScript的函数完全可以作为一个类来使用(而且它还是该类唯一的构造器);与此同时,函数本身也是一个对象,函数本身是function
实例。
函数的最大作用是提供代码复用,将需要重复使用的代码块定义成函数,提供更好的代码复用。函数可以有返回值,可以没有返回值
1.定义函数的三种方式
a)定义命名函数,
语法格式如下:
function functionName(param1,param1,...){ staments;
}
b)定义匿名函数
语法格式如下:
function(parameter list){
staments
};
与命名函数的区别是没有函数名,函数后面有个分号。
当通过这种语法格式定义了函数之后,实际上就定义了一个函数对象(即function实例),接下来可以将这个对象赋给另外一个变量。例如下面代码:
<script type='text/javascript'>
var f = function(name)
{
document.writeln('匿名函数<br/>');
document.writeln('你好'+name);
}
f('yukey');
</script>
使用匿名函数提供更好的可读性。
c)使用function类匿名函数
JavaScript提供了一个function类,该类也可以用于定义函数,Function类的构造器的参数个数可以不受限制,function可以接受一系列的字符串参数,其中
最后一个字符串参数是函数的执行体,执行体的各语句以分号(;)隔开,而前面的各字符串参数则是函数的参数,看下面定义函数的方式:
<script type='text/javascript'>
var f = new Function('name',"document.writeln('Function定义的函数<br/>');"
+"document.writeln('你好'+name);"); f('yukey');
</script>
2.局部函数
局部函数在函数里定义,看下面代码
<script type="text/javascript">
function outer(){
function inner1(){
document.write("局部函数11111<br/>");
} function inner2(){
document.write("局部函数22222<br/>");
}
inner1();
inner2();
document.write("结束测试局部函数<br/>");
}
outer();
document.write("调用outer之后...");
</script>
在外部函数里调用局部函数并不能让局部函数获得执行的机会。只有当外部函数被调用时,外部函数里调用的局部函数才获得执行的机会。
3.数,方法,对象,变量和类 函数是JavaScript的“一等公民”,函数是JavaScript变成里非常重要的一个概念,当使用JavaScript定义了一个函数之后,实际上可以得到如下四项。
函数:就像Java的方法一样,这个函数可以被调。
对象:定义一个函数时,系统也会创建一个对象,该对象时Function类的实例
方法:定义一个函数时,该函数通常会附加给某个对象,作为该对象的方法
变量:在定义一个函数的同时,也会得到一个变量
类:在定义函数的同时,也得到一个与函数同名的类
定义函数之后,有如下两种方式调用函数
直接调用函数:直接调用函数总是返回该函数体内最后一条return语句的返回值;如果该函数体内不包含return语句,则直接调用函数没有返回值。
使用new关键字直接调用函数:通过这种方式调用总有返回值,返回值就是一个Javascript对象。
<script type="text/javascript">
var test = function(name)
{
return "你好,"+name;
}
var rval = test('Sherman');
var obj = new test("Sherman");
alert( rval+"\n"+obj);
</script>
可以看出,第一种方式直接调用函数,返回的是return语句返回值,第二种使用new关键字调用给函数,也就是将函数当成类来使用,得到的是一个对象。
下面程序定义了一个person函数,也就定义了一个person类,该person函数也会作为Person类唯一的一个构造器,定义了person函数时希望为该函数定义
了一个方法。
<script type="text/javascript">
function Person(name ,age){
this.name = name;
this.age = age;
this.info=function(){
document.writeln("我的名字是"+this.name+'<br/>');
document.writeln("我的年纪是"+this.age+'<br/>');
}
}
var p = new Person('Sherman',24);
p.info();
</script>
被this关键字修饰的的变量不再是局部变量,它是该函数的实例属性。
JavaScript定义的函数可以“附加”到某个对象上,作为该对象的方法。实际上如果没有明确指定将函数“附加”到哪个对象上,该函数默认“附加”到window
对象上,作为window对象的方法。
例如如下代码:
<script type="text/javascript">
function hello(name)
{
document.write(name+",您好<br/>")
}
window.hello("孙大圣");
p = {
//定义一个函数,该函数属于p对象
walk:function(){
for(let i = 0 ; i < 2 ; i++){
document.write("慢慢地走...<br/>"); }
}
};
p.walk();
</script>
4.函数的实例属性和类属性
由于JavaScript函数不仅仅是一个函数,而且是一个类,该函数还是此类唯一的构造器,只要在调用函数时使用new关键字,就可返回一个object,这个object
不是函数的返回值,而是函数本身产生的对象。因此JavaScript中定义的变脸不仅有局部变量,还有实例属性和类属性两种。根据函数中声明变量的方式,
中的变量有三种
a)局部变量:在函数中以var声明的变量
b)实例属性:在函数中以this前缀修饰的变量
c)类属性:在函数中以函数名前缀修饰的变量
局量只能在函数里访问的变量。实例属性和类属性是面向对象的概念:实例属性是属于单个对象的,因此必须通过对象来访问,类属性是属于整个类本身
(也就是函数)的,因此必须通过类来访问。
同一个类只占用一块内存,因此每个类属性只占用一块内存;同一个类每创建一个对象,系统将为该对象的实例属性分配一块内存。
<script type="text/javascript">
function Person(national,age){
this.age = age;
Person.national = national;
var bb = 0;
}
var p1 = new Person('中国',29);
with(document){
writeln("创建第一个Person对象");
writeln("p1的age属性为:"+p1.age+"<br/>");
writeln("p1的national属性为:"+p1.national+"<br/>");
writeln("通过Person访问静态national属性为:"+Person.national+"<br/>");
writeln("p1的bb属性为"+p1.bb+"<br/><hr/>");
} var p2 = new Person('美国',32);
with(document){
writeln("创建两个Person对象中后<br/>");
writeln("p1的age属性为:"+p1.age+"<br/>");
writeln("p1的national属性为:"+p1.national+"<br/>"); writeln("p2的age属性为:"+p2.age+"<br/>");
writeln("p2的national属性为:"+p2.national+"<br/>");
writeln("通过Person访问静态national属性为:"+Person.national+"<br/>"); }
</script>
浏览器输出:
创建第一个Person对象 p1的age属性为:29
p1的national属性为:undefined
通过Person访问静态national属性为:中国
p1的bb属性为undefined
创建两个Person对象中后
p1的age属性为:29
p1的national属性为:undefined
p2的age属性为:32
p2的national属性为:undefined
通过Person访问静态national属性为:美国
值得指出的是,JavaSript和java不一样,它是一种动态语言,它允许随时为对象增加属性和方法,当直接为对象的某个属性赋值时,即可视为给对象增加属性
5.调用函数的3种方式
5.1 直接调用函数
如下代码:
//调用window对象的alert方法
window.alert();
//调用p对象的walk方法
p.walk();
当程序使用window对象调用方法时,window调用者可以省略
5.2 以call方式调用函数
直接调用函数的方式简单易用,但这种调用方式不够灵活,有时候调用函数时需要动态的传入一个函数引用,此时为了动态地调用函数,就需要call方法。call调用函数
语法格式为
函数引用.call(调用者,参数1,参数2,...)
下面通过call方法调用each函数:
<script type="text/javascript">
var each = function(array,fn){
for(var index in array){
fn.call(null,index,array[index]);
}
}
each([4,20,3],function(index,ele){
document.writeln("第"+index+"个元素是:"+ele+"<br/>");
});
</script>
浏览器输出:
第0个元素是:4
第1个元素是:20
第2个元素是:3
5.3 以apply()方法调用函数
apply()方法和call()方法比较类似,都可以动态的调用函数,他们的区别是:
a)通过call()方法调用函数时,必须在括号中列出每个参数
b)通过apply()动态地调用函数时,需要以数组形式一次性传入所有调用函数
以下代码示范了call()和apply()的关系
<script type="text/javascript">
var myfun = function(a,b){
alert('a的值是'+a+'\nb的值是'+b);
}
//以call()方式动态的调用函数
myfun.call(window,5,20);
//以apply()方式动态的调用函数
myfun.apply(window,[3,12]);
var example = function(num1,num2){
//直接用arguments代表调用example函数时传入的所有函数
myfun.apply(this,arguments);
}
example(20,40);
</script>
由此可见,apply()和call()对应关系如下:
函数引用.call(调用者,参数1,参数2,...); = 函数引用.apply(调用者,[参数1,参数2,...]);
6.函数独立性
虽然定义函数时可以将函数定义成某个类的方法,或定义成某个对象的方法。但JavaScript的函数是“一等公民”,他永远是独立的,函数永远不会从属于其他类,对象。
下面代码示范了函数的独立性:
<script type="text/javascript">
function Person(name){
this.name = name;
this.info = function (){
alert("我的name是:"+this.name);
}
} var p = new Person("Sherman");
//调用p对象的info方法
p.info(); var name = "测试名称";
//以window对象作为调用者来调用p对象的info方法
p.info.call(window);
</script>
当使用匿名内嵌函数定义某个类的方法是时,该内嵌函数一样是独立存在的,该函数也不是作为该类实例的附庸存在,这些内嵌函数也可以被分离出来独立使用,成为另一个对象的函数。如下代码再次证明函数的独立性:
<script type="text/javascript">
function Dog(name,age,bark)
{
this.name = name;
this.age = age;
this.bark = bark;
//使用内嵌函数为Dog实例定义方法
this.info = function(){
return this.name+"的年龄为:"+this.age+",它的叫声为:"+this.bark;
}
} var dog = new Dog("旺财",3,"汪汪,汪汪...");
function Cat(name,age){
this.name = name;
this.age = age;
}
//将dog实例的info方法分离出来,在通过call方法调用info方法
//此时cat为调用者
var cat = new Cat("Kitty",2)
alert(dog.info.call(cat));
</script>
7.函数提升
JavaScript允许先调用函数,然后再在后面定义函数,这就是典型的函数提升:JavaScript会将全局函数提升到根元素<script.../>元素的顶部定义
例如如下代码:
<script type="text/javascript">
console.log(add(2,5));
function add(a,b){
console.log("执行add函数")
return a+b;
}
</script>
和下面代码效果是一样的:
<script type="text/javascript">
function add(a,b){
console.log("执行add函数")
return a+b;
}
console.log(add(2,5));
</script>
效果如图:
如果使用程序先定义匿名函数,然后将匿名函数赋值给变量,在这种方式下依然会发生函数提升,但此时只提升被赋值的变量,函数定义本省不提升。例如
<script type="text/javascript">
console.log(add(2,5)); var add = function(){
console.log("执行add函数");
return a+b;
}
</script>
效果如图:
局部函数会被提升到所在函数的顶部,如
<script type="text/javascript">
function test() {
function add(a, b) {
console.log("执行add函数")
return a + b;
} console.log(add(2, 5));
}
test();
</script>
JavaScript编程时应尽量避免变量名和函数名同名。否则会发生覆盖的情形:分两种情况
a)定义变量时只用var定义变量,不分配初始值,此时函数的优先值更高,函数会覆盖变量。
b)定义变量时为变量值指定了初始值,此时变量的优先值更高,变量会覆盖函数
测试代码如下:
<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>
效果如下:
8.箭头函数
箭头函数相当于其他语言的Lambda表达式或闭包语法,箭头函数是普通函数的简化写法。语法格式如下:
(param1,param2,param3,...) => {staments}
相当于定义了如下函数:
function(param1,param2,param3,...){}
如果箭头函数的执行体只有一条return语句,则允许省略函数执行体的花括号和return关键字。
如果箭头函数的形参只有一个参数,则允许省略形参列表的圆括号。
如果箭头函数没有形参,则圆括号不可以省略。
(param1,param2,param3,...) => expression
//等同于(param1,param2,param3,...) =>{return expression}
singleParam => {staments}
//等同于(singleParam) => {staments}
下面代码示范了箭头函数代替传统函数:
<script type="text/javascript">
var arr = ["yuekey","fkit","leegang","sczit"];
var newArr1 = arr.map(function(ele){
return ele.length;
});
var newArr2 = arr.map((ele) =>{return ele.length});
var newArr3 = arr.map(ele => ele.length);
console.log(newArr3);
arr.forEach(function(ele){
console.log(ele);
});
arr.forEach((ele) => {console.log(ele);})
arr.forEach(ele => console.log(ele)); </script>
与普通函数不同的是,箭头函数并不拥有自己的this关键字,对于普通函数而言,如果程序通过new调用函数创建对象,那么该函数中的this代表所创建的对象;
如果直接调用普通函数,那么该函数的this代表全局对象(window)。例如,如下代码示范了this关键字的功能。
<script type="text/javascript">
function Person(){
this.age = 0;//Person()作为构造器使用时,this代表构造器创建的对象
setInterval(function growUp(){
console.log (this=== window);//对于普通函数来说,this代表全局对象window,总是返回true
this.age++;
},1000);
}
var p = new Person();
setInterval(function(){
console.log(p.age);//此处访问p对象的age,总是0
},1000);
</script>
箭头函数中的this总是代表包含箭头函数的上下文,例如:
<script type="text/javascript">
function Person(){
this.age = 0;
setInterval(() =>
{
console.log(this === window);
this.age++;//this总是代表包含箭头函数的上下文
},1000);
} var p = new Person();
setInterval(() => console.log(p.age),1000);//此处访问的是p对象的age,总是不断加1 </script>
如果在全局范围内定义箭头函数,那么箭头函数的上下文就是window本身,this代表全局对象window对象。
<script type="text/javascript">
var f = () => {return this;};
console.log(f() === window);//输出true </script>
箭头函数并不绑定arguments,因此不能在箭头函数中通过arguments来访问调用箭头函数的参数,箭头函数的arguments总是引用当前上下文的arguments。例如
<script type="text/javascript">
var arguments = "Sherman";
var arr = () => arguments;
console.log(arr()); function foo(){
var f = (i) => 'Hello,'+arguments[1];
return f(2);
}
console.log(foo("Sherman","Leegang"));//箭头函数中的arguments引用当前上下文的arguments,此时代表调用foo函数的参数
</script>
9.函数的参数处理
大部分时候,函数都需要接受参数传递。与Java完全类似,JavaScript的参数传递也全部采用值传递方式。
9.1基本类型和复合类型的参数传递
对于基本类型参数,JavaScript采用值传递方式,当通过实参调用函数时,传入函数里的并不是实参本身,而是实参的副本,因此在函数中修改参数值并不会对实参
有任何影响:
<script type="text/javascript">
function change(arg1) {
arg1 = 10;
document.writeln("函数执行中arg1的值为:" + arg1 + "<br/>");
}
var x = 5;
document.writeln("函数调用前x的值为"+x+"<br/>");
change(x);
document.writeln("函数调用之后的x值为"+x+"<br/>"); </script>
对于复合类型的参数,实际上采用的依然是值传递方式,只是很容易混淆。看如下程序
<script type="text/javascript">
function changeAge(person)
{
person.age = 10;
document.writeln("函数执行中age的值为:" + person.age + "<br/>");
person = null;
}
var person = {age:5};
document.writeln("函数调用之前age的值为"+person.age+"<br/>");
changeAge(person);
document.writeln("函数调用之后的age值为"+person.age+"<br/>");
document.writeln("person对象为"+person);
</script>
9.2空参数
在JavaScript中,在函数声明时包含了参数,但调用时没有传入实参,这种情况是允许的,JavaScript会自动将参数值设置为undefined值,对于JavaScript来说,函数名就是函数的唯一标识。
如果先后定义两个同名,形参列表不同的函数,这不是函数重载,这种情况后面定义的函数会覆盖前面的函数。
9.3参数类型
JavaScript是弱类型语言,参数列表无需声明参数类型。
“鸭子类型”的理论认为,弱类型语言的函数需要接收参数时,则应先判断参数类型,判断参数是否包含了需要访问的属性、方法。当条件都满足时 ,程序才会真正
执行。看如下代码
<script type="text/javascript">
function changeAge(person) {
if (typeof person == 'object' && typeof person.age == 'number') {
document.writeln("函数调用之前age的值为" + person.age + "<br/>");
person.age = 10;
document.writeln("函数执行中age的值为:" + person.age + "<br/>");
}
else {
document.writeln("参数类型不符合" + typeof person + "<br/>")
}
}
changeAge();
changeAge("Sherman");
changeAge(true); p = {abc:34};//json格式创建第一个对象
changeAge(p); person = {age:25};//json格式创建第二个对象
changeAge(person);
</script>