一、函数自调用
引例:普通函数 和 自调用函数
//普通函数:
var a=100
function fn(){
console.log(a) //打印全局变量a=100
console.log(fn) //打印全局函数fn
}
fn()
//自调用函数:
var a=100
function fn(){
console.log(111) //打印111
fn() // 无限调用全局fn函数 ————函数的自调用
}
fn()
1.利用函数自调用来执行重复的逻辑,不满足临界条件时,结束自调用
/*思想:
if语句中条件成立后就结束自调用 否则一直执行else语句里的业务代码并自调用
*/
function fn () {
if(){
return 100 //满足条件就返回某个值 不再执行else语句 结束自调用
}
else{
console.log("业务")
fn() //自调用
}
}
fn()
案例:(10的阶乘)
function fn (n) {
if(n<=1){
return 1
}
else{
return n*fn(n-1)
}
}
var re=fn(10) //1*2*3...*9*10
console.log(re) //fn(10)==>10*fn(9)==>10*9*fn(8)==>10*9*8*fn(7)==>...==>10*9...2*fn(1)
案例:(递减求和)
//要求:2022+2018+2014+2010+....n(n>0)
function fn (n) {
if(n<=0){ //当n<=0时是终止条件 结束自调用
return 0
}
else{
return n+fn(n-4) //一直加上下一次调用函数的返回值 直到不满足临界条件
}
}
var re=fn(2022)
console.log(re)
案例:(递增求和)
//要求:128+130+132+134+136.....1000(不能超过1000)
function fn (n) {
if(n>=1000){ //临界条件
return 1000
}
else{
return n+fn(n+2) //自调用
}
}
var re=fn(128)
console.log(re)
案例:(遍历数组)
//假设只知道数组内部全是(数字和数组的混套),但不知道嵌套了几层
//要求打印出所有数字
var arr=[[1,2,3,4,[2,3,4,5],[3,4,5,6],[4,5,6,7]],[5,6,7,8,9,10],20,21]
function fn(arr){
// 遍历数组
for(var i=0;i<arr.length;i++){
if(typeof(arr[i])=="number"){ //如果数组元素是数字 则打印
console.log(arr[i])
}else{ //如果数组元素是数组 则再次遍历数组 直到遍历完全部数组
fn(arr[i])
}
}
}
fn(arr)
2.注意:fn标识符的问题
// fn标识符的问题 fn是函数定义时的名字函数体内部可以直接访问
var obj={say:function fn(n){
console.log(666)
if(n<0){
return
}
// say(n-1) 报错
//想用对象的方法访问但是没有加上对象 直接访问不合理 因为作用域内部和外部都没有say这个标识符
fn(n-1) //函数自调用
}}
obj.say(10)
console.log(obj.say.name) //打印"fn" 函数的名字
3.实参arguments
实参==>调用时传入的数据(两种写法 数据直接量 变量)
- arguments.length 实参的个数
- arguments.callee 运行的函数对象(解决匿名函数没有名字无法访问不能自调用的问题)
案例:
var obj={say:function(){
obj.say()
}}
(function(n){
if(n<=1){
console.log(1)
}
else{
console.log(n);
arguments.callee(n-1) //指向正在运行的函数 实现自调用
}
})(10)
//打印10 9 8 7 ... 1
二、预编译
1.局部预编译
函数调用时 是怎么运行代码的?
- 分析代码:检查符号是否正确 、词法分析...
- 隐式操作 ==> 局部预编译
- 运行代码: 预编译过的就不在运行
局部预编译:函数调用之后 代码运行之前 js引擎会对函数进行预编译
function fm(a) {
console.log(a) //100 传入的实参
var a=20
function fn () {}
console.log(a) //20 修改后的值
}
var b=100
fm(b)
// AO:{a:100==>20,fn:function fn () {}}
步骤:
1.函数每次调用时都会生成一个AO对象:执行期上下文对象
2.给AO对象添加成员
- 函数内部的局部变量和形参变量作为AO对象的成员 值为undefined(隐式声明提升)
AO:{a:undefined}
ao.a=undefined
ao.a=undefined //形参和局部变量名一样的时候 不影响
3.把传入的实参赋值给AO对象的属性 (拓展知识:arguments和形参变量相关的技术)
AO:{a:100} //把100传进去
4.局部函数声明,赋值
- 把局部函数的名字作为AO对象的成员名,把函数体赋值给AO对象 (局部函数的隐式声明提升)
AO:{a:100,fn:function fn () {}}
2.全局预编译
全局作用域运行代码时 也有预编译
console.log(a)
var a = 20
function fn() {
console.log(666)
}
步骤:
1.生成一个对象Global Object (GO) (特殊的AO)
GO:{}
2.把所有的全局变量设置为GO对象的属性名
GO:{a:undefined}
3.把所有全局函数的名字作为GO对象的成员名,把函数体赋值给GO对象
GO:{a:undefined,fn:function fn(){console.log(666)}}
4.看是否为浏览器环境中的js脚本,如果是浏览器 会执行异步 GO给window对象共享成员
5.执行代码
GO:{a:undefined,fn:function fn(){console.log(666)}}
console.log(a) //undefined
a=20
GO:{a:undefined==>20,fn:function fn(){console.log(666)}}
拓展:
不同的环境中运行js代码不一样
GO对象的成员全部浅拷贝给环境对象window (node.js环境中没有这一步)
拓展知识:关于访问成员
console.log(a) 访问的是GO对象的成员(作用域链中没有的成员 程序会报错 防止野指针出现)
console.log(window.a) 访问的是window对象的成员 不报错 原型链也没有就返回undefined