十年程序员必读,javascript面试必考知识点

最近在学习过程中,总感觉书本上 对于javascript 的一些知识点讲的比较乱,想要看一些视频教程来深入理解一下,又发现大多数只是流于表面,阅读一些博客,又感觉在关键部分似乎总被简单的几句话带过,似乎在搪塞读者, 于是决定趁周末静下心来好好捋一下这部分内容。

涉及知识点

javascript运行过程、预编译、执行期上下文、作用域、作用域链、立即执行函数、闭包 以上的知识点就像一个有方向的圆环,在掌握每一个知识点之前似乎都得先了解其它某个或者某些知识点,让人不知从哪说起。在捋了半天关系之后,决定按照本文的顺序总结。
javascript运行过程

javascript 运行分3个步骤:

语法分析:javascript 引擎对你的代码进行分析,通篇扫描分析,检查是否有低级语法错误
预编译:可以理解为,在内存中开辟一块空间,存放一些变量和函数 (这样讲有点抽象,不要着急,后面将单独深入讲解预编译)
解释执行:就是执行代码
预编译

首先,什么叫预编译? 讲解预编译之前,首先需要知道 变量提升 和函数提升

所谓提升就是指,变量或者函数在声明之前就使用它,举个例子:

        console.log(a)
        var a=234;
        test();
        function test(){
            console.log('good');
        }
    </script>
image.png

上面的 变量a 和函数 test 在声明之前就使用了,但是没报错,为什么,因为有预编译过程,在预编译过程,将变量和函的声明进行了提升。 为什么没有输出 234 但是输出了 good ? 注意,提升的时变量的声明,而不是定义 变量:声明提升 函数:函数声明整体提升

应该有人会发现问题,如下代码

<script>
        console.log(a)
        var a=234;
        function a(){
            return a;
        }
    </script>

输出的是啥?其实就是涉及到变量和函数哪个先提升的问题是吧


image.png

注意,不要根据结果来判断 函数后提升,上面的结果不是因为有什么先后提升的说法,而是因为预编译过程,要想搞明白上面的原理,继续往下看: 提升只是预编译过程中的两个现象,要理解预编译,还需要理解一些东西

1、imply global (暗示全局变量):任何变量,如果该变量未经声明就赋值,则此变量为全局对象所有,注意,是任何,如下面语句中的 a 和 c

a = 1;
var b = c = 3;

2、一切声明的全局变量,均为window的属性
<script>

    function test(){
          var a = b = 123;
      }
      test();
      var c = 100;
  </script>
image.png

b未经声明就赋值,注意上面说的是任何变量,未经声明就赋值,即为全局对象所有,也就是不需要考虑它是在哪里赋值的,反之 a 是在函数内声明赋值的,因此它的作用域就是函数内部,在外面访问不了,于是输出undefined,而 c本身就是在全局下声明 的,自然为全局对象所有。好,前面的理解了,接下来看到预编译

预编译四部曲

创建AO对象(执行期上下文,先理解成一个对象即可,存放属性和属性值)
找形参和变量声明,并将其作为AO对象的属性,将属性值设置为undefined
将实参值和形参相统一
在函数体里找函数声明(注意是函数声明,区分函数声明和函数表达式的区别),也作为AO对象的属性,属性值即为函数体
预编译发生时间

预编译发生在函数执行前一刻

一个例子搞懂预编译四部曲,注意搞懂AO对象内部属性值的变化:

<script>
        function  func(a){
            console.log(a);
            var a=123;
            console.log(a);
            function a(){};
            console.log(a);
            var b = function (){};
            console.log(b);
            function d (){};
        }
        func(1);
    </script>

我们一步一步进行四部曲:

//第一步:创建AO对象(执行期上下文,先抽象理解成一个对象即可,
                                存放属性和属性值)
    AO{
                
            }
//第二步:找形参和变量声明,并将其作为AO对象的属性,
        将属性值设置为undefined
     AO{
         a:undefined //形参 ,注意函数内部也有一个 var a 但是注意,
                    因为a 先是形参,一个对象是不能有两个同名的属性的,
                    所以第一步之后AO对象里的 a是找的形参a
         b:undefined //变量声明
     }

//第三步:将形参值与实参相统一
     AO{
         a:1 // 实参是1 ,自此之后不用关注a怎么来的,关注的是 a 值的变化
         b:undefined //变量声明
     }
//第四步:在函数体里找函数声明(注意是函数声明,
区分函数声明和函数表达式的区别),也作为AO对象的属性,属性值即为函数体

上面的函数体中就只有 a 和 d是函数声明, b是函数表达式,不是声明, 
AO对象里已经有属性 a了,因此只需将其属性值改成函数体,
再添加属性 d,将值赋为d的函数体,最终AO对象就是下面的情况
 AO{
     a:function a(){}
     b:undefined 
     d:function d (){}
 }

接下来执行函数 func(1),函数体如下,注意观察执行过程中AO对象中属性值的变化

function  func(a){
      console.log(a); //此时AO里的a为 function a(){},输出
      var a=123;      //将AO里的 a 值改成  123
      console.log(a); //此时 AO里的 a 值为123,所以输出123
      function a(){}; //这句不看,因为在预编译过程已经将它提升了
      console.log(a); //此时AO里的a值还是 123,所以输出123
      var b = function (){}; //将AO里的b值改成function (){} 
      console.log(b); //此时AO里的b值为函数体,因此输出一个函数体
      function d (){}; //这句也不看,因为预编译过程已经提升了
  }

结果如下:


image.png

每个函数在执行时都会产生一个执行期上下文,也就是上面的AO,它是一个对象

上面说函数在执行的时候会产生一个独一无二的执行期上下文,那么,在整个全局下,往往不只有函数,还有其它的东西,比如全局变量之类的,所以,在全局下也有一个执行期上下文 ,也是个对象 (global object) 简称GO ,本文以下部分所有一般的执行期上下文都简称为AO,全局下的执行期上下文都简称为GO,我们回到一开始讲的例子

<script>
        console.log(a)
        var a=234;
        function a(){
            return a;
        }
    </script>

GO的创建过程跟一般的AO是一样的,但是有一点区别,那就是既然是在全局下 的执行期上下文,也就不存在形参了,所以只有预编译四部曲中的第1,2,4步骤,也就是只有3个步骤

//第一步:创建GO对象
GO{
            
        }
//第二步:寻找变量声明
    GO{
          a:undefined
       }
//第三步:寻找函数声明,并将其值赋为函数体
    GO{
          a:function a(){ return a; }
       }

预编译完成,然后执行console.log(a) ,此时的a是函数,所以控制台输出结果是:


image.png

作用域和作用域链

首先在javascript中,我们说一切都是对象,函数也是对象,而且函数是第一类对象,被称作一等公民。对象,有属性,函数也有属性,有些属性是我们可以访问的,有些属性是确实存在但是确不可以被我们拿来访问的,举个例子

function test(){
}

比如属性 test.name test.prototype 分别表示函数名和函数原型,这两个属性是我们可以访问的,test.[[ scope ]] 属性就属于其中一个不可以被访问的属性,scope意为范围,[[ scope ]] 这个属性存放的就是函数的作用域。准确的说存放的是函数的作用域链。 那么什么是作用域,什么是作用域链呢? 作用域 :可访问变量,对象,函数的集合 作用域链 :[[ scope ]] 中存放着执行期上下文的集合,这个集合呈链式链接,我们把这种链式链接称为作用域链。 但看概念似乎还是有点抽象,首先,从语法上来解读一下作用域链的概念:首先,作用域链由很多执行期上下文组成,这些执行期上下文的又不是散乱的摆放,有一定的位置关系:链式链接。

这里又提到了执行期上下文,也就是上面提到的AO,那么具体什么是执行期上下文呢? 执行期上下文 :某个函数或全局代码的执行环境,该环境中包含执行代码需要的所有信息。可以简单的认为它就是一个对象 当函数执行时,会创建一个称为 执行期上下文 的对象,一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行期上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行期上下文,当函数执行完毕,它创建的执行期上下文会被销毁。 下面通过一个例子来理解作用域,作用域链,执行期上下文的关系。

可以看出,真正的执行期上下文比上面讲到的 AO其实内部存放的东西是更复杂的,上面的AO只是执行期上下文的一种抽象提取。

<script>
        function a(){
            function b(){
                var b=234;
            }
            var a=123;
            b();
        }
        var glob=100;
        a();
    </script>

从全局的角度来解读上面的代码:定义了一个函数 a ,声明了并赋值变量glob 。函数a已经定义了,那它就有上面提到的一个函数该有的属性,此时,a 的 [[ scope ]]属性是什么?此时的 a.[[ scope ]] 是一个全局的执行期上下文 GO


image.png

然后 a() 执行,产生一个 执行期上下文 aAO, 并放在作用域链的顶端,那么 a 的 作用域链为


a() 要完成执行,必然 b() 要执行完成,b函数创建时

image

b在执行时产生一个执行期上下文bAO,并将其放在作用域链的顶端,那么b的作用域链为

image

从上面的流程看,如果用一种数据结构去表示作用域链的话,用栈表示比较合适。每次新产生一个执行期上下文,就会放到自己作用域链的顶端,释放的时候也是从作用域链自顶向下释放执行期上下文,即后入先出。

立即执行函数

定义:此类函数没有声明,在一次执行过后即释放。适合做初始化工作。 只执行一次,执行之后就被释放(销毁)

创建方式

1.(function (){}());W3C建议
2.(function (){})()

执行符号 :()

只有表达式才能被执行符号执行,被执行符号执行的函数会自动忽略函数名 因为执行后被释放了,函数名也访问不了了,所以直接去掉函数名也没什么区别

    ...
}()
//函数声明:
function test(){
    console.log('a'');
}

//函数表达式举例
var x = function test(){
    console.log('a'');
}

函数声明前加个符号->变成表达式,如+ - ! !! && ||等 但是注意如果是逻辑运算符,操作数数量还得符合该运算符要求,如&& ||

! function test(){
    console.log('a'');
}

复制代码
例:

function(a,b,d){
    console.log(a+b+c);
}(1,2,3);
复制代码
系统不会执行,但是也不会报错 系统如何理解:

function(a,b,d){
    console.log(a+b+c);
}

(1,2,3);
//系统将其识别为正常的分开写法

闭包

闭包是指可以访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。

闭包的特性:

1.函数内再嵌套函数 2.内部函数可以引用外层的参数和变量 3.参数和变量不会被垃圾回收机制回收

经典例子:

function test(){
    var arr=[];
    for(var i=0;i<10;i++){
        arr[i] = function(){
            document.write(i+  " ");
        }
    }
    return arr;
}
var maArr=test() 
for(var j=0;j<10;j++){
    myArr[j]();
}

执行结果是? 10 10 10...10 //总共10个10 代码解读: test() 的结果是返回一个数组,数组存放的是10个函数体, 每个函数体的作用是打印一个值 注意,只是返回函数体,不是返回函数的执行结果 myArr j 才是执行

test执行结束的判断条件:i==10终止循环,函数执行完毕 返回10个函数体

arr[i] = function(){
    document.write(i+  " ");
}

上面的是一条赋值语句 系统在识别的时候只能读到函数的引用,而不管函数体内部的是什么,

arr[i] = function(){
   
}

只有在执行的时候才会去取i值, 10个函数体在外部执行的时候才去访问i,10个函数在执行的时候分别产生一个 执行期上下文 iAO, 也就是自己作用域链的顶端,然后访问i值准备将其打印, 但是自己的AO里面没有i变量,所以沿着作用域链去找test函数执行时产生的 执行期上下文 AO,也就是大家访问的都是同一个执行期上下文, 访问的都是同一个i,也就是10

那如何想要在自己想打印的时候打印出0-9呢? 运用立即执行函数:

function test(){
    var arr=[];
    for(var i=0;i<10;i++){
        (function (j){
            arr[j] = function(){
                document.write(j+" ")
            }
        }(i));
    }
    return arr;
}
var myArr=test() 
for(var j=0;j<10;j++){
    myArr[j]();
}

怎么理解: 返回的arr里面存的是 10个立即执行函数,也就是下面的函数

(function (j){
    arr[j] = function(){
        document.write(j+" ")
    }
}(i));

将i作为实参传给形参j ,在i从0-9的循环过程里,依次发生的是

arr[0] = function(){
    
}
arr[1] = function(){
    
}
...
arr[9] = function(){
    
}

注意,立即执行,执行的是 function (j){} 它里面的只是一条赋值语句,对arr数组的每一个元素赋值,也就是一个函数的引用 这个函数的功能是打印一个值,也就是上面提到的,系统怎么识别语句的问题,此时打印语句没有被执行 当函数在外面执行的时候,就是myArr j 时,也就是打印函数的执行,此时它们去访问 j值, 但是它们自己的AO里没有 j变量,于是沿着作用域链去访问 j值,也就是立即执行函数执行时产生的AO, 而10个立即执行函数产生的AO是不一样的,里面的j值分别是0-9,所以打印出0-9

闭包的防范

闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342