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

 

posted @ 2019-04-14 22:58  胡椒粉hjf  阅读(453)  评论(0编辑  收藏  举报