Javascript二(函数详解)

Posted yumiaoxia

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Javascript二(函数详解)相关的知识,希望对你有一定的参考价值。

一.函数

           javascript是一门基于对象的脚本语言,代码复用的单位是函数,但它的函数比结构化程序设计语言的函数功能更丰富。JavaScript语言中的函数是“一等公民”,它可以独立存在;而且JavaScript的函数完全可以作为一个类来使用(而且它还是该类唯一的构造器);与此同时,函数本身也是一个对象,函数本身是function

  实例。

   函数的最大作用是提供代码复用,将需要重复使用的代码块定义成函数,提供更好的代码复用。函数可以有返回值,可以没有返回值

1.定义函数的三种方式

   a)定义命名函数,

        语法格式如下:

 

function functionName(param1,param1,...){

staments;
}

 

  b)定义匿名函数

      语法格式如下:

function(parameter list){
staments
};

     与命名函数的区别是没有函数名,函数后面有个分号。

     当通过这种语法格式定义了函数之后,实际上就定义了一个函数对象(即function实例),接下来可以将这个对象赋给另外一个变量。例如下面代码:

<script type=‘text/javascript‘>
var f = function(name)
{
  document.writeln(‘匿名函数<br/>‘);
 document.writeln(‘你好‘+name);
}
f(‘yukey‘);
</script>

   使用匿名函数提供更好的可读性。

     c)使用function类匿名函数

      JavaScript提供了一个function类,该类也可以用于定义函数,Function类的构造器的参数个数可以不受限制,function可以接受一系列的字符串参数,其中

    最后一个字符串参数是函数的执行体,执行体的各语句以分号(;)隔开,而前面的各字符串参数则是函数的参数,看下面定义函数的方式:

<script type=‘text/javascript‘>
var f = new Function(‘name‘,"document.writeln(‘Function定义的函数<br/>‘);"
          +"document.writeln(‘你好‘+name);");

  f(‘yukey‘);
</script>

2.局部函数

局部函数在函数里定义,看下面代码

 <script type="text/javascript">
       function outer(){
           function inner1(){
               document.write("局部函数11111<br/>");
           }

           function inner2(){
               document.write("局部函数22222<br/>");
           }
           inner1();
           inner2();
           document.write("结束测试局部函数<br/>");
       }
       outer();
       document.write("调用outer之后...");
   </script>

      在外部函数里调用局部函数并不能让局部函数获得执行的机会。只有当外部函数被调用时,外部函数里调用的局部函数才获得执行的机会。

3.数,方法,对象,变量和类   函数是JavaScript的“一等公民”,函数是JavaScript变成里非常重要的一个概念,当使用JavaScript定义了一个函数之后,实际上可以得到如下四项。

      函数:就像Java的方法一样,这个函数可以被调。

      对象:定义一个函数时,系统也会创建一个对象,该对象时Function类的实例

      方法:定义一个函数时,该函数通常会附加给某个对象,作为该对象的方法

      变量:在定义一个函数的同时,也会得到一个变量

      :在定义函数的同时,也得到一个与函数同名的类

   定义函数之后,有如下两种方式调用函数

      直接调用函数:直接调用函数总是返回该函数体内最后一条return语句的返回值;如果该函数体内不包含return语句,则直接调用函数没有返回值。

      使用new关键字直接调用函数:通过这种方式调用总有返回值,返回值就是一个Javascript对象。

      

<script type="text/javascript">
        var test = function(name)
        {
            return "你好,"+name;
        }
        var rval = test(Sherman);
        var obj = new test("Sherman");
        alert( rval+"\\n"+obj);
    </script>

    可以看出,第一种方式直接调用函数,返回的是return语句返回值,第二种使用new关键字调用给函数,也就是将函数当成类来使用,得到的是一个对象。

   下面程序定义了一个person函数,也就定义了一个person类,该person函数也会作为Person类唯一的一个构造器,定义了person函数时希望为该函数定义

    了一个方法。

      

<script type="text/javascript">
        function Person(name ,age){
            this.name = name;
            this.age = age;
            this.info=function(){
                document.writeln("我的名字是"+this.name+<br/>);
                document.writeln("我的年纪是"+this.age+<br/>);
            }
        }
       var p = new Person(Sherman,24);
        p.info();
    </script>

  被this关键字修饰的的变量不再是局部变量,它是该函数的实例属性。

  JavaScript定义的函数可以“附加”到某个对象上,作为该对象的方法。实际上如果没有明确指定将函数“附加”到哪个对象上,该函数默认“附加”到window

对象上,作为window对象的方法。

 例如如下代码:

<script type="text/javascript">
        function hello(name)
        {
            document.write(name+",您好<br/>")
        }
        window.hello("孙大圣");
         p = {
        //定义一个函数,该函数属于p对象
            walk:function(){
                for(let i = 0 ; i < 2  ; i++){
                    document.write("慢慢地走...<br/>");

                }
            }
        };
        p.walk();
    </script>

4.函数的实例属性和类属性

  由于JavaScript函数不仅仅是一个函数,而且是一个类,该函数还是此类唯一的构造器,只要在调用函数时使用new关键字,就可返回一个object,这个object

不是函数的返回值,而是函数本身产生的对象。因此JavaScript中定义的变脸不仅有局部变量,还有实例属性和类属性两种。根据函数中声明变量的方式,

中的变量有三种

  a)局部变量:在函数中以var声明的变量

  b)实例属性:在函数中以this前缀修饰的变量

  c)类属性:在函数中以函数名前缀修饰的变量

    局量只能在函数里访问的变量。实例属性和类属性是面向对象的概念:实例属性是属于单个对象的,因此必须通过对象来访问,类属性是属于整个类本身

(也就是函数)的,因此必须通过类来访问。

  同一个类只占用一块内存,因此每个类属性只占用一块内存;同一个类每创建一个对象,系统将为该对象的实例属性分配一块内存。

 <script type="text/javascript">
        function Person(national,age){
            this.age = age;
            Person.national = national;
            var bb = 0;
        }
        var p1 = new Person(‘中国‘,29);
        with(document){
            writeln("创建第一个Person对象");
            writeln("p1的age属性为:"+p1.age+"<br/>");
            writeln("p1的national属性为:"+p1.national+"<br/>");
            writeln("通过Person访问静态national属性为:"+Person.national+"<br/>");
            writeln("p1的bb属性为"+p1.bb+"<br/><hr/>");
        }

         var p2 = new Person(‘美国‘,32);
        with(document){
            writeln("创建两个Person对象中后<br/>");
            writeln("p1的age属性为:"+p1.age+"<br/>");
            writeln("p1的national属性为:"+p1.national+"<br/>");

            writeln("p2的age属性为:"+p2.age+"<br/>");
            writeln("p2的national属性为:"+p2.national+"<br/>");
            writeln("通过Person访问静态national属性为:"+Person.national+"<br/>");
            
        }
    </script>

浏览器输出:          

创建第一个Person对象 p1的age属性为:29
p1的national属性为:undefined
通过Person访问静态national属性为:中国
p1的bb属性为undefined


创建两个Person对象中后
p1的age属性为:29
p1的national属性为:undefined
p2的age属性为:32
p2的national属性为:undefined
通过Person访问静态national属性为:美国

   值得指出的是,JavaSript和java不一样,它是一种动态语言,它允许随时为对象增加属性和方法,当直接为对象的某个属性赋值时,即可视为给对象增加属性

5.调用函数的3种方式

 5.1 直接调用函数

     如下代码:

//调用window对象的alert方法
window.alert();
//调用p对象的walk方法
p.walk();

  当程序使用window对象调用方法时,window调用者可以省略

    5.2 以call方式调用函数

直接调用函数的方式简单易用,但这种调用方式不够灵活,有时候调用函数时需要动态的传入一个函数引用,此时为了动态地调用函数,就需要call方法。call调用函数

语法格式为

函数引用.call(调用者,参数1,参数2,...)

 

 下面通过call方法调用each函数:

 

<script type="text/javascript">
        var each = function(array,fn){
            for(var index in array){
                fn.call(null,index,array[index]);
            }
        }
        each([4,20,3],function(index,ele){
            document.writeln("第"+index+"个元素是:"+ele+"<br/>");
        });
    </script>

浏览器输出:

第0个元素是:4
第1个元素是:20
第2个元素是:3

5.3 以apply()方法调用函数

apply()方法和call()方法比较类似,都可以动态的调用函数,他们的区别是:

a)通过call()方法调用函数时,必须在括号中列出每个参数

 b)通过apply()动态地调用函数时,需要以数组形式一次性传入所有调用函数

以下代码示范了call()和apply()的关系

<script type="text/javascript">
        var myfun = function(a,b){
            alert(‘a的值是‘+a+‘\\nb的值是‘+b);
        }
        //以call()方式动态的调用函数
        myfun.call(window,5,20);
        //以apply()方式动态的调用函数
        myfun.apply(window,[3,12]);
        var example = function(num1,num2){
            //直接用arguments代表调用example函数时传入的所有函数
            myfun.apply(this,arguments);
        }
        example(20,40);
    </script>

由此可见,apply()和call()对应关系如下:

函数引用.call(调用者,参数1,参数2,...); = 函数引用.apply(调用者,[参数1,参数2,...]);

6.函数独立性

虽然定义函数时可以将函数定义成某个类的方法,或定义成某个对象的方法。但JavaScript的函数是“一等公民”,他永远是独立的,函数永远不会从属于其他类,对象。

下面代码示范了函数的独立性:

<script type="text/javascript">
        function Person(name){
            this.name = name;
            this.info = function (){
                alert("我的name是:"+this.name);
            }
        }

        var p = new Person("Sherman");
        //调用p对象的info方法
        p.info();

        var name = "测试名称";
       //以window对象作为调用者来调用p对象的info方法
        p.info.call(window);
    </script>

技术分享图片

 

当使用匿名内嵌函数定义某个类的方法是时,该内嵌函数一样是独立存在的,该函数也不是作为该类实例的附庸存在,这些内嵌函数也可以被分离出来独立使用,成为另一个对象的函数。如下代码再次证明函数的独立性:

<script type="text/javascript">
        function Dog(name,age,bark)
        {
            this.name = name;
            this.age = age;
            this.bark = bark;
            //使用内嵌函数为Dog实例定义方法
            this.info = function(){
                return this.name+"的年龄为:"+this.age+",它的叫声为:"+this.bark;
            }
        }

        var dog = new Dog("旺财",3,"汪汪,汪汪...");
        function Cat(name,age){
            this.name = name;
            this.age = age;
        }
     //将dog实例的info方法分离出来,在通过call方法调用info方法
    //此时cat为调用者
        var cat = new Cat("Kitty",2)
        alert(dog.info.call(cat));
    </script>

技术分享图片

7.函数提升

JavaScript允许先调用函数,然后再在后面定义函数,这就是典型的函数提升:JavaScript会将全局函数提升到根元素<script.../>元素的顶部定义

例如如下代码:

 

 <script type="text/javascript">
        console.log(add(2,5));
        function add(a,b){
            console.log("执行add函数")
            return a+b;
        }
    </script>

 

和下面代码效果是一样的:

 <script type="text/javascript">
        function add(a,b){
            console.log("执行add函数")
            return a+b;
        }
        console.log(add(2,5));
    </script>

效果如图:技术分享图片

 

 如果使用程序先定义匿名函数,然后将匿名函数赋值给变量,在这种方式下依然会发生函数提升,但此时只提升被赋值的变量,函数定义本省不提升。例如

 <script type="text/javascript">
        console.log(add(2,5));

        var add = function(){
            console.log("执行add函数");
            return a+b;
        }
    </script>

效果如图:技术分享图片

局部函数会被提升到所在函数的顶部,如

 <script type="text/javascript">
        function test() {
            function add(a, b) {
                console.log("执行add函数")
                return a + b;
            }

            console.log(add(2, 5));
        }
        test();
    </script>

JavaScript编程时应尽量避免变量名和函数名同名。否则会发生覆盖的情形:分两种情况

 a)定义变量时只用var定义变量,不分配初始值,此时函数的优先值更高,函数会覆盖变量。

 b)定义变量时为变量值指定了初始值,此时变量的优先值更高,变量会覆盖函数

测试代码如下:

<script type="text/javascript">
        function a(){}
        var a;
        console.log(a);
        var b;
        function b(){}
        console.log(b);
        var c = 1;
        function c(){};
        console.log(c);

        function d(){}
        var d = 1;
        console.log(d);
    </script>

效果如下:

技术分享图片

8.箭头函数

     箭头函数相当于其他语言的Lambda表达式或闭包语法,箭头函数是普通函数的简化写法。语法格式如下:

(param1,param2,param3,...) => {staments}

相当于定义了如下函数:

function(param1,param2,param3,...){}

如果箭头函数的执行体只有一条return语句,则允许省略函数执行体的花括号和return关键字。

如果箭头函数的形参只有一个参数,则允许省略形参列表的圆括号。

如果箭头函数没有形参,则圆括号不可以省略。

(param1,param2,param3,...) => expression
//等同于(param1,param2,param3,...) =>{return expression}
singleParam => {staments}
//等同于(singleParam) => {staments}

下面代码示范了箭头函数代替传统函数:

<script type="text/javascript">
        var arr = ["yuekey","fkit","leegang","sczit"];
        var newArr1 = arr.map(function(ele){
            return ele.length;
        });
        var newArr2 = arr.map((ele) =>{return ele.length});
        var newArr3 = arr.map(ele => ele.length);
        console.log(newArr3);
        arr.forEach(function(ele){
            console.log(ele);
        });
        arr.forEach((ele) => {console.log(ele);})
        arr.forEach(ele => console.log(ele));

    </script>

  与普通函数不同的是,箭头函数并不拥有自己的this关键字,对于普通函数而言,如果程序通过new调用函数创建对象,那么该函数中的this代表所创建的对象;

如果直接调用普通函数,那么该函数的this代表全局对象(window)。例如,如下代码示范了this关键字的功能。

<script type="text/javascript">
        function Person(){
            this.age = 0;//Person()作为构造器使用时,this代表构造器创建的对象
            setInterval(function growUp(){
                console.log (this=== window);//对于普通函数来说,this代表全局对象window,总是返回true
                this.age++;
            },1000);
        }
        var p = new Person();
        setInterval(function(){
            console.log(p.age);//此处访问p对象的age,总是0
        },1000);
    </script>

箭头函数中的this总是代表包含箭头函数的上下文,例如:

<script type="text/javascript">
        function Person(){
            this.age = 0;
            setInterval(() => 
            {
                console.log(this === window);
                this.age++;//this总是代表包含箭头函数的上下文
                },1000);
        }

        var p = new Person();
        setInterval(() => console.log(p.age),1000);//此处访问的是p对象的age,总是不断加1

    </script>

技术分享图片

 如果在全局范围内定义箭头函数,那么箭头函数的上下文就是window本身,this代表全局对象window对象。

<script type="text/javascript">
var f = () => {return this;};
console.log(f() === window);//输出true

</script>

 箭头函数并不绑定arguments,因此不能在箭头函数中通过arguments来访问调用箭头函数的参数,箭头函数的arguments总是引用当前上下文的arguments。例如

<script type="text/javascript">
    var arguments = "Sherman";
    var arr = () => arguments;
    console.log(arr());

    function foo(){
      var f = (i) => ‘Hello,‘+arguments[1];
      return f(2);
    }
    console.log(foo("Sherman","Leegang"));//箭头函数中的arguments引用当前上下文的arguments,此时代表调用foo函数的参数
  </script>

9.函数的参数处理

大部分时候,函数都需要接受参数传递。与Java完全类似,JavaScript的参数传递也全部采用值传递方式。

9.1基本类型和复合类型的参数传递

对于基本类型参数,JavaScript采用值传递方式,当通过实参调用函数时,传入函数里的并不是实参本身,而是实参的副本,因此在函数中修改参数值并不会对实参

有任何影响:

 

 <script type="text/javascript">
    function change(arg1) {
      arg1 = 10;
      document.writeln("函数执行中arg1的值为:" + arg1 + "<br/>");
    }
    var x = 5;
       document.writeln("函数调用前x的值为"+x+"<br/>");
       change(x);
       document.writeln("函数调用之后的x值为"+x+"<br/>");

  </script>

 

技术分享图片

对于复合类型的参数,实际上采用的依然是值传递方式,只是很容易混淆。看如下程序

<script type="text/javascript">
    function changeAge(person)
    {
      person.age = 10;
      document.writeln("函数执行中age的值为:" + person.age + "<br/>");
      person = null;
    }
    var person = {age:5};
    document.writeln("函数调用之前age的值为"+person.age+"<br/>");
    changeAge(person);
    document.writeln("函数调用之后的age值为"+person.age+"<br/>");
    document.writeln("person对象为"+person);
  </script>

技术分享图片

9.2空参数

 在JavaScript中,在函数声明时包含了参数,但调用时没有传入实参,这种情况是允许的,JavaScript会自动将参数值设置为undefined值,对于JavaScript来说,函数名就是函数的唯一标识。

如果先后定义两个同名,形参列表不同的函数,这不是函数重载,这种情况后面定义的函数会覆盖前面的函数。

9.3参数类型

JavaScript是弱类型语言,参数列表无需声明参数类型。

“鸭子类型”的理论认为,弱类型语言的函数需要接收参数时,则应先判断参数类型,判断参数是否包含了需要访问的属性、方法。当条件都满足时 ,程序才会真正

执行。看如下代码

<script type="text/javascript">
    function changeAge(person) {
      if (typeof person == ‘object‘ && typeof person.age == ‘number‘) {
        document.writeln("函数调用之前age的值为" + person.age + "<br/>");
        person.age = 10;
        document.writeln("函数执行中age的值为:" + person.age + "<br/>");
      }
      else {
        document.writeln("参数类型不符合" + typeof person + "<br/>")
      }
    }
      changeAge();
      changeAge("Sherman");
      changeAge(true);

      p = {abc:34};//json格式创建第一个对象
      changeAge(p);

      person = {age:25};//json格式创建第二个对象
      changeAge(person);
  </script>

技术分享图片

 












以上是关于Javascript二(函数详解)的主要内容,如果未能解决你的问题,请参考以下文章

javascript函数作用域及this指向详解

javascript函数作用域及this指向详解

常用Javascript代码片段集锦

如何将此 JavaScript 代码片段翻译成 Parenscript?

10个JavaScript代码片段,使你更加容易前端开发。

10个JavaScript代码片段,使你更加容易前端开发。