4.自定义函数

275 阅读8分钟

函数

  • 函数是一个可重用的代码块,用来完成某个特定功能。每当需要反复执行一段代码时,可以利用函数来避免重复书写相同代码。
  • 函数包含着的代码只能在函数被调用时才会执行,就可以避免页面载入时执行该脚本
  • 在JavaScript中,可以使用以下三种方法来定义一个函数
    • 使用function语句定义函数
    • 使用Function()构造函数来定义函数
    • 在表达式中定义函数

Function构造函数定义函数

var 函数名 = new Function(“参数1”,”参数2”,”参数3”……”函数体”);

注意:

1. 在使用Function()构造函数的时候,第一个字母要大写
2. Function()构造函数只能在JavaScript 1.1或更高版本中使用
3. 每次调用构造函数时都会解析函数体,并且创建一个新的函数对象,效率非常底

function语句定义函数

function 函数名 (参数1,参数2……[形参]){
	<语句块>
	return 返回值
}

function关键字:用于定义一个函数 函数名:函数名通常是一个合法的标识符,是唯一的,区分大小写的 参数:在函数里,可以有0个或者多个参数。如果有多个参数,参数与参数之间要用逗号隔开。无论函数是否有参数,小括号必需要有 函数体:用大括号括起来的代码块,即函数的主体 返回值:函数返回的值由关键字return完成,可选项

在表达式中直接定义函数

var 函数名 = function (参数1,参数2,…){函数体};

注意:

​ 在表达式中定义函数的方法只能在JS1.2版本或以上版本中使用 ​ 函数名() ​ 函数名/变量名

var rightKey=function(){
    if(event.button==2){
       alert("禁止使用鼠标右键");
    }  
}
window.onmousedown = rightKey;

调用函数

1.直接调用

myFunction();或window.myFunction() 

2.事件处理中调用

<div onclick="myFunction()"></div>

3.将函数的返回值赋给变量

var t = myFunction();

函数的参数

形參:定义函数时,函数名后面()中的参数;JavaScript中的函数允许给行参赋初始值

实参:调用函数时,传递的参数

参数的匹配:

​ 默认情况下,形參和实参是一一对应的

​ 但是当传递的参数个数与函数定义好的参数个数可以不匹配当不匹配时

  • 如果传递的参数个数小于函数定义的参数个数,JavaScript会自动将多余的参数值设为undefined;
  • 如果传递的参数个数大于函数定义的参数个数,那么多余传递的参数将会被忽略掉。

获取所有实参【arguments】

function Test(a,b){
		alert(typeof(arguments)); // 获得类型
		alert(arguments[1]);      // 获取下标为1的参数值
		alert(arguments.length);  // 获取参数的长度
		alert(arguments.callee);  // 获取函数的所有内容
}
Test("zhang","li");

函数的返回值

  • return语句并不是必须的
  • 默认情况下,函数的返回值为undefined
  • 在使用 return 语句时,函数会停止执行,并返回指定的值。

什么情况下添加return:

​ 如果函数中的变量要在函数外进行调用

​ 函数体的结果需要在函数外进行使用

变量的作用域

全局变量:

​ 在函数外声明的的变量为全局,在整个JS文档中生效 局部变量:

​ 在函数内用var声明的变量为局部变量,只能在函数内部使用。

全局变局部

全局变量可以在局部的任意位置使用

局部变全局

局部变量不能在全局使用,如果想要在全局使用,可以去除前边的var或者将该值使用return返回到全局

变量的生命周期

JavaScript 变量的生命期从它们被声明的时间开始。 局部变量会在函数运行以后被删除。 全局变量会在页面关闭后被删除。

<ul>
		<li>1</li>
		<li>2</li>
		<li>3</li>
		<li>4</li>
		<li>5</li>
		<li>6</li>
</ul>

var lis = document.getElementsByTagName("li");
	for(var i=0; i<lis.length;i++){
		lis[i].onclick = function(){
			alert(i);
		}
}

案例:

// 案例一
function test(){
	uname = "香水有毒";
}
console.log(uname);

// 案例二
function test(){
	uname = "香水有毒";
}
test();
console.log(uname); 

// 案例三
uname = "香水有毒";
function test(){
	console.log(uname);
}
test(); 

// 案例四
var uname = "香水有毒";
function test(){
	console.log(uname);
}
function test2(){
	uname = "老鼠爱大米";
	test();
}
test2(); 

// 案例五
var uname = "香水有毒";
function test(){
	console.log(uname); 
}
function test2(){
	uname = "老鼠爱大米";
	return test;
}
res=test2(); 
res(); 

闭包

闭包:闭包让你可以在一个内层函数中访问到其外层函数的作用域。

闭包是怎么形成的

也就是说,只要一个函数访问了外部作用域的变量,就形成了闭包。

function outer() {
	var a = '变量1'
	var inner = function () {
		console.info(a)
	}
	return inner // inner 就是一个闭包函数,因为他能够访问到outer函数的作用域
}

闭包的作用

闭包可以延长作用域链

function person(name){
    var name = name;
  	function sayName(){
    	console.log(name)
  	}
 	  return sayName;
}
var fun = person('王小明');
fun();// 王小明

fun 是一个 全局函数,但可以访问到 person里的局部变量 name,这是因为fun的值是从person函数中返回的sayName函数,而sayName 函数是可以访问到局部变量 name 的;

什么是作用域链

了解

  • 私有作用域 ----> 函数执行都会形成一个私有作用域
  • 全局作用域 ----> 页面一打开就会形成一个全局的作用域
  • 私有变量 ----> 在私有作用域里边形成的变量 (通过 var 声明; 形参)
  • 全局变量 ----> 在全局作用域形成的变量(var a = 12 或者函数内没有声明,直接赋值的变量)

当代码在一个环境中执行时,会创建变量对象的一个作用域链(作用域形成的链条)

  • 作用域链的前端,始终都是当前执行的代码所在环境的变量对象
  • 作用域链中的下一个对象来自于外部环境,而在下一个变量对象则来自下一个外部环境,一直到全局执行环境
  • 全局执行环境的变量对象始终都是作用域链上的最后一个对象

内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境的任何变量和函数

var n = 10;
function outer(){
  function inner(){
    function center(){
      console.log(n);
    }
    center();
  }
  inner();
  var n = 15;
}
outer(); //=> undefined

如函数的执行,形成一个私有作用域,形参和当前私有作用域中声明的变量都是私有变量,保存在内部的一个变量对象中,其下一个外部环境可能是函数,也就包含了函数的内部变量对象,直到全局作用域。

当在内部函数中,需要访问一个变量的时候,首先会访问函数本身的变量对象,是否有这个变量,如果没有,那么会继续沿作用域链往上查找,直到全局作用域。如果在某个变量对象中找到则使用该变量对象中的变量值。

由于变量的查找是沿着作用域链来实现的,所以也称作用域链为变量查找的机制

这个机制也说明了访问局部变量要比访问全局变量更快,因为中间的查找过程更短。但是 JavaScript 引擎在优化标识符查询方面做得很好,因此这个差别可以忽略不计。

自执行函数

(function a(){alert('我是自执行函数')} ()); // 用括号把整个表达式包起来
(function a(){alert('我是自执行函数')}) (); //用括号把函数包起来
!function a(){alert('我是自执行函数')}(); // 求反,我们不在意值是多少,只想通过语法检查。
+function a(){alert('我是自执行函数')}();
-function a(){alert('我是自执行函数')}();
~function a(){alert('我是自执行函数')}();
void function a(){alert('我是自执行函数')}();
new function a(){alert('我是自执行函数')}();

自执行函数通常都是定义之后立即执行,以后都不再会调用,所以声明时可以省略函数名,因此自执行函数又叫**匿名函数**

(function(){console.log('我是匿名函数')})();// 我是匿名函数

如果上一行代码没有使用 分号 ' ; '结束,可能会导致匿名函数通不过语法检查,所以通常会在小括号前加个分号

;(function(){console.log('我是匿名函数')})()// 我是匿名函数

自执行函数的效果

自执行函数可以用来保存变量的作用域,防止污染全局变量

以一个经典的面试题为例;

<ul>
  <li>这是第1个li</li>
  <li>这是第2个li</li>
  <li>这是第3个li</li>
  <li>这是第4个li</li>
  <li>这是第5个li</li>
  <li>这是第6个li</li>
</ul>

一个列表里有6个li,要求点击li的时候打印当前被点击li的索引。

如果我们直接通过for循环绑定事件:

var lis = document.querySelectorAll('li');
for(var i = 0;i<lis.length;i++){
  lis[i].onclick = function (){
    console.log(i)
  }
}
// 无论哪个li被点击,打印的永远都是相同的值,因为在所有的点击事件中,访问的都是全局的 i,当事件触发时,i的值已经变成是lis.length了。

在es6语法出现之后,我们使用 let 关键字创建的块级作用域可以解决这种问题,在 let 出现以前,通常是使用匿名函数+闭包的方式创建函数作用域来保存每一步循环里 i 的值;

var lis = document.querySelectorAll('li');
for(var i = 0;i<lis.length;i++){
  (function(i){
    lis[i].onclick = function (){
      console.log(i)
    }
  })(i)
}
// 使用这种方法,每次循环都会创建一个匿名函数,这就形成了函数作用域,事件处理函数中访问的 i 不再是全局变量,而且匿名函数中的局部变量。

递归函数

递归函数:如果一个函数在内部调用自身本身,这个函数就是递归函数。

好处:递归函数最大的好处在于可以精简程序中繁杂,重复调用程序

递归只是一种思想,只不过在程序,依靠函数自身嵌套来实现

案例:求1 * 1+2 * 2+3 * 3+4 * 4....的和

function sum(n){
	if(n==0){
		return 0;
	}else{
		return n*n + sum(n-1);
	}
}
console.log(sum(5));

案例:小熊掰玉米,给定的是总的米数,但是获取10米需要依赖于9米,求9米,需要依赖8米...以此类推,所以想要获取10米的需要知道没走的时候,0米的时候手里有几个玉米

function getTotle(length){
	total = 0;
	if(length == 0){
			total = 1;
	}else{
		total = 2*(getTotle(length-1)+1);
	}
	return total;
}
console.log(getTotle(10));

递归的特点:

​ 函数内部调用函数本身

​ 有进有出

​ 必须有出口

每次调用函数都会用掉一点内存,在足够多次数的函数调用发生后(在之前的调用返回后),空间就不够了,程序会以一个“超过最大递归深度”的错误信息结束。