let/const <-> 作用域类型
- 函数作用域内,提前使用了let定义的变量,报:Uncaught ReferenceError: Cannot access ‘a’ before initialization。因为变量有提升,但是没初始化填充
- 全局作用域内,提前使用let定义的变量,报:Uncaught ReferenceError: ttt is not defined。没有变量提升。
d8打印一下作用域信息:
可以看到函数作用域内,对于let/const的变量说:尚未分配,洞初始化操作被忽略/跳过
而全局作用域下直接先在 context[index] 中占位,但是此时。。i dont know。总之没有提升变量声明。
在类作用域下同函数作用域下报相同错误,其实很正常,因为js本质上没有类,class只是函数的一种语法糖
惰性编译与预扫描
虽然惰性编译函数作用域,但在编译全局作用域之前,会先对顶级函数作用域进行 预扫描。
eval
chromium源码中对eval作用域做了大量的处理,我看到其中一点是:
eval的作用域就是 最近的非eval的父级作用域下,但是非严格模式下,如果在eval中直接出现的变量(没用var/let/const声明),没有在其上层作用域中出现过, 则会透传到window下,造成内存泄漏。
关于各种eval嵌套(配合block/function/eval/catch scope),做了大量处理,看的我💔,后来都不知道读了啥
忽略/报错重复的声明
function a(e) {
console.log(e===arguments[0], e, a);
var e = 3;//这个声明合法,但是会被v8忽略再次声明,因为你在function scope里跟参数e重复了
//let e = 2;//直接报错,v8不让let,const重复声明; 而且const必须有初始化值
var a ;
console.log(e, arguments[0]);
}
a({});
catch scope
这个比较特殊,它的作用域就是 catch后面 圆括号里的范围,难道你没发现:
function test(){
var t = 'ok'
try {
var t = '33';
throw 1;
} catch (e=t) {//这么写运行时直接报错,Uncaught SyntaxError: Unexpected token '='
console.log(e);
}
}
test();
Find variable with (variable->mode() <= |mode_limit|) that was declared in
|scope|. This is used to catch patterns like `try{}catch(e){let e;}` and
function([e]) { let e }, which are errors even though the two 'e's are each
time declared in different scopes. Returns the first duplicate variable
name if there is one, nullptr otherwise.
查找在 |scope| 中声明的具有 (variable->mode() <= |mode_limit|) 的变量。
这用于捕获诸如 `try{}catch(e){let e;}` 和 `function([e]) { let e }` 之类的模式,
即使这两个 e 每次都在不同的scope内声明,这些模式也是错误的。
如果有,则返回第一个重复变量名,否则返回 nullptr。
来个冗杂的示例
不能调试,硬读chromium真的太难了,。。细节上还是没太懂,怎么查找变量插入声明覆盖值的
只能依据实际情况配合那些源码里的名字,望文生义了,淦,以后一定要想办法调试起来。!!
//with内部如果使用了未声明的变量,就会从上层作用域里找,最多找到native scope
// 总之在所能找到的最上层scope添加这个变量声明;(前提:所过之处,没有同名词法变量)
//如果with的对象内没有该字面量函数,并且该变量没有使用声明类型var/let/const :
//则该变量在with-block scope内先插入一个let g声明;
//然后再一直向外层scope找g,一直找到最近的终止层scope;期间若遇到var声明同名变量,
//则沿用这个var初始声明,并插入var g undefined在终止层scope。
//最后,执行到fn g时,就把这个fn重新赋值给 f 里的g
//但如果终止层scope内(后代scope也算)有g同名的词法声明(let/const), 则不做任何操作
// 即这个函数字面量只能在with内生效
let obj = {g: 'ccgg'};
function f() {
let g = 'what_g'; // prevent sloppy block function hoisting.
function m(){
let t = 3;
console.log(t, g);//3 undefined
}
m();
//undefined, fn m.. 说明with中的g在作用域分析时就已经被insert了var声明
console.log(g, m);
// var g = 3;
let aa = 'old aa';
{
console.log(g, aa);//undefined 77
//到这里aa一定是77
//注意:with和eval的上下文、scope可以是动态的查找
// var g = 3;
let f = 'f99';
//这么理解:obj中的变量默认在一个with作用域内,obj内变量假设初始是var类型
with(obj) {
console.log('with内', g);//ccgg
aa = 'new aa';
//下述前提:f函数内没有g的同名词法声明(即无 let/const g)
//如果obj中有同名变量,此处又用了var g,则依然会将g的var声明插入到上面的f内
//并给予undefined初始值。然后执行到下面这行后,就把这个函数g给了obj.g
//但是不会赋值给f里的g为函数!它仍为undefined
function g() {
try { throw 0; }
catch { eval(`console.log('f-block-with-g-eval', f, g)`); }
console.log('with-g内的aa', aa);
return 'g();返回g';
}
console.log('执行: ', g());
}
console.log('with外面1 aa', aa, g);//从这层scope往上找g
}
console.log('with外面2 aa', aa, g);
}
f();
console.log(obj.g);//ccgg
// console.log(aa, g);//这俩都会报错的,未定义,因为f内部我有对应的词法声明