js 写了好几年,总是对其用法懵懂,恨其没有标准语法书籍,这次下很心看了ECMA-262-3标准。
整理自己比较模糊的核心概念
Execution context 运行上下文,avascript 是单线程语言,同一时间只有一个任务被执行。当javascript 开始解析运行代码时,默认先进入全局运行上下文(global execution context),然后在全局上下文中每调用一次函数生成新的运行上下文。也可以调用eval 方法进入 eval 上下文 (eval execution context)。这个过程如下
将运行上下文栈(Execution Context Stack): 所有的运行上下文存储到栈中如下
运行上下文可以抽象成一个对象
variable object :变量对象,该对象是一个与运行上下文相关的的容器对象。用于存储 variable .function 声明定义
注意函数表达式不存储在 变量对象中
var foo = 10;
function bar() {} // function declaration, FD
(function baz() {}); // function expression, FE
console.log(
this.foo == foo, // true
window.bar == bar // true
);
console.log(baz); // ReferenceError, "baz" is not defined
以上式全局上下文 global context
在全局上下文中 Variable Object 是全局对象本身
在eval context eval 上下文中 variable object 即可以用全局的variable object 也可用调用它的function的variable object
Activation object: 函数上下文中 variable Object 叫做 Activation object,其除变量variable 与函数funciton的声明定义外,还有形参及arguments 对象
function foo(x, y) {
var z = 30;
function bar() {} // FD
(function baz() {}); // FE
}
foo(10, 20);
函数声明预先读写 hoisting of function variable
alert(sum(10,10));//20;因为预先读取到了sum()函数的申明 alert(sum1(10,10))//报错,因为找不到sum1()函数; function sum(num1,num2){ return num1+num2; } var sum1=function(num1,num2){ return num1+num2; }
scope chain 作用域链: 在当前运行上下文可访问的变量。即一个可以内部函数可以访问它父函数上下中变量variable object及全局上下中的变量 global variable。
var 作用域 是 function scope
function f(shouldInitialize: boolean)
{
if (shouldInitialize) { var x = 10; } return x;
} f(true); // returns '10'
f(false); // returns 'undefined'
var 作用域问题是相同变量名可以重复声明
function sumMatrix(matrix) {
var sum = 0;
for (var i = 0; i < matrix.length; i++) {
var currentRow = matrix[i];
for (var i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
}
return sum;
}
sumMatrix([[1,2],[3,6]]) //return 3
var 作用域链
var x = 10;
(function foo(i) {
var y = 20;
(function bar() {
var z = 30;
// "x" and "y" and "i" are "free variables"
// and are found in the next (after
// bar's activation object) object
// of the bar's scope chain
console.log(x + y + z + i);
})();
})(40); //return 100
其外还有一些特殊的动态添加作用域
如 with-objects 或 catch-clauses.
Object.prototype.x = 10;
var w = 20;
var y = 30;
// in SpiderMonkey global object
// i.e. variable object of the global
// context inherits from "Object.prototype",
// so we may refer "not defined global
// variable x", which is found in
// the prototype chain
console.log(x); // 10
(function foo() {
// "foo" local variables
var w = 40;
var x = 100;
// "x" is found in the
// "Object.prototype", because
// {z: 50} inherits from it
with ({z: 50}) {
console.log(w, x, y , z); // 40, 10, 30, 50
}
// after "with" object is removed
// from the scope chain, "x" is
// again found in the AO of "foo" context;
// variable "w" is also local
console.log(x, w); // 100, 40
// and that's how we may refer
// shadowed global "w" variable in
// the browser host environment
console.log(window.w); // 20
})();
不建议使用with,使作用域链多了查找维度
注意不是所有 global vo 都 prototype object
Closures 闭包: 由于javascript函数作为第一公民的语言。可以将函数作为参数,也可以作为返回值。函数在创建时[[scopes]] 属性会保存parent funtion 作用域链 scope chain,然后当函数激活时,合并作用域链
Scope chain = Activation object + [[Scope]]
下面是函数作为返回值,它的作用域链是向上寻找父函数作用域
function foo() {
var x = 10;
return function bar() {
console.log(x);
};
}
// "foo" returns also a function
// and this returned function uses
// free variable "x"
var returnedFunction = foo();
// global variable "x"
var x = 20;
// execution of the returned function
returnedFunction(); // 10, but not 20
上图在作用域叫静态作用域 static scope or lexical scope,其他语言也有dynamic scope 动态作用域
下面是函数作为实参,它的作用域链是向下寻找父函数作用域
// global "x"
var x = 10;
// global function
function foo() {
console.log(x);
}
(function (funArg) {
// local "x"
var x = 20;
// there is no ambiguity,
// because we use global "x",
// which was statically saved in
// [[Scope]] of the "foo" function,
// but not the "x" of the caller's scope,
// which activates the "funArg"
funArg(); // 10, but not 20
})(foo); // pass "down" foo as a "funarg"
上图 foo 作用域是函数创建时生成[[scopes]]。
总结: 静态作用域是作为语言拥有闭包closure 特性的一种必要条件。有些语言可以同时拥有静态作用域,动态作用域,并可以开发的时候选择。
闭包的定义 是代码块作用域(var 是 function scope let {} scope )与静态作用域的集合,改静态作用域保存父作用域链variable object 的引用。
共享父作用域链的情况
function baz() {
var x = 1;
return {
foo: function foo() { debugger; return ++x; },
bar: function bar() { debugger; return --x; }
};
}
var closures = baz();
console.log(
closures.foo(), // 2
closures.bar() // 1
);
典型的问题循环中调用函数
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = function () {
console.log(k);
};
}
data[0](); // 3, but not 0
data[1](); // 3, but not 1
data[2](); // 3, but not 2
解决方法 使用function 隔离作用域
var data = [];
for (var k = 0; k < 3; k++) {
data[k] = (function (x) {
return function () {
console.log(x);
};
})(k); // pass "k" value
}
// now it is correct
data[0](); // 0
data[1](); // 1
data[2](); // 2
但是ES6 let 块作用域
let data = [];
for (let k = 0; k < 3; k++) {
data[k] = function () {
console.log(k);
};
}
data[0](); // 0
data[1](); // 1
data[2](); // 2
context object 上下文 this对象的值,属于运行上下文execute context的属性,
与variable object相比this 不存在作用域链问题 scope chain,这个值是运行上下文激活是被赋值即函数调用时,且只能被赋值一次
通常由函数的调用者决定, 一下三种不同context
// the code of the "foo" function
// never changes, but the "this" value
// differs in every activation
function foo() {
console.log(this);
}
// caller activates "foo" (callee) and
// provides "this" for the callee
foo(); // global object
foo.prototype.constructor(); // foo.prototype
var bar = {
baz: foo
};
bar.baz(); // bar
(bar.baz)(); // also bar
(bar.baz = bar.baz)(); // but here is global object
(bar.baz, bar.baz)(); // also global object
(false || bar.baz)(); // also global object
var otherFoo = bar.baz;
otherFoo(); // again global object
参考 http://dmitrysoshnikov.com/ecmascript/javascript-the-core/