2021-08-09

web前端之javascript

递归、预编译
其实递归就是找规律和找出口的方法,他特别适合人的思维过程。

练习:用函数体写出n的阶乘。
function mul(n){ 
 if(n == 1 || n == 0){ 
 return 1; 
 } 
 return n * mul(n - 1); } 
mul();

备注:这里边没有传实参,实验的时候必须有实参,不然会报错。
解析:我们先来找规律,比如说实参传一个 5 进去,我们就来求 5 的阶乘,首先,我们写的这个函数他的功能就是求 n 的阶乘的,我们发现 5 的阶乘就是 5 乘以 4 的阶乘,4 的阶乘就等于 4 乘以 3 的阶乘,那么,n 的阶乘就是 n 乘以 n-1 的阶乘,我们这个函数就是求阶乘的,所以有 return n * mul(n - 1);好,我们来验证一下,mul(5)=5mul
(4),由于前边是 return,所以这么算肯定还没完,return 最后必须返回一个具体的值才可以,所以系统会继续算 mul(4)=4
mul(3),然后 mul(3)=3mul(2),mul
(2)=2
mul(1)……他会一直这么循环的算下去,所以递归必须找到一个出口,不然就是死循环,由于我们知道 1 的阶乘等于 1,所以我们用 if 语句表示当 i 等于 1 或 者 0 的时候返回 1,因为 0 的阶乘是 1,那么,这就可以了,反着推,mul(1)=1,mul
(2)=21=2,mul(3)=32=6,mul(4)=46=24,那 mul(5)=524=120.
注意:递归必须最后用 return,return 必须返回一个具体的值,所以他会一直循环的计算。

练习:写一个函数,实现斐波那契数列。
function fb(n){ 
 if(n == 1 || n == 2){ 
 return 1; 
 } 
 return fb(n - 1) + fb(n - 2); } 
fb();

备注:这里边没有传实参,实验的时候必须有实参,不然会报错。
解析:斐波那契数列就是第三位等于头两位的和,那第 n 位 fb(n)就等于 fb(n - 1)
fb(n - 2);,好,假如说我们求第 5 位,实参传 5,fb(5)=fb(4)+fb(3),fb。
(4)=fb(3)+fb(2),fb(3)=fb(2)+fb(1)……好,现在我们找出口,因为我们知道前两位是 1,所以当 n= =1 或者 n==2 时返回 1,即 fb(1)=1,fb(2)=1,那么fb(3)=1+1=2,fb(4)=2+1=3,fb(5)=3+2=5.

预编译的前奏

javascript执行三部曲:我们知道js是解释性语言,解释一行执行一行,但是在解释之前,他会有两个过程,语法分析和预编译,语法分析就是他会把整篇js代码扫描一边,看看有没有上面语法错误,例如少一个括号,标点错误等等,如果有语法错误,则一行都不执行,接下来进行的就是预编译。预编译结束之后才是解释一行执行一行。在学习预编译之前我们要了解的一些知识:
(1)比如说:
test();
function test(){
console.log(‘a’);
}

我们知道把test()写在下边的话,他也会执行,但是我们把test()写在上边他也会执行,输出a,我们js不是解释一行执行一行吗?为什么写在上面还可以执行呢?其实就是预编译起作用,再比如:
console.log(a);
var a=123;

我们如果把输出放在下面,输出123,但是反过来他会输出undefined,我们假如没有下面的var a=123就是变量未声明肯定会报错,但是有了之后为啥不报错?而是输出undefined?这其实也是预编译起作用。

函数声明整体提升
解释:不管你的函数声明写在那里,他在逻辑上都会把你提在最前面,所有前面的test()写在前面后面效果是一样的。

变量 声明提升
解释:比如上面的var a=123其实是两个过程,变量声明加赋值,var a;a=123;声明提升就是他会把变量声明var a提到逻辑的最前面,所以会输出undefined。

但是上面两种也不能完全解释,比如:
console.log(a);
function a(a){
var a = 234;
var a = function (){

}
a();
}
var a = 123;

这个时候 a 得啥?函数是 a,变量是 a,形参也是 a,而且不会报错,那么输出得啥?这就是那两句话解决不了的,那两句话只是把预编译过程中的两个现象抽象出来当做方法来用,其实那不是知识点,等我们学完预编译,这个问题轻松解决。在学习预编译之前,我们还要知道以下两个知识点:

imply global 暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局变量所有。
解释:假如我们直接 console.log(a)这叫变量未经声明就使用肯定会报错,但是我们 a = 10,这叫变量未经声明就赋值,系统不会报错,而且你输出 a 得 10.就好像这个变量被声明了一样,但是这个和声明的变量还是不一样。我们再来看,变量未经声明就赋值,此变量为全局变量,全局对象就是 window,这个 window 属于对象,对象上可以加一些属性,比如说 window.a=10,我们 console.log(window.a)就得到 10.好,现在我们单纯访问 a,即 console.log(a)也得 10.就是说未经声明就赋值的变量为全
局所有也属于 window 所有,其实全局对象就是 window,即 a=10 就是 window.a=10.

一切声明的全局变量,全是 window 属性。
解释:全局上的任何变量即使你声明了也归 window 所有,比如说你在全局 var b=234,那么你输出 window.b 也是 234,即全局 var b=234 就是 window.b=234。其实 window就是全局的域,比如说我们在全局 var a=123,当我们要输出 a 的时候去哪里拿呢?就去 window 里拿,其实你 var 一个 a 等于 123,就相当于在 window 里边挂了一个 a 等于123,所以你输出 window.a 也是 123,你下一次访问 a 的时候他就会去 window 里边找
看有没有这个 a。其实你在全局 console.log(a)访问的 a 就是 window.a。

比如说:
function test(){ var a = b = 123;
}
test();

变量赋值是从后往前的,所以这句的过程是先让 b=123 然后 var 一个 a 等于 b 的值,所以我们这里的 b 属于未经声明就赋值的变量,所以我们输出 window.b 得 123,输出window.a 得 undefined,因为只有声明的全局变量才属于 window,这里的 a 是局部变量。

函数预编译

预编译四部曲:
第一步:创建AO对象。
第二步:找形参和变量声明,将变量和形参名作为AO对象的属性名,值为undefined。
第三步:将实参值与形参统一。
第四步:在函数体里找函数声明,值赋予函数体。

例:
function fn(a){
console.log(a);○1
var a = 123;○2
console.log(a);○3
function a(){};○4
console.log(a);○5
var b = function (){};○6
console.log(b);○7
function d(){};○8
}
fn(1);

备注:为了方便起见,解析时提到那行就是上面的圈几。

解析:这里边又有函数声明,又有变量,而且都叫 a,都提升的话,到底谁再谁前边?
谁覆盖谁?这里边有一个覆盖的问题,所以这一块就是预编译发生的过程,预编译发生在函数执行的前一刻,所以等函数执行的时候,预编译已经发生完了。预编译就把这些矛盾调和了,那么,现在就这个例子,我们来讲预编译四部曲。
第一步:创建 AO 对象,AO 对象就是 Activation Object,它是活跃对象,我们可以理解成作用域,但是他翻译过来叫执行期上下文。创建完之后就是 AO{ }(通常第一步都是创建 AO 对象,不重要,重要的是下边的,以后解析时忽略第一步)
第二步:他会去找函数里的形参和变量声明,将变量和形参名作为 AO 的属性名,值为undefined。在此例中,形参是 a,然后第 2 行有变量声明是 a,第 6 行有个变量声明是 b(虽然后边是函数,但是前面属于变量声明),这里边有两个 a,只记一个,所以:
AO{
a:undefined, b:undefined
}
第三步:将实参值与形参统一。这里的形参是 a,实参是 1,上一步的 a 是 undefined,所以在这一步把 a 的值改为 1:
AO{
a:1, b:undefined
}
第四步:在函数体里找函数声明,值赋予函数体。第 4 行有一个函数声明 a,第 8 行有个函数声明是 d(第 6 行是函数表达式,不是函数声明),找到了之后,依然把函数名作为 AO 对象的属性名挂起来,值为函数体,所以这时 a 的值改为函数体,再挂个 d,值为函数体。
AO{
a:function a(){}, b:undefined, d:function d(){}
}
这样,AO 对象就创建完了,预编译就完了,现在来解释执行: 第 1 行:输出 a,去哪里找 a,就去 AO 对象里找 a(不是在下边的语句找 a,你写的语
句是给电脑看的,你想拿东西就必须在电脑定义的冰箱里拿,AO 就是那个冰箱。真正的存储机构是 AO,因为预编译就是把 AO 创建好方便你去用),所以第 1 行输出得function a(){}

第 2 行:var a = 123,其实这一句不完全执行,因为在预编译的第二步将变量和形参名作为 AO 的属性名,这里就是变量声明提升的过程,变量声明已经被提升上去了,你再声明就不用看了,因为已经看过了,所以第二句只剩下 a=123 没执行,执行后 a 的值改为 123:
AO{
a:123, b:undefined, d:function d(){}
}
第 3 行:输出 a,去 AO 对象里找,输出得 123.
第 4 行:在预编译时看过了,不用看了
第 5 行:输出 a 得 123
第 6 行:var b 看过了,执行 b = function(){}
AO{
a:123, b:function(){},
d:function d(){}
}
第 7 行:输出 b 得 function(){}
第 8 行:看过了
最后输出的结果:function a(){} 123 123 function(){}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值