javascript面试必考知识点
栏目: JavaScript · 发布时间: 5年前
内容简介:最近在学习过程中,总感觉书本上 对于javascript 的一些知识点讲的比较乱,想要看一些视频教程来深入理解一下,又发现大多数只是流于表面,阅读一些博客,又感觉在关键部分似乎总被简单的几句话带过,似乎在搪塞读者, 于是决定趁周末静下心来好好捋一下这部分内容。javascript运行过程、预编译、执行期上下文、作用域、作用域链、立即执行函数、闭包 以上的知识点就像一个有方向的圆环,在掌握每一个知识点之前似乎都得先了解其它某个或者某些知识点,让人不知从哪说起。在捋了半天关系之后,决定按照本文的顺序总结。jav
最近在学习过程中,总感觉书本上 对于javascript 的一些知识点讲的比较乱,想要看一些视频教程来深入理解一下,又发现大多数只是流于表面,阅读一些博客,又感觉在关键部分似乎总被简单的几句话带过,似乎在搪塞读者, 于是决定趁周末静下心来好好捋一下这部分内容。
涉及知识点
javascript运行过程、预编译、执行期上下文、作用域、作用域链、立即执行函数、闭包 以上的知识点就像一个有方向的圆环,在掌握每一个知识点之前似乎都得先了解其它某个或者某些知识点,让人不知从哪说起。在捋了半天关系之后,决定按照本文的顺序总结。
javascript运行过程
javascript 运行分3个步骤:
- 语法分析:javascript 引擎对你的代码进行分析,通篇扫描分析,检查是否有低级语法错误
- 预编译:可以理解为,在内存中开辟一块空间,存放一些变量和函数 (这样讲有点抽象,不要着急,后面将单独深入讲解预编译)
- 解释执行:就是执行代码
预编译
首先,什么叫预编译? 讲解预编译之前,首先需要知道 变量提升 和函数提升
所谓提升就是指,变量或者函数在声明之前就使用它,举个例子:
<script> console.log(a) var a=234; test(); function test(){ console.log('good'); } </script> 复制代码
上面的 变量a 和函数 test 在声明之前就使用了,但是没报错,为什么,因为有预编译过程,在预编译过程,将变量和函的声明进行了提升。 为什么没有输出 234 但是输出了 good ? 注意,提升的时变量的声明,而不是定义 变量:声明提升 函数:函数声明整体提升
应该有人会发现问题,如下代码
<script> console.log(a) var a=234; function a(){ return a; } </script> 复制代码
输出的是啥?其实就是涉及到变量和函数哪个先提升的问题是吧
注意,不要根据结果来判断 函数后提升,上面的结果不是因为有什么先后提升的说法,而是因为预编译过程,要想搞明白上面的原理,继续往下看: 提升只是预编译过程中的两个现象,要理解预编译,还需要理解一些东西
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> 复制代码
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 (){}; //这句也不看,因为预编译过程已经提升了 } 复制代码
结果如下:
每个函数在执行时都会产生一个执行期上下文,也就是上面的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是函数,所以控制台输出结果是:
作用域和作用域链
首先在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
然后 a() 执行,产生一个 执行期上下文 aAO, 并放在作用域链的顶端,那么 a 的 作用域链为
a() 要完成执行,必然 b() 要执行完成,b函数创建时
b在执行时产生一个执行期上下文bAO,并将其放在作用域链的顶端,那么b的作用域链为
从上面的流程看,如果用一种数据结构去表示作用域链的话,用栈表示比较合适。每次新产生一个执行期上下文,就会放到自己作用域链的顶端,释放的时候也是从作用域链自顶向下释放执行期上下文,即后入先出。
立即执行函数
定义:此类函数没有声明,在一次执行过后即释放。适合做初始化工作。 只执行一次,执行之后就被释放(销毁)
创建方式
1.(function (){}());W3C建议 2.(function (){})() 复制代码
执行符号 :()
只有表达式才能被执行符号执行,被执行符号执行的函数会自动忽略函数名 因为执行后被释放了,函数名也访问不了了,所以直接去掉函数名也没什么区别
var test = 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
闭包的防范
闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 95%的技术面试必考的JVM知识点都在这,另附加分思路!
- 讲讲大厂面试必考的假设检验
- Go 面试必考题目之 method 篇
- Go面试必考题目之slice篇
- 2020 年,入职前端开发的必考点
- 前端面试必考题:React Hooks 原理剖析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Probability and Computing
Michael Mitzenmacher、Eli Upfal / Cambridge University Press / 2005-01-31 / USD 66.00
Assuming only an elementary background in discrete mathematics, this textbook is an excellent introduction to the probabilistic techniques and paradigms used in the development of probabilistic algori......一起来看看 《Probability and Computing》 这本书的介绍吧!