作用域
1.概述
作用域:标识符(变量和函数名)在哪些地方(函数的代码块内部和外部)能够被访问,哪些地方就是这个标识符的作用。
规则:函数内部的代码可以访问函数内部的标识符,也可以访问函数外部的标识符,但是反过来不行,也就是说外部不能访问函数内部的标识符。
//案例:
var a = 200;
function fn(a) {
//var a=90//210
function fm() {
console.log(a, 122)//210
a = 90
console.log(a, 123)//90
}
fm(a)//fm(210)
console.log(a, 124)//90
}
fn(a+10)//fn(210)
console.log(a, 125)//200
//案例
var total=0;
function increment () {
var total=0
total=total+2
}
function decrease() {
total=total-2
}
increment()
increment()
decrease()
console.log(total)//0
//案例: 函数的调用是运行一次代码:每一次调用都会重新执行所有代码
var total = 0;
function increment() {
// var total = 0
total = total + 2
console.log(total)
}
increment()//{var total=0; total = total + 2}
increment()//{var total=0; total = total + 2}
2.同名标识符提升问题
总结:每一个作用域在运行时,js引擎会先把作用域内部的关键字隐式提前扫描 并声明 。
var a = 10
function fn() {
//隐式操作:把var 修饰的变量名提前声明
console.log(a)//声明了却没有赋值的变量undefined
a = 40//给隐式声明的变量赋初始化值
var a = 20//更新a的值 40==>20
console.log(a)//20
}
console.log(a)//10
fn()//
console.log(a)//10
-
变量函数同名时 变量然后函数
console.log(a) var a=20 function a () { console.log(100) } console.log(a) /* 1. var a function a () { console.log(100) } console.log(a) a=20 console.log(a) */
-
变量变量同名时
console.log(a)//unf var a=10; console.log(a)//10 var a=20; console.log(a)//20 /* 1.var a;var a; 2.console.log(a)//undef 3.a=10; 4.console.log(a)//10 a=20; console.log(a)//20 */
-
函数和函数同名时
function fn () { console.log(111) } function fn () { console.log(2222) } fn()
-
同名标识符提升顺序问题:记住四个字
法则:形(形参和变量)实函运
标识符有三种写法:
var function function(a){}var a=20 function fn(a){ console.log(a,1)//函数 a=90 console.log(a,2) var a=100 console.log(a,3) function a () { console.log(6666) } console.log(a,4) } fn(a) /* var a; a=20 function a () { console.log(6666) } //打印函数 a=90 console.log(a,2)//打印90 a=100 console.log(a,3)//100 console.log(a,4) //100 */
这个流程就是:
1.先隐式提升当前作用域内部的所有形参变量和局部变量 (只是声明提升,不提升赋值)
2.再把实参赋值给形参变量
3.然后执行函数的隐式提前声明
4.再按照代码顺序运行代码
3.函数运行时的作用域
函数运行时 ,是在写函数代码的地方运行代码 , 不是在调用代码的地方运行代码。
function fn () {
var a=90
function fm () {
console.log(a)
}
return fm//a//200
}
var a=100
var re=fn()
// console.log(re)
re()//console.log(a)
function fn(a) {
function fm() {
a = a + 1
console.log(a)
}
return fm
}
var f1=fn(10)
f1()
f1()
/*
{
var a=12
return fm
{
a = a + 1
console.log(a)//11
}
{
a = a + 1
console.log(a)//12
}
}
f1()==>fm()
*/
function fn(a) {
function fm() {
a = a + 1
console.log(a)
}
return fm
}
var f1=fn(10)
f1()
var f2=fn(10)
f2()
/*
{
var a=10
{
a = a + 1
console.log(a)//11
}
}
{
var a=10
{
a = a + 1
console.log(a)//11
}
}
*/
4.js函数预编译
什么是预编译?
函数运行时,代码的运行步骤就是函数预编译。
js完成解释执行分为三个步骤:
1.语法分析;
2.预编译(全局预编译、函数预编译);记住四个字:形(形参和变量)实函运;
3.执行语句。
深度理解一下上面的第二步 预编译:
函数每调用一次,会生成一个新的对象,自调用且无终止条件的话生产无数个对象,浏览器就会瘫痪。
- 给这个AO对象添加成员,函数内部的局部变量和形参变量 名 作为AO对象的属性名。形参名和实参名冲突时,并不影响。
- 把传入的实参赋值给AO对象的属性;
- 把局部函数的名字 让AO对象也有相同的成员名,把函数赋值给这个属性;
- 运行代码。
全局作用域运行代码时,也有预编译。
- 生产一个对象 Global Object(GO);
- 把所有的全局变量,设置为GO的属性名;
- 把所有函数名作为GO的程序名,把函数名赋值给这个成员;
- 看是不是浏览器环境中的js脚本,如果是浏览器,还会执行异步,GO给window对象共享成员;
- 运行代码。
全局预编译还有一步: 不同的环境中运行js代码不一样,GO的成员全部浅拷贝给环境对象window。node.js中没有这一步。
拓展知识:
关于访问成员,console.log(a)访问的时GO对象的成员。
console.log(window.a);原型链中没找到的话不报错,浏览器返回undifined;但是作用域链中GO中找不到的话就会报错。
5.作用域链
执行期上下文:当函数执行时,会创建一个称为执行期上下文的内部对象。一个执行期上下文定义了一个函数执行时的环境,函数每次执行时对应的执行上下文都是独一无二的,所以多次调用一个函数会导致创建多个执行上下文,当函数执行完毕,它所产生的执行上下文被 “销毁”。
js对象有两种成员:
- 一种是上文成员(js语法可以访问的成员);
- 一种是下文成员(底层语言,只能看不能运用)。
- [[]] 括起来的成员名 就是下文成员。
- 例如:[[scope]]这个对象内部保存的就是函数的作用域。现在我们知道作用域就是一个对象,函数在定义或者申明的时候就有了作用域[[scope]]。里面保存了上文的AO对象。
function fn(n){
var a=20;
function fm(){
}
}
fn(100);//AO{n:undifined==> 100, a:undif==>20,function fm(){}}
fn();//AO{n:undif,a:undif ==>20,function fm(){}}
function fn(n){
var a=20;
function fm(){
}
}
fn(100);
fn(200);
函数生成了就会有个属性[[scope]]作用域“数组”(只能引擎使用)
函数调用时生成AO对象 , 会把AO对象放在scope
每次调用都会放在scopes前面(顶部)
每个函数scopes数组中天生就有一个AO对象 就是这个函数的上层的AO
function fn(a){
var a2=100;
function fm(b){
var b2=200;
}
fm(20);
}
fn(10);
分析上面这段代码的作用域链
如下:
/*
Go:{fn函数创建了 放在Go对象内部}
fn.[scope]=[
Go:{fn函数创建了 放在Go对象内部}
]
fn(10)=>
fn.[scope]=[
AO-fn(10):{
a:10,
a2:100,
fm创建了
},
Go:{fn函数创建了 放在Go对象内部}
]
fm.[scope]=[
AO-fn(10):{
a:10,
a2:100,
fm创建了
},
Go:{fn函数创建了 放在Go对象内部}
]
fm(20)
fm.[scope]=[
AO-fn(10)-fm(20):{
b:20,
b2:200
console.log(a,a2,b,b2,c)
},
AO-fn(10):{
a:10,
a2:100,
fm创建了
},
Go:{fn函数创建了 放在Go对象内部,c}
]
fm(90)
fm.[scope]=[
AO-fn(10)-fm(90):{
b:90,
b2:200
console.log(a,a2,b,b2,c)
},
AO-fn(10):{
a:10,
a2:100,
fm创建了
},
Go:{fn函数创建了 放在Go对象内部}
]
*/
难题分析:
function fun(n, o) {
console.log(o);
return {
fun: function (m) {
return fun(m, n);
}
};
}
var a = fun(0);
a.fun(1);
a.fun(2);
a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1);
c.fun(2);
c.fun(3);js
结果:
分析:
/*
Go:{fun函数创建了,a:und,b:und,c:und}
fun.[scope]=[
Go:{fun函数创建了,a:und,b:und,c:und}
]
//a = fun(0);
fun.[scope]=[
AO-fun(0):{
n:0,
o:und
return {fun:function(m) {return fun(m, n)}}
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
a.fun.[scope]=[
AO-fun(0):{
n:0,
o:und //第一次打印undefined
return {fun:function(m) {return fun(m, n)}}
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
//a.fun(1);
a.fun.[scope]=[
AO-fun(0)-a.fun(1):{
m:1
return fun(1, 0)
},
AO-fun(0):{
n:0,
o:und,
return {fun:function(m) {return fun(m, n)}}
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
fun.[scope]=[
AO-fun(1, 0):{
n:1,
o:0, //第二次打印0
return {fun: function(m) {return fun(m, n)}}
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
//a.fun(2)
a.fun.[scope]=[
AO-fun(0)-a.fun(2):{
m:2,
return fun(2, 0);
},
AO-fun(0):{
n:0,
o:und
return {fun:function(m) {return fun(m, n)}}
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
fun(2, 0);
fun.[scope]=[
AO-fun(2,0):{
n:2,
o:0 //第三次打印0
return {fun: function(m) {return fun(m, n)}};
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
a.fun(3)同理==>第四次打印0
*/
/*
Go:{fun函数创建了,a:und,b:und,c:und}
fun.[scope]=[
Go:{fun函数创建了,a:und,b:und,c:und}
]
//var b = fun(0).fun(1).fun(2).fun(3);
==>
fun(0)
fun.[scope]=[
AO-fun(0):{
n:0,
o:und
return {fun: function(m) {return fun(m, n)}}
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
fun(0).fun.[scope]=[
AO-fun(0):{
n:0,
o:und //第五次打印undefined
return {fun: function(m) {return fun(m, n)}}
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
fun(0).fun(1)
fun(0).fun.[scope]=[
AO-fun(0)-fun(0).fun(1):{
m:1
return fun(1, 0);
},
AO-fun(0):{
n:0,
o:und
return {fun: function(m) {return fun(m, n)}}
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
fun(1, 0)
fun.[scope]=[
AO-fun(1, 0):{
n:1,
o:0, //第五次打印0
return {fun: function(m) {return fun(m, n)}}==>fun(0).fun(1)
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
fun(0).fun(1).fun.[scope]=[
AO-fun(0)-fun(0).fun(1):{
m:1
return fun(1, 0);
},
AO-fun(0):{
n:0,
o:und
return {fun: function(m) {return fun(m, n)}}
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
fun(0).fun(1).fun.[scope]=[
AO-fun(0).fun(1):{
m:1
return fun(1, 0);
},
AO-fun(0):{
n:0,
o:und
return {fun: function(m) {return fun(m, n)}}
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
fun.[scope]=[
AO-fun(1,0):{
n:1
o:0
return {fun: function(m) {return fun(m, n)}}
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
fun(0).fun(1).fun(2)
[scope]=[
AO-fun(0).fun(1).fun(2):{
m:2,
return fun(2,1)
},
AO-fun(1,0):{
n:1
o:0
return {fun: function(m) {return fun(m, n)}}
},
Go:{fun函数创建了,a:und,b:und,c:und}
]
fun(2,1)
AO:{
m:2
o:1
}