深入理解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 ...
随机推荐
- Html Agility Pack 解析Html
Hello 好久不见 哈哈,今天给大家分享一个解析Html的类库 Html Agility Pack.这个适用于想获取某网页里面的部分内容.今天就拿我的Csdn的博客列表来举例. 打开页面 用Fir ...
- 深入理解DIP、IoC、DI以及IoC容器
摘要 面向对象设计(OOD)有助于我们开发出高性能.易扩展以及易复用的程序.其中,OOD有一个重要的思想那就是依赖倒置原则(DIP),并由此引申出IoC.DI以及Ioc容器等概念.通过本文我们将一起学 ...
- 《Django By Example》第一章 中文 翻译 (个人学习,渣翻)
书籍出处:https://www.packtpub.com/web-development/django-example 原作者:Antonio Melé (译者注:本人目前在杭州某家互联网公司工作, ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统 (源码购买说明)
系列目录 升级日志 !!!重大版本更新:于2016-12-20日完成了系统的结构重构并合并简化了T4(这是一次重要的更新,不需要修改现有功能的代码),代码总行数比上个版本又少了1/3.更新了代码生成器 ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(75)-微信公众平台开发-用户管理
系列目录 前言 本节主要是关注者(即用户)和用户组的管理,微信公众号提供了用户和用户组的管理,我们可以在微信公众号官方里面进行操作,添加备注和标签,以及移动用户组别,同时,微信公众号也提供了相应的接口 ...
- 【Web动画】SVG 线条动画入门
通常我们说的 Web 动画,包含了三大类. CSS3 动画 javascript 动画(canvas) html 动画(SVG) 个人认为 3 种动画各有优劣,实际应用中根据掌握情况作出取舍,本文讨论 ...
- IE8/9 本地预览上传图片
本地预览的意思是,在选择图片之后先不上传到服务器,而是由一个<img>标签来预览本地的图片,非 IE8/9 浏览器可以从<input type="file"/&g ...
- vue双向数据绑定原理探究(附demo)
昨天被导师叫去研究了一下vue的双向数据绑定原理...本来以为原理的东西都非常高深,没想到vue的双向绑定真的很好理解啊...自己动手写了一个. 传送门 双向绑定的思想 双向数据绑定的思想就是数据层与 ...
- MFC中成员变量的声明顺序与析构顺序
第一次用博客,第一篇随笔,就写今天遇到的一个问题吧. 在VS2008的MFC对话框程序,窗口成员变量的声明顺序与其析构顺序相反,即,先声明的变量后析构,后声明的变量先析构.未在其他模式下测试. cla ...
- Flexible 弹性盒子模型之CSS flex-flow
实例 让弹性盒的元素以相反的顺序显示,且在必要的时候进行拆行: display:flex; flex-flow:row-reverse wrap; 效果预览 浏览器支持 表格中的数字表示支持该属性 ...