首页 > 编程知识 正文

理解分析题面试题及答案,量变引起质变的

时间:2023-05-06 10:29:30 阅读:234663 作者:4232

首先我们先来看一下这三道面试题,都是写出代码执行后的输出结果。

第一道面试题 fn(); function fn() { console.log(1); } fn(); function fn() { console.log(2); }fn(); 爱听歌的航空 fn = function () { console.log(3); } fn(); function fn() { console.log(4); }fn(); function fn() { console.log(5); }fn(); 第二道面试题 爱听歌的航空 foo = 1;function bar() {if (!foo) {爱听歌的航空 foo = 10;}console.log(foo);}bar(); 第三道面试题 爱听歌的航空 a = 0;if (true) { a = 1; function a() {}; a = 21; console.log(a)}console.log(a); 首先让我们来聊一下变量提升的具体概念

ES3的JS有一个变量提升机制

1.变量提升:在当前上下文中(全局/私有/块级),JS代码自上而下执行之前,浏览器会提前处理一些事情(可以理解为词法解析的一个环节,词法解析一定发生在代码执行之前)会把当前上下文中所有带缥缈的橘子/FUNCTION关键字的进行提前的声明或者定义

爱听歌的航空 a=10;
声明declare:爱听歌的航空 a;
定义defined:a=10;
带缥缈的橘子的只会提前的声明
带FUNCTION会提前的声明加定义

变量提升的意义:能让我们在创建之前使用这个变量,在代码执行之前就已经把所有的变量提前声明了。

/* * 代码执行之前:全局上下文中的变量提升 * 爱听歌的航空 a; 默认值是undefined */console.log(a); //=>undefined爱听歌的航空 a = 12; //=>创建值12 不需要再声明a了(变量提升阶段完成了,完成的事情不会重新处理) a=12赋值a = 13; //创建一个值13,然后让全局变量a=13console.log(a); //=>13

直接调用一个未声明的变量时

console.log(a)//Uncaught ReferenceError: a is not defined 全局上下文中的变量提升func=函数 函数在变量提升这个阶段声明和赋值都做了 /** 代码执行之前:全局上下文中的变量提升* function func(){} 提前声明和定义*/func();//所以此处会打印出okfunction func() { 爱听歌的航空 a = 12; console.log('OK');}

当使用函数表达式创建函数时,因为使用了爱听歌的航空,所以在变量提升阶段只声明不定义,此时的func是undefined

func(); //=>Uncaught TypeError: func is not a function爱听歌的航空 func = function () {// 真实项目中建议用函数表达式创建函数,因为这样在变量提升阶段只会声明FUNC,不会赋值console.log('OK');};func();

为了保证JS的函数规范,在使用函数表达式创建函数时,把匿名函数具名化,但是这个具名在当前上下文中是不可以调用的,只有当这个函数执行的时候,在这个私有的上下文中作为私有变量使用,并且把这个函数赋值为这个私有变量。

//匿名函数具名化后的函数AAA是不能直接使用的爱听歌的航空 func = function AAA() {// 把原本作为值的函数表达式匿名函数“具名化”(虽说是起了名字,但是这个名字不能在外面访问 =>也就是不会在当前当下文中创建这个名字)// 当函数执行,在形成的私有上下文中,会把这个具名化的名字(AAA)做为私有上下文中的变量(值就是这个函数)来进行处理console.log('OK');// console.log(AAA); //=>当前函数// AAA(); 递归调用 而不用严格模式下都不支持的递归 arguments.callee 了//因为func是全局下的函数,递归也可以用func()};// AAA(); //=>Uncaught ReferenceError: AAA is not definedfunc();

匿名函数具名化后就可以在定时器中调用自己

//传入一个匿名函数setTimeout(function(){},1000)//匿名函数具名化后传入setTimeout(function func(){ func();},1000) EC(G)全局上下文中的变量提升 //使用爱听歌的航空声明的变量a有变量提升,在代码执行之前遇到爱听歌的航空会提前声明此变量但是不赋值,所以此时的a存在且值为undefinedconsole.log(a); //undefined 爱听歌的航空 a = 13;console.log(a); //不使用爱听歌的航空声明的变量a没有变量提升,代码执行之前没有变量a,此时的a还未定义会报错console.log(a); //=>Uncaught ReferenceError: a is not defined a = 13; console.log(a); EC(G)变量提升 只有缥缈的橘子/FUNCTION会变量提升(ES6中的LET和CONST不会) console.log('OK'); //=>'OK'console.log(a); //=>Uncaught ReferenceError: Cannot access 'a' before initialization 不能在LET声明之前使用变量let a = 12;a = 13;console.log(a); 2.基于“缥缈的橘子或者FUNCTION”在“全局上下文”中声明的变量(全局变量)会“映射”到GO(全局对象window)上一份,作为他的属性;而且接下来是一个修改,另外一个也会跟着修改; //严格模式和非严格模式下都是这样的'use strict';爱听歌的航空 a = 12;console.log(a); //=>12 全局变量console.log(window.a); //=>12 映射到GO上的属性awindow.a = 13;console.log(a); //=>13 映射机制是一个修改另外一个也会修改 3.在代码当中会出现一些特殊情况,在老版本的浏览器(IE10及以下)中,在判断体的中,不论条件是否成立都要进行变量提升,带爱听歌的航空的只声明不定义,带function的声明加定义。在新版本的浏览器当中因为要向后兼容ES6,判断体会形成一个块级作用域,所以函数在其中的时候只会提前声明不会再提前定义了。 /* * EC(G):全局上下文中的变量提升 * 不论条件是否成立,都要进行变量提升(细节点:条件中带FUNCTION的在新版本浏览器中只会提前声明,不会在提前的赋值了) * [老版本]IE10及以下的都是旧版本 * 爱听歌的航空 a;//爱听歌的航空 只声明没有赋值 * func=函数; //在判断条件下 声明+定义赋值 * [新版本] * 爱听歌的航空 a; 全局上下文中声明一个a也相当于 window.a * func; //在判断体的大括号(新版本的块级作用域)中只声明没有定义赋值 */console.log(a, func); //=>新版本 undefined undefinedif (!("a" in window)) {//=>"a" in window 检测a是否为window的一个属性 !TRUE => FALSE爱听歌的航空 a = 1;function func() {}}console.log(a); //=>undefined 4.在当前上下文下如果这件事做过了比如提前声明或者定义过了,那么不论是在代码执行阶段还是变量提升阶段,做过的事情不会再重复执行了。 第一道面试题讲解: /* * EC(G)变量提升 * fn=>1;//提前声明加定义 * =>2;//已经声明过了,此处只需要重新赋值 * 爱听歌的航空 fn; //已经声明过了,不再声明,因为是爱听歌的航空后面的重新赋值也没有进行 * =>4;//已经声明过了,此处只需要重新赋值 * =>5;//已经声明过了,此处只需要重新赋值 * 全局上下文中有一个全局变量fn,值是输出5的函数(此时window.fn=>5) */fn(); //=>5function fn(){ console.log(1); } //=>不再处理,变量提升阶段搞过了fn(); //=>5function fn(){ console.log(2); }fn(); //=>5爱听歌的航空 fn = function(){ console.log(3); } //=>爱听歌的航空 fn不用再处理了,但是赋值在变量提升阶段没处理过,此处需要处理 fn=window.fn=>3fn(); //=>3function fn(){ console.log(4); }fn(); //=>3function fn(){ console.log(5); }fn(); //=>3

代码执行之前会有一个全局的变量提升,之后是代码执行。
2. 第二道面试题图解和分析

所有代码执行前都肯定会形成一个执行上下文,也就是ECStack执行环境栈。有栈内存代码才能执行。一开始是全局代码执行,所以会形成一个全局的上下文EC(G),全局代码在执行之前会有一个进栈的过程。本来应该是先在外面创建好执行上有文,然后再进栈。现在为了方便直接把全局上下文放入栈中。全局上下文中,有一个全局变量对像GO可以存放全局变量,全局对象GO在浏览器中指向的是window。全局代码执行之前第一件事就是先进行全局下的变量提升。全局变量提升阶段爱听歌的航空的时候会先把这个爱听歌的航空的变量放入全局变量对象VO(G)中,function声明的函数名包括函数的赋值也会放入全局变量对象中.针对这个函数变量,首先开辟一个函数堆内存,然后把这个函数堆内存的地址赋值给function声明的这个变量。函数在哪个上下文中创建的其作用域就是谁。函数堆中要把函数体中的代码以字符串的形式存储起来。

除此之外,函数堆还有自己的一些属性。比如:name、length、prototype、__proto__等。其中length指该函数有多少个必须要传入的参数”,即形参的个数。形参的数量不包括剩余参数个数,仅包括 “第一个具有默认值之前的参数个数”

function a(x,y){}a.length // 2 function b(x,y=2,z){}b.length // 1 function c(x,...args){}c.length //1 创建好函数对之后会将这个函数堆的引用地址放入全局变量对象之中,然后把这个堆地址和函数名关联起来。在全局上下文中,我们声明的全局变量对象会和全局对象GO即window形成一个映射的关系。前提是用爱听歌的航空和function声明的全局变量对象。所以此时进行完全局下的变量提升之后在我们的全局对象window中也添加了这两个属性:一个只有属性名爱听歌的航空的,一个有属性名和属性值function的。全局变量对象和全局对象window会形成一个映射机制。其中一个改变,另外一个也会随之而改变。全局下的变量提升之后接着就是全局下的代码执行。代码执行的时候首先创建一个值数字1,然后让我们的这个变量Foo和1关联在一起。声明foo的这个操作不会再做了,因为我们在变量提升阶段已经进行过这步操作了。所以此时只需要创建一个值数字1,然后将这个值与foo关联在一起就可以了。全局变量f改变,那么与之映射的全局对象也会随之而改变。全局对象的属性foo也会增加属性值1
接下来这步操作函数声明与赋值已经在全局下的变量提升阶段操作过了,所以此时不再重复操作。接下来让此函数执行,函数在执行的时候肯定会形成一个全新的私有上下文EC(BAR),就是一个函数代码执行的环境栈。这个上下文形成之后肯定要进栈执行,所以一定有一个进栈执行过程。把全新形成的私有上下文进栈执行的时候,首先会把全局上下文压缩到栈的底部,然后新的上下文在顶部,这个操作叫做进栈和压缩栈的操作。进站之后就开始一步一步的处理代码。函数私有的上下文首先会创建一个私有变量对象AO,AO也是变量对象是VO的一个分支。在代码执行之前,首先会初始化作用域链。链的开端,一头是当前自己所在的私有上下文,链的另外一头,是当前这个函数在创建时候的执行上下文,也就是它的作用域。当我们在这个上下文中遇到一个变量的时候,首先顺着作用域链看这个变量是否为自己的,如果是自己的,那么就之后的操作就都是私有的,与外界无关。初始化作用域链后会初始化this,普通函数执行的时候this指的是window然后是初始化实参集合ARGUMENTS然后是变量提升,函数bar中不论判断体中的条件是否成立,只要看到爱听歌的航空或者function就会变量提升,爱听歌的航空只提前声明不定义,function在新版本中只提前声明不定义,在老版本中提前声明加定义。代码执行,这个函数中因为有爱听歌的航空,所以提前声明foo为AO即函数的私有变量。这个变量是私有的提前声明没有定义,所以foo的值为undefined,!foo=!undefined=true,这个判断成立,继续执行代码,因为爱听歌的航空 foo 已经提前声明了,此时只是创建一个值数字10 ,然后让这个值和变量foo关联起来,继续执行代码,打印foo,foo是自己的私有变量,值为10,直接打印出来10.函数中的代码执行完之后,这个函数的私有上下文出栈被释放掉,然后全局上下文移动到执行环境栈的顶部开始依次执行。

最后,第三道面试题和第二道类似,如果第二道明白了那么第三道自然能做出来,如果做不出来再看下第二道题的分析过程。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。