ES-预编译过程详解

前言

这期分享javascrit的预编译的内容,这里面涉及到很多知识点,概念。结合自己的理解,再加上实例,做一次完整总结。主要内容有:整个编译流程介绍。在这个之上还会涉及到,作用域,闭包,以及this等知识点。

脚本预编译

编译流程:

—> 脚本:
创建全局对象GO(window) ( 上下文)
加载脚本文件
预编译:
* 找出所有的变量声明,按照变量名加入全局对象,如果已经存在,忽略该变量声明。
* 找出所有函数声明,按照函数名加入全局对象,若果已经存在同名变量和函数,替换。(如果有变量声明和函数声明的,不管两者出现的先后位置如何,函数都会优先替换与他同名变量。)
* 非声明不予理睬。(预编译阶段除了变量,函数声明,其他语句待处理)

解释-执行

下面用实例拆解每一个编译步骤:

let a = i;
function b(xx) {
	let xx = 'abc';
	funciton xx(yy){
		let xx = 1;
    }
};

let c = function() {};
function a () {};
aa = 15;
b(100);

创建全局对象GO,加载全局变量
	window.a -> undefined
	window.b -> function() {}
	window.c -> undefined
	window.a -> function(){}

//预编译完成 开始执行
window.a -> 1,
window.b -> function(){}
window.c -> function(){}
window.a - 1	//	
window.aa -> 15;

这里分析编译时变量函数声明,加载到全局过程

函数的预编译-函数调用

函数调用:
创建活动对象AO上下文(object)
预编译:
scope-chain
初始化arguments
初始化形参,类数组中的值传给形参。
加入全局变量
加入函数声明。
this 初始化。
函数执行。

结合上面列子继续分析函数预编译过程

let a = i;
function b(xx) {
	let xx = 'abc';
	funciton xx(yy){
		let xx = 1;
    }
};

let c = function() {};
function a () {};
aa = 15;
b(100);

//创建全局对象GO,加载全局变量
	window.a -> undefined
	window.b -> function() {}
	window.c -> undefined
	window.a -> function(){}

//预编译完成 开始执行
	window.a -> 1,
	window.b -> function(){}
	window.c -> function(){}
	window.a - 1	//	这里复制声明,覆盖了函数声明,因此结果是 ---> 1
	window.aa -> 15;
//调用函数,调入函数体内,函数预编译。
生成AO,
AO-object.xx: 'abc',
AO-object.xx:function(){}
//由于作用域的提升规则,实际编译结果是:
AO-object-xx:'abc',

执行结果
.....

作用域(scope)

js中的作用域是基于函数函数级别的,没有块级别的作用域概念。(es5是这样),换句话说,就是进入函数或者退出函数,函数的作用域都会发生变化。
它是用于查询变量的一套规则。程序中函数声明位置,就出现了作用域。作用域不是单独的存在的,他像一条链,名为作用域链,存在每一个函数声明中。我们在任何地方引用变量时,就会去遍历作用域链查找变量。

变量提升

变量的声明位置,与作用域紧密联系. 变量(函数)在程序中出现的位置不同,那么执行结果也会不同。其中原因就是变量提升。

看一段程序。

	console.log(a)	//undefined
	var a = 2;		

键入控制台执行,可以看到结果为 undefined,这里并不是报错,refrenceERROR. a is not defined, 而是 a = undefined。为什么会这样,我们企图在调用一个未定义变量,难道不应该报错吗?为什么会这样?
这里就是变量提升了,准确的说在预编译结束后,未初始化变量就引用变量。

函数优先

首先说,函数和变量都会被提升。当程序中函数和变量重名时,函数优先提升。具体是个什么效果呢,实例来。

foo();	//1
var foo;
function foo() {
	console.log(1);
}

foo = function() {
	console.log(2);
};

//这里结果是1而不是 2 ! 同样是提升。

//这段程序重写为

	function foo() {
		console.log(1);
	}
	foo();
	var foo = function() {
		console.log(2);
	};

重要说明:程序中避免出现变量重名,否则程序会更加难以维护。

作用域链(scope-chain)

什么是作用域链,简单来说就是各函数作用域(AO+ GO)像串珠子一样,串连在一起,形成的一条链。它的作用是,在执行环境内,通过作用域链查找变量和函数。

函数作用域链的生成
  • 每个函数在定义(函数声明或者函数表达式)时,会拷贝其父亲函数的作用域链。
  • 函数在调用时,生成的AO然后将AO压入作用域链的栈定。

实例拆解分析,如下是模拟的一个具体的从脚本编译到执行的过程。

 let sToken = 'g';
 function foo() {
    let sToken_a = 'a';
    function fn(){
         let sToken_b = 'b';
    }
    fn();
 }

foo();

//栈内存
// ST001:GEC-SC:HP001  脚本执行,生成作用域链(栈),作用域链(栈)是个对象,存在堆内。
// ST002:
// ST003:
// ST004:
// ST005:
// ST006:

// //堆内存
// HP001:GEC-SC:[ HP002-(GO) ](1) // 全局作用域链的第一次引用,他是一个数组,value指向GO的引用
// HP002:Go:{sToken:'g', this:window, foo:function(){ HP002-(GO) //GO作用域链拷贝 }} (3)
// HP003:fOO-ESC-SC: {HP002-(GO):window,HP004(foo-AO)}(1)    //函数foo scope-chain压栈
// HP004:foo-AO:{this:window,sToken_a:'a',fb:function(){ [HP002-(AO),HP004(fa-AO)] }} (2)
// HP005:fN-AO:{this:window,sToken_b:'b'} (1)
// HP006:fNEC-SC:{HP002(GO),HPOO4(FOO-AO),HP005-AO}(1) //压栈

改变作用域

js中使用with ,能骗过编译过程的词法分析,改变作用域.用处不多,了解即可。

let object = {
	name: 'shanshan'
}

with(object){
	console.log(name);
}
//这里的属性值name,实际在with形成的作用域中引用。

// 他可以改变对象的原有词法作用域,形成一个完全隔离的词法作用域,然后压入作用域栈链的栈顶。

附:

执行环境
函数调用会生成执行上下文,执行上下文对应着一个scope-chain。而scope-chain其本质来说是一个引用类型的栈对象。类似数组的一个结构。里面存有当执行环 境的上下文。

作用域链形成阶段会有一个压栈过程,他是存在于作用域链的一个栈(虚拟的栈,有js引擎创建并访问)。
js引擎遇到的每一个函数执行,都会形成单独的作用域,作用域链(scope chain).scope chain 独立每一个函数的运行期间,
意思是每一层函数调用,都会存在各自的scope chain栈。函数在执行时,会生成执行上下文,同事还会拷贝当前父辈的scope chain.
形成自己的scope chain,用于函数变量的查询。

执行环境分为:

  • 全局执行环境
    GO
    见到脚本时创建EC
    网页关闭时销毁。(所以说除非网页关闭,否则全局对象一直存在)
  • 函数执行环境
    AO
    从函数调用开始创建
    导函数退出时销毁(这里体现了立即执行函数的好处)

执行环境可以理解为当前的一个作用域,AO或者GO

作用域链应用

  • 效率:
    尽量减少使用考经上层的变量,提高查找效率。
  • 重名:
    减少不同层次的变量重名
    避免函数名,变量重名。

函数执行完时,AO一定会销毁吗?接下来才是主角登场。。。。

闭包

函数的AO通过scope-chain 相互连接起来,使得父函数体内的变量可以保存在子函数的AO中.这一效果称之为闭包。
首先见识一下闭包:

function outer(){
    let scope = 'outer';
    function inner(){
        return scope;
    }
    return inner;
}
let fn = outer();	//outer

console.log(fn( ));	//这里我们意外拿到了已经’销毁‘函数的变量 scope, oh! my god

闭包都有哪些神奇之处呢,一起看看

  • 实现共有变量的存储
  • 缓存存储结构
  • 封装。实现属性私有化
  • 模块化开发,防止全局变量污染。

//实现共有变量,比如累加器

 function add(){
   let count = 0;
   function addAction(){
        count++;
        console.log(count);
        return count;
    }
     return addAction;
 }

 let myAdd = add();
 myAdd();
 myAdd();
 myAdd();

//缓存存储结构,把父函数多个返回,用数组返回。

 function add(){
     let count = 0;
     function addAction(){
        count++;
         console.log(count);
         return count;
      }
    function clearAction(){
        count = 0;
         console.log(count);
         return count;
     }
     return [addAction,clearAction];
}

 let myAdd = add();
myAdd[0]();
myAdd[0]();
myAdd[1]();
myAdd[0]();
myAdd[0]();
//重写
 function add(){
     let count = 0;
     let adder = {
         addAction:function (){
            count++;
             console.log(count);
             return count;
         },
         clearAction:function (){
             count = 0;
             console.log(count);
             return count;
         }
     }
    return adder;
 }

 let myAdd = add();
 myAdd.addAction();
 myAdd.addAction();
 myAdd.addAction();
 myAdd.addAction();
 myAdd.addAction();
 myAdd.addAction();
myAdd.clearAction();
 myAdd.addAction();
 myAdd.addAction();
 myAdd.addAction();

以上便是闭包的实际应用。
还有一个案例个人觉得比较经典的问题,可谓始于闭包,终于闭包。

// function outer(){
//     let result = new Array();
//     for(var i = 0; i < 2; i++){
//         result[i] = function(){
//             return i;   //i = 2;
//         };
//     }
//     return result;
// }
// let fn1 = outer();
// console.log(fn1[0]());
// console.log(fn1[1]());

// 2 2

//解决方案
// function outer(){
//     let result = new Array();
//     for(var i = 0; i < 2; i++){
//         result[i] = (function(x){
//             return function foo(){
//                 return x;   //i = 2;
//             }
//         })(i);
//     }
//     return result;
// }
// let fn1 = outer();
// console.log(fn1[0]());
// console.log(fn1[1]());
//2.let
// function outer(){
//      let result = new Array();
//      for(let i = 0; i < 2; i++){
//          result[i] = function(){
//             return i;   //i = 2;
//          };
//      }
//      return result;
// }
// let fn1 = outer();
// console.log(fn1[0]());
// console.log(fn1[1]());

this用法总结

this是js当中,很不好掌握的一个知识点,由于它的多边性,使的使用它的人很不好把握去向。
不要方,如下几点,轻松掌握this。
1.脚本中,this初始化为window
2.普通函数中,this初始化window
3.在object中调用的函数,this被指定为第object,谁调用,指向谁。
//注意赋值:会隐式绑定
4.call/apply中,this可以被指定,被指定为第一参数。
5.在new构造函数中,this被指向正在创建对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值