javascript教程(3)——JS函数的基础知识

1.函数基础知识

1.1函数的定义

函数是由事件驱动的或者当它被调用时执行的可重复使用的代码块。【简而言之,函数是为了实现某种特定功能的代码块】。

 

1.2函数的分类和调用

我们JS里面分为系统函数和自定义函数。所谓的系统函数就是JS为我们已经提供好的函数,而自定义函数就是我们自己写的为了实现某种特定功能的函数。    

系统函数:  

console.log()  // 控制台输出
document.write()   // 页面输出
alert() // 弹框弹出
prompt()  // 弹框输入
confirm()   // 确认弹框
encodeURI()接受一个字符串,将字符串进行UTF-8编码。
encodeURI("中国") ;   // 结果是%E4%B8%AD%E5%9B%BD
decodeURI()//接受一个UTF-8编码后的字符串,把字符串解码成普通的字符串形式。
decodeURI("%E4%B8%AD%E5%9B%BD"); //中国
//等等…………
 

自定义函数:    

在JavaScript中,函数是由关键字function 函数名加一组参数以及置于大括号中需要执行的一段代码定义的。

语法:

//定义方法1【声明方式】:
function 函数名(){
    执行语句;
}
var a = 10;
var b =20;
function add(){
    var c = a + b;
    console.log(c);  // 30
}
add();
//定义方法2【字面量方式】
var 函数名 = function(){ 执行语句 }
var a = 10;
var b =20;
var add = function (){
    var c = a + b;
    console.log(c);  // 30
}
add();
//定义方法3【new 关键字方式】
var 函数名 = new Function(“参数列表”,”函数体”);
var a = 10;
var b =20;
var add = new Function("var c = a + b;console.log(c);" );
add()

我们调用函数是使用函数名加上括号的方式。

函数名()
add();

函数如果不调用基本没有任何意义。就像汽车造出来不开一样,放在那里不用。

 

1.3 函数的参数和返回值

1.3.1参数

在上面的案例我们发现我们只能求出a和b的和不是很方面。这个时候我们可以尝试给函数参数。函数的定义的时候参数不是要求必须给的,但是一旦定义的时候给了,那么在调用的时候也就必须给值。我们把定义的时候给的值称为形参,把调用的时候给的值称为实参。

语法:
function 函数名(形参1,形参2){
    代码块
}
函数名(实参1,实参2)

如:
var a = 10;
var b =20;
function add(x,y){
    var c = x + y;
    console.log(c);  // 30
}
add(10,20);

arguments    注意:如果我们有一个函数的形参的个数是不确定的。也就是说我们实际调用的时候不知道要传入多少实参,这个时候我们可以不用给定形参, 调用的时候直接传入实参。在函数内部我们所有的传入的实参都保存在arguments对象里面。

function add(){
    console.log(arguments);  // 输出 [1, 2, 3, 4]
}
add(1,2,3,4)

简而言之,arguments就是我们的参数对象,它保存着我们所有函数被实际调用的时候传入的参数。有了他我们就不需要考虑实参和形参之间的一一对应的关系。

 

1.3.2 返回值

如果我们想用一个变量承接求和的结果,我们可能需要在全局定义一个变量c来承接。这个时候我们用到了函数的返回值。当我们的函数内部代码遇到return以后会立刻执行跳出函数,不会执行后面的代码。并且将return 后面的内容返回。这个时候我们要注意考虑是否需要用变量承接。

语法:
function 函数名(形参1,形参2){
    代码块
    return;
}
函数名(实参1,实参2)

var a = 10;
var b =20;
function add(x,y){
  return x+y;
}
var res = add(10,20);
console.log(res);  

注意:return的作用主要是用来返回。

 

综合练习

给函数传入两个数求出大的那个数,传入三个数求出最大那个数

 

1.4 函数的作用域和变量提升

1.4.1 作用域

在我们的JS里面分两种作用域,第一个是局部作用域又称函数作用域,第二个是全局作用域。如:

var a = 10;  // 全局的变量
function bbb(){
    var a = 20;
    console.log(a);  // 局部变量a,输出20
}
bbb();
console.log(a); // 全局的a,输出10

1.4.2 变量提升

所谓的变量提升,就是JS在运行的时候会将所有变量的声明放到当前作用域的最前面。如:

console.log(a);// 输出undefined
var a = 10;
console.log(a); // 输出10

这段代码其实整整在运行的时候是下面这样的。

var a;
console.log(a);// 输出undefined
a = 10;
console.log(a); // 输出10

因为a是放到最前面的声明的,所以我们前面输出a的时候输出为没有定义,后面的一句就是给a赋值,所以后面输出有值。    接着我们讨论函数声明提前。

say();
function say(){
    console.log("牵引力教育")  //  输出"牵引力教育"
}

其实我们按照声明方式去定义的函数同样我们在函数之前调用函数也是没问题的。因为我们的JS在运行之前会将所有的变量和定义的函数都放在代码的最前面。

 

1.4.3 作用域链

我们首先看以下代码:

var aa = 10;
function bb(){
    aa++;
    var aa= 21;
    console.log(aa) // 输出21
    function cc(){
        aa += 10;
        console.log(aa); // 输出31
    }
    cc();
    console.log(aa)  // 输出31
}
bb();
console.log(aa);  // 输出10

不知道答案是否和大家心中所想一直。所谓的作用域链就是我们JS的代码在运行的时候讲对应的声明提前都处理好以后,在当前的作用域范围内找不到这个变量的声明就会去往外城作用域找该变量的声明,找到了说明,就是使用这一层的变量。如果一直都向找,都找不到,说明这个变量就是一个全局边的变量。

我们看,我们的函数bb里面虽然 var aa=21 写在a++的后面,但是实际运行时却会在a++;签名加上var aa;将变量声明提前,所以a++其实就是将undefined进行了累加,然后重新又给aa赋值为21。函数cc被调用,这个时候在cc的函数作用域范围内没有声明变量aa,就会去外层找,发现bb所在作用域有,就共用该变量。   而我们全局的aa和里面没有任何关系,所以一直是10。

 

2.递归函数

所谓递归函数就是函数在自身的函数体内调用自身,使用递归函数时一定要当心,处理不当会使程序进入死循环,递归函数只有在特定的情况下使用,如处理阶乘问题。

语法:
var outter=10;
function functionName(parameters1){ 
    functionName(parameters2);
}

会进入死循环的递归函数

firstFun(x){    
    document.write(x+++"<br />")    
    firstFun(x);
}
firstFun(1);

不会进入死循环的递归函数

function fun(num){
    if(num<=1){ 
        return 1;   
    }else{  
        return num*fun(num - 1);    
    }   
}   
console.log(fun(4)) // 结果24

 

3.闭包

3.1闭包是什么?

闭包(Closure)这个词的意思是封闭,将外部作用域中的局部变量封闭起来的函数对象称为闭包。被封闭起来的变量与封闭它的函数对象有相同的生命周期。闭包非常的抽象,理解起来比较困难。  

闭包(closure)是javascript的一大难点,也是它的特色。很多高级应用都要依靠闭包来实现。那么,到底怎么理解闭包呢?  接下来我们来思考一个问题:我们都知道,一个函数内部的变量是局部变量,正常情况下,在函数外部是没办法读取到函数内部的局部变量,但是由于种种原因,有时候我们又需要获取到函数内部的局部变量,这时候我们该怎么做呢?    

如何从外部读取函数内部的局部变量?

function f1(){
 var n=999;
 function f2(){
   alert(n); // 999
 }
    return f2();
}

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。

结论:这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除;那为什么会这样呢?原因是f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。    闭包是个有状态(不消失的私有数据)的函数。闭包是个有记忆的函数。闭包相当于一个只有一个方法的紧凑对象。

代码演示数据累加常规写法

var add = function() {
//传统的函数是没有状态的,它们的局部变量都是保存在函数调用栈(Stack)上的,随着函数调用的结束、退出,这些栈上变量的值也会被清空。 
    var v = 1; 
    return v++;
};
console.log(add());
console.log(add());

 

3.2 闭包的写法

//该函数体中的语句将被立即执行
var add = (function() {
    //局部变量
    var v = 1; 
    //返回一个内嵌的闭包函数引用
    return function() {
        //当外部add()函数return后,这里的v不再是外部函数的局部变量v了。
        //v有了记忆
        return v++;
    };
})();
console.log(add()); //1
console.log(add()); //2
console.log(add()); //3
console.log(add()); //4

 

3.3 对象与闭包

函数对象不一定是闭包。Javascript中,每个函数都有一个与之相关联的作用域链。每次调用JavaScript函数的时候,都会为之创建一个新的对象用来保存局部变量,并把这个对象添加至作用域链中。当函数返回时,再将这个对象删除, 此对象会被当做垃圾回收。但如果这个函数定义了嵌套的函数,并将它存储在某处的属性里,就意味着有了一个外部引用指向这个嵌套的函数。它就不会被当作垃圾回收,它所指向的变量绑定对象同样不会被回收。    

由此可见,JavaScript中的函数对象是闭包——可以把外部作用域的局部变量“封闭”起来。    

 

3.4 闭包应用场景

保护函数内的变量安全: 如迭代器、生成器。      

在内存中维持变量: 如果缓存数据。

简化代码: 在一个窗口上有一个按钮控件,当点击按钮时会产生事件,如果我们选择在按钮中处理这个事件,那就必须在按钮控件中保存处理这个事件时需要的各个对象的引用。 另一种选择是把这个事件转发给父窗口,由父窗口来处理这个事件,或是使用监听者模式。无论哪种方式,编写代码都不太方便, 甚至要借助一些工具来帮助生成事件处理的代码框架。用闭包来处理这个问题则比较方便,可以在生成按钮控件的同时就写下事件处理代码。

加强模块化:闭包有益于模块化编程,它能以简单的方式开发较小的模块,从而提高开发速度和程序的可复用性。和没有使用闭包的程序相比,使用闭包可将模块划分得更小。  抽象:闭包是数据和行为的集合,这使得闭包具有较好抽象能力。  保护函数内的变量安全在内存中维持变量  

 

3.5  闭包注意事项

  1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

  2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(privatevalue),这时一定要小心,不要随便改变父函数内部变量的值。

4.匿名函数

4.1 什么是匿名函数

从简单的字面理解就是一个没有名字的函数。当我们使用函数字面量声明函数时:

var fnName = function(){
    alert(1);
}

把一个函数赋值给变量fnName,而这个函数是没有名字的,即匿名函数。实际上,相当多的语言都有匿名函数。

(function(){})()
//匿名函数
(function(){
    //code,运行的代码
})();

()在JavaScript中有两种含义: 一是运算符;二是分隔符。

上面匿名函数需要说明两点:

1.第一个括号里是一个匿名函数,红色括号代表分割,表示里面的函数是一个部分;

2.最后一个括号表示一个运算符,表示红色括号里面的函数要运行; 相当于定义完一个匿名函数后就让它直接运行。

function show(){
    console.log("show");
}
show();

var show = (function(){}) 
show()

var show2 = (function(){ 
    console.log("show2");
});
show2();

(function(){
    console.log("show3");
})();

4.2 匿名函数的写法

匿名函数在书写之后需要自调用才能执行,不然就相当于写了一段无用代码。匿名函数自调用就是在函数末尾加上左右圆括号 ()。

//常见的匿名函数书写方式
(function(){
    alert("这是一个匿名函数");
})()

(function(){})()

另外的书写方式:

-function(){ 
    console.log(1);
}()
+function(){ 
    console.log(2);
}()
~function(){ 
    console.log(3);
}()

&function(){ 
    console.log(5);
}()

|function(){ 
    console.log(6);
}()
//JQuery 就是使用以下的书写方式
!function(){ 
    console.log(4);
}()

4.3 匿名函数的参数传递

(function(a,b){ 
    alert(a+b);
})(10,20);

 

4.4 匿名的应用

  1. 匿名函数和闭包

  2. 使用匿名函数书写闭包函数

function foo() { 
    var i = 0;
    //返回匿名函数,闭包
    return function(){
        i = i + 1; console.log(i);
    }
}

上面这种方式跟下面这种方式是等价的。

function foo(){ 
    var i = 0; 
    function(){
        return i++;
    }
    return info;
}
//获取闭包函数
var bar = foo();
//调用
bar();//1
bar();//2
bar();//3

使用匿名函数解决命名冲突    团队开发时,经常需要书写多个JS文件,但这些文件需要多个人书写完成。如果没有一定的规范要求,特别容易造成命名冲突,导致部分代码运行错误。  

可以通过匿名函数的方法解决冲突。

//page1.js 
(function(){
    var a = 123;
    var b = "string";
    alert(a);//123
})()

//page2.js 
(function(){
    var a = "abc"; 
    alert(a);//abc
    //因为当前函数中并没有变量b 所以该变量不存在alert(b);
    //b is not defined
})()

两个函数中的变量a是互相不影响。

 

4.5 匿名函数的优缺点

优点  

  1. 减少命名冲突问题安全性    

  2. 将所以变量数据变成局部变量进行操作,可以防止外部人员通过控制台进行数据修改,<span style='color:red'>导致数据出现BUG!严重影响网站的安全<span>。

缺点  

  1. 调试比较麻烦:由于匿名函数没有具体的函数名,但触发错误时,在控制台中。难以寻找到是哪个函数触发的错误。  

  2. 事件绑定上无法移除:使用addEventListener这个方法绑定事件时,使用匿名函数,那这个事件就无法取消。

 

 

PS:纯属本人原创,如有错误 欢迎指点  不才会及时更正  互相学习   

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值