发表于: 2020-04-09 23:49:45
1 1744
今天完成的事情:今天学到了一下js预解析的详细的分析以及技巧
明天计划的事情:继续学习之前没有深入理解的内容以及加强angular的应用
遇到的问题:实际操作还是不多需要更多的练习以及更深入的理解
收获:预解析的一些内容重新理解了一下
在ES6之前,变量使用var声明,会存在变量的预解析(函数也有预解析)。ES6引了let和const,但是现阶段ES6并没有完全普及,而且很多比较老的代码都还是按照ES5的标准甚至是ES3的标准来书写的,要想得心应手的维护之前的代码个人感觉还是很有必要了解下js的预解析机制。
一、变量和函数在内存中的存在形式
JavaScript中的变量类型和其他语言一样,有基本数据类型和引用数据类型。基本数据类型包括:undefined、null、boolean、String、Number;引用数据类型主要是对象(包括{}、[]、/^$/、Date、Function等)。
以上的代码在内存中的模型为:
- 基本数据类型按照值来操作,引用数据类型按照地址来操作。
- 基本类型在栈区:
引用数据类型:
基本类型是直接存储在栈内存中,而对象是存储在堆内存中,变量只是持有该对象的地址。所以obj持有一个对象的地址oxff44,函数func持有一个地址oxff66。
在以上的代码的基础上再执行:
console.log(func);
console.log(func());
第一行输出的是整个函数的定义部分(函数本身):
上面已经说明了,func存储的是一个地址,该地址指向一块堆内存,该堆内存就保留了函数的定义。
第二行输出的是func函数的返回结果:
由于func函数没有返回值,所以输出undefined。
注意:函数的返回结果,return后面写的是什么,返回值就是什么,如果没有return,默认返回值是undefined。
二、预解析
有了以上的内存模型的理解之后,就能更好的了解预解析的机制了。所谓的预解析就是:在当前作用域中,JavaScript代码执行之前,浏览器首先会默认的把所有带var和function声明的变量进行提前的声明或者定义。
2.1. 声明和定义
var num = 24;
这行简单的代码其实是两个步骤:声明和定义。
声明:var num; 告诉浏览器在全局作用域中有一个num变量了,如果一个变量只是声明了,但是没有赋值,默认值是undefined。
定义:num = 12; 定义就是给变量进行赋值。
2.2. var声明的变量和function声明的函数在预解析的区别
var声明的变量和function声明的函数在预解析的时候有区别,var声明的变量在预解析的时候只是提前的声明,function声明的函数在预解析的时候会提前声明并且会同时定义。也就是说var声明的变量和function声明的函数的区别是在声明的同时有没同时进行定义。
2.3. 预解析只发生在当前的作用域下
打印结果
第一次输出num的时候,由于预解析的原因,只声明了还没有定义,所以会输出undefined;第二次输出num的时候,已经定义了,所以输出24。
由于函数的声明和定义是同时进行的,所以func()虽然是在func函数定义声明处之前调用的,但是依然可以正常的调用,会正常输出300。
三、 作用域链
先理解以下三个概念:
函数里面的作用域成为私有作用域,window所在的作用域称为全局作用域;
在全局作用域下声明的变量是全局变量;
在“私有作用域中声明的变量”和“函数的形参”都是私有变量;
在私有作用域中,代码执行的时候,遇到了一个变量,首先需要确定它是否为私有变量,如果是私有变量,那么和外面的任何东西都没有关系,如果不是私有的,则往当前作用域的上级作用域进行查找,如果上级作用域也没有则继续查找,一直查找到window为止,这就是作用域链。
当函数执行的时候,首先会形成一个新的私有作用域,然后按照如下的步骤执行:
如果有形参,先给形参赋值;
进行私有作用域中的预解析;
私有作用域中的代码从上到下执行
函数形成一个新的私有的作用域,保护了里面的私有变量不受外界的干扰(外面修改不了私有的,私有的也修改不了外面的),这也就是闭包的概念。
以上代码执行的时候,第一次输出total的时候会输出undefined(因为预解析),当执行func(100,200)的时候,会执行函数体里的内容,此时func函数会形成一个新的私有作用域,按照之前描述的步骤:
先给形参num1、num2赋值,分别为100、200;
func中的代码进行预解析;
执行func中的代码
因为在func函数内进行了预解析,所以func函数里面的total变量会被预解析,在函数内第一次输出total的时候,会输出undefined,接着为total赋值了,第二次输出total的时候就输出300。 因为函数体内有var声明的变量total,函数体内的输出total并不是全局作用域中的total。
最后一次输出total的时候,输出0,这里输出的是全局作用域中的total。
将代码作小小的变形之后,func函数体内的total并没有使用var声明,所以total不是私有的,会到全局作用域中寻找total,也就说说这里出现的所有total其实都是全局作用域下的。
四、 全局作用域下带var和不带var的区别
在全局作用域中声明变量带var可以进行预解析,所以在赋值的前面执行不会报错;声明变量的时候不带var的时候,不能进行预解析,所以在赋值的前面执行会报错。
打印结果
num2 = 12; 相当于给window增加了一个num2的属性名,属性值是12;
var num1 = 12; 相当于给全局作用域增加了一个全局变量num1,但是不仅如此,它也相当于给window增加了一个属性名num,属性值是12;
问题:在私有作用域中出现一个变量,不是私有的,则往上级作用域进行查找,上级没有则继续向上查找,一直找到window为止,如果window也没有呢?
获取值:console.log(total); --> 报错 Uncaught ReferenceError: total is not defined
设置值:total= 100; --> 相当于给window增加了一个属性名total,属性值是100
注意:JS中,如果在不进行任何特殊处理的情况下,上面的代码报错,下面的代码都不再执行了
剩下的明天再继续进行
评论