首先说一下js的作用域,变量有4种基本方式进入作用域:
1.语言内置: 所有的作用域里都有this和arguments;(需要注意的是arguments在全局作用域是不可见的)
2.形式参数: 函数的形式参数会作为函数体作用域的一部分;
3.函数声明: 像这种形式: functionfn() {};
4.变量声明: 像这样: var name;
前两种我们这里不做讨论。
什么是作用域?
简单来说就是变量和函数可作用的范围。在ECMAScript6之前js是没有块级作用域的,只有全局作用域跟函数作用域(局部作用域)。
下面的这段代码,可能会经常遇到:
//要获取列表元素的索引
var li=document.querySelectorAll('li');
for(var i=0; i<li.length; i++){
li[i].οnclick=function(){
alert(i);
}
}
为什么得到始终都是li.length?
其实每个li对象在触发click事件时,for循环已经执行完毕,匿名函数中并没有重新定义变量i,所以此时都指向全局变量i(for循环不存在作用域),所以结果都是li.length。
说到作用域,变量提升就不得不说。上面情况就是存在变量提升导致的。
一、变量提升
看下面这段代码:
function fn(){
if(!b){
var b=10
}
console.log(b) //10
}
fn();
这里可能刚接触js的会感到诧异。if语句根本执行,为什么打印结果是10 。
其实js在执行这段代码的时候回提前声明变量b,但是没有赋值。
上面的代码在执行时,会解析成:
function fn(){
var b; // 声明提前
if(!b){
b=10
}
console.log(b)
}
fn();
这里也是js中比较怪的一个现象,在js中,用var关键字声明的变量,在js解析时会提前声明。
二、函数声明提前
在js中不仅变量会声明提前,函数同样也会声明提前
function fn(){
console.log(test); // function test(){}
console.log(test2) //undefined
return ;
function test(){}
var test2=function(){}
}
fn();
这里的test2为什么没有提前。这里还得说一下函数声明的两种方式
1. 函数声明:用function关键字声明函数。
2. 函数表达式:用var定义一个变量指向一个匿名函数(非function关键字声明的函数)。
test2只是声明了一个变量,指向了一个匿名函数。声明提前了,没有给他赋值。
上面的代码在执行时,会解析成:function fn(){
function test(){}; //整体提前
var test2; //声明提前
console.log(test);
console.log(test2)
return ;
test2=function(){} //函数表达式
}
fn();
注:在js中用function关键字声明的函数,在js解析时,会整体提前到该作用域的最前面。
三、作用域链
作用域链想说清楚比较麻烦,这里只是简单的说一下。有点js基础的应该都知道,所有末定义直接赋值的变量自动声明为拥有全局作用域。这是为什么?
我们先看下面的这段代码:
function fn1(){
function fn2(){
function fn3(){
n=10;
}
fn3();
}
fn2();
console.log(n) // 10
}
fn1();
console.log(n); // 10
作用域链:fn3 -> fn2-> fn1-> global;
var n="a";
function fn1(){
function fn2(){
var n="b";
function fn3(){
console.log(n) // b
}
fn3();
}
fn2();
function test(){
g=10;
}
test();
console.log(g) //10
console.log(n) //a
}
fn1();
作用域链:fn3 -> fn2-> fn1-> global;
作用域链:test -> fn1-> global;
四、with语句
with 语句用于设置代码在特定对象中的作用域。
var sex="woman";
var age=20;
function fn(){
var sex='man';
var name='lisi';
var json={
name:'zhangsan'
}
with(json){
console.log(name); //zhangsan
console.log(sex); // man
<span style="white-space:pre"> </span>console.log(age); //20
}
<span style="white-space:pre"> </span>console.log(name) // lisi
}
fn();
当设置的对象中没有想对应用的属性时,会向with设置的特定作用域逐级向上一作用域中找。当with语句执行完毕时,作用域链回复正常。
上面只是自己对作用域的一些浅解,有不当之处还请指正。