1 定义

函数是由一连串的子程序(语句的集合)所 组成 的,可以被外部程序调用。向函数传递参数之后,函数可以返回一定的值 。
通常情况下, JavaScript 代码是自上而下执行的,不过函数体内部的代码则不是这样。如果只是对函数进行 了声明,其中的代码并不会执行。只有在调用函数时才会执行函数体内部的代码。
这里要注意的是 JavaScript 中的函数也是一个对象。函数也是在堆内存中保存的。

2 函数的声明

2.1 构造函数创建方式

函数声明比较特殊,需要使用 function 关键字声明。

  1. var sum = new function(a,b){
  2. return a+b
  3. };
  4. /*上边的例子就是创建了一个函数对象,并将函数对象赋值给了 sum 这个变量。其中()中的内容表示执
  5. 行函数时需要的参数,{}中的内容表示函数的主体。*/

2.2 函数声明方式(常用)

可以通过函数声明语句来定义一个函数。函数声明语句以关键字function 开始,其后跟有函数名 、参数列表和函数体。

        /*语法:
             *         function 函数名([形参1,形参2...形参N]){
             *             语句...
       */
function fun2(){
                console.log("这是我的第二个函数~~~");
                alert("哈哈哈哈哈");
                document.write("~~~~(>_<)~~~~");
            }

2.3 使用 函数表达式 来创建一个函数

            /*var 函数名  = function([形参1,形参2...形参N]){
             *      语句....
       */
var fun3 = function(){
                console.log("我是匿名函数中封装的代码");
            };

3 函数的调用

调用函数时,传递给函数的参数称为实参(实际参数)。
封装到函数中的代码不会立即执行
函数中的代码会在函数调用的时候执行

//如果想调用我们上边定义的 sum 函数,可以这样写:
var result = sum(123,456);
/*这样表示调用 sum 这个函数,并将 123 和 456 作为实参传递给函数,函数中会将两个参数求和
并赋值给 result 。*/

4.函数的参数

在调用函数时,可以在()中指定实参(实际参数)
实参将会赋值给函数中对应的形参

调用函数时解析器不会检查实参的类型, 所以要注意,是否有可能会接收到非法的参数,如果有可能则需要对参数进行类型的检查

            function sum(a,b){
                console.log("a = "+a);
                console.log("b = "+b);
                console.log(a+b);
            }
            sum(1,2);
//调用函数
//相当于使用的函数的返回值
            sum
//函数对象
//相当于直接使用函数对象

调用函数时,解析器也不会检查实参的数量 多余实参不会被赋值 如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined

实参可以是一个变量,也可以是一个对象,甚至是函数

            /*
             * 创建一个函数,可以在控制台中输出一个人的信息
             *     可以输出人的 name age gender address
             * 
             * 实参可以是任意的数据类型,也可以是一个对象
             *     当我们的参数过多时,可以将参数封装到一个对象中,然后通过对象传递
             */
    function sayHello(o){

                //console.log("o = "+o);
                console.log("我是"+o.name+",今年我"+o.age+"岁了,"+"我是一个"+o.gender+"人"+",我住在"+o.address);
            }

            //创建一个对象
            var obj = {
                name:"孙悟空",
                age:18,
                address:"花果山",
                gender:"男"        
            };

            sayHello(obj);
//我是孙悟空,今年我18岁了,我是一个男人,我住在花果山


function fun(a){
    console.log("a = " +a);
}

fun(sayHello);
//会将sayHello这个函数对象的函数输出
/* a = function sayHello(o){
 *                
 *            //console.log("o = "+o);
 *                console.log("我是"+o.name+",今年我"+o.age+"岁了,"+"我是一个"+o.gender+"人"+",我住在"+o.address);
 *        }
 *    
 */

5.函数返回值

return 来设置函数的返回值

5.1 语法:

  return 值

5.2 含义

return后的值将会会作为函数的执行结果返回,可以定义一个变量,来接收该结果

//创建一个函数,用来计算三个数的和
function sum(a , b , c){
                //alert(a + b +c);
                var d = a + b + c;
                return d;
                //return undefined;        
            }
var result = sum(4,5,6);  //15
  • 在函数中return后的语句都不会执行。
  • 如果return语句后不跟任何值就相当于返回一个undefined。
  • 如果函数中不写return,则也会返回undefined。
  • return后可以跟任意类型的值
console.log("result = "+result);

变量result的值就是函数的执行结果,函数返回什么result的值就是什么


            /*
             * 定义一个函数,判断一个数字是否是偶数,如果是返回true,否则返回false
             */

            function isOu(num){

                return num % 2 == 0;
            }
            var result = isOu(15);

5.3 使用return结束循环


            function fun(){
                alert("函数要执行了~~~~");

                for(var i=0 ; i<5 ; i++){


                    if(i == 2){
                        //使用break可以退出当前的循环
                        //break;

                        //continue用于跳过当次循环
                        //continue;

                        //使用return可以结束整个函数
                        //return;
                    }

                    console.log(i);
                }

                alert("函数执行完了~~~~");
            }

            fun();

5.4 返回值可以是一个对象,也可以是一个函数

function fun2(){

                //返回一个对象
                return {name:"沙和尚"};
            }
var a = fun2();
console.log("a = "+a);
//a = [object Object]
    function fun3(){
                //在函数内部再声明一个函数
                function fun4(){
                    alert("我是fun4");
                }

                //将fun4函数对象作为返回值返回
                return fun4;
            }

            a = fun3();
            //console.log(a);
 /*   a的结果://function fun4(){
                    alert("我是fun4");
                } */
            //a();  
            fun3()();

6.立即执行函数


            //函数对象()
            /*
             * 立即执行函数
             *     函数定义完,立即被调用,这种函数叫做立即执行函数
             *     立即执行函数往往只会执行一次
             */
            /*(function(){
                alert("我是一个匿名函数~~~");
            })();*/

            (function(a,b){
                console.log("a = "+a);
                console.log("b = "+b);
            })(123,456);

7.函数作用域

7.1 作用域的定义

 作用域指一个变量的作用的范围

7.2作用域的种类

在JS中一共有两种作用域:

7.2.1.全局作用域

- 直接编写在script标签中的JS代码,都在全局作用域<br />       - 全局作用域在页面打开时创建,在页面关闭时销毁<br />- 在全局作用域中有一个全局对象**window**,
    它代表的是一个浏览器的窗口,它由浏览器创建我们可以直接使用
    • 在全局作用域中:
  • 创建的变量都会作为window对象的属性保存
  • 创建的函数都会作为window对象的方法保存
//    创建的变量都会作为window对象的属性保存        
            var a = 10;
            var b = 20;
            //var c = "hello";

            //console.log(window.c);//和console.log(window.c)结果一样
//创建的函数都会作为window对象的方法保存        
    function fun(){
                console.log("我是fun函数");
            }
//window.fun();//和fun()结果一样
   全局作用域中的变量都是全局变量,
在页面的任意的部分都可以访问的到

7.2.2.函数作用域

   调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁<br />       每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的<br />       在函数作用域中可以访问到全局作用域的变量
            var a = 10;

            function fun(){
                console.log("a = "+a);
      }
            fun()    
                //
   在全局作用域中无法访问到函数作用域的变量
    var a = "我是fun函数中的变量a";

                //console.log("a = "+a);

                function fun2(){
                    var b = 20;
                }
console.log("b = "+b);//报错
   当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用
    var a = 20;

                //console.log("a = "+a);

                function fun2(){
          var a = "我是fun函数中的变量a";
                    console.log("a = "+window.a);
                }
fun2();//a = 我是fun函数中的变量a
   如果没有则向上一级作用域中寻找,直到找到全局作用域<br />如果全局作用域中依然没有找到,则会报错ReferenceError<br />在函数中要访问全局变量可以使用window对象
    var a = 10;

            function fun(){

            //    var a = "我是fun函数中的变量a";//放开结果就是a = 我是fun函数中的变量a
                var b = 20;

                //console.log("a = "+a);

                function fun2(){
                    console.log("a = "+window.a);
                }

                fun2();//此时结果是a = 10

            }
            /*
             * 在函数作用域也有声明提前的特性,
             *     使用var关键字声明的变量,会在函数中所有的代码执行之前被声明
             *     函数声明也会在函数中所有的代码执行之前执行
             */
    function fun3(){

                fun4();

                //console.log(a);

                var a = 35;

                function fun4(){
                    alert("I'm fun4");
                }

            }

            //fun3();

在函数中,不使用var声明的变量都会成为全局变量

            var c = 33;

            /*
             * 在函数中,不使用var声明的变量都会成为全局变量
             */
            function fun5(){
                //console.log("c = "+c);
                //c = 10;

                //d没有使用var关键字,则会设置为全局变量
                d = 100;
            }

            fun5();//c = 33
            //console.log("c = "+c); //c = 10
            //在全局输出c
            //console.log("d = "+d); //d = 100

定义形参就相当于在函数作用域中声明了变量

var e = 23;

      /*
    * 定义形参就相当于在函数作用域中声明了变量
    */
      function fun6(e){
    alert(e);
      }

      fun6();

8. 声明提前

8.1 变量的声明提前

   使用var关键字声明的变量,会在所有的代码执行之前被声明(但是不会赋值),<br />       但是如果声明变量时不适用var关键字,则变量不会被声明提前
console.log("a = "+a);            
var a = 123;//(此时控制台会显示a = undefined)
此时会先声明a变量,但是不会将123赋值,所以a是没有值的。

8.2 函数的声明提前

使用函数声明形式创建的函数 function 函数(){}
它会在所有的代码执行之前就被创建,所以我们可以在函数声明前来调用函数
使用函数表达式创建的函数,不会被声明提前,所以不能在声明前调用

            fun();//此时不会报错。
            fun2();//只执行这一条,会报错
            //函数声明,会被提前创建
            function fun(){
                console.log("我是一个fun函数");
            }

            //函数表达式,不会被提前创建,会提前声明fun2,但是不会赋值
            var fun2 = function(){
                console.log("我是fun2函数");
            };

            fun2();

9. this的用法

* 解析器在调用函数每次都会向函数内部传递进一个隐含的参数,<br />    *  这个隐含的参数就是this,this指向的是一个对象,<br />    *  这个对象我们称为函数执行的 上下文对象,<br />    *  根据函数的调用方式的不同,this会指向不同的对象

9.1.以函数的形式调用时,this永远都是window

    function fun(s,b){
                //console.log("a = "+a+", b = "+b);
                console.log(this);
            }

            //fun(1,2);//[object Window]

9.2 以方法的形式调用时,this就是调用方法的那个对象

        function fun(s,b){
                //console.log("a = "+a+", b = "+b);
                console.log(this);
            }    
fun();

   //创建一个对象
            var obj = {
                name:"孙悟空",
                sayName:fun
            };

            var obj2 = {
                name:"沙和尚",
                sayName:fun
            };

            //console.log(obj.sayName == fun);//true
            var name = "全局的name属性";
            //obj.sayName();//object
            //以函数形式调用,this是window
            //fun();

            //以方法的形式调用,this是调用方法的对象
            //obj.sayName();
            obj2.sayName();//this是object obj2里的内容
            //创建一个name变量
            var name = "全局";

            //创建一个fun()函数
            function fun(){
                console.log(this.name);
            }

            //创建两个对象
            var obj = {
                    name:"孙悟空",
                    sayName:fun
            };

            var obj2 = {
                    name:"沙和尚",
                    sayName:fun
            };

            //我们希望调用obj.sayName()时可以输出obj的名字
            obj.sayName();//孙悟空
            obj2.sayName();//沙和尚
            fun(); //全局

10. 使用工厂方法创建对象(了解)

通过该方法可以大批量的创建对象

function createPerson(name , age ,gender){
                //创建一个新的对象 
                var obj = new Object();
                //向对象中添加属性
                obj.name = name;
                obj.age = age;
                obj.gender = gender;
                obj.sayName = function(){
                    alert(this.name);
                };
                //将新的对象返回
                return obj;
            }

            /*
             * 用来创建狗的对象
             */
            function createDog(name , age){
                var obj = new Object();
                obj.name = name;
                obj.age = age;
                obj.sayHello = function(){
                    alert("汪汪~~");
                };

                return obj;
            }

            var obj2 = createPerson("猪八戒",28,"男");
            var obj3 = createPerson("白骨精",16,"女");
            var obj4 = createPerson("蜘蛛精",18,"女");
            /*
             * 使用工厂方法创建的对象,使用的构造函数都是Object
             *     所以创建的对象都是Object这个类型,
             *     就导致我们无法区分出多种不同类型的对象
             */
            //创建一个狗的对象
            var dog = createDog("旺财",3);

            console.log(dog);
            console.log(obj4);

11. 构造函数

11.1 创建一个构造函数

*  构造函数就是一个普通的函数,创建方式和普通函数没有区别,<br />    *  不同的是构造函数习惯上首字母大写<br />    * 构造函数总是由 new 关键字调用。

11.2 调用方式

* 构造函数和普通函数的区别就是调用方式的不同<br />    *  普通函数是直接调用,而构造函数需要使用new关键字来调用

11.3 构造函数的执行流程:

  1. 刻创建一个新的对象
  2. 将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
  3. 逐行执行函数中的代码
  4. 将新建的对象作为返回值返回

    11.4 类和实例

    • 使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。
      * 我们将通过一个构造函数创建的对象,称为是该类的实例

      11.5 this的情况:

    • 1.当以函数的形式调用时,this是window
      2.当以方法的形式调用时,谁调用方法this就是谁
      3.当以构造函数的形式调用时,this就是新创建的那个对象

          function Person(name , age , gender){
              this.name = name;
              this.age = age;
              this.gender = gender;
              this.sayName = function(){
                  alert(this.name);
              };
          }
      
          function Dog(){
      
          }
      
          var per = new Person("孙悟空",18,"男");
          var per2 = new Person("玉兔精",16,"女");
          var per3 = new Person("奔波霸",38,"男");
      
          var dog = new Dog();
      
          /*console.log(per);
          console.log(dog);*/
      
          /*
           * 使用instanceof可以检查一个对象是否是一个类的实例
           *     语法:
           *         对象 instanceof 构造函数
           * 如果是,则返回true,否则返回false
           */
          //console.log(per instanceof Person);//true
          //console.log(dog instanceof Person);//false
      
          /*
           * 所有的对象都是Object的后代,
           *     所以任何对象和Object左instanceof检查时都会返回true
           */
          //console.log(dog instanceof Object);
      

      11.6 向原型中添加方法

      11.6.1 原型的定义

      所谓的原型实际上指的是,在函数中存在着一个名为原型的 ( prototype )对象,这个对象中保存着一些属性,凡是通过该构造函数创建的对象都可以访问存在于原型中的属性。
      最典型的原型中的属性就是 toString() 函数,实际上我们的对象中并没有定义这个函数,但是却可以调用,那是因为这个函数存在于 Object 对应的原型中。

      11.6.2 设置原型

      原型就是一个对象,和其他对象没有任何区别,可以通过构造函数来获取原型对象。
      构造函数 . prototype

      和其他对象一样我们可以添加修改删除原型中的属性,也可以修改原型对象的引用。 需要注意的是 prototype 属性只存在于函数对象中,其他对象没有 prototype 属性。 每一 个对象都有原型,包括原型对象也有原型。特殊的是Object 的原型对象没有原型。

11.6.3 获取原型对象的方法

除了可以通过构造函数获取原型对象以外,还可以通过具体的对象来获取原型对象。
Object.getPrototypeOf( 对象 )
对象 .``__``proto__
对象.constructor.prototype
需要注意的是,我们可以获取到 Object 的原型对象,也可以对它的属性进行操作,但是我们不能修改Object 原型对象的引用。

            /*
             * 原型 prototype
             * 
             *     我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype
             *         这个属性对应着一个对象,这个对象就是我们所谓的原型对象
             *     如果函数作为普通函数调用prototype没有任何作用
             *     当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,
             *         指向该构造函数的原型对象,我们可以通过__proto__来访问该属性
             * 
             *     原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,
             *         我们可以将对象中共有的内容,统一设置到原型对象中。
             * 
             * 当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,
             *     如果没有则会去原型对象中寻找,如果找到则直接使用
             * 
             * 以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,
             *     这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了
             */

            function MyClass(){

            }

            //向MyClass的原型中添加属性a
            MyClass.prototype.a = 123;

            //向MyClass的原型中添加一个方法
            MyClass.prototype.sayHello = function(){
                alert("hello");
            };

            var mc = new MyClass();

            var mc2 = new MyClass();

            //console.log(MyClass.prototype);
            //console.log(mc2.__proto__ == MyClass.prototype);//true

            //向mc中添加a属性
            mc.a = "我是mc中的a";

            //console.log(mc2.a);

            mc.sayHello();

11.6.4 原型链

基于我们上边所说的,每个对象都有原型对象,原型对象也有原型对象。
对象,和对象的原型,以及原型的原型,就构成了一个原型链。
当从一个对象中获取属性时,会首先从当前对象中查找,如果没有则顺着向上查找原型对象,直到找到 Object 对象的原型位置,找到则返回,找不到则返回 undefined 。
image.png

            /*
             * 创建一个Person构造函数
             *     - 在Person构造函数中,为每一个对象都添加了一个sayName方法,
             *         目前我们的方法是在构造函数内部创建的,
             *             也就是构造函数每执行一次就会创建一个新的sayName方法
             *         也是所有实例的sayName都是唯一的。
             *         这样就导致了构造函数执行一次就会创建一个新的方法,
             *             执行10000次就会创建10000个新的方法,而10000个方法都是一摸一样的
             *             这是完全没有必要,完全可以使所有的对象共享同一个方法
             */
            function Person(name , age , gender){
                this.name = name;
                this.age = age;
                this.gender = gender;
                //向对象中添加一个方法
                //this.sayName = fun;
            }

            //将sayName方法在全局作用域中定义
            /*
             * 将函数定义在全局作用域,污染了全局作用域的命名空间
             *     而且定义在全局作用域中也很不安全
             */
            /*function fun(){
                alert("Hello大家好,我是:"+this.name);
            };*/
            //向原型中添加sayName方法
            Person.prototype.sayName = function(){
                alert("Hello大家好,我是:"+this.name);
            };
            //创建一个Person的实例
            var per = new Person("孙悟空",18,"男");
            var per2 = new Person("猪八戒",28,"男");
            per.sayName();
            per2.sayName();

12.函数常用的检查

12.1 检查一个对象是否是另一个对象的实例

学习基本数据类型时我们学习了 typeof 用来检查一个变量的类型。
typeof 对于对象来说却不是那么好用,因为任何对象使用typeof 都会返回 Object
这时就需要使用 instanceof 运算符了,它主要用来检查一个对象的具体类型。
var result = 变量 instanceof 类型

            function Person(name , age , gender){
                this.name = name;
                this.age = age;
                this.gender = gender;
                this.sayName = function(){
                    alert(this.name);
                };
            }

            function Dog(){

            }

            var per = new Person("孙悟空",18,"男");


            var dog = new Dog();

            /*
             * 使用instanceof可以检查一个对象是否是一个类的实例
             *     语法:
             *         对象 instanceof 构造函数
             * 如果是,则返回true,否则返回false
             */
            //console.log(per instanceof Person);//true
            //console.log(dog instanceof Person);//false

            /*
             * 所有的对象都是Object的后代,
             *     所以任何对象和Object左instanceof检查时都会返回true
             */
            //console.log(dog instanceof Object);

12.2 对象中是否具有某个属性

使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性
使用该方法只有当对象自身中含有属性时,才会返回true
对象名.hasOwnProperty("属性")

/*
             * 创建一个构造函数
             */
            function MyClass(){

            }

            //向MyClass的原型中添加一个name属性
            MyClass.prototype.name = "我是原型中的名字";

            var mc = new MyClass();
            mc.age = 18;

            //console.log(mc.name);

            //使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
            //console.log("name" in mc);

            //可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性
            //使用该方法只有当对象自身中含有属性时,才会返回true
            //console.log(mc.hasOwnProperty("age"));//true

            //console.log(mc.hasOwnProperty("hasOwnProperty"));//false

            /*
             * 原型对象也是对象,所以它也有原型,
             *     当我们使用一个对象的属性或方法时,会现在自身中寻找,
             *         自身中如果有,则直接使用,
             *         如果没有则去原型对象中寻找,如果原型对象中有,则使用,
             *         如果没有则去原型的原型中寻找,直到找到Object对象的原型,
             *         Object对象的原型没有原型,如果在Object原型中依然没有找到,则返回undefined
             */

            //console.log(mc.__proto__.hasOwnProperty("hasOwnProperty"));//false

            //console.log(mc.__proto__.__proto__.hasOwnProperty("hasOwnProperty"));//true,在原型的原型里找

            //console.log(mc.__proto__.__proto__.__proto__);//null

            //console.log(mc.hello);

            //console.log(mc.__proto__.__proto__.__proto__)