JavaScript 函数详解
JavaScript函数的概述
什么是函数
函数是定义一次但却可以调用或执行任意多次的一段 JS 代码。具有特定功能的n条语句的封装体,只有函数是可执行的, 其它类型的数据是不可执行的
函数有时会有参数,即函数被调用时指定了值的局部变量。函数常常使用这些参数来计算一个返回值,这个值也成为函数调用表达式的值。(简单的说就是完成一个特定功能的代码块)。
通过函数可以封装任意多条语句,而且可以在任何地方、任何时候调用执行。
在 javaScript 中,Function(函数)类型实际上是对象(使用typeof检查一个函数对象时,会返回function)。每个函数都是 Function 类型的实例,而且都与其他引用类型一样具有属性和方法。
由于函数是对象,因此函数名实际上也是一个指向函数对象的指针。我们在实际开发中很少使用构造函数来创建一个函数对象
创建一个函数对象,可以将要封装的代码以字符串的形式传递给构造函数
var fun = new Function("console.log('Hello 这是我的第一个函数');"); console.log(typeof fun) // function console.log(fun) // [Function: anonymous] fun() // Hello 这是我的第一个函数,当调用函数时,函数封装的代码会按照顺序执行
显示的构造函数来创建对象
任何一个函数只要被new运算符使用了,那么这个函数就可以变成构造函数
var M = function(){ this.name = "o2"; var a = '123' console.log(a) } var o2 = new M(); // 创建对象的时候,里面的代码就会被执行:123 console.log(o2.name); console.log(typeof o2); // object
函数的声明
函数的声明不带参数
function box1() { console.log('只有函数被调用,我才会被之执行'); } box1(); //直接调用函数,函数本身不会自己执行,需要调用才会执行
函数的声明带参数(基本数据类型)
多个形参之间使用,隔开,声明形参就相当于在函数内部声明了对应的变量,但是并不赋值,所以如果调用函数没有传参数的话,那么参数的值就是undefined
调用函数时解析器不会检查实参的类型,所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行类型的检查,函数的实参可以是任意的数据类型
调用函数时,解析器也不会检查实参的数量,多余实参不会被赋值,如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined
function box(name, age) { console.log('你的姓名:'+name+',年龄:'+age); } box('huang', 23);//调用函数,并传参,执行结果:你的姓名:huang,年龄:23 box();//如果没传参数,也不会报错,但两个参数的值都是undefined,执行结果:你的姓名:undefined,年龄:undefined
函数的声明带参数(函数作为参数)
传递的实参可以是一个函数,将函数作为参数传递给另一个函数有很多种情况,通常是将一个函数的返回值传递给另一个函数(将带返回值的函数作为参数传递)
function fun(a){ console.log("a = "+a); } function mianji(r){ return 3.14*r*r; } fun(mianji(10)); //a = 314,相当于将mianji函数的返回值传给了fun函数的a参数
function box(sum, num) { return sum+num; } function sum(num) { return num + 10; } var result = box(sum(10), 10);//将第二个函数的返回值传递给第一个函数的第一个参数 console.log(result); // 30
function box(sum, num) { return sum(num); } function sum(num) { return num + 10; } var result = box(sum, 10);//传递函数到另一个函数里 实际上执行的还是sum这个方法 console.log(result); // 20
但是如果作为参数传递的这个函数没有返回值,那么接收参数的那个函数的参数值将为undefined(函数默认返回undefined)
function fun1(a) { console.log("a = "+a); // 结果是:a = undefined,因为传递进来的函数mainji没有写return ,所以默认返回undefined } function mianji(r) { console.log(3.14 * r * r) // 会先执行,结果是314 // 函数如果不写return ,那么会默认返回undefined } fun1(mianji(10))
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> function fun1(a) { console.log(this) // window console.log("a = "+a); // 结果是:a = undefined,因为传递进来的函数mainji没有写return ,所以默认返回undefined } function mianji(r) { console.log(r) // window console.log(this) // window // 函数如果不写return ,那么会默认返回undefined } fun1(mianji(this)) </script> </body> </html>
传递的是一个匿名函数,那么接收参数的函数的参数值是传递进来的函数声明(因为这个匿名函数没有被调用)
function fun1(a) { console.log("a = "+a); } fun1(function () { console.log('hello') }) /* 结果是: a = function () { console.log('hello') } */
函数的声明带参数(对象作为参数)
当我们的参数过多时,可以将参数封装到一个对象中,然后通过对象传递
function sayHello(o){ console.log("我是"+o.name+",今年我"+o.age+"岁了,"+"我是一个"+o.gender+"人"+",我住在"+o.address); } //创建一个对象 var obj = { name:"孙悟空", age:18, address:"花果山", gender:"男" }; sayHello(obj); // 我是孙悟空,今年我18岁了,我是一个男人,我住在花果山
还可以用表达式的方式声明函数
var f2 = function () { // 表达式 console.log('f2()') }
函数的return 返回值
实际上,任何函数都可以通过 return 语句跟后面的要返回的值来实现返回值。
function box1() { return '我被返回了!'; //通过 return 把函数的最终值返回 } console.log(box1()); //调用函数会得到返回值,然后输出
function box2(name, age) { return '你的姓名:'+name+',年龄:'+age;//通过 return 把函数的最终值返回 } console.log(box2('李炎恢', 28)); //调用函数得到返回值,然后外面输出
还可以把函数的返回值赋给一个变量,然后通过变量进行操作。
function box3(num1, num2) { return num1 * num2; } var num = box3(10, 5); //函数得到的返回值赋给变量 console.log(num);
return 语句还有一个功能就是退出当前函数,注意和 break 的区别:break 用在循环和 switch 分支语句里
在函数中return后的语句都不会执行,如果return语句后不跟任何值就相当于返回一个undefined,如果函数中不写return,则也会返回undefined
function box(num) { if (num < 5) return num; //满足条件,就返回 num,返回之后,就不执行下面的语句了 return 100; } console.log(box(2));
定义一个函数,判断一个数字是否是偶数,如果是返回true,否则返回false
function isOu(num){ return num % 2 == 0; } var result = isOu(15);
定义一个函数,可以根据半径计算一个圆的面积,并返回计算结果:3.14*r*r
function mianji(r){ return 3.14*r*r; } result = mianji(5); console.log("result = "+result); // result = 78.5
return和break,continue的区别
function fun(){ console.log("函数要执行了~~~~"); for(var i=0 ; i<5 ; i++){ if(i == 2){ //使用break可以退出当前的循环 break; //continue用于跳过当次循环 //continue; //使用return可以结束整个函数 //return; } console.log(i); } console.log("函数执行完了~~~~"); } fun(); /* * 执行结果: * 函数要执行了~~~~ * 0 * 1 * 函数执行完了~~~~ * */
return后可以跟任意类型的值,可以是一个对象,也可以是一个函数
function fun2(){ //返回一个对象 return {name:"沙和尚"}; } var a = fun2(); console.log("a = "+a); // a = [object Object] console.log("a = "+a.name); // a = 沙和尚
function fun3(){ //在函数内部再声明一个函数 function fun4(){ console.log("我是fun4"); } return fun4; //将fun4函数对象作为返回值返回 } a = fun3(); console.log(a);//[Function: fun4] a(); // 我是fun4 fun3()(); //我是fun4,第一个()执行了fun3返回fun4,第二个()就是执行了fun4
函数的调用
通过DOM元素的事件进行触发
<script type="text/javascript"> function box() { //没有参数的函数 alert('只有函数被调用,我才会被之执行'); } </script> </head> <body> <input type="button" value="点我" onclick="box()"/> </body>
将函数赋值给一个变量
function sum1(num) { return num + 10; } var box1 = sum1; console.log(box1()); //NaN function sum2(num) { return num + 10; } var box2 = sum2(2); console.log(box2); // 12
function getSum(){ return 100; } var sum = getSum; //getSum本身是一个函数名,而函数本身在js中就是一个对象,getSum就是这个函数对象的引用, //将getSum这个引用的地址赋给了sum,这时sum也指向了这个函数对象,相当于这个函数有两个函数名 console.log("sum:"+sum);//打印结果是这个函数的定义格式,也就是将函数本体打印出来了 console.log("sum:"+sum());//打印结果是这个函数的返回
将一个函数赋值给一个变量的时候,注意不能将调用函数写在定义函数之前详情查看:执行上下文与执行上下文栈中的变量声明提升与函数声明提升
JavaScript函数的arguments对象
JavaScript函数的arguments对象概述
在调用函数时,浏览器每次都会传递进两个隐含的参数:函数的上下文对象 this和封装实参的对象 arguments
ECMAScript 函数不介意传递进来多少参数,也不会因为参数不统一而错误。
函数体内可以通过 arguments 对象来接收传递进来的参数,并且保存函数参数
function box() { return arguments[0]+' | '+arguments[1]+' | '+arguments[5]; //得到每次参数的值,最后结果是1 | 2 | 6 } console.log(box(1,2,3,4,5,6)); //传递参数:1 | 2 | 6
JavaScript函数的arguments 的length 属性
arguments 对象的下的length 属性可以得到参数的数量
function box() { return arguments.length; } console.log(box(1,2,3,4,5,6));//得到 6
利用 length 这个属性,来智能的判断有多少参数,然后把参数进行合理的应用。比如,要实现一个加法运算,将所有传进来的数字累加,而数字的个数又不确定。
function box() { var sum = 0; if (arguments.length == 0){ return sum; //如果没有参数,退出并且返回0 }else{ for(var i = 0;i < arguments.length; i++) { //如果有,就累加 sum = sum + arguments[i]; } return sum; //返回累加结果 } } console.log(box(5,9,12)); // 26
一个简单的递归算法
function box(num) { if (num <= 1) { return 1; } else { return num * box(num-1); //一个简单的的递归 } } console.log(box(4));//4*3*2*1 = 24 结果是24
arguments 的callee 的属性与递归算法
表示的是一个指针,指向拥有这个 arguments 对象的函数
对于阶乘函数一般要用到递归算法,所以函数内部一定会调用自身
如果函数名不改变是没有问题的,但一旦改变函数名,内部的自身调用需要逐一修改
为了解决这个问题,我们可以使用 arguments.callee 来代替
function box(num) { if (num <= 1) { return num; } else { return num * arguments.callee(num-1);//使用 callee 来执行自身,实现递归 } } console.log(box(5)); // 120
递归算法详情查看:递归算法
函数的重载问题
ECMAScript 中的函数,没有像其他高级语言那种函数重载功能。(如果有相同名称的函数,则以最后一个为主)
function box(num) { return num + 100; } function box (num) { return num + 200; } console.log(box(50));//会执行第二个函数,返回结果是250,即调用的是第二个函数
function box(num,a) { return num + 100; } function box (num) { //会执行这个函数 return num + 200; } console.log(box(50,3)); //结果还250 说明还是执行第二个函数
JavaScript函数的length属性
length 属性表示函数希望接收的命名参数的个数
function box(name, age) { console.log(name + age); } box("中国",20); // 中国20 console.log(box.length); //2 即该函数中参数的个数
JavaScript函数的this属性
JavaScript函数的this属性详解
函数内部另一个特殊对象是 this,其行为与 Java 和 C#中的 this 大致相似。
换句话说,this 引用的是函数据以执行操作的对象,或者说函数调用语句所处的那个作用域。
解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this,this指向的是一个对象,这个对象我们称为函数执行的 上下文对象
当在全局作用域中调用函数时,this 对象引用的就是 window(是一个对象,而且是js里面最大的对象,是最外围的对象)。在定义函数时, this还没有确定, 只有在执行时才动态确定(绑定)的
alert(window);//结果[object Window] 是object类型 alert(this);//结果[object Window] 是object类型 因为在window范围下 所以this就是window
window.color = '红色的'; //全局的,或者 var color = '红色的';也行 alert(this.color); //打印全局的 color
var color = '红色的'; //全局的,或者 var color = '红色的';也行 var box = { color : '蓝色的',//局部的 color sayColor : function () { console.log(this.color); //此时的 this 只能 box 里的 color } }; box.sayColor(); console.log(this.color); //还是全局的,结果是:红色的
window.color = '红色的'; //全局的,或者 var color = '红色的';也行 function sayColor(){ console.log(this.color); } sayColor(); //这里调用sayColor,其实还是在window范围下的 这种调用函数的方式,在函数中的this指的是window
window.color = '红色的'; //全局的,或者 var color = '红色的';也行 var box={ color:'蓝色' } function sayColor(){ console.log(this.color); } box.sayColor = sayColor; box.sayColor();//蓝色
根据函数的调用方式的不同,this会指向不同的对象:以函数的形式调用时,this永远都是window,作为某个对象的方法的形式调用时,this就是调用方法的那个对象
function fun1() { function fun2() { console.log(this);// this是window } fun2(); } fun1();
function Person(color) { console.log(this) this.color = color; this.getColor = function () { console.log(this) return this.color; }; this.setColor = function (color) { console.log(this) this.color = color; }; } Person("red"); //this是window var p = new Person("yello"); //this是p这个对象 p.getColor(); //this是p这个对象 var obj = {}; p.setColor.call(obj, "black"); //this是obj这个对象 var test = p.setColor; test(); //this是window
// 如何确定this的值? test() //window obj.test()// obj new test() // 新创建的那个对象 test.call(obj) //obj
this应用
在实际的应用开发过程中,通常需要使用到事件的绑定一个函数来执行比较复杂的逻辑,那么这时候函数里面的this表示的是谁呢
<script> window.onload = function(){ var aBtn = document.getElementsByTagName('input'); for(var i=0; i<aBtn.length; i++){ aBtn[i].onclick = function (){ this.style.background = 'yellow'; //这里this表示的是aBtn[i],而不是window } } } </script> </head> <body> <input type="button" value="按钮1" /> <input type="button" value="按钮2" /> <input type="button" value="按钮3" /> </body>
<script> window.onload = function(){ var aBtn = document.getElementsByTagName('input'); for(var i=0; i<aBtn.length; i++){ aBtn[i].onclick = fn1; } function fn1(){ this.style.background = 'yellow'; //这里this同样表示的是aBtn[i] } } </script> </head> <body> <input type="button" value="按钮1" /> <input type="button" value="按钮2" /> <input type="button" value="按钮3" /> </body>
<script> window.onload = function(){ var aBtn = document.getElementsByTagName('input'); for(var i=0; i<aBtn.length; i++){ aBtn[i].onclick = function (){ fn1(); //如果这样调用的话,fn1函数里面的this代表的是window } } function fn1(){ this.style.background = 'yellow'; //this代表的是window } } </script> </head> <body> <input type="button" value="按钮1" /> <input type="button" value="按钮2" /> <input type="button" value="按钮3" /> </body>
<script> window.onload = function(){ var aBtn = document.getElementsByTagName('input'); var that = null; // 空 for(var i=0; i<aBtn.length; i++){ aBtn[i].onclick = function (){ that = this; fn1(); //这里this就表示aBtn[i] } } function fn1(){ that.style.background = 'yellow'; //这里this同样表示的是aBtn[i] } } </script> </head> <body> <input type="button" value="按钮1" /> <input type="button" value="按钮2" /> <input type="button" value="按钮3" /> </body>
<script> window.onload = function (){ var aLi = document.getElementsByTagName('li'); var that = null; for( var i=0; i<aLi.length; i++ ){ aLi[i].onmouseover = function (){ //this.getElementsByTagName('div')[0].style.display = 'block'; //这里this代表的是aLi[i] //如果这里要调用外部的方法,将this这个属性赋值给一个变量,然后调用这个函数,那么函数里的this就表示的是aLi[i]它了 that = this; fn1(); }; aLi[i].onmouseout = function (){ this.getElementsByTagName('div')[0].style.display = 'none'; }; } function fn1(){ that.getElementsByTagName('div')[0].style.display = 'block'; } }; </script> <style> li { width:100px; height:150px; float:left; margin-right:30px; background:#f1f1f1; position:relative; z-index:1; } div { width:80px; height:200px; background:red; position:absolute; top:75px; left:10px; display:none; } </style> </head> <body> <ul> <li> <div></div> </li> <li> <div></div> </li> <li> <div></div> </li> </ul> </body>
JavaScript函数的prototype属性
prototype 下有两个方法:apply()和 call(),每个函数都包含这两个非继承而来的方法。
这两个方法的用途都在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。
call()方法于 apply()方法相同,他们的区别仅仅在于接收参数的方式不同。
对于 call()方法而言,第一个参数是作用域,没有变化,变化只是其余的参数都是直接传递给函数的。
apply()方法
function box(num1, num2){ return num1 + num2; } console.log(box(10,10)); //结果是20 function sayBox(num1, num2) { return box.apply(this, [num1, num2]); //this 表示作用域,这里是 window []表示 box 所需要的参数 } console.log(sayBox(10,10));//结果也是20 该函数含义是冒充box函数执行 因为sayBox函数没有相加的功能,需要冒充box函数来执行,这样sayBox就有了相加的功能
如果box函数的参数过多的话,还可以使用下面这种方式
function box(num1,num2){ return num1+num2; } console.log(box(10,10)); //结果是20 function sayBox(num1, num2) { return box.apply(this,arguments);//arguments 对象表示 box 所需要的参数 } console.log(sayBox(10,10));//结果也是20 该函数含义是冒充box函数执行 因为sayBox函数没有相加的功能,需要冒充box函数来执行,这样sayBox就有了相加的功能了 console.log(sayBox(10,90,90,20));//这里不管你传入了多少个参数,box函数只有两个参数的话,那么这里传入有效的参数是前面两个
call()方法
function box(num1, num2) { return num1 + num2; } console.log(box(10,10)); //20 function callBox(num1, num2) { return box.call(this, num1, num2); //和 apply 区别在于后面的传参 } console.log(callBox(10,10)); //20
apply()和 call()方法其他用武之地
事实上,传递参数并不是 apply()和 call()方法真正的用武之地;它们经常使用的地方是能够扩展函数赖以运行的作用域
var color = '红色的'; //或者 window.color = '红色的';也行 var box = { color : '蓝色的' }; function sayColor() { console.log(this.color); } sayColor(); //作用域在 window sayColor.call(this); //作用域在 window sayColor.call(window); //作用域在 window sayColor.call(box); //作用域在 box,对象冒充
我们可以发现当我们使用 call(box)方法的时候,sayColor()方法的运行环境已经变成了 box 对象里了。
使用 call()或者 apply()来扩充作用域的最大好处,就是对象不需要与方法发生任何耦合关系(耦合,就是互相关联的意思,扩展和维护会发生连锁反应)。
也就是说,box 对象和sayColor()方法之间不会有多余的关联操作,比如 box.sayColor = sayColor;
JavaScript的匿名函数
把匿名函数赋值给变量
var box = function () { //将匿名函数赋给变量 return 'Lee'; }; console.log(box()); //调用方式和普通函数调用相似
通过表达式自我执行
全称: Immediately-Invoked Function Expression 立即调用函数表达式(IIFE),别名: 匿名函数自调用
作用:隐藏内部实现,不污染外部命名空间,可以用来编写js模块
(function() { //封装成表达式 console.log('Lee'); })(); //()表示执行函数 (function(num1,num2) { //封装成表达式 console.log(num1+num2) })(100,90); //()表示执行函数,并且传参
(function (i) { var a = 4 function fn() { console.log('fn ', i+a) } fn() })(3)
(function () { var a = 1 function test() { console.log(++a) } window.$ = function () { // 向外面暴露一个全局的函数 return { test: test } } })() console.log($) console.log($()) //{test: ƒ},是一个对象,里面有个test方法 $().test() //$是一个函数,调用$这个函数返回一个对象,对象里有一个test方法
把匿名函数自我执行的返回值赋给变量
匿名函数自我执行的写法必须要有返回值才能赋值给变量,没有返回值的情况下赋值给变量,变量的值是undefined
var box = (function() { return('Lee'); })(); console.log(box); // Lee
函数里的匿名函数
function box () { return function () { //函数里的匿名函数,产生闭包 return 'Lee'; } } console.log(box()()); //调用匿名函数 /* 还可以这样调用: var b = box(); console.log(b()); */
函数闭包详情查看:JavaScript的函数闭包详细解释 和 JavaScript 函数闭包的应用
回调函数
什么函数才是回调函数
你定义的,你没有直接调用,但最终它执行了(在特定条件或时刻)
常见的回调函数—DOM事件函数
//1. DOM事件函数 var btn = document.getElementById('btn') btn.onclick = function () { alert(this.innerHTML) }
常见的回调函数—定时器函数
//2. 定时器函数 setInterval(function () { alert('到点啦!') }, 2000)
常见的回调函数—ajax回调函数
常见的回调函数—生命周期回调函数
js调用函数时传递变量参数时, 是值传递还是引用传递
只有值传递, 没有引用传递, 传递的都是变量的值, 只是这个值可能是基本数据, 也可能是地址(引用)数据
如果后一种看成是引用传递, 那就值传递和引用传递都可以有
function f(a) { console.log(a) } var n = 4 f(n) //传递的是n的值 --->值传递 function f2(a) { a.name = 'atguigu' } n = {} f2(n) // 传递的是n指向的对象 ---> 引用传递 console.log(n.name) // atguigu