深入理解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 ...
随机推荐
- 在SQL2008查找某数据库中的列是否存在某个值
在SQL2008查找某数据库中的列是否存在某个值 --SQL2008查找某数据库中的列是否存在某个值 create proc spFind_Column_In_DB ( @type int,--类型: ...
- .NET基础拾遗(5)多线程开发基础
Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...
- 移动先行之谁主沉浮? 带着你的Net飞奔吧!
移动系源码:https://github.com/dunitian/Windows10 移动系文档:https://github.com/dunitian/LoTDotNet/tree/master/ ...
- Python标准库--typing
作者:zhbzz2007 出处:http://www.cnblogs.com/zhbzz2007 欢迎转载,也请保留这段声明.谢谢! 1 模块简介 Python 3.5 增加了一个有意思的库--typ ...
- 8、Struts2 运行流程分析
1.流程分析: 请求发送给 StrutsPrepareAndExecuteFilter StrutsPrepareAndExecuteFilter 询问 ActionMapper: 该请求是否是一个 ...
- zookeeper源码分析之二客户端启动
ZooKeeper Client Library提供了丰富直观的API供用户程序使用,下面是一些常用的API: create(path, data, flags): 创建一个ZNode, path是其 ...
- input标签中button在iPhone中圆角的问题
1.问题 使用H5编写微信页面时,使用<input type="button"/>时,在Android手机中显示正常,但是在iPhone手机中则显示不正常,显示为圆角样 ...
- 介绍一款原创的四则运算算式生成器:CalculateIt2
家里小朋友读一年级了,最近每天都有一些10以内的加减法口算练习,作为程序员爸爸,自然也是想办法能够偷懒,让电脑出题,给小朋友做些练习.于是,自己在业余时间开发了一个四则运算算式生成器,名为:Calcu ...
- Effective java笔记(二),所有对象的通用方法
Object类的所有非final方法(equals.hashCode.toString.clone.finalize)都要遵守通用约定(general contract),否则其它依赖于这些约定的类( ...
- DirectX Graphics Infrastructure(DXGI):最佳范例 学习笔记
今天要学习的这篇文章写的算是比较早的了,大概在DX11时代就写好了,当时龙书11版看得很潦草,并没有注意这篇文章,现在看12,觉得是跳不过去的一篇文章,地址如下: https://msdn.micro ...