JS 预编译:
JS 在执行的时候分为以下三步:
1.语法分析
2.预编译
3.解释执行
1.预编译的特点
特点:1.函数声明整体提升,而对于变量而言,只有声明提升。
例如:
<script>
console.log(a); //undefined
console.log(b); // function b() {.....}
var a = 1;
function b () {
var c = 0;
}
</script>
2.任何未经声明而直接赋值的变量为全局变量:当声明一个全局变量的时候,实际上是定义了全局对象window的一个属性.
<script>
function test() {
var a = b =100;
}
test();
console.log(window.b); //100
</script>
2.预编译的过程
(预编译发生在函数执行的前一刻,分为以下四个过程)
1.创建AO对象(active object)(执行期上下文)。
2.寻找实参和变量声明,将变量和形参作为AO的属性名,赋值为undefined。
3.将实参和形参统一。
4.在函数体内找函数声明,并将值赋予函数体。
举一个例子:
<script>
function fn(a){
console.log(a); //function a () {}
var a = 123;
console.log(a); //123
function a () {
};
console.log(a); //123
var b = function () {
};
console.log(b); // function () {}
function d(){
};
}
fn(1);
</script>
预编译 过程如下:
AO{
a:undefined → 1 → function a() {}
b:undefined → function () {}
d:function d() {}
}
预编译完成后各变量如上,而在函数执行时,预编译发生的过程便不再执行:
再举一个例子:
当有全局变量存在时:预编译会先创建一个GO对象,也就是window对象
<script>
console.log(test); // function test (test) {....}
function test(test) {
console.log(test); //function test () {}
var test = 234;
console.log(test); //234
function test() {
}
}
test(1);
var test = 123;
console.log(test) //123
</script>
预编译 过程如下:
GO{
test:undefined
}
AO{
test:undefined → 1 → function test(){}
}
JS作用域问题:
[[scope]] 作用域链
function a() {
}
var glob = 100;
a();
a.[[scope]] 定义的时候产生一个 GO 放在自己的作用域链的顶端
当a执行的时候,会产生一个自己的AO 放在自己的作用域链的顶端,而GO会放在再后面,类似于数组
a.[[scope]] -- 0 - AO{}
a.[[scope]] -- 1 - GO{}
每个函数在执行完之后会销毁自己的执行期上下文
举个例子:
function a() {
function b(){
function c(){
}
c();
}
b();
}
a();
作用域链如下:
a 定义 a.[[scope]] --> 0 :GO{}
a 执行 a.[[scope]] --> 0 : AO{}
1 : GO{}
b 定义 b.[[scope]] --> 0:aAO{} //b在定义的时候继承a所产生的作用域链,
1:GO{}
b 执行 b.[[scope]] --> 0:bAO{}
1:aAO{}
2:GO{}
JS闭包问题
1.什么是闭包?
当内部函数被保存到外部来使用时,将会生成闭包。
先来一个例子:
<script>
function a () {
var num = 100;
function b(){
num ++;
console.log(num);
}
return b;
}
var demo = a()
demo() //101;
demo(); //102
a 定义 执行 生成执行期上下文 a.[[scope]] --> 0:AO
1:GO
b 定义 执行 生成执行期上下文 b.[[scope]] --> 0;bAO
1:aAO
2:GO
函数执行两次结果不一样是因为a执行完之后销毁了自己的执行期上下文,但是b在定义时继承了a的执行期上下文,依旧可以对a生成的AO进行操作。
2.闭包的特点
缺点: 闭包会导致原有的作用域链不释放,造成内存泄露(也可以理解为内存占有)
好处:1.实现共有变量 (eg:函数累加器)
function add(){
var count = 0;
function demo(){
count ++;
console.log(count);
}
return demo;
}
var myadd() = add ();
myadd(); //1
myadd(); //2
myadd(); //3
//可以测试一个函数执行了几次。
2.可以做缓存:(存储结构)
function eater(){
var food = “”;
var obj = {
eat : function (){
console.log(“i am eating” + food);
food = "";
}
push : function(myfood){
food = myfood;
}
}
return obj;
}
var eater1 = eater();
eater1("apple"); //i am eating apple.
eater1("banana"): //i am eating banana.
//一个对象封装了两个并列函数,相当于一个1对2的闭包。 闭包在这里充当了一个缓存的效果
3.实现封装,属性私有化。
4.模块化开发,防止污染变量。
(3,4学习的不够深,下篇博客补上)
3.闭包一个比较优秀的问题:
function test() {
var arr = [];
for (var i = 0;i < 10; i++)
{
arr[i] = function () {
console.log(i + " ");
}
}
return arr;
}
var myArr = test();
for(var j =0; j < 10; j++)
{
myArr[j]();
}
//上述问题会打印处0-9吗?
实际结果为 10 个 10;
相当于1对10的闭包,都相应的改变了继承来的AO,最后返回最终的结果。
如果说我就是想打印出0-9呢?
下一个知识点来了:
立即执行函数
举个例子:
{function a() {
.........此处省略十万行代码
}()}
立即执行函数,只要执行一次后便销毁,不会继续占有空间。
格式: (function () {} ())
(function add() {
var a = 1;
var b = 2;
console.log(a+b); //3
}())
add(); //undefined
//传参
(function add(a,b) {
console.log(a+b); //3
}(1,2))
add(); //undefined
立即执行函数的底层原理:对于立即执行函数而言,只有表达式可以被执行:
var a = function() {
console.log ("123"); //123
}
而 function(){
console.log(“123”;
}(); //不能执行
但是给其加上数学运算符就可以变为一个表达式从而顺利执行:
例如 + - () 等等。
//对于如上例子:
1. + function a() {
console.log("456");
}(); //456
2. - function a() {
console.log("789");
}(); //789
3. (function a() {
console.log("xiaolaji");
}()); //立即执行函数
问题解决:
而对于刚才打印10个10问题的闭包而言,即生成了1对10的闭包,10个函数公用了一个执行期上下文,导致作用域没有被释放,最终返回10个10的问题,利用立即执行函数可以很好的解决。
代码如下:
function test() {
var arr = [];
for (var i = 0;i < 10; i++)
{
(function (j){
arr[j] = function (){
console.log(j);
}
}(i)) //使用快速执行函数,相当于把问题变成了10对10的闭包。 最终打印 0 -9
}
return arr;
}
var myArr = test();
for(var j =0; j < 10; j++)
{
myArr[j]();
}