1.变量提升:在当前上下文中(全局/私有/块级),JS代码自上而下执行之前,浏览器会提前处理一些事情(可以理解为词法解析的一个环节,词法解析一定发生在代码执行之前)
* 会把当前上下文中所带VAR/FUNCTION关键字进行提前的声明和定义
* var a=10
* 声明declare: var a
* 定义defined: a=10
* 带var的只会提前声明
* 带function会提前声明+定义
var
/**
* 代码执行之前:全局上下文中的变量提升
* var a; 默认值是undefined
*/
console.log(a) // undefined
var a = 12 // 1.创建值12 2.不需要再声明a了(变量提升阶段完成了,完成的事情不会重新处理),3.a=12赋值
a = 13 // 全局变量a=13
console.log(a) // => 13
function
/**
* 全局上下文,变量提升(自上而下的过程,var和function没有优先级之分)
* func= 0x000(函数);scope:G
*
*/
// // 创建函数
// // 1.直接function声明
// func()
// function func() {
// var a = 12
// console.log('OK')
// }
// // 2.表达式赋值方式
// // func() // => Uncaught TypeError: func is not a function
// var func = function () {
// // 真实项目中推荐用函数表达式方式创建函数,因为这样爱变量提升阶段只会声明func,不会赋值
// console.log('OK')
// }
// func()
// 3.表达式赋值方式-匿名函数具名化(可以内部调用,替代arguments.callee)
// func() // => Uncaught TypeError: func is not a function
var func = function AAA() {
// 把原本作为值的函数表达式匿名函数“具名化”(虽说起了名字,但是这个名字不能再外面访问=》也就是不会再当前上下文中创建这个名字)
// 当函数执行,再形成的私有上下文中,会把这个具名化的名字作为私有上下文中的变量(值是这个函数)来进行处理
console.log('OK')
// console.log(AAA) // => 当前函数
// AAA() // 递归调用,而不用严格模式下都不支持的 arguments.callee了。*注意:死循环此处直接这样写,递归不能终止,会造成循环嵌套,内存溢出
}
// AAA() // => Uncaught ReferenceError: AAA is not defined
func()
// 匿名函数具名化,可以内部调用,替代arguments.callee
// setTimeout(function func () {
// func()
// },1000)
var
/**
* EC(G)变量提升
* 无
*/
console.log(a) // => Uncaught ReferenceError: a is not defined
a = 13
console.log(a)
/**
* EC(G)变量提升,只有var/function会变量提升(ES6中的let和const不会)
*/
console.log('OK') // => 'Ok'
console.log(a) // => Uncaught ReferenceError: Cannot access 'a' before initialization
// 不能再let声明之前使用变量
let a = 12
a = 13
console.log(a)
/*
Ok
Uncaught ReferenceError: Cannot access 'a' before initialization
*/
2.相互映射VO/GO:全局变量对象VO(G),全局对象属性GO
/**
* 基于var或者function,在全局上下文中声明的变量(全局变量),会映射到GO(全局对象window)上一份,作为他的属性;
* 而且接下来是一个修改,另外一个也会跟着修改
*/
var a = 12
console.log(a) // => a 全局
console.log(window.a) // => 12 映射到GO上的属性a
window.a = 13
console.log(a) // => 13 映射机制是一个修改,另外一个也会修改
3.大括号(判断体,循环体,代码块)中的变量提升(新老浏览器不同)
3.1
/**
* EC(G):全局上下文中的变量提升
* 不论条件是否成立,都要进行变量提升(细节点:条件中带function的在新版本浏览器中只会提前声明,不会提前赋值了)
* 「老版本<=ie10」
* var a
* func = 函数
* 「新版本,以新版本为准」
* var a // 在全局上下文声明一个a,相当于window.a
* func
*/
// 以新版本浏览器为准
console.log(a, func) // =》undefined undefined
if (!('a' in window)) { // => 'a' in window 检测a是否为window的一个属性,即 !true = false
var a = 1;
function func() { }
}
console.log(a) // => undefined
/*
undefined undefined
undefined
*/
3.2 重复声明处理
/**
* EC(G) 全局变量提升
* fn=>1
* =>2
* var fn 已经声明过了
* =>4
* =>5
* 最终全局上下文中有一个全局变量fn,值是输出5的函数(此时window.fn=>5)
*/
fn() // 5
function fn() { console.log(1) } // 不再处理,变量提升阶段搞过了
fn() // 5
function fn() { console.log(2) }
fn() // 5
var fn = function () { console.log(3) } // var fn不用处理了,但是赋值再变量提升阶段没处理过,此时需要处理 fn=window.fn=>3
fn() // 3
function fn() { console.log(4) }
fn() // 3
function fn() { console.log(5) }
fn() // 3
/*
5
5
5
3
3
3
*/
3.3
/**
* EC(G)
* vo
* foo=1
* up:
* var foo
* bar=0x000;scope:EC(G)
* run:
*
*/
var foo = 1
function bar() {
/**
* EC(bar)
* ao
* foo=10
* scope: bar,G
* this:window
* arg: -
* up:
* var foo
*/
if (!foo) { // => undefined
var foo = 10
}
console.log(foo) // => 10
}
bar()
/*
10
*/
3.4 声明后未赋值的情况
/**
* EC(G)
* vo
* foo=1
* up
* var foo
* bar=0x000;scope:G
* run
*
*/
var foo = 1
function bar() {
/**
* EC(bar)
* ao
* foo=undefined
* scope: bar,G
* this: window
* arg: -
* up:
* foo
*/
if (false) { // run阶段,没有重新赋值
var foo = 10
}
console.log(foo) // => undefined
}
bar()
/*
undefined
*/
VO(G) 全局变量对象「全局上下文中声明的变量」 window GO全局对象「浏览器默认开辟的一个堆内存,存储供JS调用的各种API」 console.log(a) // 首先看VO(G)中是否有,无则看G),也无报错:a is not defined
// 在全局上下文中,VO和GO区别
var n = 10
// 老机制: VO(G)中声明一个n的全局变量,赋值10;特殊:全局上下文中 && 基于var/function声明的变量,也相当于给GO新增属性;并且之前存在映射关系「一个修改,另外一个也跟着修改」
// 新版本机制:全局上下文中 && 基于var/function声明的变量,直接都当做GO的属性存储起来
console.log(n) // 10
console.log(window.n) // 10
m = 10; // 给GO设置一个属性m -> window.m =10
// 基于let/const声明的变量,只是给VO(G)中设置全局变量,和GO没有任何关系
let n = 10;
console.log(n) // 10
console.log(window.n) // undefined
// debugger:chrome浏览器中查看SCOPE(script;GO)
题
1.
console.log(a, b, c);
var a = 12,
b = 13,
c = 14;
function fn(a) {
console.log(a, b, c);
a = 100;
c = 200;
console.log(a, b, c);
}
b = fn(10);
console.log(a, b, c);
/*
10 13 14
100 13 200
12 undefined 200
*/
2.
/**
* EC(G)变量提升
* var i;
* A = 0x000; [[scope]]:EC(G)
* var y
* B = 0x001; [[scope]]:EC(G)
*/
var i = 0;
function A() {
/**
* EC(A)私有上下文
* scope:<EC(A),EC(G)>
* param:
* 变量提升:
* var i
* x = 0x002;[[scope]]:EC(A)
*/
var i = 10;
function x() {
/**
* EC(A)私有上下文
* scope:<EC(X1),EC(A)>
* param:
* 变量提升:
*/
/**
* EC(A)私有上下文
* scope:<EC(X2),EC(A)>
* param:
* 变量提升:
*/
console.log(i);
}
return x; // return 0x002
}
var y = A(); // y=0x002
y(); // 10
function B() {
/**
* EC(B)私有上下文
* scope:<EC(B),EC(G)>
* param:
* 变量提升:
* var i
*/
var i = 20;
y(); // 10
}
B();
/*
10
10
*/
3.
/**
* EC(G)
* var a
* var obj
* fn = 0x000; [[scope]]:EC(G)
*/
var a = 1;
var obj = {
name: "tom"
} // obj=ox001
function fn() {
/**
* EC(FN)
* scope: FN,G
* up:var a2
*/
var a2 = a; // 私有a2=1
obj2 = obj;
/**
* 全局找,也没有的话
* 情况1:输出obj2,如console.log(obj2):,直接报错
* 情况2:赋值:相当于给window设置一个obj2属性,并赋值 window.obj2=0x0001
*/
a2 = a; // 私有a2=1
obj2.name = "jack"; // 遇到obj2,但是发现全局没有这个变量,它会继续再看windwo下是否有这个属性,如果window下也没有这个属性,报错位(未定义变量) -》把0x001的name改为‘jack’
}
fn();
console.log(a); // =》1
console.log(obj); // => {name:'jack'}
/*
1
{ name: 'jack' }
*/
4.
/**
* EC(G) up
* var a
* fn = 0x000; scope:ec(G)
*/
var a = 1;
function fn(a) {
/**
* EC(fn)
* scope: fn,G
* param: a =10
* up: var a「不会重复声明,重新赋值」
* a=0x001;scope:EC(fn)
*/
console.log(a) // => 函数
var a = 2; // 私有a=2
function a() { }
console.log(a) // => 2
}
fn(a); // fn(1)
console.log(a) // => 1
/*
[Function: a]
2
1
*/
5.1
/**
* EC(G) up:
* var a
* fn 0x000; [[scope]]:EC(G)
*/
console.log(a); // undefined
var a = 12;
function fn() {
/**
* EC(fn)
* scope: fn
* param:
* up:
* var a
*/
console.log(a); // => undefined
var a = 13;
}
fn();
console.log(a); // => 12
/*
undefined
undefined
12
*/
5.2
/**
* EC(G) up:
* var a
* fn 0x000; [[scope]]:EC(G)
*/
console.log(a); // => undefined
var a = 12;
function fn() {
/**
* EC(fn)
* scope: fn,G
* param:
* up:
*/
console.log(a); // => 12
a = 13;
}
fn();
console.log(a); // => 13
5.3
/**
* EC(G) up:
* fn 0x000; [[scope]]:EC(G)
*/
console.log(a); // => a is not defined
a = 12;
function fn() {
console.log(a);
a = 13;
}
fn();
console.log(a);
/*
报错:a is not defined
*/
6.
/**
* EC(G) up
* var foo
*
*/
var foo = 'hello';
(function (foo) {
/**
* EC(AN)
* scope: AN,G
* param: foo='hello'
* up: var foo;
*/
console.log(foo); // => 'hello'
var foo = foo || 'world'; // A&&B,A假返回A,A真返回B,同时存在&&优先级高于||
console.log(foo); // => 'hello'
})(foo); // 自执行函数执行,把foo的值hello当做实参传进来
console.log(foo); // => 'hello'
/*
hello
hello
hello
*/
7.1
// 新版本浏览器「兼容ES3/ES5,也需要兼容ES6,所以产生一些非常变态的机制」
// 除函数对象等大括号外,在其他的大括号(判断体,循环体,代码块)中出现let/const/function(仅var无效),则会单独形成块级私有上下文
// 出现再其他大括号中的function,不再是声明+定义,而只是声明
/**
* 老版本浏览器「不支持块级上下文」
* EC(G) up
* foo=undefined
* 被
*/
console.log(foo) // => undefined
{
/**
* 形成新的私有块级上下文
* EC(B)
* scope: B,G
* 无this,arguement,形参赋值
* up:
* foo(声明+定义)=0x0000; scope: B
*/
console.log(foo) // => 函数
function foo() { } // 按常理变量提升阶段搞过了,此处应该直接跳过,但是它被两家宠幸(全局声明过,私有声明+定义过)过,所以有特殊机制:把当前这一行代码,之前foo的操作,都映射给全局一份,但是之后再对foo的操作,都认为是自己私有的。
foo = 1; // 只是把私有的变量foo改为1
console.log(foo) // => 1
}
console.log(foo); // => 函数
/*
undefined
[Function: foo]
1
[Function: foo]
*/
7.2
/**
* EC(G) up
* function foo;
* function foo;
*/
console.log(foo); // => undefined
{
/**
* EC(B)
* up
* foo = 0x000
* foo = 0x001
*/
console.log(foo) // => 0x001
function foo() { 0 } // 特殊:之前foo的操作都给全局一份,全局foo=0x0001
console.log(window.foo) // 0x0001
foo = 1; // 私有的foo=1
function foo() { 1 } // 特殊:之前foo的操作都给全局一份,全局foo=1
console.log(foo) // => 1
}
console.log(foo); // => 1
/**
ƒ foo() { 1 }
ƒ foo() { 1 }
1
1
*/
7.3
{
function foo() { }
foo = 1;
function foo() { } // 特殊:之前foo操作都给全局一份,foo=1
foo = 2;
}
console.log(foo); // => 1
8.1
/**
* EC(G)
* vo
* x = 1
* func=0x000;scope:EC(G)
* up
* var x
* func=0x000;scope:EC(G)
*/
var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
/**
* EC(func)
* ao:
* x = 5
* = 3
* = 2
* y = 0x001:scope:func
* scope: func,G
* arg:
* x = 5
* y = 0x001:scope:func
* run
* x = 3
*/
x = 3;
y()
/**
* EC(y)
* ao: -
* scope:func,G
* arg: -
* up: -
* run:
* x = 2
*/
console.log(x) // 2
}
func(5)
console.log(x) // 1
/*
2
1
*/
8.2 原理同8.3
/**
* 原理:
* 1.如果函数应用了“形参赋值默认值”「不论赋值为啥,也不论是否传实参」
* 2.并且函数体出现了基于var/let/const等生命变量的方式(特殊:let/const声明的变量不能和形参一样,否则报重复声明的错误)
*
* =》 这样除了函数执行,会产生一个私有上下文外「步骤运行到形参赋值后就结束了」
* =》 会把函数整体及其中的代码当做一个“全新的块级上下文”
*/
/**
* EC(G)
* vo
* x = 1
* func=0x000;scope:EC(G)
* up
* var x
* func=0x000;scope:EC(G)
*/
var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
/**
* EC(func)
* ao:
* scope: func,G
* arg:
* x = 5
* = 2 // 0x0001执行
* y = 0x001:scope:func
* 满足变态机制,到此结束--------------------
* EC(B)私有块级上下文
* vo(B)
* x = 5 // 默认值,因为名字一致,从上级同步5过来
* = 3 // 本地执行代码,更改私有变量,不影响上级的同名变量
* scope:B,func
* up
* var x
* run
* var x = 3
* y() // 不是自己私有的,是上级上下文EC(func)中的
* console.log(x) // => 3
*/
var x = 3;
y()
/**
* EC(y)
* ao: -
* scope:y,func
* arg: -
* up: -
* run:
* x = 2
*/
console.log(x) // => 3
}
func(5)
console.log(x) // 1
/*
3
1
*/
8.3 带function变态机制
/**
* EC(G)
* vo
* x = 1
* func=0x000;scope:EC(G)
* up
* var x
* func=0x000;scope:EC(G)
*/
var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
/**
* EC(func)
* ao:
* scope: func,G
* arg:
* x = 5
* y = 0x001:scope:func
*
* 满足变态机制,到此结束--------------------
* EC(B)私有块级上下文
* ao(B)
* x = 5
* = 3 // x=3 执行
* = 4 // 0x002执行
* y = 0x001:scope:func
* = 0x002:scope:B
* scope:B,func
* up
* var x
* var y
* run
* var x = 3
* var y = function anonymous2() { x = 4 }
* y() // 不是自己私有的,是上级上下文EC(func)中的
* console.log(x) // => 3
*/
var x = 3;
var y = function anonymous2() { x = 4 }
y();
/**
* EC(y)
* ao: -
* scope:y,func
* arg: -
* up: -
* run:
* x = 4
*/
console.log(x) // => 4
}
func(5)
console.log(x) // =》 1
/*
4
1
*/