JavaScript函数函数进阶作用域及预解析
Posted 遥岑.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript函数函数进阶作用域及预解析相关的知识,希望对你有一定的参考价值。
函数
什么是函数
函数:可以实现某种特定功能的代码块。
在编写代码时,可能会出现非常多的相同代码或者功能类似的代码,这些代码可能需要大量重复使用,此时就可以使用函数。
模块:模块就是从系统层次去分成不同的部分,每个部分就是一个模块。将大型系统的复杂问题,分成不同的小模块去处理问题
内聚:块内联系,模块的功能强度的度量,一个模块内部各个元素彼此结合的紧密程度的度量。一个模块内各元素联系的越紧密,内聚性越高
耦合:块间联系,模块和模块之间相互联系紧密程度的一种度量。模块之间联系越紧密,耦合性越强,模块的独立性则越差
耦合性和内聚性是模块独立性的两个定性标准,尽量做到高内聚低耦合,提高模块独立性
使用函数
函数在使用时分为两步:声明函数、调用函数。
- 声明函数
function 函数名(参数)
{
函数体语句
}
- function是一个关键字,全部小写
- 函数名:是一个标识符,建议使用驼峰命名,做到见名知义
- 函数可以有参数也可以没有参数,可以有多个参数,若有多个参数,参数之间用逗号分隔
- “ { } ”表示函数的控制范围,不能省略
- 函数体语句可以有多条,建议一个函数只完成一个功能
- 调用函数
函数名(参数)
事件名 = 函数名(参数)
函数的参数
函数的参数分为形参和实参。
- 形参(形式参数):在定义函数时出现在函数首部的参数,在定义函数时形参只是占位符,没有确定的值(占用空间)
- 实参(实在参数):函数调用时出现在函数首部的参数,是实际的值
- 参数的传递方式:实参将数据传递给形参
- 实参向形参传递参数是按从左到右的顺序进行参数匹配的,和参数的名称无关
- 当形参的个数比实参的个数多时,函数不会报错,多出的形参会是undefined类型
- 当实参的个数比形参的个数多时,多余的实参会被忽略
- 实参向形参传递数据是单向的,形参的改变不会影响到实参
函数的返回值
函数的返回值可以将函数的处理结果返回,用于根据函数的执行结果来决定下一步要做的事情,函数的返回值通过return语句实现。
function 函数名()
{
return 要返回的值 //返回一个值给调用者
}
- 当执行return语句时,函数会终止运行,程序的流程回到主流程中
- return将函数的运算结果返回给函数的调用者
- 在开发中,当需要返回多个值时,可以用数组来实现,也就是将要返回的多个值写在数组中,作为一个整体来返回
arguments的使用
当不确定函数中接收了多少实参时,可以用arguments来获取实参。
arguments是函数的一个内置对象,该对象保存了函数调用时传递的所有的实参。
函数的进阶
函数表达式
函数表达式(匿名函数):是将声明的函数赋值给一个变量,通过变量完成函数的调用和参数的传递,函数表达式的定义必须在调用前。
var 变量名 = function(参数)
{
函数体
}
回调函数
回调函数:一个函数A作为参数传递给函数B,然后在B的体内调用函数A,则A称为回调函数。
理解:回调就是一个函数的调用过程,函数B把自己的事情做完再调用函数A。回调函数会更灵活一些。
function a(callback)
{
console.log("调用回调函数")
callback()
}
function b()
{
console.log("我是回调函数b")
}
function c()
{
console.log("我是回调函数c")
}
a(b)
a(c)
输出结果:
递归函数
递归调用:指的是一个函数在其函数体内调用自身的过程,这种函数称为递归函数。
递归的使用前提:
- 问题可以分解(即复杂的问题可以逐步分解成简单的问题)
- 分解得到的新问题的解法和原来老问题的解法是相同的
- 必须有明确的终止条件
递归过程:
- 自上而下分解问题
- 自下而上回溯得到问题的解
理解:
求1+2+3+…+100的和。
思路:
- 求前1~100的和,我们不知道前 100 项的和是多少,可以求 100+前99项 的和
- 不知道前 99 项的和,可以求 99+前98项 的和,以此类推(递归过程)
- 直到求前 1 项的和,即为 1(结束条件)
function sum(n)
{
if(n==1) //给出结束条件
{
return 1
}
return n + sum(n-1) //递归过程
}
console.log(sum(100))
输出结果:
作用域
作用域的分类
作用域:变量需要在它的作用范围内才可以被使用,这个作用范围被称为变量的作用域。
变量在某个范围内起作用和效果,目的是提高程序的可靠性、减少命名冲突。
javascript(es6前)中的作用域分为两种:
- 全局作用域:作用于整个script标签或者是一个单独的js文件
- 局部作用域(函数作用域):在函数内部就是局部作用域,变量只在函数内部起作用和效果
es6:版本,目前常用为es5
块作用域由 { } 包含,比如 if { } ,for { } 。现阶段js没有块级作用域,因此 if 和 for 中定义的变量在外侧可以随意使用
根据作用域使用范围不同:
- 全局变量:在函数外部定义的变量(显式定义)或在函数内部不用var定义的变量(在函数内部没有声明直接赋值的变量也属于全局变量)(隐式定义)。浏览器页面关闭后才会销毁,比较占内存资源
- 局部变量:在函数体内部利用var关键字定义的变量,函数的形参也可以看作是一个给局部变量。函数调用结束后就销毁,比较节省内存资源
- 块级变量:ES6中使用let关键字在语句块中定义的变量,当语句块结束后就销毁
作用域链
作用域链:当在一个函数内部声明另一个函数时,内层函数只能在外层函数作用域内执行。在内层函数执行的过程中,若需要引入某个变量,首先会在当前作用域中寻找,没有找到会继续向上一层级的作用域中寻找,直到全局作用域,这种链式的查询关系为作用域链。
- 采取就近原则来查找变量最终的值
- 只要是代码,就至少有一个作用域
- 写在函数内部的是局部作用域
- 如果函数中还有函数,那么在这个作用域中就又可以诞生一个新的作用域
- 根据内部函数可以访问外部函数变量的这种机制,用链式查找决定哪些数据能被内部函数访问,就称作作用域链
闭包函数
所谓闭包(closure),就是有权访问另一函数作用域内变量(局部变量)的函数。
一个作用域可以访问另外一个函数的局部变量。
js作用域环境中访问变量的权利是由内向外的,内部作用域可以获得当前作用域下的变量并且可以获得当前包含当前作用域的外层作用域下的变量,反之不能。
也就是外层作用域无法获取内层作用域下的变量,在不同的函数作用域中也不能相互访问彼此变量的。
于是,闭包函数是在一个函数内部创建另一个函数,在一个函数内部也有权访问另一个函数内部的变量
作用:
- 可以在函数外部读取函数内部的变量
- 可以让变量始终保持在内存中
- 延伸了变量的作用范围(等到所有的函数调用完了才可以销毁)
特性:
- 函数嵌套函数
- 函数内部可以引用函数外部的参数和变量
- 参数和变量不会被垃圾回收机制回收
理解:
function fn()
{
var num = 10
function fun()
{
console.log(num)
}
return fun //闭包的主要实现原理
//相当于
/* return function fun()
{
console.log(num)
} */
}
var f = fn()
//相当于
/* var f = function fun()
{
console.log(num)
} */
f() //一个全局作用域下可以访问函数内部的作用域 闭包
输出结果:
注意:由于闭包函数会使函数中的变量一直被保存在内存中,内存消耗大,所以闭包的滥用可能会降低程序的处理速度,造成内存消耗等问题
预解析
JavaScript代码是由浏览器中的JavaScript解析器来执行的,JavaScript解析器在运行JavaScript代码时分两步:预解析和代码执行。
预解析:js引擎会把js里面所有的var变量声明和function函数声明提升到当前作用域的最前面。
代码执行:按照代码书写的顺序从上往下依次执行。
预解析分为:变量预解析(变量提升)、函数预解析(函数提升)。
- 变量预解析(变量提升):就是把所有的变量声明提升到当前作用域最前面,不提升赋值操作
- 函数预解析(函数提升):就是把所有的函数声明提升到当前作用域的最前面,不调用函数
函数表达式调用必须写在函数表达式的下面
以上是关于JavaScript函数函数进阶作用域及预解析的主要内容,如果未能解决你的问题,请参考以下文章