ECMA基础(二)
预编译
不管是语法错误的代码前后,结果输出都是语法错误
console.log(a);console.log(a);console.log(a);
说明预编译的步骤:
- 检查通篇的语法错误
- 预编译的过程
- 解释一行执行一行
现象一
test();function test() {console.log(1);}
现象二
console.log(a);var a = 10; //undefinedvar a; //undefined
以上总结:函数声明是要整体提升到逻辑代码的最上方,变量只有声明提升,赋值不提升
AO
activation object活跃对象,函数上下文
function test(a) {console.log(a);var a = 1;console.log(a);function a() { }console.log(a);var b = function () { }console.log(b);function d(){}}test(2);
函数预编译其实是函数执行之前要进行的步骤
- 寻找函数形参和变量声明(包括函数表达式声明)
- 将实参的参数值赋值给形参
- 寻找函数声明赋值函数体
- 执行函数
/*** AO = {a: undefined -> 2 -> function a(){} -> 1,b: undefined -> function(){},d: function d(){}}*/function test(a) {console.log(a); //function a(){}; a赋值前 是 function a(){}var a = 1;console.log(a); //1 被赋值function a() {}console.log(a); //1 结果同上var b = function () { }console.log(b); //function (){} 对应d: function(){}function d(){}}test(2);
练习1
/*** AO = {a: undefined -> 1 -> 5,b: undefined -> function b() {} -> 6,c: undefined -> 0,d: function d() {}}*/function test(a, b) {console.log(a); //1c = 0;var c;console.log(c); //0a = 5;b = 6;console.log(b); //6function b() { }function d() { }console.log(b); //6}test(1);
GO
global object 全局上下文 执行期上下文
在整篇JS执行之前就产生了全局上下文
- 寻找变量声明(包括函数表达式声明)
- 寻找函数声明赋值函数体
- 执行函数
/*** GO = {a: undefined -> function a(){...} -> 1}*/
console.log(a); //function a(){...}var a = 1;function a() {console.log(2);}console.log(a); //1
总结:GO === window
练习
/*** GO = {a: function a() {},b: undefined -> function(){}}*/console.log(a); //function a() {}console.log(b); //undefinedfunction a() { }var b = function(){}console.log(b); //function(){}
同时存在GO和AO的情况:
//执行顺序://1.创建全局上下文GO//2.看到函数test,创建函数上下文AO//3.AO里寻找变量声明,a: undefined//4.AO里执行,看到b没有声明,挂载b到全局 b: 1,b的值赋给a,a往全局找b,并将1赋值给a/*** GO = {b: 1,test: function test(){...}}AO ={a: undefined -> 1}*/function test() {var a = b = 1;console.log(a); // 1}test();
AO函数上下文内部若找到变量的值优先输出而不是向外找全局变量的值
//执行顺序://1.创建全局上下文GO//2.看到函数test,创建函数上下文AO//3.AO里寻找变量声明,a: undefined, b:undefined//4.AO里执行,a : 1, b: 1/*** GO = {b: 1,test: function test(){...}}AO ={a: undefined -> 1,b: undefined -> 2}*/var b = 1;function test() {var a = 1;var b = 2;console.log(b); //2}test();
练习2
/*** GO = {b: undefined -> 3,a: function a(){...}}AO = {a: undefined -> 1 -> fuction a(){...} -> 2,b: undefined -> 5}*/var b = 3;console.log(a); //function a(){...}function a(a) {console.log(a); //fuction a(){...}var a = 2;console.log(a); //2function a() { }var b = 5;console.log(b); //5}a(1);
练习3
/*** GO = {a: undefined -> 1,test: function test(){...}}AO = {a: undefined -> 2 -> 3,}*/a = 1;function test() {console.log(a); //undefineda = 2;console.log(a); //2var a = 3;console.log(a); //3}test();var a;
练习4 预编译时先不看if语句,但可以先看该语句里的变量声明,等函数执行的时候才看if语句
/*** GO = {a: undefined -> 1,test: function test(){...},c: 3}AO = {b: undefined}*/function test() {console.log(b); //undefined//test() -> a = undefined -> falseif (a) {var b = 2;}c = 3;console.log(c); //3}var a;test();a = 1;console.log(a); //1
暗示全局变量
imply global variable
变量未被声明就赋值,该变量就为全局变量(暗示全局变量体现)
该变量所有权归window全局对象,window本身就为全局域,一切的全局域变量都归window所有
a = 1;console.log(a); //1
var a = 1;console.log(a); //1
全局对象window
在全局下,不管声明变量与否,都会挂在window对象里面
var a = 1;b = 2;window = {a: 1,b: 2}console.log(a); //1console.log(b); //2console.log(window.a); //1console.log(window.b); //2
在函数内部没声明的变量而直接给变量赋值会提升到全局变量,挂载到window对象身上
访问对象(window)里面不存在的属性默认是undefined
function test() {//此写法b没有在函数内部声明var a = b = 1;}test();console.log(window.b); //1console.log(window.a); //undefined a并没有挂在全局console.log(a); //报错
作用域
AO/GO预编译解决JS一系列关于作用域或作用链相关所产生的一切问题
AO-> function-> 独立的仓库
对象有它的属性
var obj = {name: 'xiaoye',address: 'xxxx',teach: function(){}}console.log(obj.name); //xiaoye
同样,函数也有它的属性test.name,test.length,test.prototype
function test(){};console.log(test.name); //testconsole.log(test.length); //0 形参长度
说明函数也是一种对象类型,也是一种引用类型,引用值。
对象有一些属性是无法访问的,JS引擎内部固有的隐式属性,内部私有属性
关于作用域的隐式属性是[[scope]]域
- 函数创建时生成的一个JS内部的隐式属性
- 它是函数存储作用域链的容器
关于作用域链:
- 作用域链存储的是AO(函数执执行期上下文)/GO(全局执行期上下文)
函数执行完成以后,AO是要被销毁的,如果再次执行函数,会重新生成新的AO,说明AO是一个即时的存储容器
作用域过程
function a() {function b() {var b = 2;}var a = 1;b();}var c = 3;a();
作用域:
[[scope]],存储作用域链的容器
作用域链:
Scope Chain负责存储的是AO(函数执执行期上下文)/GO(全局执行期上下文)
注:
每个函数的作用域链里都包含GO(内可访外,外不能)
变量是从作用域链里顶端向下查找
外层函数执行内层函数被定义
内层函数没有执行之前与外层函数执行时的作用域链是相同的
正常阶段:
全局执行前:
- 函数声明提升
全局执行:
- 将匿名函数赋值给变量
函数a定义:
- 生成作用域
[[scope]] - 生成作用域链,第0位存储当前环境下的
GO GO存储全局下的所有对象,其中包含函数a和全局变量c
- 生成作用域
函数a执行:
- 作用域链的第0位存储a函数的
AO - 作用域链的第1位存储
GO
- 作用域链的第0位存储a函数的
函数b定义:
- 生成作用域
[[scope]] - 生成作用域链
- 作用域链的第0位存储a函数的
AO - 作用域链的第1位存储
GO
- 生成作用域
函数b执行:
- 作用域链的第0位存储b函数的
AO - 作用域链的第1位存储a函数的
AO - 作用域链的第2位存储
GO
- 作用域链的第0位存储b函数的
销毁阶段:
函数b执行结束(相当于回到函数b定义时):
- 销毁:作用域链的第0位存储b函数的
AO
- 销毁:作用域链的第0位存储b函数的
函数a执行结束(相当于回到函数a定义时):
- 销毁:作用域链的第0位存储a函数的
AO - 销毁:b函数的作用域
[[scope]]和AO
- 销毁:作用域链的第0位存储a函数的
简易理解:
function a(){function b(){function c(){}c();}b();}a();a定义: a.[[scope]] -> 0: GOa执行: a.[[scope]] -> 0: a -> A01: GOb定义: b.[[scope]] -> 0: a -> A01: GOb执行: b.[[scope]] -> 0: b -> A01: a -> A02: GOc定义: c.[[scope]] -> 0: b -> A01: a -> A02: GOc执行: c.[[scope]] -> 0: c -> A01: b -> A02: a -> A03: GO//销毁c结束: c.[[scope]] -> 0: b -> A01: a -> A02: GOb结束: b.[[scope]] -> 0: a -> A01: GOc.[[scope]] -> 销毁了a结束: a.[[scope]] -> 0: GOb.[[scope]] -> 销毁了
闭包
function test1() {function test2() {var b = 2;console.log(a);}var a = 1;return test2;}var c = 3;var test3 = test1();test3();/*** test1定义: test1.[[scope]] -> 0: GO** GO = {* c: 3,* test1: function test1(){...},* test3: function test1(){...}* }** test1执行: test1.[[scope]] -> 0: test1 -> AO* 1: GO* test1.AO = {* a: 1,* test2: fuction test2(){...}* }** test2定义: test1.[[scope]] -> 0: test1 -> AO* 1: GO*/
分析:
- 当
test1函数被执行结束时直接return test2,且test2没有执行被全局变量test3接收,这时test1的AO并没有被销毁因为被test2的作用域链还连着(test2定义时有着跟test1执行时一模一样的作用域作用域链和AO) - 当
test3执行,test2的作用域链连着自己的AO - 当打印a的时候,在自己的
AO没有查到,则向test1的AO查找,实际上操作的仍然是test1的AO
当内部函数被返回到外部并保存时,一定会产生闭包,闭包会产生原来的作用链不释放,过渡的闭包会产生内存泄漏,或加载过慢
利用数组返回函数内部两个函数的方法
//累加function test() {var n = 100;function add() {n++;console.log(n);}function reduce() {n--;console.log(n);}return [add, reduce];}var arr = test();arr[0](); //101arr[1](); //100
侧面说明闭包可以做数据缓存
案例:面包管理器
function breadMgr(num) {var breadNum = arguments[0] || 10;function supply() {breadNum += 10;console.log(breadNum);}function sale() {breadNum--;console.log(breadNum);}return [supply, sale];}var breadMgr = breadMgr(50);breadMgr[0](); //60breadMgr[1](); //59
利用对象的方式写闭包
function sunSched() {var sunSched = '';var operation = {setSched: function (thing) {sunSched = thing;},showSched: function () {console.log('My schedule on sunday is ' + sunSched);}}return operation;}var sunSched = sunSched();sunSched.setSched('studying');sunSched.showSched();//My schedule on sunday is studying
关于return和window返回闭包
function test() {var a = 1;function plus1() {a++;console.log(a);}return plus1;}var plus = test();plus();plus();
function test() {var a = 1;function add() {a++;console.log(a);}window.add = add;}test();add(); //2add(); //3
闭包形式
什么是闭包?
- 函数的执行,导致函数被定义
- 闭包和函数的定义有关
是否为闭包?
function foo(fn) {var n = 0;fn();}function test() {console.log(n);}foo(test); //报错
为什么不是?
简单的函数执行并不是闭包
闭包的常见形式:
- 函数的返回值是函数
- 返回的变量是函数
- 全局变量定义的闭包
- 函数的参数的方式
//1.函数的返回值是函数function foo() {var n = 0;return function () {console.log(n);}}foo(test);
//2.返回的变量是函数function foo() {var n = function () { };return n;}foo()();
//3.全局变量定义的闭包var outter;function foo() {var a = 10;outter = function () {console.log(a); //10};return outter;}foo();outter();
//4.函数的参数的方式var inner = function (fn) {console.log(fn());}function foo() {var b = 'local';var n = function () {return b;}inner(n);}foo();
循环赋值:
- 没有用闭包的方式会产生闭包,会拿到循环之后的值
- 用闭包的方式保存参数解决循环问题
面试题
每一个闭包中对应的状态,决定返回值
//第二步function fun(n, o) { //0, undefinedconsole.log(o);return {//第一步fun: function (m) { //1 //2 //3return fun(m, n); //(1, 0) //(2, 0) //(3, 0)}}}//fun(0)为全局函数var a = fun(0); //undefined//a.fun(1); //0a.fun(2); //0a.fun(3); //0
//第二步function fun(n, o) { //0, undefined //1, 0 //2, 1 //3, 2console.log(o);return {//第一步fun: function (m) { //1 //2 //3return fun(m, n); //(1, 0) (2, 1) (3, 2)}}}//var b = fun(0) //undefined//对象的属性调用.fun(1) //0.fun(2) //1.fun(3); //2
//第二步function fun(n, o) { //0, undefined //1, 0 //2, 1 //3, 1console.log(o);return {//第一步fun: function (m) { //1 //2 //3return fun(m, n); //(1, 0) //(2, 1) //(3, 1)}}}var c = fun(0).fun(1); //undefined 0c.fun(2); //1c.fun(3); //1
闭包进阶
私有属性概念
返回的闭包的函数上下文里面的属性称为私有属性
function test() {var num = 0; //私有属性var compute = {add: function () {num++;console.log(num);},minus: function () {num--;console.log(num);}}return compute;}var compute = test();compute.add();
通过对象形成闭包
function test() {var num = 0; //私有属性var compute = {add: function () {num++;console.log(num);},minus: function () {num--;console.log(num);}}return compute;}var compute = test();compute.add();
通过构造函数形成闭包
function Compute() {var num = 0;this.add = function () {num++;console.log(num);}this.minus = function () {num--;console.log(num);}//闭包形成//return this//return 原始值 不影响结果//return 引用值 报错}var compute = new Compute();compute.add();
