JavaScript的函数进阶

函数进阶
1立即执行函数表达式


立即执行的函数表达式的英文全称为Immediately Invoked Function Expression,简称就为IIFE。这是一个如它名字所示的那样,在定义后就会被立即调用的函数。
使用IIFE来进行初始 化,这样不会污染到全局环境。
通过IIFE,我们可以对我们的代码进行分块。并且块与块之间不会互相影响,哪怕有同名的变量 也没问题,因为IIFE也是函数,在函数内部声明的变量是一个局部变量
全局作用域是指声明的变量可在当前环境的任何地方使用。
函数作用域则只能在当前函数所创造的环境中使用。
块级作用域是指每个代码块也可以有 自己的作用域。
用var声明的变量是不存在块级作用域的,所以即使在if块中用var声明变量,它也能在外 部的函数或者全局作用域中使用
解决这个问题有两种方法,第一:使用ES6中的let关键字声明变量,这样它就有块级作用域。 第二:使用IIFE


2变量初始化


1执行上下文
在ECMAScript中代码的运行环境分为以下三种:
・全局级别的代码:这是默认的代码运行环境,一旦代码被载入,JS引擎最先进入的就是这个环境
・函数级别的代码:当执行一个函数时,运行函数体中的代码。
・EvaI级别的代码:在EvaI函数内运行的代码。
・不管什么情况下,只存在一个全局的上下文,该上下文能被任何其它的上下文所访问到。也 就是说,我们可以在test的上下文中访问到全局上下文中的o ne变量,当然在函数test2或者 test3中同样可以访问到该变量。
・至于函数上下文的个数是没有任何限制的,每到调用执行一个函数时,引擎就会自动新建出 —个函数上下文,换句话说,就是新建一个局部作用域,可以在该局部作用域中声明私有变 量等,在外部的上下文中是无法直接访问到该局部作用域内的元素的。
对于执行上下文这个抽象的概念,可以归纳为以下几点:
・单线程
· 同步执行
・唯一的一个全局上下文
・函数的执行上下文的个数没有限制
・每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。
2函数上下文的建立与激活
每当我们调用一个函数时,一个新的执行上下文就会被创建出来。然而,在 js引擎的内部,这个上下文的创建过程具体分为两个阶段,分别是建立阶段和代码执行阶段。
建立阶段:发生在当调用一个函数,但是在执行函数体内的具体代码之前
•建立变量对象(arguments对象,形式参数,函数和局部变量)
•初始化作用域链
•确定上下文中this的指向对象
代码执行阶段:发生在具体开始执行函数体内的代码的时候
•执行函数体内的每一句代码
我们将建立阶段称之为函数上下文的建立,将代码执行阶段称之为函数上下文的激活。
变量对象
将整个上下 文看做是一个对象以后得到的一个词语。具体来讲,我们可以将整个函数上下文看做是一个对 象,那么既然是对象,对象就应该有相应的属性。对于我们的执行上下文来说,有如下的三个属 性:
变量对象,作用域链以及this
在函数的建立阶段,首先会建立arguments对象。然后确定形式参数,检查当然上下文中的函数 声明,每找到一个函数声明,就在variableObject下面用函数名建立一个属性,属性值就指向该 函数在内存中的地址的一个引用。如果上述函数名已经存在于variableObject(简称V0)下面,那 么对应的属性值会被新的引用给覆盖。最后,是确定当前上下文中的局部变量,如果遇到和函数 名同名的变量,则会忽略该变量。
在建立阶段,除了arguments,函数的声明,以及形式参数被赋予了具体的属性值 外,其它的变量属性默认的都是undefinedo并且普通形式声明的函数的提升是在变量的上面的。
3作用域链
所谓作用域链,就是内部上下文所有变量对象(包括父变量对象)的列表。此链主要是用于变量查 询。
公式作用域链(ScopeChain) = AO + [[scope]]
A0,简单来说就是VO, AO全称为active object(活动对象),对于当前的上下文来讲,一般 将其称之为A0,对于不是当前的上下文,一般被称为V0
[[scope]]:所有父级变量对象的层级列表(也被称之为层级链)
[[scope]],有一个一个非常重要的特性,那就是[[scope]]是在函数创建的时候,就已经被存 储了,是静态的。所谓静态,就是说永远不会变,函数可以永远不被调用,但是[[scope]]在创建 的时候就已经被写入了,并且存储在函数作用域链对象里面。


3闭包


1闭包基本介绍
在一个函数的外部,可以访问并使用它内部的变量
狭义的闭包
・形成闭包环境的函数能够被外部变量引用,这样就算它外部上下文销毁,它依然存在。
・在内部函数中要访问外部函数的局部变量。
优 点
・通过闭包可以让外部环境访问到函数内部的局部变量。
・通过闭包可以让局部变量持续保存下来,不随着它的上下文环境一起销毁。
2闭包的更多作用
1.封装变量
闭包可以帮助把一些不需要暴露在全局的变量封装成"私有变量"。
2.延续局部变量的寿命
3闭包和面向对象设计
过程与数据的结合是形容面向对象中的"对象"时经常使用的表达。对象以属性的形式包含了数 据,以方法的形式包含了过程。而闭包则是在过程中以环境的形式包含了数据。通常用面向对象 思想能实现的功能,用闭包也能够实现,反之亦然。


4递归函数


递归函数是一个一直直接或者间接调用它自己本身,直到满足某个条件才会退出的函数。

let numCalc = function(i){
if(i == 1)
{
return 1;
}
else{
return i * numCalc(i-1);
}
}
console.log(numCalc(4));//24

1•使用递归计算从m加到n
let numCalc = function (m, n) {
if (m === n) {
return m;
}
else {
return n + numCalc(m, m > n ? n + 1 : n - 1);
}
}
console.log(numCalc(100, 1));//5050
2•使用递归计算出某一位的斐波那契数
let numCalc = function (i) {
if (i == 1) {
return 0;
}
else if (i == 2) {
return 1;

else {
return numCalc(i - 1) + numCalc(i - 2); }
} console.log(numCalc(8));//13
3•使用递归打印出多维数组里面的每一个数字
let arr = [1, 2, [3, 4, [5, 6], 7, 8], 9, 10]; let test = function (arr) {
for (let i = 0; i < arr.length; i++) { if (typeof arr[i] == 'object') { test(arr[i]);
}
else { console.log(arr[i]);
}
}
};
test(arr);


5高阶函数


1高阶函数介绍
高阶函数(higher-order-function)指的是操作函数的函数,一般有以下两种情况:
・函数可以作为参数被传递
・函数可以作为返回值输出
2参数传递
1.回调函数
在Ajax异步请求的应用中,回调函数的使用非常频繁。想在Ajax请求返回之后做一些事情,但又 并不知道请求返回的确切时间时,最常见的方案就是把回调函数当作参数传入发起Ajax请求的方 法中,待请求完成之后执行回调函数。
一个函数不适合执行一些请求时,也可以把这些请求封 装成一个函数,并把它作为参数传递给另外一个函数,"委托"给另外一个函数来执行。
2.数组排序
sort()方法 封装了数组元素的排序方法。把可变的部分封装在函数参数里, 动态传入sor t()方法,使sor t()方法方法成为了一个非常灵活的方法。

for Each() , map() , eve ry() , some()等函数,也 是常见的回调函数。
3返回值输出
1.判断数据的类型
使用 Object,prototype.toString 来计算
Object.prototype.toString.call(obj)返回一个字符串
2. getSingle
4面向切面编程
AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,这些跟业务 逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来之后,再通 过"动态织入"的方式掺入业务逻辑模块中。这样做的好处首先是可以保持业务逻辑模块的纯净和 高内聚性,其次是可以很方便地复用日志统计等功能模块
在JavaScript中实现AOP, 都是指把一个函数"动态织入"到另外一个函数之中。


6高阶函数的其他应用


函数节流(throttle)或函数去抖(debounce),核心其实就是限制某一 个方法的频繁触发
1函数防抖
函数防抖的原理是将即将被执行的函数用setTimeout延迟一段时间执行。对于正在执行的函数和 新触发的函数冲突问题有两种处理,也分别对应了定时器管理的两种机制。
第一种是只要当前函数没有执行完成,任何新触发的函数都会被忽略
第二种是只要有新触发的函数,就立即停止执行当前函数,转而执行新函数
下面是一 个比较完整的防抖函数(debounce),该函数接受2个参数,第一个参数为需要被延迟执行的函 数,第二个参数为延迟执行的时间
2函数节流
数节流使得连续的函数执行,变为固定时间段间断地执行。关于节流的实现,有两种主流的实 现方式,一种是使用时间戳,一种是设置定时器。
1.使用时间戳
触发事件时,取出当前的时间戳,然后减去之前的时间戳(最一开始值设为0),如果大于设置的 时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。
2.使用定时器
触发事件时,设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执 行,然后执行函数,清空定时器,这样就可以设置下个定时器。

posted @ 2019-08-11 21:10  风雨载明  阅读(160)  评论(0编辑  收藏  举报