let/const <-> 作用域类型

  1. 函数作用域内,提前使用了let定义的变量,报:Uncaught ReferenceError: Cannot access ‘a’ before initialization。因为变量有提升,但是没初始化填充
  2. 全局作用域内,提前使用let定义的变量,报:Uncaught ReferenceError: ttt is not defined。没有变量提升。

d8打印一下作用域信息:
image.png
image.png
image.png
可以看到函数作用域内,对于let/const的变量说:尚未分配,洞初始化操作被忽略/跳过
而全局作用域下直接先在 context[index] 中占位,但是此时。。i dont know。总之没有提升变量声明。
在类作用域下同函数作用域下报相同错误,其实很正常,因为js本质上没有类,class只是函数的一种语法糖

惰性编译与预扫描

image.png
虽然惰性编译函数作用域,但在编译全局作用域之前,会先对顶级函数作用域进行 预扫描。

eval

chromium源码中对eval作用域做了大量的处理,我看到其中一点是:
eval的作用域就是 最近的非eval的父级作用域下,但是非严格模式下,如果在eval中直接出现的变量(没用var/let/const声明),没有在其上层作用域中出现过, 则会透传到window下,造成内存泄漏。

关于各种eval嵌套(配合block/function/eval/catch scope),做了大量处理,看的我💔,后来都不知道读了啥

忽略/报错重复的声明

  1. function a(e) {
  2. console.log(e===arguments[0], e, a);
  3. var e = 3;//这个声明合法,但是会被v8忽略再次声明,因为你在function scope里跟参数e重复了
  4. //let e = 2;//直接报错,v8不让let,const重复声明; 而且const必须有初始化值
  5. var a ;
  6. console.log(e, arguments[0]);
  7. }
  8. a({});

catch scope

这个比较特殊,它的作用域就是 catch后面 圆括号里的范围,难道你没发现:

  1. function test(){
  2. var t = 'ok'
  3. try {
  4. var t = '33';
  5. throw 1;
  6. } catch (e=t) {//这么写运行时直接报错,Uncaught SyntaxError: Unexpected token '='
  7. console.log(e);
  8. }
  9. }
  10. test();
  1. Find variable with (variable->mode() <= |mode_limit|) that was declared in
  2. |scope|. This is used to catch patterns like `try{}catch(e){let e;}` and
  3. function([e]) { let e }, which are errors even though the two 'e's are each
  4. time declared in different scopes. Returns the first duplicate variable
  5. name if there is one, nullptr otherwise.
  1. 查找在 |scope| 中声明的具有 (variable->mode() <= |mode_limit|) 的变量。
  2. 这用于捕获诸如 `try{}catch(e){let e;}` `function([e]) { let e }` 之类的模式,
  3. 即使这两个 e 每次都在不同的scope内声明,这些模式也是错误的。
  4. 如果有,则返回第一个重复变量名,否则返回 nullptr

来个冗杂的示例

不能调试,硬读chromium真的太难了,。。细节上还是没太懂,怎么查找变量插入声明覆盖值的
只能依据实际情况配合那些源码里的名字,望文生义了,淦,以后一定要想办法调试起来。!!

  1. //with内部如果使用了未声明的变量,就会从上层作用域里找,最多找到native scope
  2. // 总之在所能找到的最上层scope添加这个变量声明;(前提:所过之处,没有同名词法变量)
  3. //如果with的对象内没有该字面量函数,并且该变量没有使用声明类型var/let/const :
  4. //则该变量在with-block scope内先插入一个let g声明;
  5. //然后再一直向外层scope找g,一直找到最近的终止层scope;期间若遇到var声明同名变量,
  6. //则沿用这个var初始声明,并插入var g undefined在终止层scope。
  7. //最后,执行到fn g时,就把这个fn重新赋值给 f 里的g
  8. //但如果终止层scope内(后代scope也算)有g同名的词法声明(let/const), 则不做任何操作
  9. // 即这个函数字面量只能在with内生效
  10. let obj = {g: 'ccgg'};
  11. function f() {
  12. let g = 'what_g'; // prevent sloppy block function hoisting.
  13. function m(){
  14. let t = 3;
  15. console.log(t, g);//3 undefined
  16. }
  17. m();
  18. //undefined, fn m.. 说明with中的g在作用域分析时就已经被insert了var声明
  19. console.log(g, m);
  20. // var g = 3;
  21. let aa = 'old aa';
  22. {
  23. console.log(g, aa);//undefined 77
  24. //到这里aa一定是77
  25. //注意:with和eval的上下文、scope可以是动态的查找
  26. // var g = 3;
  27. let f = 'f99';
  28. //这么理解:obj中的变量默认在一个with作用域内,obj内变量假设初始是var类型
  29. with(obj) {
  30. console.log('with内', g);//ccgg
  31. aa = 'new aa';
  32. //下述前提:f函数内没有g的同名词法声明(即无 let/const g)
  33. //如果obj中有同名变量,此处又用了var g,则依然会将g的var声明插入到上面的f内
  34. //并给予undefined初始值。然后执行到下面这行后,就把这个函数g给了obj.g
  35. //但是不会赋值给f里的g为函数!它仍为undefined
  36. function g() {
  37. try { throw 0; }
  38. catch { eval(`console.log('f-block-with-g-eval', f, g)`); }
  39. console.log('with-g内的aa', aa);
  40. return 'g();返回g';
  41. }
  42. console.log('执行: ', g());
  43. }
  44. console.log('with外面1 aa', aa, g);//从这层scope往上找g
  45. }
  46. console.log('with外面2 aa', aa, g);
  47. }
  48. f();
  49. console.log(obj.g);//ccgg
  50. // console.log(aa, g);//这俩都会报错的,未定义,因为f内部我有对应的词法声明