高效的JS是如何锻造的-理论篇(一)

 如何写出高效的JS,被不同浏览器内核的JS引擎搞的手足无措,频繁的发生内存泄漏却没有一款称手的定位工具,看其他语言各种性能分析,游荡分析,定位工具灵活多变,再看JS心已经凉了一小半。本文从理论(JS语言的最底层的本质),实践(实际产品级开发)来寻求真正高效的JS之路。阅读本文需要有javascript的编程经验,ECMAScripting262第三版协议基础,基本的C++语言基础,堆栈的数据结构理论基础。 开篇引用ECMAScripting262的开篇介绍一下JS的渊源:

This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. 

现代的JS是基于Netscape javascript以及微软的JScript,最先始于IE3.0, netscape2.0,由Brendan Eich发明。而Ecma规范已经成为JS和AS的实现基础,但他也来源于最古老的JS

关于理论部分这里重点关注的是JS的几个概念包括原生的数据类型,比如对象,构造函数、作用域、原型链、上下文无关文法、闭包等以及一个JS解释器的具体实现--google的V8 javascript引擎。

对象(OBJECT)

ECMAScripting是基于对象的:语言的基础部分和宿主设施都是有对象提供,一个ECMAScripting是一组可通信的对象集合,而ECMAScripting对象都是一些未经排序的属性集合。(ECMAScripting262 3th)

ECMAScripting所描述的对象究竟与C++, JAVA, SmallTalk有何区别?

第一个重要的区别ECMAScripting是没有类的概念的,但是他支持构造函数(constructors)创建对象。这里所有构造函数都是对象,但并非所有对象都是构造函数。

第二个重要的区别ECMA的对象是基于原型继承的,每个构造函数都有一个 Prototype(原型)属性,被用于实现基于原型继承(prototype-based inheritance)和共享属性(shared properties)。

下图展示了基于原型继承的ECMA对象的继承与共享属性的关系,以下表述来源ECMAScripting262 3th:

CF 是一个构造函数(当然也是一个对象)。使用 new 表达式,我们创建了五个对象:cf1,cf2,cf3,cf4 和cf5。这些对象中的每一个都包含了名为 q1 和 q2 的属性。虚线表示隐含的原型关系;比方说,cf3 的原型是 CFp。构造函数 CF自己拥有两个属性,名为 P1和P2,它们对于 CFp,cf1,cf2,cf3,cf4 或 cf5 而言都是不可见的。CFp中名为 CFP1的属性被cf1,cf2, cf3,cf4 和 cf5(除了CF)共享,这样,CFp的隐含原型链中的所有属性没有名为q1,q2 或 CFP1 的。需要注意的是,CF 和 CFp 之间没有隐含的原型关联。

不同于基于类的面向对象语言,ECMAScript 中的属性可以通过给它们赋值的方式,把它们动态添加给对象。也就是说,构造函数不需要给所构造的对象的部分或全部属性命名或赋值。在下面的图表中,通过给 CFp 中的属性赋新值就可以给 cf1,cf2,cf3,cf4 和cf5 添加新的共享属性。

 

原型链(PROTOTYPE CHAIN)

根据ECMA规范,当我们查询某个构造函数的属性的时候,首先会在构造函数内部查找如果没有则会顺着constructor.prototype所指向的关联原型去查找,直到找到该属性位置,如果一直到最上层的原型依然没有查到则返回undefined值。这样一条路径就是我们所讲的原型链,以上图为例:CP->Cfp->cf1就是一条原型链。

作用链(SCOPE CHAIN)

作用链首先要介绍一下执行上文(Execution contexts)、作用域(scope)的概念。

执行上文(Execution contexts)

每次当控制器转到ECMAScript可执行代码的时候,即会进入到一个执行上下文。执行上下文(简称-EC)是一个抽象概念,ECMA-262标准用这个概念同可执行代码(executable code)概念进行区分。标准规范没有从技术实现的角度准确定义EC的类型和结构;这应该是具体实现ECMAScript引擎时要考虑的问题。活动的执行上下文在逻辑上组成一个堆栈。堆栈底部永远都是全局上下文(global context),堆栈顶部是当前(活动的)执行上下文。堆栈在EC类型的变量(various kingds of EC)被推入或弹出的同时被修改。

全局代码的执行栈

全局代码不包括任何函数体内的代码。在初始化(程序启动)阶段,ECStack是这样的:

 

 ECStack = [
   globalContext
 ]; 


函数代码

当进入函数代码(所有类型的函数),ECStack被推入新元素。要注意的是,具体的函数代码不包括内部函数(inner functions)代码。如下所示,我们使函数自己调自己的方式递归一次:

  (function
  foo(bar) {
   if (bar) {
     return;
   }
   foo(true);
 })();


那么,ECStack以如下方式被改变:

// first activation of foo
 ECStack = [
    functionContext
   globalContext
 ]; 
 // recursive activation of foo
 ECStack = [
    functionContext – recursively
    functionContext
   globalContext
 ];


每次返回存在的当前执行上下文和ECStack弹出相应的执行上下文的时候,栈指针会自动移动位置,这是一个典型的堆栈实现方式。一个被抛出但是没有被截获的异常,同样存在一个或多个执行上下文。当相关段代码执行完以后,直到整个应用程序结束,ECStack都只包括全局上下文(global context)。

Eval 代码 eval 代码有点儿意思。它有一个概念: 调用上下文(calling context), 这是一个当eval函数被调用的时候产生的上下文。eval(变量或函数声明)活动会影响调用上下文(calling context)。

 eval('var x = 10');
  (function foo() {
   eval('var y = 20');
 })(); 
 alert(x);
 // 10alert(y);
 // "y" is not defined 

 

ECStack的变化过程:

 

 ECStack = [
   globalContext];
 
  // eval('var x = 10');
 ECStack.push(
   evalContext,
   callingContext:
 globalContext); 
 
 // eval exited context
 ECStack.pop(); 
 
 // foo funciton call
 ECStack.push(
  functionContext); 
 
 // eval('var y = 20');
 ECStack.push(
   evalContext,
   callingContext: 
  functionContext);
 
  // return from eval
 ECStack.pop(); 
 
 // return from foo
 ECStack.pop(); 



看到了吧,这是一个非常普通的逻辑调用堆栈 。在版本号1.7以上的SpiderMonkey(内置于Firefox,Thunderbird)的实现中,可以把调用上下文作为第二个参数传递给eval。那么,如果这个上下文存在,就有可能影响“私有”(类似以private关键字命名的变量一样)变量。 

 function foo() {
   var x = 1;
   return
 function () {
 alert(x);
 };
 };
 var bar = foo();
 bar();// 1
 eval('x = 2', bar);
 // pass context, influence on internal
 var "x"bar();// 2 



以上是关于执行上下文的概念,而变量对象的描述中,一个执行上下文的数据(变量、函数声明和函数的形参)作为属性存储在变量对象中。变量对象在每次进入上下文时创建,并填入初始值,值的更新出现在代码执行阶段。

每个上下文拥有自己的变量对象 ,对于全局上下文,它是全局对象自身,对于函数,它是激活对象。

作用域链完全是内部上下文所有变量对象(包括父变量对象)的列表。此链用来变量查询。即在上面的例子中,“bar”上下文的作用域链包括AO(bar)、AO(foo)和VO(global)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值