网站开发进阶(四十九)由JS报“未结束的字符串常量”引发的思考_js跳转出现未结束的字符串常量

  • 函数声明:执行上下文的变量对象的一个属性,属性名和值都是函数对象创建出来的;如果变量对象已经包含了相同名字的属性,则会替换它的值。
  • 变量声明:执行上下文的变量对象的一个属性,其属性名即为变量名,其值为undefined;如果变量名和已经声明的函数名或者函数的参数名相同,则不会影响已经存在的函数声明的属性。

变量对象/活动对象(VO/AO)填充的顺序也是按照以上顺序:函数的形参->函数声明->变量声明;

在变量对象/活动对象(VO/AO)中权重高低也按照函数的形参->函数声明->变量声明顺序来。
如下代码:

var a=1;  
function b(a) {   
    alert(a);  
}  
var b;  
alert(b); // function b(a) { alert(a); } 
b();  //undefined 

以上代码在进入执行上下文时,按照函数的形参->函数声明->变量声明顺序来填充,并且优先权永远都是函数的形参>函数声明>变量声明,所以只要alert(a)中的a是函数中的形参,就永远不会被函数和变量声明覆盖。就算没有赋值也是默认填充的undefined值。

真题解析、进阶学习笔记、最新讲解视频、实战项目源码、学习路线大纲
详情关注公中号【编程进阶路】

第二部分:执行代码
经过“预解析”创建执行上下文之后,就进入执行代码阶段,VO/AO就会重新赋予真实的值,“预解析”阶段赋予的undefined值会被覆盖。

此阶段才是程序真正进入执行阶段,Javascript引擎会一行一行的读取并运行代码。此时那些变量都会重新赋值。

假如变量是定义在函数内的,而函数从头到尾都没被激活(调用)的话,则变量值永远都是undefined值。

进入了执行代码阶段,在“预解析”阶段所创建的任何东西可能都会改变,不仅仅是VO/AO,this和作用域链也会因为某些语句而改变,后面会讲到。

了解完Javascript的解析过程,最后我们再来了解下firebug控制台对Javascript的报错提示。

其实firebug的控制台也算是JavaScript解释器,而且他们会提示我们哪行出现了错误或者错误发生在哪个时期,语法检查阶段错误,还是运行期错误。

如下:

alert(var);// SyntaxError: syntax error 语法分析阶段错误 :语法错误 
var=1; // SyntaxError: missing variable name 语法分析阶段错误 :var是保留字符,导致变量名丢失 
a=b=v // ReferenceError: v is not defined 运行期错误: v 是未定义的JavaScript错误信息) 

有如此详细的错误提示,是不是就很快就知道代码中到底是哪里错了呢!

四、作用域链(Scope Chain)

作用域链是处理标识符时进行变量查询的变量对象列表,每个执行上下文都有自己的变量对象:对于全局上下文而言,其变量对象就是全局对象本身;对于函数而言,其变量对象就是活动对象。

4.1 作用域链以及执行上下文的关系

Javascript中只有函数能规定作用域,全局执行上下文中的 Scope 是全局上下文中的属性,也是最外层的作用域链。

函数的属性Scope是在“预解析”的时候就已经存在的了,它包含了所有上层变量对象,并一直保存在函数中。就算函数永远都没被激活(调用),Scope也都还是存在函数对象上。

创建执行上下文的 Scope 属性和进入执行上下文的过程如下:

Scope = AO + [[Scope]] //预解析时的 Scope 属性 
Scope = [AO].concat([[Scope]]); //执行阶段,将AO添加到作用域链的最前端

4.2 执行上下文定义的 Scope 属性变化过程

执行上下文中的AO是函数的活动对象,而Scope则是该函数属性作用域。当前函数的AO永远是在最前面的,保存在堆栈上,而每当函数激活的时候,这些AO都会压栈到该堆栈上,查询变量是先从栈顶开始查找,也就是说作用域链的栈顶永远是当前正在执行的代码所在环境的VO/AO(当函数调用结束后,则会从栈顶移除)。

通俗点讲就是:JavaScript解释器通过作用域链将不同执行位置上的变量对象串连成列表,并借助这个列表帮助JavaScript解释器检索变量的值。作用域链相当于一个索引表,并通过编号来存储它们的嵌套关系。当JavaScript解释器检索变量的值,会按着这个索引编号进行快速查找,直到找到全局对象为止,如果没有找到值,则传递一个特殊的undefined值。

是不是又想到了一条JavaScript高效准则:为什么说在该函数内定义的变量,能减少函数嵌套,能提高JavaScript的效率?因为函数定义的变量,此变量永远在栈顶,这样子查询变量的时间变短了。

4.3 作用域的特性

保证有序的访问所有变量和函数;
作用域链感觉就是一个VO链表,当访问一个变量时,先在链表的第一个VO上查找,如果没有找到则继续在第二个VO上查找,直到搜索结束,也就是搜索到全局执行环境的VO中。这也就形成了作用域链的概念。

var color="blue";  
function changecolor(){   
    var anothercolor="red";   
    function swapcolors(){  
        var tempcolor=anothercolor;   
        anothercolor=color;   
        color=tempcolor; // Todo something 
    }   
    swapcolors();  
}  
changecolor();//这里不能访问tempcolor和anothercolor;但是可以访问color; 
alert("Color is now "+color);  

五、原型链查询

在介绍“预解析”阶段时,我们有提到当创建函数时,同时也会创建原型链对象。原型链对象在作用域链中没有找到变量时,那么就会通过原型链来查找。

function Foo() {   
    function bar() {   
        alert(x);   
    }   
    bar();  
}  
Object.prototype.x = 10;  
Foo(); // 10 

上例中在作用域链中遍历查询,到了全局对象了,该对象继承自Object.prototype,因此,最终变量“x”的值就变成了10。不过,在原型链上定义变量对象有些浏览器不支持,譬如IE6,而且这样增加了变量对象的查询时间。所以变量声明尽量在调用函数AO里,即在用到该变量的函数内声明变量对象。

作用域是在“预解析”时就已经决定的,所以作用域被叫做静态作用域,而在执行阶段的则被叫做动态链,因为在执行阶段会改变作用域链中填充的值。
代码执行阶段对“预解析”的改变

创建了函数就有一个闭包,而变量是在函数的执行上下文保存起来的静态作用域链上查询的,而当前函数内创建的的变量会在函数结束后就被销毁。而闭包就能在函数结束之后还能让这些变量一直保存在作用域链上。

六、自由变量

自由变量是指在函数中使用的,但既不是函数参数也不是函数局部变量的变量。

七、闭包

理论角度:所有函数都是闭包。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量。
应用角度:当在代码中引用了自由变量,即使创建它的上下文已经销毁,此变量还能访问。

ECMAScript标准中,同一个上下文创建的闭包(理论上的闭包)是共用一个作用域的,也就是说在闭包中对其中变量修改会影响到其他闭包对其变量的读取。

所谓创建额外的闭包就是创建函数,不管是匿名函数、函数表达式、函数声明(除了构造函数),只要能创建作用域链就行,与函数类型无关,然而创建额外的函数不是唯一的方法。
遍历最外层代码:

for (i = 0; i < len; i++) {...}

使用函数创建闭包:

方法一:使用函数闭包获取索引值

(function(i){
    lists[i].onmouseover = function () {
        alert(i); 
    };
})(i);

直接将匿名函数赋值给事件,创建额外的函数来创建多个作用域。

方法二:使用函数闭包获取索引值

lists[i].onmouseover = (function (x) { 
    return function (){ 
        alert(i); 
    };
})(i);

利用return在闭包中返回,而闭包中返回的语句会将控制流返回给调用上下文,也就是返回几个就有几个执行上下文,相应的作用域链也有相同的个数。

使用try { ... } catch (ex) { ... }改变作用域链:
try-catch改变作用域链的原理跟with一样,try 部分包含需要运行的代码,而 catch 部分包含错误发生时运行的代码。如下:

var array = null;  
    var x=10;  
    try {   
        document.write(array[0]);  
    } catch(x) {  
        x =20;   
        document.writeln("catch内的x值"+x); //20 
    }  
    document.writeln("catch外的x值"+x); //10 

try-catch语句在代码调试和异常处理中非常有用,因此不建议完全避免。

八、JavaScript解析引擎与ECMAScript是什么关系

JavaScript引擎是一段程序,我们平时写的JavaScript代码也是程序,如何让程序去读懂程序呢?这就需要定义规则。比如,之前提到的var a = 1 + 1;,它表示:

  • 左边var代表了这是申明(declaration),它申明了a这个变量;
  • 右边的+表示要将1和1做加法;
  • 中间的等号表示了这是个赋值语句;
  • 最后的分号表示这句语句结束了;

上述这些就是规则,有了它就等于有了衡量的标准,JavaScript引擎就可以根据这个标准去解析JavaScript代码。那么这里的ECMAScript就是定义了这些规则。其中ECMAScript 262这份文档,就是对JavaScript这门语言定义了一整套完整的标准。其中包括:

  • var,if,else,break,continue等是JavaScript的关键词;
  • abstract,int,long等是JavaScript保留词;
  • 怎么样算是数字、怎么样算是字符串等等;
  • 定义了操作符(+,-,>,<等);
  • 定义了JavaScript语法;
  • 定义了对表达式,语句等标准的处理算法,比如遇到==该如何处理;
  • ⋯⋯

九、JS兼容性问题的原因

标准的JavaScript引擎就会根据这套文档去实现,注意这里强调了标准,因为也有不按照标准来实现的,比如IEJS引擎。这也是为什么JavaScript会有兼容性的问题。至于为什么IEJS引擎不按照标准来实现,就要说到浏览器大战了,这里就不赘述了,自行Google之。

所以,简单的说,ECMAScript定义了语言标准,JavaScript引擎根据它来实现,这就是两者的关系。

十、JavaScript解析引擎与浏览器是什么关系

简单地说,JavaScript引擎是浏览器的组成部分之一。因为浏览器还要做很多别的事情,比如解析页面、渲染页面、Cookie管理、历史记录等等。那么,既然是组成部分,因此一般情况下JavaScript引擎都是浏览器开发商自行开发的。比如:IE9ChakraFirefoxTraceMonkeyChromeV8等等。

从而也看出,不同浏览器都采用了不同的JavaScript引擎。因此,我们只能说要深入了解哪个JavaScript引擎

十一、深入了解其内部原理的途径有哪些

搞清楚了前面三个问题,那这个问题就好回答了。主要途径有如下几种(依次由浅入深):

11.1 阅读JavaScript引擎工作原理相关的书

这种方式最方便,不过我个人了解到的这样的书几乎没有,但是Dmitry A.Soshnikov博客上的文章真的是非常的赞,建议直接看英文,实在英文看起来吃力的,可以看译本。

结尾

学习html5、css、javascript这些基础知识,学习的渠道很多,就不多说了,例如,一些其他的优秀博客。但是本人觉得看书也很必要,可以节省很多时间,常见的javascript的书,例如:javascript的高级程序设计,是每位前端工程师必不可少的一本书,边看边用,了解js的一些基本知识,基本上很全面了,如果有时间可以读一些,js性能相关的书籍,以及设计者模式,在实践中都会用的到。

html5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值