一、Javascript执行环境
Javascript代码片段在运行时,js解释器会首先创建一个执行环境,这个执行环境就是定义"变量对象"(变量所属的对象)。执行环境中的所有变量、函数(数据值为函数的变量)都作为变量对象的属性:
var foo = "some text";
function bar() {
} //等同于下面
var bar = function () { };
那么 此处的变量对象就是:
{
foo : "some text",
bar : function () { },
}
1.全局变量对象
嵌入浏览器的Javascript,要在全局环境中执行的代码,它的变量对象是window。页面中所有(包含了不同的script中)的全局变量、程序顶层中的函数(非嵌套函数)都是window对象的属性,可以使用window.变量
或者window.函数()
来访问。
// 定义全局变量
var foo = "some text";
// 定义全局函数
function f() {
var bar = "hello world";
function g() {
}
return "这是全局函数f里面的内容";
}
// 查看全局变量对象 window
console.log(window);
// 使用变量对象访问全局变量
console.log(window.foo, window.f());
2.函数激活对象
函数在执行时 也会为函数体内的代码创建它们的执行环境,此时它有一个更加直观的称呼:“激活对象”(Active Object 一般简称AO,函数执行时的变量对象)。函数体内的局部变量、形参、嵌套的函数都是这个对象的属性。不同于在全局执行环境中的变量对象window,函数激活对象只在函数调用时产生,用户代码中无法直接引用它:
function f(x) {
var y = "hello";
function g() { ... }
}
}
f(1,2)
调用时 的激活的对象(执行环境的变量对象)fAO 相当于:
{
x : 1, // 形参
y : "hello", //
g : function () { ... }
}
}
二、 代码编译与执行
尽管JS被认为是"动态"或者"解释执行"的脚本语言,事实上它也是一门"编译型"语言(类似于C语言)。这是因为Javascript解释器
解析的过程和传统的编译语言非常相似,有些过程甚至更为复杂。
如果页面中存在有多个script定义的代码片段时,JS会首先读取一段要执行的script,然后编译该段代码,最后执行该段代码。
1.编译代码(拓展)
在程序执行之前,我们需要将源码(JS代码)中的高级语言转化成机器可执行的语言,例如二进制等,这个过程 称为"编译"。它的主要工作包括有词法分析(分词)、语法分析(解析)以及代码生成。
词法分析
词法分析是对代码字符串进行分析,分解出有意义的代码块 也叫"词法单元",例如 var bar = "test"
;可以分解为: var关键字、bar标识符、=等号运算、test的值,空格是否作为词法单元取决于它在这门语言是否有意义。
语法分析
接下来的语法分析是将词法单元流转化为一个代表了程序语法的树状结构流(一个由元素逐级嵌套的结构)称为"抽象语法树(AST:Abstruct Sytax Tree
)" 它可以用一个对象来表示:
AST对象表示法 :
{
type : "Program",
body : [
{
type : "VaribaleDeclaration", //语句类型
kind : "var", //语法关键字
declarations : [ //声明列表
{
type : "VariableDeclarator",//语句类型
id : {
type : "identifier",//标识符
name : "bar", //名字
},
init : {
type : "Literal", //原意
value : "test", //值
raw : "\"test\"" //转义值
},
},
]
},
{},
]
}
抽象语法树是语言跨平台交流的工具,可以用来生成目标代码以及代码检查和优化:语法检查,风格检查,格式化,高亮,错误提示,自动补全等等;很多IDE的语法提示借助于该工具。
代码生成
将AST转换为可执行代码的过程 称为代码生成(字节码或机器码)。抛开所有的细节 简单来说 编译阶段完成了变量的声明(分配内存)以及值的存储,但并不执行语句。
var bar;
function foo() {}
2.执行结果
联系上下文环境 执行语句,变量的赋值、函数调用等。例如:bar = "test"; foo();
。一个有意思的现象是:变量的声明和函数的定义是发生在编译阶段 它总在执行之前。也就是说 无论变量和函数在页面的出场顺序如何 在执行时它们总是有定义的,会出现声明提升。
代码一:
console.log(a); // undefined
var a = "javascript"; // 相当于
var a; // 提升到最上面
a = "javascript";
代码二:
a = 2;
var a;
console.log(a);
三、 错误处理
JS代码运行时的错误 分为编译错误和执行错误,其中编译的错误主要是语法错误(SyntaxError),而执行时错误 受上下文环境影响 可能性较多 它有引用错误(ReferenceError)、类型错误(TypeError)和范围错误(RangeError)等。无论何种错误它们都是Error的子类。
1) 编译和执行错误
当发生编译错误时 代码不执行;而执行时的错误 会影响错误之后的代码执行 而不影响前面部分的代码;总之,无论发生何种错误 一段script代码发生了错误 不影响另一段script的运行。
2) 查看错误提示信息
JS代码运行时的错误 在页面不显示 不像PHP
等其他语言 但可以在控制台查看;当发生错误时,JS会自动抛出一个错误对象(Error) 如果要处理错误 你只需要使用try/catch
来捕获该错误对象 就类似于Java/PHP
等的异常Exception
处理。
try {
console.log(a);
} catch (e) {
console.log(e); //查看错误对象
document.write(e.message); //错误提示信息
document.write(e.fileName); //对应脚本文件
}
3) 手动抛出错误异常
四、 严格模式
背景介绍
ECMAScript5
中新增了“严格模式”,这是ES语言发展过程中一道绕不过的坎。早在ES3升级ES4时,ECMA(语言标准化组织)就曾有想法将ES转变为强类型(静态类型)的语言。由于目标过于激进,各方分歧严重,该预案最终下马,ES4也就此终结。但在接下来的ES5和ES6等版本中,我们依然能看到它逐步细化该过程的影子。正如现在所看到了,“严格模式”开始对语言的使用环境有了更明确的规定。总的来说,"严格模式"通过严格限定某些行为的规则,将代码保持在一个更安全、更适当的规范集合内。这样的操作 有一个显而易见的好处,就是更加有利于javascript
引擎来优化用户的代码。
根据严格模式编译指示放置的位置,用户可以选择使用单独的函数、某段script代码或者整个文件来遵循严格模式,透过在代码片段的头部声明use strict
。可能令你感到不适的是这样的字符串声明在语法中确实极其少见。
function foo() {
"use strict";
//这段代码是严格模式
function bar () {
//这里还是严格模式
}
}
//foo.js
"use strict"; // 文件代码以严格模式运行
常见行为约束
对于严格模式具体限定的行为主要有以下部分:
① 变量必须声明后再使用,就是不能省略var
② 函数的参数不能有同名属性,否则报错
③ 不能使用with
语句
④ 不能对只读属性赋值,否则报错
⑤ 不能使用前缀 0 表示八进制数,否则报错
⑥ 不能删除不可删除的属性,否则报错
⑦ 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
⑧ eval不会在它的外层作用域引入变量
⑨ eval和arguments不能被重新赋值
⑩ arguments不会自动反映函数参数的变化
⑪ 不能使用arguments.callee
⑫ 不能使用arguments.caller
⑬ 禁止this指向全局对象
⑭ 不能使用fn.caller
和fn.arguments
获取函数调用的堆栈
⑮ 增加了保留字(比如protected、static和interface)
⑯ 其中,尤其需要注意this的限制。ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。
五、 符号类型
ES6中新加入了一个基本类型数据–符号类型。符号是具有唯一性的特殊值,用它来命名对象属性不会导致重名,避免属性值相互覆盖;Symbol()构造器返回symbol类型的值作为自定义符号,它比较特殊 不能带有new关键字。语法格式如下:
Symbol([description])
description 可选
可选的字符串,symbol的描述。可用于调试但不能访问symbol本身。
//使用Symbol构造器创建符号类型数据
var sym = Symbol();
console.log(typeof sym); //返回它的类型为symbol
console.log(sym.toString()); //返回symbol的描述,此处为symbol()
//Symbol()每次都会创建一个新的symbol类型
var sym1 = Symbol('foo'),
sym2 = Symbol('foo');
//Symbol("foo")不会强制字符串“foo”成为一个symbol类型,它每次都会创建新的 symbol类型
Symbol("foo") === Symbol("foo"); // false
Eg: 给对象创建Symbol类型属性,并用一个可选的字符串作为其描述
Eg: Symbols属性可以在控制台查看,但在 for...in
中不可枚举
Eg:使用Object.getOwnPropertySymbols()方法查找对象中的符号类型属性
Symbol类型的值具有静态属性和静态方法。它们往往代表了这些对象内部语言的行为,例如:
Symbol.iterator
属性返回一个对象默认的迭代器(自定义for…of操作);
Symbol.hasInstance
用于判断对象是否为某构造器的实例(自定义instanceof 运算);
Symbol.unscopables
用于对象with环境绑定中排除的属性名称。
六、 练习和面试题
- 简述全局变量对象和函数激活对象
- 如何看待javascript是动态编译语言
- 以下代码执行的结果是什么?
var foo = 10;
function foo() {
console.log("hello world");
}
foo();
-
如何进行在页面中查看代码运行时的错误
-
列举几个严格模式下的行为约束