最近在学习过程当中,总感受书本上 对于javascript 的一些知识点讲的比较乱,想要看一些视频教程来深刻理解一下,又发现大多数只是流于表面,阅读一些博客,又感受在关键部分彷佛总被简单的几句话带过,彷佛在搪塞读者, 因而决定趁周末静下心来好好捋一下这部份内容。javascript
javascript运行过程、预编译、执行期上下文、做用域、做用域链、当即执行函数、闭包 以上的知识点就像一个有方向的圆环,在掌握每个知识点以前彷佛都得先了解其它某个或者某些知识点,让人不知从哪提及。在捋了半天关系以后,决定按照本文的顺序总结。java
javascript 运行分3个步骤:数组
首先,什么叫预编译? 讲解预编译以前,首先须要知道 变量提高 和函数提高bash
所谓提高就是指,变量或者函数在声明以前就使用它,举个例子:数据结构
<script>
console.log(a)
var a=234;
test();
function test(){
console.log('good');
}
</script>
复制代码
应该有人会发现问题,以下代码闭包
<script>
console.log(a)
var a=234;
function a(){
return a;
}
</script>
复制代码
输出的是啥?其实就是涉及到变量和函数哪一个先提高的问题是吧 函数
注意,不要根据结果来判断 函数后提高,上面的结果不是由于有什么前后提高的说法,而是由于预编译过程,要想搞明白上面的原理,继续往下看: 提高只是预编译过程当中的两个现象,要理解预编译,还须要理解一些东西学习
一、imply global (暗示全局变量):任何变量,若是该变量未经声明就赋值,则此变量为全局对象全部,注意,是任何,以下面语句中的 a 和 cui
a = 1;
var b = c = 3;
复制代码
二、一切声明的全局变量,均为window的属性spa
<script>
function test(){
var a = b = 123;
}
test();
var c = 100;
</script>
复制代码
b未经声明就赋值,注意上面说的是任何变量,未经声明就赋值,即为全局对象全部,也就是不须要考虑它是在哪里赋值的,反之 a 是在函数内声明赋值的,所以它的做用域就是函数内部,在外面访问不了,因而输出undefined,而 c自己就是在全局下声明 的,天然为全局对象全部。好,前面的理解了,接下来看到预编译
预编译发生在函数执行前一刻
一个例子搞懂预编译四部曲,注意搞懂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 (){}; //这句也不看,由于预编译过程已经提高了
}
复制代码
结果以下:
上面说函数在执行的时候会产生一个独一无二的执行期上下文,那么,在整个全局下,每每不仅有函数,还有其它的东西,好比全局变量之类的,因此,在全局下也有一个执行期上下文 ,也是个对象 (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个函数体, 每一个函数体的做用是打印一个值 注意,只是返回函数体,不是返回函数的执行结果 myArrj才是执行
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数组的每个元素赋值,也就是一个函数的引用 这个函数的功能是打印一个值,也就是上面提到的,系统怎么识别语句的问题,此时打印语句没有被执行 当函数在外面执行的时候,就是myArrj时,也就是打印函数的执行,此时它们去访问 j值, 可是它们本身的AO里没有 j变量,因而沿着做用域链去访问 j值,也就是当即执行函数执行时产生的AO, 而10个当即执行函数产生的AO是不同的,里面的j值分别是0-9,因此打印出0-9
闭包会致使多个执行函数共用一个公有变量,若是不是特殊须要,应尽可能防止这种状况发生。