最近在学习javascript的函数,函数是javascript的一等对象,想要学好javascript,就必须深刻理解函数。本人把思路整理成文章,一是为了加深自己函数的理解,二是给读者提供学习的途径,避免走弯路。内容有些多,但都是笔者对于函数的总结。

1.函数的定义

  1.1:函数声明

  1.2:函数表达式

  1.3:命名函数的函数表达式

  1.4:函数的重复声明

2.函数的部分属性和方法

  2.1:name属性

  2.2:length属性

  2.3:toString()方法

3.函数作用域

  3.1:全局作用域和局部作用域

  3.2:函数内部的变量提升

  3.3:函数自身的作用域

1.函数的定义

  1.1:函数声明

  函数就是一段可以反复调用的代码块。函数声明由三部分组成:函数名,函数参数,函数体。整体的构造是function命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。当函数体没有使用return关键字返回函数时,函数调用时返回默认的undefined;如果有使用return语句,则返回指定内容。函数最后不用加上冒号。

    function keith() {}
    console.log(keith())   // 'undefined'

    function rascal(){
        return 'rascal';
    }
    console.log(rascal())    // 'rascal'

  函数声明是在预执行期执行的,也就是说函数声明是在浏览器准备解析并执行脚本代码的时候执行的。所以,当去调用一个函数声明时,可以在其前面调用并且不会报错。

     console.log(rascal())   // 'rascal'
     function rascal(){
         return 'rascal';
     }

  其实这段代码没有报错的原因还有一个,就是与变量声明提升一样,函数名也会发生提升。函数名提升会在下面小节谈到。

  1.2:函数表达式

  函数表达式是把一个匿名函数赋给一个全局变量。这个匿名函数又称为函数表达式,因为赋值语句的等号右侧只能放表达式。函数表达式末尾需要加上分号,表示语句结束。

     var keith = function() {
         //函数体
     };

  函数表达式与函数声明不同的是,函数表达式是浏览器解析并执行到那一行才会有定义。也就是说,不能在函数定义之前调用函数。函数表达式并不像函数声明一样有函数名的提升。如果采用赋值语句定义函数并且在声明函数前调用函数,JavaScript就会报错。

     keith();
     var keith = function() {};
     // TypeError: keith is not a function

  上面的代码等同于下面的形式。

     var keith;
     console.log(keith());   // TypeError: keith is not a function
     keith = function() {};

  上面代码第二行,调用keith的时候,keith只是被声明了,还没有被赋值,等于undefined,所以会报错。

  1.3:命名函数的函数表达式

  采用函数表达式声明函数时,function命令后面不带有函数名。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。

     var keith = function boy(){
       console.log(typeof boy);
     };

     console.log(boy);
     // ReferenceError: boy is not defined

     keith();
     // function

上面代码在函数表达式中,加入了函数名boy。这个boy只在函数体内部可用,指代函数表达式本身,其他地方都不可用。这种写法的用处有两个,一是可以在函数体内部调用自身,二是方便除错(除错工具显示函数调用栈时,将显示函数名,而不再显示这里是一个匿名函数)。

  1.4:函数的重复声明

  如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。

     function keith() {
         console.log(1);
     }
     keith();
     function keith() {
         console.log(2);
     }
     keith(); 

  上面代码中,后一次的函数声明覆盖了前面一次。而且,由于函数名的提升,前一次声明在任何时候都是无效的。JavaScript引擎将函数名视同变量名,所以采用函数声明的方式声明函数时,整个函数会像变量声明一样,被提升到代码头部。表面上,上面代码好像在声明之前就调用了函数keith。但是实际上,由于“变量提升”,函数keith被提升到了代码头部,也就是在调用之前已经声明了。

2.函数的部分属性和方法

  2.1:name属性

  name属性返回紧跟在function关键字之后的那个函数名。

     function k1() {};
     console.log(k1.name); //'k1'

     var k2 = function() {};
     console.log(k2.name); //''

     var k3 = function hello() {};
     console.log(k3.name); //'hello'

  上面代码中,name属性返回function 后面紧跟着的函数名。对于k2来说,返回一个空字符串,注意:匿名函数的name属性总是为空字符串。对于k3来说,返回函数表达式的名字(真正的函数名为k3,hello这个函数名只能在函数内部使用。)

  2.2:length属性

  length属性返回函数预期传入的参数个数,即函数定义之中的参数个数。返回的是个数,而不是具体参数。

     function keith(a, b, c, d, e) {}
     console.log(keith.length)    

  上面代码定义了空函数keith,它的length属性就是定义时的参数个数。不管调用时输入了多少个参数,length属性始终等于5。也就是说,当调用时给实参传递了6个参数,length属性会忽略掉一个。

  2.3:toString()方法

  函数的toString方法返回函数的代码本身。

     function keith(a, b, c, d, e) {
         // 这是注释。
     }
     console.log(keith.toString());
     //function keith(a, b, c, d, e) { // 这是注释。 }

  可以看到,函数内部的注释段也被返回了。

3.函数作用域

  3.1:全局作用域和局部作用域

  作用域(scope)指的是变量存在的范围。Javascript只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取,在全局作用域中声明的变量称为全局变量;另一种是局部作用域,变量只在函数内部存在,此时的变量被称为局部变量。

  在全局作用域中声明的变量称为全局变量,也就是在函数外部声明。它可以在函数内部读取。

     var a=1;
     function keith(){
         return a;
     }
     console.log(keith())    

  上面代码中,全局作用域下的函数keith可以在内部读取全局变量a。

  在函数内部定义的变量,只能在内部访问,外部无法读取,称为局部变量。注意这里必须是在函数内部声明的变量。

     function keith(){
         var a=1;
         return a;
     }
     console.log(a)    //Uncaught ReferenceError: a is not defined

  在上面代码中,变量a在函数内部定义,所以是一个局部变量,外部无法访问。

  函数内部定义的变量,会在该作用域下覆盖同名变量。注意以下两个代码段的区别。

     var a = 2;

     function keith() {
         var a = 1;
         console.log(a);
     }
     keith(); 

     var c = 2;

     function rascal() {
         var c = 1;
         return c;
     }
     console.log(c); 

  上面代码中,变量a和c同时在函数的外部和内部有定义。结果,在函数内部定义,局部变量a覆盖了全局变量a

  注意,对于var命令来说,局部变量只能在函数内部声明。在其他区块声明,一律都是全局变量。比如说if语句。

     if (true) {
         var keith=1;
     }
     console.log(keith);    

  从上面代码中可以看出,变量keith在条件判断区块之中声明,结果就是一个全局变量,可以在区块之外读取。但是这里如果采用ES6中let关键字,在全局作用域下是无法访问keith变量的。

  3.2:函数内部的变量声明提升

  与全局作用域下的变量声明提升相同,局部作用域下的局部变量在函数内部也会发生变量声明提升。var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。

     function keith(a) {
         if (a > 10) {
             var b = a - 10;
         }
     }

     function keith(a) {
         var b;
         if (a > 10) {
             b = a - 10;
         }
     }

  上面两个函数段是相同的。

  3.3:函数本身的作用域

  函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。

     var a = 1;
     var b = function() {
         console.log(a);
     };
     function c() {
         var a = 2;
         b();
     }
     c(); 

     var a = 1;
     var b = function() {
         return a;
     };
     function c() {
         var a = 2;
         return b();
     }
     console.log(c()); 

  以上两个代码段相同。函数b是在函数c外部声明的。所以它的作用域绑定在函数外层,内部函数a不会到函数c体内取值,所以返回的是1,而不是2。

  很容易犯错的一点是,如果函数A调用函数B,却没考虑到函数B不会引用函数A的内部变量。

     var b = function() {
         console.log(a);
     };
     function c(f) {
         var a = 1;
         f();
     }
     c(b); //Uncaught ReferenceError: a is not defined

     var b = function() {
         return a;
     };
     function c(f) {
         var a = 1;
         return f();
     }
     console.log(c(b)); //Uncaught ReferenceError: a is not defined

  上面代码将函数b作为参数,传入函数c。但是,函数b是在函数c体外声明的,作用域绑定外层,因此找不到函数c的内部变量a,导致报错。

  同样的,函数体内部声明的变量,作用域绑定在函数体内部。

     function keith() {
         var a = 1;

         function rascal() {
             console.log(a);
         }
         return rascal;
     }

     var a = 2;
     var f = keith();
     f(); 

  上面代码中,函数keith内部声明了rascal变量。rascal作用域绑定在keith上。当我们在keith外部取出rascal执行时,变量a指向的是keith内部的a,而不是keith外部的a。这里涉及到函数另外一个重要的知识点,即在一个函数内部定义另外一个函数,也就是闭包的概念。下次有机会会分享。

  总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。

 

  完。

  感谢大家的阅读。

  

  转载请注明出处:http://www.cnblogs.com/Uncle-Keith/p/5789385.html

深入理解javascript函数定义与函数作用域的更多相关文章

  1. 理解javascript中的回调函数(callback)【转】

    在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实 ...

  2. 深入理解javascript中执行环境(作用域)与作用域链

    深入理解javascript中执行环境(作用域)与作用域链 相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行 ...

  3. 理解JavaScript的立即调用函数表达式(IIFE)

    首先这是js的一种函数调用写法,叫立即执行函数表达式(IIFE,即immediately-invoked function expression).顾名思义IIFE可以让你的函数立即得到执行(废话). ...

  4. 深入理解JavaScript执行上下文、函数堆栈、提升的概念

    本文内容主要转载自以下两位作者的文章,如有侵权请联系我删除: https://feclub.cn/post/content/ec_ecs_hosting http://blog.csdn.net/hi ...

  5. javascript . 03 函数定义、函数参数(形参、实参)、函数的返回值、冒泡函数、函数的加载、局部变量与全局变量、隐式全局变量、JS预解析、是否是质数、斐波那契数列

    1.1 知识点 函数:就是可以重复执行的代码块 2.  组成:参数,功能,返回值 为什么要用函数,因为一部分代码使用次数会很多,所以封装起来, 需要的时候调用 函数不调用,自己不会执行 同名函数会覆盖 ...

  6. 理解javascript中的回调函数(callback)

    以下内容来源于:http://www.jb51.net/article/54641.htm 最近在看 express,满眼看去,到处是以函数作为参数的回调函数的使用.如果这个概念理解不了,nodejs ...

  7. javaScript原生定义的函数

    1.JavaScript中的算术运算 包括加(+).减(-).乘(*).除(/)和求余(取模)(%)运算,除了这些基本的运算外,JavaScript还支持更加复杂的算术运算,这些复杂算术运算作为Mat ...

  8. c语言函数定义、函数声明、函数调用以及extern跨文件的变量引用

    1.如果没有定义,只有声明和调用:编译时会报连接错误.undefined reference to `func_in_a'2.如果没有声明,只有定义和调用:编译时一般会报警告,极少数情况下不会报警告. ...

  9. JS中函数定义和函数表达式的区别

    摘要: (function() {})();和(function(){}());的区别 Javascript中有2个语法都与function关键字有关,分别是: 函数定义:function Funct ...

随机推荐

  1. 【.net 深呼吸】细说CodeDom(4):类型定义

    上一篇文章中说了命名空间,你猜猜接下来该说啥.是了,命名空间下面就是类型,知道了如何生成命名空间的定义代码,之后就该学会如何声明类型了. CLR的类型通常有这么几种:类.接口.结构.枚举.委托.是这么 ...

  2. PHP-生成缩略图和添加水印图-学习笔记

    1.开始 在网站上传图片过程,经常用到缩略图功能.这里我自己写了一个图片处理的Image类,能生成缩略图,并且可以添加水印图. 2.如何生成缩略图 生成缩略图,关键的是如何计算缩放比率. 这里,我根据 ...

  3. Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part1:准备工作

    Linux平台 Oracle 10gR2(10.2.0.5)RAC安装 Part1:准备工作 环境:OEL 5.7 + Oracle 10.2.0.5 RAC 1.实施前准备工作 1.1 服务器安装操 ...

  4. 通往全栈工程师的捷径 —— react

    腾讯Bugly特约作者: 左明 首先,我们来看看 React 在世界范围的热度趋势,下图是关键词“房价”和 “React” 在 Google Trends 上的搜索量对比,蓝色的是 React,红色的 ...

  5. 使用Java原生代理实现AOP

    ### 本文由博主柒.原创,转载请注明出处 ### 完整源码下载地址 [https://github.com/MatrixSeven/JavaAOP](https://github.com/Matri ...

  6. ASP.NET Core的路由[3]:Router的创建者——RouteBuilder

    在<注册URL模式与HttpHandler的映射关系>演示的实例中,我们总是利用一个RouteBuilder对象来为RouterMiddleware中间件创建所需的Router对象,接下来 ...

  7. .NET Core的日志[4]:将日志写入EventLog

    面向Windows的编程人员应该不会对Event Log感到陌生,以至于很多人提到日志,首先想到的就是EventLog.EventLog不仅仅记录了Windows系统自身针对各种事件的日志,我们的应用 ...

  8. 【开源】.Net Aop(静态织入)框架 BSF.Aop

    BSF.Aop .Net 免费开源,静态Aop织入(直接修改IL中间语言)框架,类似PostSharp(收费): 实现前后Aop切面和INotifyPropertyChanged注入方式. 开源地址: ...

  9. 对Thoughtworks的有趣笔试题实践

    记得2014年在网上看到Thoughtworks的一道笔试题,当时觉得挺有意思,但是没动手去写.这几天又在网上看到了,于是我抽了一点时间写了下,我把程序运行的结果跟网上的答案对了一下,应该是对的,但是 ...

  10. HTML 5 应用程序缓存manifest

    什么是应用程序缓存(Application Cache)? HTML5 引入了应用程序缓存,这意味着 web 应用可进行缓存,并可在没有因特网连接时进行访问. 应用程序缓存为应用带来三个优势: 离线浏 ...