深入理解javascript函数定义与函数作用域
最近在学习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函数定义与函数作用域的更多相关文章
- 理解javascript中的回调函数(callback)【转】
在JavaScrip中,function是内置的类对象,也就是说它是一种类型的对象,可以和其它String.Array.Number.Object类的对象一样用于内置对象的管理.因为function实 ...
- 深入理解javascript中执行环境(作用域)与作用域链
深入理解javascript中执行环境(作用域)与作用域链 相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行 ...
- 理解JavaScript的立即调用函数表达式(IIFE)
首先这是js的一种函数调用写法,叫立即执行函数表达式(IIFE,即immediately-invoked function expression).顾名思义IIFE可以让你的函数立即得到执行(废话). ...
- 深入理解JavaScript执行上下文、函数堆栈、提升的概念
本文内容主要转载自以下两位作者的文章,如有侵权请联系我删除: https://feclub.cn/post/content/ec_ecs_hosting http://blog.csdn.net/hi ...
- javascript . 03 函数定义、函数参数(形参、实参)、函数的返回值、冒泡函数、函数的加载、局部变量与全局变量、隐式全局变量、JS预解析、是否是质数、斐波那契数列
1.1 知识点 函数:就是可以重复执行的代码块 2. 组成:参数,功能,返回值 为什么要用函数,因为一部分代码使用次数会很多,所以封装起来, 需要的时候调用 函数不调用,自己不会执行 同名函数会覆盖 ...
- 理解javascript中的回调函数(callback)
以下内容来源于:http://www.jb51.net/article/54641.htm 最近在看 express,满眼看去,到处是以函数作为参数的回调函数的使用.如果这个概念理解不了,nodejs ...
- javaScript原生定义的函数
1.JavaScript中的算术运算 包括加(+).减(-).乘(*).除(/)和求余(取模)(%)运算,除了这些基本的运算外,JavaScript还支持更加复杂的算术运算,这些复杂算术运算作为Mat ...
- c语言函数定义、函数声明、函数调用以及extern跨文件的变量引用
1.如果没有定义,只有声明和调用:编译时会报连接错误.undefined reference to `func_in_a'2.如果没有声明,只有定义和调用:编译时一般会报警告,极少数情况下不会报警告. ...
- JS中函数定义和函数表达式的区别
摘要: (function() {})();和(function(){}());的区别 Javascript中有2个语法都与function关键字有关,分别是: 函数定义:function Funct ...
随机推荐
- Python高手之路【六】python基础之字符串格式化
Python的字符串格式化有两种方式: 百分号方式.format方式 百分号的方式相对来说比较老,而format方式则是比较先进的方式,企图替换古老的方式,目前两者并存.[PEP-3101] This ...
- 简谈百度坐标反转至WGS84的三种思路
文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1.背景 基于百度地图进行数据展示是目前项目中常见场景,但是因为百度地图 ...
- 用CIL写程序:你好,沃尔德
前言: 项目紧赶慢赶总算在年前有了一些成绩,所以沉寂了几周之后,小匹夫也终于有时间写点东西了.以前匹夫写过一篇文章,对CIL做了一个简单地介绍,不过不知道各位看官看的是否过瘾,至少小匹夫觉得很不过瘾. ...
- SQL Server 致程序员(容易忽略的错误)
标签:SQL SERVER/MSSQL/DBA/T-SQL好习惯/数据库/需要注意的地方/程序员/容易犯的错误/遇到的问题 概述 因为每天需要审核程序员发布的SQL语句,所以收集了一些程序员的一些常见 ...
- category中重写方法?
问:可以在category中重写方法吗? 答:代码上可以实现 在category中重写方法,但在实际开发中,不建议这样做.如果确实需要重写原有方法也建议使用子类进行重写. category是为了更方便 ...
- ASP.NET Core的路由[1]:注册URL模式与HttpHandler的映射关系
ASP.NET Core的路由是通过一个类型为RouterMiddleware的中间件来实现的.如果我们将最终处理HTTP请求的组件称为HttpHandler,那么RouterMiddleware中间 ...
- 深入浅出JavaScript之闭包(Closure)
闭包(closure)是掌握Javascript从人门到深入一个非常重要的门槛,它是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现.下面写下我的学习笔记~ 闭包-无处不 ...
- vscode 1.5安装体验
1.下载安装 官方下载地址: http://code.visualstudio.com/ 界面截图: 2.图标显示功能File Icon Themes vscode1.5版本文件夹视图,可显示文件类型 ...
- git基本操作
一.在Windows平台上安装Git,可以下载一个msysGit的安装包,点击exe即可安装运行.安装包下载地址:https://git-for-windows.github.io/备注:git命令行 ...
- 记录在Windows上安装和使用Oracle数据库过程中的坑
1.安装Oracle Oracle软件是免费的,可以去官网下载相应的安装包.但是如果用于商业用途需要购买License.官网上针对各种平台,32位和64位都有,如果在Windows一般会下载到两个文件 ...