作用域
[[scope]]:每个 javascript函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供javascript引擎存取,[[scope]]就是其中一个。[[scope]]指的就是我们所说的作用域其中存储了运行期上下文的集合。
作用域链:[[scope]]中所存储的执行期上下文对象的集合,这个集合呈链式链接,我们把这种链式链接叫做作用域链。
运行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被销毁。
查找变量:从作用域链的顶端依次向下查找。
执行期上下文就是下面要讲的预编译中的AO、GO
function a () {
function b () {
function c () {
}
c();
}
b();
}
a();
// a defined a.[[scope]] -- > 0 : GO
// a doing a.[[scope]] -- > 0 : aAO
// 1 : GO
// b defined b.[[scope]] -- > 0 : aAO
// 1 : GO
// b doing b.[[scope]] -- > 0 : bAO
// 1 : aAO
// 2 : GO
// c defined c.[[scope]] -- > 0 : bAO
// 1 : aAO
// 2 : GO
// c doing c.[[scope]] -- > 0 : cAO
// 1 : bAO
// 2 : aAO
// 3 : GO
闭包
当内部函数被保存到外部时,将会生成闭包。闭包会导致原有作用域链不释放,造成内存泄露。
闭包的作用
- 实现公有变量
- eg:函数累加器
- 可以做缓存(存储结构)
- eg:eater
- 可以实现封装,属性私有化。
- eg:Person();
function Person (name, say) { var says = "hehe";// 私有化的变量,也是闭包的作用 this.name = name; this.say = say; this.changeSay = function () {// 私有变量赋值 this.say = says; } this.changeSays = function (target) {// 修改私有变量 says = target; } this.showSays = function () {// 显示私有变量 console.log(says); } } var person = new Person('Yin', 'haha');
- 模块化开发,防止污染全局变量
var num = 100;
function a () {
var num = 100;
function b () {
num ++;
console.log(num);
}
return b;
}
var demo = a();
demo();// 101
// 因为a()中return b;所以demo()的方法就是b(),bAO中有aAO和GO,所以b()中的num就是aAO的num
// 所以第一次打印出来的就是101
demo();// 102
// 因为是function,不是原始值,而是引用值,所以第二次指向的num变量就是第一次指向的num变量
// 所以第二次打印出来的就是102
// 这个就是所谓的累加器
function test() {
var num = 100;
function a () {
num ++;
console.log(num);
}
function b () {
num --;
console.log(num);
}
return [a,b];
}
var myArr = test();
myArr[0]();// 101
myArr[1]();// 100
立即执行函数
定义:此类函数没有声明,在一次执行过后即释放。适合做初始化工作。
// 立即执行函数 (function () {}());W3C 建议第一种
// (function () {})();
(function abc () {
var a = 123;
var b = 234;
console.log(a + b);// 357
}())
// 带参、返回的立即执行函数
var num = (function (a, b, c){
console.log(a + b + c);// 6
var d = a + b + c * 2 - 2;
return d;
}(1, 2, 3));
console.log(num);// 7
function test () {
var arr = [];
for(var i = 0; i < 10; i ++) {
arr[i] = function () {// 存在数组里的是函数体,这边主要是存函数体,不是执行函数体
document.write(i + "");
}
}
return arr;
}
var myArr = test();// 这边执行的只是存储十次函数体
for(var j = 0; j < 10;j++) {
myArr[j]();// 只有在执行这个函数体的时候,才会用到i,这时候的i已经循环完成,i为10
}
// 最后打印出十个10
function test () {
var arr = [];
for(var i = 0; i < 10; i ++) {
(function (j) {
arr[j] = function () {
document.write(j + "");
}
}(i));
}
return arr;
}
var myArr = test();
for(var j = 0; j < 10;j++) {
myArr[j]();
}
// 最后打印0123456789
闭包的防范
闭包会导致多个执行函数共用一个公有变量,如果不是特殊需要,应尽量防止这种情况发生。
js运行三部曲
- 语法分析
先通篇扫描一遍有没有语法错误 - 预编译
函数声明整体提升
变量 声明提升
console.log(a);// a = undefined
var a = 123;// 但是如果把var a = 123;这行去掉,console.log()就会报错--未找到a变量
test();
function test () {
console.log('a');
}
// 函数声明整体提升
// 变量 声明提升
- 解释执行
预编译前奏
- 1.imply global暗示全局变量:即任何变量,如果变量未经声明就赋值,此变量就为全局对象所有
- eg: a = 123;
- eg: var a = b = 123;
- 2.一切声明的全局变量,全是 window的属性。
- eg: var a = 123; ===> window.a = 123;
window 就是全局的域
就是说在全局的范围内,var a = 1 和 直接用 a = 1这里面的a都归为windows.a( console.log(a) == console.log(window.a) )
全局变量都是在window上
预编译
- 四部曲:重点重点重点
- 创建AO对象 Activation Object (执行期上下文)
- 找形参和变量声明,将变量和形参名作为AO属性名,值为 undefined
- 将实参值和形参统一
- 在函数体里面找函数声明,值赋予函数体
function fn (a) {
console.log(a);
var a = 123;
console.log(a);
function a () {}
console.log(a);
var b = function (n) {}
console.log(b)
function d () {}
}
fn(1);
// 预编译发生在函数执行的前一刻
程序分析:(预编译)
第一步:创建AO,函数产生的存储空间库
AO{
a :
b :
}
第二步:把变量和形参名作为AO属性名,赋值undefined
AO{
a : undefined,
b : undefined
}
第三步:将实参值和形参统一
AO{
a : 1,
b : undefined
}
第四步:在函数体里面找函数声明,a里面的1就会被覆盖掉
AO{
a : function a () {},
b : undefined,
d : function d () {}
}
四个console.log()打印输出
第一个为 :
f a () {}
因为 a在预编译的过程中就声明了,但是 a = 123的赋值语句还没有执行,所以 AO中的a 又被赋值为123!所以第二个为:
123
因为a已经被覆盖成123,所以第三个为:
123
第四个为:
f d () {}
全局的预编译因为没有实参形参,所以少了第三步,其他一样
全局生成的对象不是叫AO对象了,叫GO对象 Global Object
GO {
}
GO === window
重点!!!!!!!!!!!!!!
function test () {
var a = b = 123;
console.log(window.a);// a这个变量是var a,所以它只是个局部变量,打印出来是undefined
console.log(window.b);// 而b这个变量,从右往左时,他不是var出来的,直接是 b = 123; 所以它是个全局变量!!!打印出来是123
}
test();
先找AO,再找GO!如果AO对象中找不到这个值,那么会往GO中找
举例:
global = 100;
function fn() {
console.log(global);// AO中有global,但是第一句执行是还是undefined,所以AO中有了,就不会再去找GO中的
global = 200;
console.log(global);// global被200覆盖了,所以执行后为200
var global = 300;
}
fn();
var global;
function test () {
console.log(b);// AO中有个b :undefined,所以b为undefined
if(a) {
var b = 100;// a = undefined,所以跳过if
}
c = 234;// AO里面没C,所以c给GO
//此时的GO对象添加C
// GO {
// a : undefined,
// c : 234
//}
console.log(c);// AO中没有var C,所以去GO中找,可以找到C为234
}
var a;
//因为要调用test了,所以提前生成一个AO对象预编译一下
//注意的是:不管里面if是否会执行,只要有var声明,都给我放到AO中
//AO {
// b : undefined
//}
test();
a = 10;
console.log(c);// 经过test();后,GO中有C了,所以C为234
小知识: 在现在的谷歌浏览器中,if()语句内部不能再定义function了 (可以试着在if中创建一个函数,打印这个变量看一下,会发现是undefined)