(1) Scope
The scope of a variable is the area of the variable defined in the program source code.
1. Lexical scope is used in JS
Variables that are not declared within any function (var is omitted in a function are also considered global) are called global variables (global scope)
Variables declared within a function have function scope and are local variables
Local variables have higher priority than global variables
var name="one"; function test(){ var name="two"; console.log(name); //two } test();
Omitting var in the function will affect the global variable, because it has actually been rewritten into a global variable
var name="one"; function test(){ name="two"; } test(); console.log(name); //two
Function scope, that is to say, function is the basic unit of a scope. js does not have block-level scope like c/c, such as if for, etc.
function test(){ for(var i=0;i<10;i++){ if(i==5){ var name = "one"; } } console.log(name); //one } test(); //因为是函数级作用域,所以可以访问到name="one"
Of course, higher-order functions are also used in js, which can actually be understood as nested functions
function test1(){ var name = "one"; return function (){ console.log(name); } } test1()();
After test1(), the outer function will be called, and an inner function will be returned. Then continue(), and the inner function will be called and executed accordingly, so "one"
will be output.
Nested functions involve closures, which we will discuss later. Here, the inner function can access the variable name declared in the outer function, which involves the scope chain mechanism
2. Declaration in JS in advance
The function scope in js means that all variables declared within the function are always visible within the function body. Moreover, the variable can be used before it is declared. This situation is called hoisting
tip: Declaration in advance is performed when the js engine is pre-compiled. The phenomenon of declaration in advance occurs before the code is executed
For example
var name="one"; function test(){ console.log(name); //undefined var name="two"; console.log(name); //two } test();
The above achieves the following effect
var name="one"; function test(){ var name; console.log(name); //undefined name="two"; console.log(name); //two } test();
Try removing var again? This is the name within the function that has become a global variable, so it is no longer undefined
var name="one"; function test(){ console.log(name); //one name="two"; console.log(name); //two } test();
3. It is worth noting that none of the above mentioned parameters are passed. What if test has parameters?
function test(name){ console.log(name); //one name="two"; console.log(name); //two } var name = "one"; test(name); console.log(name); // one
As mentioned before, basic types are passed by value, so the name passed into the test is actually just a copy. This copy is cleared after the function returns.
Don’t think that name="two" in the function changes the global name, because they are two independent names
(2) Scope chain
The advanced functions mentioned above involve scope chain
function test1(){ var name = "one"; return function (){ console.log(name); } } test1()();
1. Introduce a large paragraph to explain:
Each piece of js code (global code or function) has a scope chain associated with it.
This scope chain is a list or linked list of objects. This group of objects defines the variables "in scope" in this code.
When js needs to find the value of variable x (this process is called variable resolution), it will start from the first object in the chain. If this object has an attribute named x, then The value of this attribute will be used directly. If there is no attribute named x in the first object, js will continue to search for the next object in the chain. If the second object still does not have an attribute named x, it will continue to look for the next one, and so on. If no object in the scope chain contains attribute x, then it is considered that x does not exist in the scope chain of this code, and eventually a ReferenceError exception is thrown.
2. Scope chain example:
In the top-level code of js (that is, the code that does not include any function definition), the scope chain consists of a global object.
In a function body that does not contain nesting, there are two objects on the scope chain. The first is the object that defines function parameters and local variables, and the second is the global object.
In a nested function body, there are at least three objects in the scope.
3. Scope chain creation rules:
When a function is defined (note, it starts when it is defined), it actually saves a scope chain.
When this function is called, it creates a new object to store its parameters or local variables, adds the object to that scope chain, and creates a new, longer representation of the function calling scope. The "chain".
For nested functions, the situation changes again: every time the external function is called, the internal function will be redefined again. Because every time an external function is called, the scope chain is different. Inner functions need to be subtly different each time they are defined - the code of the inner function is the same every time the outer function is called, and the scope chain associated with this code is also different.
(tip: Understand the above three points well and remember it. It is best to say it in your own words, otherwise you will have to memorize it, because the interviewer will ask you directly: Please describe the scope chain... )
A practical example of scope chaining:
var name="one"; function test(){ var name="two"; function test1(){ var name="three"; console.log(name); //three } function test2(){ console.log(name); // two } test1(); test2(); } test();
上边是个嵌套函数,相应的应该是作用域链上有三个对象
那么在调用的时候,需要查找name的值,就在作用域链上查找
当成功调用test1()的时候,顺序为 test1()->test()->全局对象window 因为在test1()上就找到了name的值three,所以完成搜索返回
当成功调用test1()的时候,顺序为 test2()->test()->全局对象window 因为在test2()上没找到name的值,所以找test()中的,找到了name的值two,就完成搜索返回
还有一个例子有时候我们会犯错的,面试的时候也经常被骗到。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <script type="text/javascript"> function buttonInit(){ for(var i=1;i<4;i++){ var b=document.getElementById("button"+i); b.addEventListener("click",function(){ alert("Button"+i); //都是 Button4 },false); } } window.onload=buttonInit; </script> </head> <body> <button id="button1">Button1</button> <button id="button2">Button2</button> <button id="button3">Button3</button> </body> </html>
为什么?
根据作用域链中变量的寻找规则:
b.addEventListener("click",function(){ alert("Button"+i); },false);
这里有一个函数,它是匿名函数,既然是函数,那就在作用域链上具有一个对象,这个函数里边使用到了变量i,它自然会在作用域上寻找它。
查找顺序是 这个匿名函数 -->外部的函数buttonInit() -->全局对象window
匿名函数中找不到i,自然跑到了buttonInit(), ok,在for中找到了,
这时注册事件已经结束了,不要以为它会一个一个把i放下来,因为函数作用域之内的变量对作用域内是一直可见的,就是说会保持到最后的状态
当匿名函数要使用i的时候,注册事件完了,i已经变成了4,所以都是Button4
那怎么解决呢?
给它传值进去吧,每次循环时,再使用一个匿名函数,把for里边的i传进去,匿名函数的规则如代码
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <script type="text/javascript"> function buttonInit(){ for(var i=1;i<4;i++){ (function(data_i){ var b=document.getElementById("button"+data_i); b.addEventListener("click",function(){ alert("Button"+data_i); },false); })(i); } } window.onload=buttonInit; </script> </head> <body> <button id="button1">Button1</button> <button id="button2">Button2</button> <button id="button3">Button3</button> </body> </html>
这样就可以 Button1..2..3了
4.上述就是作用域链的基本描述,另外,with语句可用于临时拓展作用域链(不推荐使用with)
语法形如:
with(object)
statement
这个with语句,将object添加到作用域链的头部,然后执行statement,最后把作用域链恢复到原始状态
简单用法:
比如给表单中各个项的值value赋值
一般可以我们直接这样
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;
那么使用
with(o){ x = 2; }
就可以转换成 o.x = 2;
假如o没有定义属性x,它的功能就只是相当于 x = 2; 一个全局变量罢了。
因为with提供了一种读取o的属性的快捷方式,但他并不能创建o本身没有的属性。
要理解变量的作用域范围就得先理解作用域链
用var关键字声明一个变量时,就是为该变量所在的对象添加了一个属性。
作用域链:由于js的变量都是对象的属性,而该对象可能又是其它对象的属性,而所有的对象都是window对象的属性,所以这些对象的关系可以看作是一条链
链头就是变量所处的对象,链尾就是window对象
看下面的代码: