我只挑了一些我觉得有用东西的出来,那些老生常谈的内容不过多叙述.
作用域是什么
1.1编译原理
第一步 分词/语法分析(Tokenizing/Lexing)
分解字符串为词法单元:
var=2;
=> var, a, =, 2
词法单元生成器在判断a是一个独立词法单元/其他词法单元一部分时,调用的是有状态的分析规则,则该过程为词法分析,无状态为"分词".
第二步 解析/语法分析
将词法单元流(数组)转换成由元素节点逐级嵌套所组成的代表程序语法结构的树,即"AST",抽象语法树.
var a = 2;
的语法树如下:
variableDeclaration (var)底层
Identifier(a)分支1
AssignmentExpression(=)分支2
NumbericLiteral(2)分支2-1
第三步 代码生成
将AST转换为可执行代码的过程.(转换为机器指令);
1.2理解作用域.
编译器将代码分解为词法单元,再生成抽象语法树(语法分析&词法分析).
编译时,编译器查询作用域:是否已有同名变量存在于此作用域
是:忽视本次声明;
否:在该作用域创建一个新变量名为a.
编译器为引擎生成运行时所需代码.
引擎查询作用域:当前作用域集合内是否有a,是则使用,否则引擎会继续查找.
查找作用域链由引擎执行,作用域协助,但查找的结果仍取决于引擎执行的查找方法,分为LHS左查询,RHS右查询.一个赋值操作的左右侧知左查右,知变量查值,知父查子都是RHS,调用函数.赋值、知右查左为LHS,以下是一个示例:
function Tes(a) {Tes(2);}
RHS:查找Tes调用的值;
LHS:a=2
词法作用域
作用域分为词法作用域与动态作用域,词法作用域广为使用, JS也用.
以前说过编译器先进行词法分析(分词/词法化),词法作用域即词法分析阶段的作用域概念,其直接取决于代码中定义的块级作用域和变量的位置
.
让词法作用域在词法分析器处理过后根据词法关系保持原样是一种最佳实践.
作用域查找是由内部向外部逐级进行,找到即停.
遮蔽效应: 多层嵌套作用域内不同层内可定义同名标识符,内部标识符会遮蔽外部的标识符,前面说到编译器会到作用域找是否有同名变量,如果是则忽略声明继续编译.
eval和with可以欺骗词法解析使解析出的词法作用域改动,也就是解析完以后你又放出来一段代码,但这会导致词法解析时引擎针对你的代码做出的优化失效或受阻, 虽然有时能实现一些hack功能,但总的来说不是什么好主意.
函数作用域
基于函数划定的作用域,在js没有块作用域概念时广泛使用.
最小特权原则(最小授权原则、最小暴露原则):
软件设计中应最小程度地仅暴露必要内容,将其他内容"隐藏"起来,为了保护一些方法,防止其在其无法正常工作的位置被调用.也为防止命名冲突.
若以function关键字打头为函数声明,否则为函数表达式
(function foo() {})()
:表达式
settimeout(function() {})
:匿名表达式
推荐用函数表达式而非函数声明. 声明会污染其所在作用城.但也不推荐匿名表达式,不好读,报错后找不到具体位置不好调试,用具名表达式是一种最佳实践:
settimeout (function foo() {});
立即执行函数是一种函数表达式,简称IIFE (immediately Inroked Function.Expression),其无必要用具名,但一个函数名可以说明它在做什么,这是值得推广的实践. 写作:
(function() {}());
或
(function() {})();
均可
块作用域
函数作用域是大多数JS中普遍的设计方法,(现在了js有块作用域概念了),使用{}来显式创建一个块.
变量声明应靠近使用地,并尽可能本地化(最小授权原则)
用一对大括号把结构框起来即可形成一个块作用域.