JS高级
作用域&作用域链
作用域:
1.作用域的个数:n(函数声明的个数)+1(全局作用域)
2.作用域不会存储变量,只是执行查询规则
3.作用域分全局作用域和函数(局部)作用域
4.作用域是编译时产生的
5.没有块作用域(ES5)
6.作用域管理执行上下文,执行上下文管理变量
7.一般情况下,一个作用域对应一个执行上下文,但递归情况下一个作用域可以对应多个执行上下文。值得注意的是,任何情况下一个作用域只有一个活动的执行上下文
作用域链:
函数嵌套时会产生作用域链
--
作用域的作用:
1.隔离变量
2.为变量查询制定的一套规则。
--
变量查询的规则:
左查询:等号左边的变量用左查询。
--先在变量的当前作用域里面找变量是否声明,没有就到上一层找,直到整条作用域链都没有找到(也就是全局),那么浏览器会自动为变量声明。
右查询:等号非左边的变量用右查询规则。
--先在变量的当前作用域里面找变量是否声明,没有就到上一层找,直到整条作用域链都没有找到(也就是全局),那么报错。
--
左右查询案例:
<script>
console.log(b); // 报错
(function(){
function test(a){
var b=a;
console.log(b);//2
}
test(2);
})()
console.log(b);//b is not defined
</script>
执行步骤:
1.console.log(b); 对b右查询,在当前作用域(全局)查找b的声明,没有则报错
2.执行立即执行函数,然后调用test(2)函数,将实参2传给形参a
3.var b=a;对b执行左查询,在当前作用域有声明var b,且b=a,则b为2,输出2
4.立即执行函数运行结束, 运行console.log(b);对b进行右查询,在当前作用域(全局)查找b的声明,整条作用域链上没有找到,则报错。
作用域面试题:
image.png
以上示例图的执行步骤:
1.先执行var x =10;(x为全局变量)
2.执行show(fn);调用show()函数,将实参fn函数传给形参f,那么形参f拥有fn函数的地址值
3.f(),调用fn()函数。
4.console.log(x);对x执行右查询,在fn()函数中没有对变量x进行声明,那么到全局作用域里面去找,刚好找到var x =10;所以结果为10.
变量释放&内存回收
变量释放:
局部变量————>对应函数的作用域执行完后则变量释放
全局变量————>当运行程序关闭时变量才会释放
内存回收:
垃圾收集器会按照固定的时间间隔周期性的回收内存。一般使用标记清除,引用计数两种策略。
执行上下文&执行上下文栈
执行上下文(可以认为是可执行代码的执行环境):
1.执行上下文的个数:n(函数的调用次数)+1(全局执行上下文)
2.在函数被调用时,都会创建新的其对应作用域的执行上下文。
3.一般情况下,一个作用域对应一个执行上下文,但递归时,一个作用域可能对应多个执行上下文。(每调用一次函数创建一个执行上下文)
4.一个作用域只能有一个活动状态的执行上下文。(单线程、同步执行)
5.执行上下文分为:全局执行上下文(只有一个)、函数执行上下文(可以有无数个)
--
执行上下文栈:
当一个函数被调用时,会创建一个当前被调用函数的执行上下文,并将执行上下文压入执行栈中。当js第一次加载脚本时,默认进入全局执行上下文中。
image.png
<!--
1. 依次输出什么?
2. 整个过程中产生了几个执行上下文?
-->
<script type="text/javascript">
var i
console.log('global begin: '+ i) //undefined
i = 1
foo(1);
function foo(i){
if (i == 4) {
return;
}
console.log('foo() begin:' + i); //1,2,3
foo(i + 1);
console.log('foo() end:' + i); //3,2,1
}
console.log('global end: ' + i) //1
</script>
image.png
执行上下文的作用:
1.存储对应的变量
2.规则
---全局执行上下文规则:
1.var定义的全局变量,添加为window的属性
2.function声明的全局函数,添加为window的方法
3.提升(函数的提升优于变量的提升)
4.this指向(window)
--
---函数执行上下文规则
1.var定义的局部变量会成为局部执行上下文(在js中拿不到局部执行上下文对象)的属性
2.function声明的局部函数会成为局部执行上下文(在js中拿不到局部上下文对象)的方法
3.形参变量赋值给实参
4.为arguments赋值(实参列表)
5.提升(函数表达式不会提升)
6.this指向(看调用函数的对象的调用形式)
--
---函数执行上下文规则的this指向
1.普通调用 this—>window
在严格模式下 this—>undefined
2.隐式调用 this—>最近的调用者
3.显示调用 this—>指定的对象
4.构造调用 this—>构造出的实例对象
作用域与执行上下文的关系
对应的执行上下文 会 成为 作用域的属性(执行上下文最终会挂靠给作用域)
--作用域:
从浏览器的角度看作用域,它是一个c++对象,对象的属性就是执行上下文
从js语言的角度看作用域,它是一套规则(左查询、右查询)
--执行上下文:
从浏览器的角度看执行上下文,它是一个c++对象,对象的属性就是它所管理的变量
从js语言的角度看执行上下文,它是一套规则(规则如上)
提升
总结:
1.提升会提升到当前作用域的最顶层
2.变量提升是声明的提升
3.函数提升是整体提升
4.函数的提升优于变量的提升
注意点:如果有两个同名函数,那么后面的函数会覆盖前面所定义的函数(在js语言中没有重载的概念,所以无法通过参数来判断到底调用哪个函数)。
编码规范
1.使用变量前一定要先声明(避免污染全局,迎合js引擎的提升规则)
2.在分支中最好不要定义函数(因为在分支中的函数会变为函数表达式)
注意点:当函数名与变量名重名时
function demo(){...}==>var demo = function(){...}
var demo //跟函数名重复,js引擎在解析代码时会忽略此条声明
demo=5;
demo()
//最终demo = function(){...}=5 5() 结果报错
隐式丢失
当我们对函数进行隐式调用(对象.属性)时,可能会发生隐式丢失的问题。
1.以 对象.的形式 进行变量的赋值时容易发生隐式丢失
var fn = obj.test;
fn() // 发生了隐式丢失,原本this应该指向obj,结果fn()独立调用函数,this指向全局
var write = document.write;
write("****") // 原本this应该指向document,结果发生隐式丢失,所以报错
--
bind()函数
bind()函数(硬绑定)是用来解决隐式丢失问题的一种手段。
bind(this,[Arg1,Arg2...]),使用bind来实现硬绑定时,会产生一个新的函数,这个新的函数的this指向被绑定的对象(也就是bind中第一个参数this指定的对象),新的函数相当于拷贝了一份原函数,将bind中括号[]中的参数相当于实参([]中的参数可以省略),传给新函数中的形参。
<script>
var module = {
x: 42,
getX: function (a,b) {
console.log(a,b);
return this.x;
}
}
var unboundGetX = module.getX;
/*
使用bind硬绑定产生一个boundGetX的函数,boundGetX函数拥有跟原函数一样的函数体(this指向了module),
相当于拷贝了一份原函数,bind中[1,2]相当于新函数的实参
*/
var boundGetX = unboundGetX.bind(module,1,2);
console.log(boundGetX());
console.log(boundGetX);
</script>
函数调用优先级
构造函数 > 显示调用 > 隐式调用 > 普通调用
网友评论