范围链是因为js的所有变量都是对象的属性,可能是其他对象的属性,所有对象都是窗口对象的属性,所以这些对象之间的关系可以看作一个链。
(1)作用域
变量的作用域是程序源代码中定义的变量区域。
1. 在JS中使用的是词法作用域(lexical scope)
没有在任何函数中声明的变量(如果在函数中省略了var,也是全局范围称为全局变量。
函数中声明的变量有函数作用域,属于局部变量。
局部变量优先于全局变量。
var name= one
功能测试(){
var name= two
console.log(名称);//两个
}
test();
如果函数中省略了var,就会影响全局变量,因为它实际上已经被重写为全局变量了。
var name= one
功能测试(){
name= two
}
test();
console.log(名称);//两个
函数作用域,也就是说,函数是一个作用域的基本单位。js没有if for等块级作用域。比如C/C。
功能测试(){
for(var I=0;i10i ){
if(i==5){
var name= one
}
}
console.log(名称);//一个
}
test();//因为是函数级作用域,所以可以访问name=one
当然js也用高阶函数,其实可以理解为嵌套函数。
函数test1(){
var name= one
返回函数(){
console.log(名称);
}
}
test1()();
test1()之后,将调用外部函数,并返回内部函数。如果继续(),内部函数会被调用并相应执行,所以会输出“一”。
嵌套函数涉及闭包,这将在后面讨论.这里,内部函数可以访问外部函数中声明的变量名,这涉及到作用域链机制。
2. JS中的声明提前
js中的函数作用域意味着函数中声明的所有变量在函数体中总是可见的。而且变量在声明之前就可以使用,这叫提前声明。
提示:预先声明是在js引擎预编译的时候进行的,在代码执行之前已经出现了预先声明的现象。
例如
var name= one
功能测试(){
console.log(名称);//未定义
var name= two
console.log(名称);//两个
}
test();
上侧实现以下效果。
var name= one
功能测试(){
var名称;
console.log(名称);//未定义
name= two
console.log(名称);//两个
}
test();
再次尝试移除var?这个函数中的名字已经变成了一个全局变量,所以它不再是未定义的。
var name= one
功能测试(){
console.log(名称);//一个
name= two
console.log(名称);//两个
}
test();
3.值得注意的是,上面提到的参数都没有通过。如果测试有参数怎么办?
功能测试(名称){
console.log(名称);//一个
name= two
console.log(名称);//两个
}
var name= one
测试(名称);
console.log(名称);//一个
如前所述,基本类型是按值传递的,所以传入测试的名字实际上只是一个副本,这个副本在函数返回后被清除。
千万不要更改函数中name=two 的全局名称,因为它们是两个独立的名称。
(2)作用域链
上述高级功能涉及范围链。
函数test1(){
var name= one
返回函数(){
console.log(名称);
}
}
test1()();
1.引入一个很长的段落来解释:
每段js代码(全局代码或函数)都有一个关联的作用域链。
这个作用域链是定义代码中“作用域内”变量的对象列表或链接列表。
当js需要寻找变量X的值时(这个过程称为变量解析),会从链中的第一个对象开始。如果这个对象有一个名为X的属性,它将直接使用这个属性的值。如果第一个对象中没有名为X的属性,js将继续查找链中的下一个对象。如果第二个对象仍然没有名为x的属性,它将继续寻找下一个,以此类推。如果作用域链上没有对象包含属性X,那么就认为这段代码的作用域链上没有X,最后抛出ReferenceError异常。
2. 作用域链举例:
在js的顶层代码中(即不包括任何函数定义中的代码),作用域链由一个全局对象组成。
在没有嵌套的函数体中,作用域链上有两个对象,第一个是定义函数参数和局部变量的对象,第二个是全局对象。
在嵌套函数体内,至少有三个对象在作用域内。
3. 作用域链创建规则:
当一个函数被定义时(注意它是在定义时开始的),它实际上保存了一个作用域链。
当调用这个函数时,它创建一个新的对象来存储它的参数或局部变量,将这个对象添加到那个作用域链中,并创建一个新的更长的“链”来表示函数的调用作用域。
对于嵌套函数,情况又发生了变化:每次调用外层函数时,都要重新定义内层函数。因为每次调用外部函数,作用域链都是不一样的。每次定义一个内部函数,都要有细微的不同——每次调用一个外部函数,内部函数的代码都是一样的,与这个代码关联的作用域链也是不一样的。
(提示:理解好以上三点,并且记住,最好用自己的话讲,或者背下来,因为面试官会直接问你:请描述一下scope chain.)
举一个范围链的实际例子:
var name= one
功能测试(){
var name= two
函数test1(){
var name= three
console.log(名称);//三个
}
函数test2(){
console.log(名称);//两个
}
test1();
test2();
}
test();
最上面是一个嵌套的函数,对应的作用域链上有三个对象。
然后在调用的时候,需要找到name的值,在作用域链上查找就可以了。
成功调用test1()时,顺序是test1()-test()-全局对象窗口。因为在test1()上找到了名称三的值,所以搜索完成并返回。
成功调用test1()时,顺序是test2()-test()-全局对象窗口。因为name的值在test2()中没有找到,所以在test()中找到了它,搜索完成并返回。
再比如,有时候我们会犯错,我们在面试中经常被骗。
! DOCTYPE html PUBLIC -//W3C//DTD XHTML 1.0 Transitional//EN http://www . w3 . org/TR/XHTML 1/DTD/XHTML 1-Transitional . DTD
html xmlns= http://www . w3 . org/1999/XHTML
头
脚本类型=文本/javascript
函数buttonInit(){
for(var I=1;i4;i ){
var b=document . getelementbyid( button I);
b.addEventListener(click ,function(){
alert(按钮 I);//都是Button4
},假);
}
}
window.onload=buttonInit
/脚本
/头
身体
按钮id=button1 按钮1/按钮
按钮id=button2 按钮2/按钮
按钮id=button3 按钮3/按钮
/body
/html
为什么?
根据作用域链中变量的搜索规则:
b.addEventListener(click ,function(){
alert(按钮 I);
},假);
这里有一个函数,是匿名的。因为它是一个函数,所以它在作用域链上有一个对象。这个函数中用到变量I,自然会在作用域中寻找。
搜索顺序是这个匿名函数——外部函数button init()——全局对象窗口。
在匿名函数里找不到我,自然就跑到buttonInit(),ok,在for里找到了,
此时,报名活动结束。不要以为它会把I一个一个放下,因为函数作用域内的变量对于作用域来说始终是可见的,也就是说,它们会保持在最后的状态。
当匿名函数要用I的时候,注册事件完成,I变成了4,所以都是Button4。
那怎么解决呢?
为它传入值。每次循环时,使用匿名函数传入I in for。匿名函数的规则是代码。
! DOCTYPE html PUBLIC -//W3C//DTD XHTML 1.0 Transitional//EN http://www . w3 . org/TR/XHTML 1/DTD/XHTML 1-Transitional . DTD
html xmlns= http://www . w3 . org/1999/XHTML
头
脚本类型=文本/javascript
函数buttonInit(){
for(var I=1;i4;i ){
(function(data_i){
var b=document . getelementbyid( button data _ I);
b.addEventListener(click ,function(){
alert( Button data _ I);
},假);
})(一);
}
}
window.onload=buttonInit
/脚本
/头
身体
按钮id=button1 按钮1/按钮
按钮id=button2 按钮2/按钮
按钮id=button3 按钮3/按钮
/body
/html
就是它,按钮1.2.3.
4.上述就是作用域链的基本描述,另外,with语句可用于临时拓展作用域链(不推荐使用with)
语法就像:
带有(对象)
声明
这个with语句将对象添加到作用域链的头部,然后执行该语句,最后将作用域链恢复到其原始状态。
简单用法:
例如,为表单中的每一项赋值。
是的,我们通常直接做。
var f=document . forms[0];
f . name . value=“”;
f . age . value=“”;
f . email . value=“”;
在with的介绍之后(因为用with会引起一系列问题,还是用上面的形式吧)
with(document.forms[0]){
f . name . value=“”;
f . age . value=“”;
f . email . value=“”;
}
另外,如果一个对象o有x属性,o . x=1;
然后使用
用(o){
x=2;
}
可以换算成o . x=2;
o如果没有定义属性X,那么它的作用只等价于X=2;这是一个全局变量。
因为with提供了读取O的属性的快捷方式,它不能创建O本身没有的属性。
要理解变量的作用域,首先要理解作用域链。
当您用var关键字声明变量时,您向变量所在的对象添加了一个属性。
作用域链:因为js的变量是对象的属性,可能是其他对象的属性,所有对象都是窗口对象的属性,所以这些对象之间的关系可以看作一个链。
链的头是变量所在的对象,链的尾是窗口对象。
请看下面的代码:
复制代码如下:
函数t() {
var a;
函数t2() {
var b;
}
}
js中的函数也是对象,所以变量A的对象是T,T在窗口对象中,所以A的作用域链如下
t形窗
所以B的现有对象是t2,t2包含在T中,T在窗口对象中,所以B的作用域链如下
t2时间窗
看,作用域链,我们开始变量的作用域分析。
1不带var的javascript变量是全局变量,是窗口对象的属性。
复制代码如下:
函数test1() {
//执行这句话的时候,会找到作用域对象。这个函数是作用域链中的第一个对象,但是这个对象中没有相关的var语句。
//只寻找作用域链的第二个对象,也就是全局对象,全局对象中没有相关的var语句。
//因为没有相关的var语句,js在函数中隐式声明了变量,即var all
all=30
警报(全部);
}
test1();
警报(全部);
alert(window . all);
2函数内定义的变量(函数内的函数除外)在整个函数内有效。
复制代码如下:
函数test2() {
var t=0;
//在for的条件中定义变量,这个变化的作用域链对象就是这个函数。
//所以在整个函数中都有效
for(var I=0;i5;i ) {
t=I;
}
警报(一);
}
test2();
函数中的3个变量替换同名的全局变量。
复制代码如下:
var t= bb
功能测试(){
//执行T时,会先寻找作用域链对象。因为它是在函数内部定义的,所以这个函数是其作用域链的第一个对象。
//而且这个对象中有T的定义,所以T是局部变量,代替了全局变量T。
//t此时只定义了,没有赋值。赋值在下一行,所以这里输出undefined。
警报(t);
var t= aa
警报(t);
}
test();
4块示波器
复制代码如下:
如果(真){
//块中定义了一个变量,其作用域链的第一个对象是全局对象窗口。
var tmp=0;
}
//tmp作用域链的第一个对象是全局对象窗口,上面的全局对象中有相关的var语句,所以输出0。
警报(tmp);
以下内容来自于阅读网络博客的总结。当你做笔记时,只记住重点。同时非常感谢愿意分享的博主们。你让我站在巨人的肩膀上!
1、
复制代码如下:
var temp=(function(){
var name= test
返回函数(){
nbsp警报(名称);
}
})();
上面的代码片段经常是jser写的,是传说中的闭包。众所周知:调用temp();会弹出“测试”;这个过程可以基于以下三个理论来解释:
1)1)js的作用域只与函数的定义者有关,函数的嵌套形成了作用域链;
2)作用域链的创建规则是复制上一个环境的作用域链,将指向环境变量对象的指针放在链的开头;
3)在Javascript中,如果一个对象不再被引用,那么这个对象将被GC回收。如果两个对象互相引用,并且不再被第三个对象引用,那么互相引用的两个对象也会被回收。
如果看完以上三条还是不明白,可以结合理论看下面代码的详细解释:
首先,执行并销毁外部函数;但是,外部函数的作用域链被复制到内部函数的作用域链中,形成了内部函数作用域链的一部分。记住是复制,不是引用(根据第2条),所以内部函数还是可以访问名字的;因为返回的内部函数是被temp引用的,所以当外部函数执行后被销毁时,内部函数仍然作为外部函数的一部分存在,和第三条一样被第三方引用。这就是传说中的封闭的原因。