8. 什么是变量
A symbolic name associated with a value and whose
associated value may be changed.
-- Wikipedia
变量是一种关联关系(association)
关联的目标:
符号名(symbolic name)
值(value)
关联是单向的– 永远不可能根据值找到变量
Identifier
name
Value
‘GrayZhang’
14. 可执行代码(Executable Code)
Global Code Function Code
<script>
var name = ‘GrayZhang’;
var prefix = ‘Hello‘;
var phrases = [prefix, name];
console.log(phrases.join(‘ ‘));
</script>
Eval Code
var source =
‘var x = 3;’ +
‘console.log(x);’
eval(source);
function sayHello(name) {
var prefix = ‘Hello’;
var phrases = [prefix, name];
console.log(phrases.join(‘ ‘));
}
function getName() {
var input = $(‘#name’);
return input.val();
}
getName();
15. 执行环境(Execution Context)
当进入(开始执行)一段可执行代码时,生成
一个执行环境对象。
执行环境对象通过栈(Stack)维护。
新建的执行环境对象称为“当前运行的执行环境
对象”。
function enterCode(code) {
var ec = new ExecutionContext();
control.ecStack.push(ec);
control.runningEC = ec;
control.execute(code);
}
30. 创建函数(Create Function Object)
作用域([[Scope]])是函数对象的一个属性
function hello() {
var o = {};
o.name = ‘GrayZhang’;
return o;
}
var person = hello();
console.log(person.name);
function outer() {
var name = ‘GrayZhang’;
function say() {
alert(name);
}
return say;
}
var inner = outer();
// inner.[[Scope]]
inner();
32. 进入函数(Entering Function Code)
var ec = new ExecutionContext();
if (thisArg == null) {
thisArg = global;
}
if (typeof thisArg !== 'object') {
thisArg = ToObject(thisArg);
}
ec.thisBinding = thisArg;
var localEnv = NewDeclarativeEnvironment(fn.[[Scope]]);
ec.lexicalEnvironment = localEnv;
ec.variableEnvironment = localEnv;
initializeBinding(fn.[[Code]], fn.[[FormalParameterList]]);
33. 进入函数(Entering Function Code)
执行函数时,在作用域([[Scope]])的基础
上添加词法环境(LexicalEnvironment)
Global Environment
Outer Environment
Current Environment
// Global Environment
function outer() {
// Outer Environment
function inner() {
// Current Environment
}
}
var inner = outer();
// [[Scope]] === Outer Environment
inner();
34. 定义绑定初始化
(Declaration Binding Instantiation)
从Hosting Behavior说起……
function sayHello(name) {
if (!name) {
throw new Error();
}
else {
var prefix = 'Hello ';
alert(prefix + name);
}
}
function sayHello(name) {
var prefix;
if (!name) {
throw new Error();
}
else {
prefix = 'Hello ';
alert(prefix + name);
}
}
36. 定义绑定初始化
(Declaration Binding Instantiation)
function format(template, data) {
var regex = /{(w+):(w+)}/g;
function replacer(match, name, type) {
var value = data[name];
switch (type) {
case 'boolean':
value = !!value;
break;
case 'html':
value = encodeHTML(value);
break;
}
return value;
}
var html = template.replace(regex, replacer);
return html;
}
arguments
Variable Environment
39. 变量查找
function GetIdentifierReference(lex, name) {
if (lex == null) {
return new Reference(name, undefined);
}
var envRec = lex.environmentRecords;
if (envRec.hasBinding(name)) {
return new Reference(name /* name */, envRec /* base */);
}
return GetIdentifierReference(lex.outerEnvironment, name);
}
function GetValue(reference) {
var envRec = reference.base;
return envRec.getValue(reference.name);
}
40. 大串烧
进入全局环境
创建全局执行环境并入栈
创建全局环境对象
全局的词法环境对象指向该对象
全局的变量环境对象指向该对象
在变量环境中添加outer绑定并赋值
在变量环境中添加prefix绑定
在变量环境中添加inner绑定
// script…
var prefix = ‘Hello ‘;
function outer() {
var name = ‘GrayZhang’;
function say() {
var message = prefix + name;
alert(message);
}
return say;
}
var inner = outer();
// inner.[[Scope]]
inner();
41. 大串烧
创建outer函数
创建一个对象
设置[[Call]]、[[Construct]]、[[HasInstance]]等
设置[[Scope]]为当前词法环境– 全局环境
设置[[Code]]、[[FormalParameterList]]等
设置length、prototype等
// script…
var prefix = ‘Hello ‘;
function outer() {
var name = ‘GrayZhang’;
function say() {
var message = prefix + name;
alert(message);
}
return say;
}
var inner = outer();
// inner.[[Scope]]
inner();
42. 大串烧
Global Environment
outer: { [[Scope]] }
inner: undefined
prefix: undefined
Global Execution Context
VariableEnvironment
LexicalEnvironment
43. 大串烧
为prefix变量赋值
在全局环境中寻找name绑定– 找到
得到上一步返回的Reference的base – 即全局环境
的环境数据对象
调用其setBinding(‘prefix’, ‘Hello ’)
// script…
var prefix = ‘Hello ‘;
function outer() {
var name = ‘GrayZhang’;
function say() {
var message = prefix + name;
alert(message);
}
return say;
}
var inner = outer();
// inner.[[Scope]]
inner();
44. 大串烧
Global Environment
outer: { [[Scope]] }
inner: undefined
prefix: ‘Hello ‘
Global Execution Context
VariableEnvironment
LexicalEnvironment
45. 大串烧
执行outer函数
创建执行环境并入栈
创建一个词法环境– DeclarativeEnvironment
outer的词法环境对象指向该对象
outer的变量环境对象指向该对象
在变量环境中添加say绑定并赋值
在变量环境中添加name绑定
// script…
var prefix = ‘Hello ‘;
function outer() {
var name = ‘GrayZhang’;
function say() {
var message = prefix + name;
alert(message);
}
return say;
}
var inner = outer();
// inner.[[Scope]]
inner();
46. 大串烧
创建say函数
创建一个对象
设置[[Call]]、[[Construct]]、[[HasInstance]]等
设置[[Scope]]为当前词法环境– outer的词法环境
设置[[Code]]、[[FormalParameterList]]等
设置length、prototype等
// script…
var prefix = ‘Hello ‘;
function outer() {
var name = ‘GrayZhang’;
function say() {
var message = prefix + name;
alert(message);
}
return say;
}
var inner = outer();
// inner.[[Scope]]
inner();
56. 大串烧
获取inner的值
在inner的词法环境中寻找message绑定– 找到
得到上一步返回的Reference的base – 即inner的词法环境
的环境数据对象
调用该对象的getValue(‘message’)
获取alert的值
…
将inner作为参数,调用alert函数
// script…
var prefix = ‘Hello ‘;
function outer() {
var name = ‘GrayZhang’;
function say() {
var message = prefix + name;
alert(message);
}
return say;
}
var inner = outer();
// inner.[[Scope]]
inner();
alert从何而来?
57. 大串烧
function born() {
var name = 'unknown';
var age = 1;
return {
setName: function(value) { name = value; },
grow: function() { age++; },
print: function() {
var parts = [name, age];
var joint = ' is now ';
alert(parts.join(joint));
}
};
}
var god = born();
god.setName(‘leeight’);
god.grow();
god.grow();
god.print();
60. 继续消化
• 我以为我懂了,直到……
– How with works
– How catch works
– How let works
– When code meets eval
– When code meets new Function
– When there is strict mode
61. 从代码说起
function outer() {
var o = LargetObject.fromSize('400MB');
return function() {
console.log('inner');
};
}
var inner = outer();
// 对象图此时对象之间的引用关系?
Global Function
Lexical
Environment
Global和o有间接引用,无法回收o
Environment
Records
Binding
Object
Large
Object
inner [[Scope]]
environmentRecords
o bindingObject
62. 但是事实上……
function outer() {
var i = 3;
return function() {
debugger;
};
}
var inner = outer();
inner();
javascript引擎有能力回收i
63. 如果你是计算机……
function outer() {
var i = 3;
var j = 4;
var k = 5;
function prepare() {
i = i + k;
}
function help() {
i = i + j;
}
prepare();
return function() {
help();
console.log(i);
};
}
var inner = outer();
inner();
i: 不可回收
j: 不可回收
k: 可回收
prepare: 可回收
help: 不可回收
人类的智商
计算机的智商
68. 直接引用
Engine Collectable
Chrome – V8 NO
Firefox – SpiderMonkey NO
IE9 - Chakra NO
function outer() {
var i = 3;
return function() {
i;
};
}
var inner = outer();
69. 间接引用
Engine Collectable
Chrome – V8 NO
Firefox – SpiderMonkey NO
IE9 - Chakra NO
function outer() {
var i = 3;
function help() {
i;
}
return function() {
help();
};
}
var inner = outer();
70. 嵌套函数的平衡
function outer() {
var i = 0;
function help() {
i++;
}
help();
return function() {
console.log('nothing');
}
}
var inner = outer();
function outer() {
var i = 0;
function help() {
i++;
return inner();
}
function inner() {
return i > 3 ? i : help();
}
return inner();
}
var inner = outer();
需要图的遍历
需要处理环引用
高成本+ 低效
71. 嵌套函数的平衡
Engine Collectable
Chrome – V8 NO
Firefox – SpiderMonkey NO
IE9 - Chakra NO
function outer() {
var i = 3;
function help() {
i;
}
return function() {
};
}
var inner = outer();
72. 大恶魔eval
function outer() {
var i = 3;
?
return function() {
return eval(‘i’);
}
}
var inner = outer();
var result = inner();
console.log(result); // 3
由字符串从词法环境中获取对象的唯一途径
可变性特殊性
73. 大恶魔eval
var reference = eval(‘someObject’); 字符串分析
var reference = eval(‘some’ + ‘Object’);
常量预计算
var s = ‘some’;
var reference = eval(s + ‘Object’); 变量->常量替换
var array = [‘some’, ‘ject’];
var reference = eval(array.join(‘Ob’));
74. 大恶魔eval
function outer() {
var i = 3;
return function(variableName) {
return eval(variableName);
}
}
var inner = outer();
var input = document.getElementById(‘variable_name’);
var name = input.value.trim();
var result = inner(name);
console.log(result); // 3
75.
76.
77. 大恶魔eval
Engine Collectable
Chrome – V8 NO
Firefox – SpiderMonkey NO
IE9 - Chakra NO
function outer() {
var i = 3;
return function() {
eval(‘’); // 无论eval的内容是什么
};
}
var inner = outer();
78. 间接eval和new Function
间接eval
window.eval(coe) | (1, eval)(code) | (true && eval)(code)
In Edition 5, indirect calls to the eval function use the global
environment as both the variable environment and lexical
environment for the eval code.
new Function
Return a new Function object created as specified in 13.2 passing P
as the FormalParameterList and body as the FunctionBody. Pass in
the Global Environment as the Scope parameter and strict as the
Strict flag.
79. 间接eval和new Function
var i = 3;
function outer() {
var i = 4;
return function() {
X
return window.eval('i');
/*
* var fn = new Function('return i;');
* return fn();
*/
}
}
var inner = outer();
var result = inner();
console.log(result); // 3
80. 间接eval和new Function
Engine Collectable
Chrome – V8 YES
Firefox – SpiderMonkey YES
IE9 - Chakra YES
function outer() {
var i = 3;
return function() {
window.eval(‘i’);
};
}
var inner = outer();
81. 关于with的分歧
function outer() {
var scope = { i: 3, j: 4 };
var m = 4;
var n = 5;
with (scope) {
return function() {
i++;
m++;
};
};
}
var inner = outer();
inner();
?
?
82. 关于with的分歧
Engine Collectable
Chrome – V8 NO
Firefox – SpiderMonkey 回收k, scope,不回收i, j
IE9 - Chakra 回收k,不回收i, j,scope未知
function outer() {
var scope = { i: 3, j: 4 };
var k = 4;
with (scope) {
return function() {
i;
};
}
}
var inner = outer();
83. 不被重视的catch
Engine Collectable
Chrome – V8 回收i,不回收ex
Firefox – SpiderMonkey 回收i,不回收ex
IE9 - Chakra 回收i和ex
function outer() {
var i = 3;
try {
throw { j: 4 };
}
catch (ex) {
return function() {};
}
}
var inner = outer();
84. 你能骗过引擎吗?
function outer() {
var i = 3;
var j = 4;
return function(i) {
var j = 5;
console.log(i + j);
};
}
var inner = outer();
inner(6);
?
Engine Collectable
Chrome – V8 YES
Firefox – SpiderMonkey YES
IE9 - Chakra YES
85. 总结
outer声明的– c1 = (i, j, k, m)
inner声明的– c2 = (i, j)
inner用到的– c3 = (i, j, k)
help声明的– c4 = ()
help用到的– c5 = (j, k)
可回收的= c1- (c3 – c2) – (c5 – c4)
遇上eval则不回收任何变量
注意with和catch的影响
function outer() {
var i = 3;
var j = 4;
var k = 5;
var m = 6;
function help() {
console.log(j + k);
}
return function(i) {
var j = 5;
console.log(i + j + k);
};
}
var inner = outer();
inner(6);