前言
一直对js的作用域不是很熟悉,专门查了资料研究清楚了。
作用域
js中一共存在全局、函数、块三种作用域,块作用域在es6后出现。我们先讨论常规的全局、函数作用域。
块级作用域和函数级作用域
js的作用域和C、Java、Python类型的语言不一样,js的作用域是函数内的,Java的作用域是块内的。
如以下Java的代码,表明块级作用域
for语句定义的变量i只能在for语句中使用,外面使用不了。
外面对应的变量i,可以在内部的块中使用,块内部不能再定义同名变量。
private int func(){
int i = 1;
for(int j = 0; j < 10; j++){
System.out.println(i+""+j);
} //输出10到19
System.out.println(i); //输出1
//System.out.println(j); //报错
}
以下为js中的代码,表明函数作用域
函数内的为一个作用域,块内块外都是同一个作用域,可以相互访问
function t(flag){
var a = 'hello';
if(flag){
console.log(a); //输出hello
var s="ifscope";
for(var i=0;i<2;i++)
;
}
console.log(i); //输出2
console.log(s); //输出ifscope
}
t(true);
js中全局作用域
js中如果不在函数内部声明变量, 或者没有声明变量直接赋值,则会属于全局作用域,实际上是绑定在全局对象上。在浏览器环境,最上级的全局对象是window,在node环境则是global。
如:
node环境
var name="global";
function t(){
console.log(name);
console.log(global.name);
}
t();
// 输出两个global
如果函数内再次定义了全局变量明的变量,那么会“屏蔽”全局变量。
如:
浏览器环境
var n = 10;
function func(){
var n = 1;
console.log(n);
console.log(window.n);
}
func();
console.log(n);
会输出
1
1
10
我们还可以直接赋值而不定义
function func(){
n = 1;
console.log(n); // 1
}
func();
console.log(n); //1
就像alert()、location()这样的window下的api,我们不加window可以直接调用,js引擎会理解为全局变量下的api。所以我们也可以直接使用n,表示window.n=1;
全局作用域中var加与不加的区别
在全局作用域下,我们定义变量可以加var也可以不加,都能达到我们一般想要的效果,都会把变量放在全局对象的属性上。如:
var name="global";
// 或者
// name="global";
function t(){
console.log(name);
}
t();
他们的区别在于:使用var定义变量不能删除这个属性
var name="global";
delete name; //false
console.log(name); // "global"
sex = "man";
delete man; //true
console.log(sex); //Uncaught ReferenceError: sex is not defined
Javascript的变量声明、创建属性以及每个Javascript中的每个属性都有一定的标志说明它们的属性----如只读(ReadOnly)不可枚举(DontEnum)不可删除(DontDelete)等等
当我们使用var定义变量name时,声明了它不可删除的属性,所以不能删除。而直接赋值使用的情况下,可以删除这个变量。
js作用域链和with语句
我们看这样的代码:
name="fay";
function t(){
var name = "local name";
(function (){
var name = "local1 name";
(function t2(){
//a
console.log(name);
})()
//b
console.log(name);
})();
//c
console.log(name);
}
t();
它会输出
local1 name
local1 name
local name
js中存在一种作用域链。
因为js作用域是函数,代码执行后,会创建全局可执行环境,把name放入其中。
执行t函数后,会在全局环境下再开一个执行环境入栈,把里面name放入其中,这时name已经有一条从local name到fay的链了。
之后再执行内部函数会重复这个步骤,直到最里面的一层,那时的name的作用域链是local1 name到local name到fay,a处输出name时默认使用指针指向的及最里面的作用域。所以输出了一个local1 name,之后这个最里层函数t2所在的执行环境会出栈连带所有变量(代码中并没有)。
之后b处此时name的作用域链还没有变,输出local1 name。b输出后会弹出环境栈。
此时c处再输出时,name的作用域链已经是local name->fay 了,这样会输出local name。
总结
-
外层函数是无法读取内层函数的变量,除非内层函数声明的是全局变量,而内层函数因为作用域链的关系是可以访问到外层的变量的。
-
赋值时,只其他其他外层部分没有找到这个变量,才会绑定在全局变量中。
如:
function t(){ var name = 1; function t1(){ name =2; console.log(name); } t1(); console.log(name); } t(); console.log(name); //输出 //2 //2 //ReferenceError: name is not define
with语句
person={name:"fay",age:22,birthday:{year:2000}};
with(person.birthday){
console.log(year); //输出2000
}
with语句主要用来临时扩展作用域链,将语句中的对象添加到作用域的头部。
js变量提升
function func(){
console.log(a);//undefined
var a = 10;
console.log(b); //ReferenceError: b is not defined
}
func();
var定义变量并赋值时,变量的定义实际上会被提升至作用域开始,这样var a =10;会被分解为
var a;
console.log(a);
a=10;
所以,a会提示undefined,而b就会提示变量不存在了。
题外:函数声明提升
函数声明的方式
- 函数声明:function test(){};
- 函数表达式:var test = function(){};
代码一:
test();//输出:函数声明
var test = function(){
console.log("函数表达式");
}
test();//输出:函数表达式
function test(){
console.log("函数声明");
}
test();//输出:函数表达式
解释:函数function test()被声明提升了,并且优先级高于了变量声明。但是后来的test函数表达式又覆盖了之前的函数声明;
代码二:
console.log(test);/*输出:ƒ test(){console.log("函数申明");}*/
function test(){
console.log("函数申明");
}
var test;
console.log(test);/*输出:ƒ test(){console.log("函数申明");}*/
var test = 1;
console.log(test);//1
解释:函数声明的优先级高于了变量声明,并且再次声明后,变量也test不会覆盖之前的函数声明。如果赋值了test=1,test才会被(覆盖原则)改变为1
es6后的let和const
let和const与var拥有不同的作用域、变量提升
快速使用
let就时我们熟悉的java和c类型语言的作用域,使用的是块作用域。
如:
function t(){
for(let i = 0;i<10;i++){
console.log(i);
}
console.log(i);
}
t();
//输出
//每行0~9
//ReferenceError: i is not defined
而在使用var时,还会输出10
const
const是定义常量的,就像java中的final一样,当我们使用const定义了一个Object类型变量,那么我们实际上是限定了这个变量的引用指向,而Object中的内容是可以改变的。如:
const person = {
"name":"fay",
"age":20
}
console.log(person.age);
person.age=21;
console.log(person.age);
会正常输出20和21
另外注意,const必须在声明的时候赋值。
全局使用
let name = 1;
console.log(name); // 1
console.log(global.name); //undefined
let和const在全局使用时,也不会绑定在全局对象上。
变量提升
let和const不存在变量提升,并且作用域是从定义开始到块结束,所以存在“变量死区”概念。你用c和java的变量作用范围去理解就很好理解了。
function func(){
console.log(name);
let name = "fay";
console.log(name);
}
func();
ReferenceError: Cannot access 'name' before initialization
使用建议
乱用var会导致一些意想不到的“错误”:
-
一个变量可以使用两次var(重声明)
-
默认情况下,顶级变量是全局变量(全局对象)
-
可以在声明前使用变量(提升)
-
循环中的变量重复使用同样的引用(闭包)
尽量使用let、const代替var,但是如果你要兼容es6之前的代码环境,那么还是需要使用var
如果你是小程序中的js代码,那么尽情的使用let吧,这样才是真正意义上的变量。
总结
js的作用域分析主要是针对es6前只有var声明变量的情况,我们可以清楚看到js的作用域以及变量提升等特性,这些特性十分特别。
在es6后,为了让代码更加清晰规范,带来了let和const,和常见的其他语言的使用方法类似。
我们尽可能使用let和const代替var声明变量,但是也要考虑兼容性。
参考
https://blog.csdn.net/yueguanghaidao/article/details/9568071
最近在搞JavaScript吗?
javascript.info 怎么样
最近补一下前端基础知识哈哈哈。
javascript.info好网站啊!mark了,Thanks