8. 什么是变量
A symbolic name associated with a value and whose
associated value may be changed.
-- Wikipedia
变量是一种关联关系(association)
关联的目标:
符号名(symbolic name)
值(value)
关联是单向的 – 永远不可能根据值找到变量
Identifier Value
name ‘GrayZhang’
14. 可执行代码(Executable Code)
Global Code Function Code
<script> function sayHello(name) {
var name = ‘GrayZhang’; var prefix = ‘Hello’;
var prefix = ‘Hello‘; var phrases = [prefix, name];
var phrases = [prefix, name];
console.log(phrases.join(‘ ‘)); console.log(phrases.join(‘ ‘));
</script> }
Eval Code function getName() {
var input = $(‘#name’);
var source = return input.val();
‘var x = 3;’ + }
‘console.log(x);’
eval(source); 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() { function outer() {
var o = {}; var name = ‘GrayZhang’;
o.name = ‘GrayZhang’; function say() {
return o; alert(name);
} }
var person = hello(); return say;
console.log(person.name); }
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
function outer() { Global Environment
// Outer Environment
function inner() { Outer Environment
// Current Environment
} Current Environment
}
var inner = outer();
// [[Scope]] === Outer Environment
inner();
34. 定义绑定初始化
(Declaration Binding Instantiation)
从Hosting Behavior说起……
function sayHello(name) { function sayHello(name) {
if (!name) { var prefix;
throw new Error(); if (!name) {
} throw new Error();
else { }
var prefix = 'Hello '; else {
alert(prefix + name); prefix = 'Hello ';
} alert(prefix + name);
} }
}
36. 定义绑定初始化
(Declaration Binding Instantiation)
function format(template, data) { arguments
var regex = /{(w+):(w+)}/g;
function replacer(match, name, type) {
var value = data[name];
switch (type) {
Variable Environment
case 'boolean':
value = !!value;
break;
case 'html':
value = encodeHTML(value);
break;
}
return value;
}
var html = template.replace(regex, replacer);
return html;
}
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绑定并赋值
// script…
在变量环境中添加prefix绑定 var prefix = ‘Hello ‘;
function outer() {
在变量环境中添加inner绑定
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绑定并赋值
// script…
在变量环境中添加name绑定 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的值
… // script…
var prefix = ‘Hello ‘;
将inner作为参数,调用alert函数 function outer() {
var name = ‘GrayZhang’;
function say() {
var message = prefix + name;
alert从何而来? }
alert(message);
return say;
}
var inner = outer();
// inner.[[Scope]]
inner();
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();
// 对象图 此时对象之间的引用关系?
Lexical
Global Function
inner [[Scope]] Environment
Global和o有间接引用,无法回收o environmentRecords
Large o Binding bindingObject Environment
Object Object Records
62. 但是事实上……
function outer() {
var i = 3;
return function() {
debugger;
};
}
var inner = outer();
inner();
javascript引擎有能力回收i
63. 如果你是计算机……
function outer() {
var i = 3; i: 不可回收
var j = 4;
var k = 5; j: 不可回收
function prepare() { k: 可回收
}
i = i + k;
prepare: 可回收
function help() {
help: 不可回收
i = i + j;
}
prepare();
人类的智商
return function() {
help();
console.log(i);
}; 计算机的智商
}
var inner = outer();
inner();
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() { function outer() {
var i = 0; var i = 0;
function help() { function help() {
i++; i++;
} return inner();
}
help();
function inner() {
return function() { return i > 3 ? i : help();
console.log('nothing'); }
}
} return inner();
}
var inner = outer();
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)
function outer() {
help声明的 – c4 = () var i = 3;
var j = 4;
help用到的 – c5 = (j, k) var k = 5;
可回收的 = c1- (c3 – c2) – (c5 – c4) var m = 6;
function help() {
console.log(j + k);
遇上eval则不回收任何变量 }
注意with和catch的影响 return function(i) {
var j = 5;
console.log(i + j +
k);
};
}
var inner = outer();
inner(6);