声明修饰符
let具有块级作用域
let声明的变量具有块级作用域,for循环流程控制部分(小括号内),循环体部分(大括号内)、以及函数体、大括号包裹的代码块、中使用let声明的变量仅在该块状作用域内可见.如果在全局环境中使用let声明则是全局变量.
在chrome控制台使用let声明变量虽然在当前控制台属于全局变量,但是并不会添加到window对象上
for循环流程控制部分(小括号内)使用let声明的变量循环体内也可见.并且每次循环都会声明一个新的循环变量,js引擎会'记住'上次循环变量的值,并为新声明的循环变量赋值.看下面的代码
let logfuns=[];
let addfuns=[];
for(let i=0;i<10;i++){
logfuns[i]=function(){
console.log(i);
}
addfuns[i]=function(){
i++;
}
}
logfuns[8]();//输出8
addfuns[8]();//此时i的值变为9
logfuns[8]();//输出9
console.log(i);//报错 ReferenceError: i is not defined
由于for中用let声明的变量i,那么每次循环时都会新声明一个同名变量i(同名但不同),由于logfuns和addfuns是全局变量,且对循环中声明的变量i有引用,所以循环结束后i并没有释放,但是由于作用域限制外部无法直接访问变量i,但是函数可以访问到(这里就是js的闭包,可以理解成java的private修饰的属性用get/set访问)
const具有块级作用域
const和let一样具有块级作用域,不过const修饰的变量无法修改变量的值,而且必须声明时同时赋值,无法先声明然后再赋值.
const j=10;
const i;//会报错
i=100;
var具有函数级作用域
var声明的变量具有函数级作用域,且有声明提前(let/const没有)
var在函数内的任意位置声明,声明前的代码依然可以访问该变量,就相当于把声明提到了函数的开头.在赋值前此值都是undefined.
var没有块级作用域,也就是在一个块(例如 if
语句、for
循环等)内部声明,它实际上是在包围它的函数作用域或全局作用域中声明的。
同样使用function关键字
传统方式声明的函数,形如:function funName(){}也在其所在作用域内声明提前,函数表达式(包括使用 var
, let
, const
和箭头函数)的行为与此不同,它们不会被提前。例如,使用 var abc = function() {}
方式声明的函数,abc
作为变量会被提前,但是其赋值(即函数本身)不会被提前。看下面的示例:
fun1();// 正常输出
console.log(fun2);// undefined
fun2();//报错 TypeError: fun2 is not a function
function fun1(){
console.log("使用function关键字声明的函数");
}
var fun2=function(){
console.log("使用函数表达式声明的函数");
}
可以简单的理解: function关键字声明的函数为一个整体,声明提前包括函数体,函数表达式定义的函数只是变量(函数名)声明提前,赋值(函数体)并没有提前,这个和变量声明提前一样.
声明提升的优先级
function定义的函数声明优先级高于变量.同名的函数声明和变量声明会重叠,但函数声明的优先级更高。看下面的例子
1: console.log(a.toString())
2: var a="flag1: 使用var关键字声明的变量会有声明提升";
3: function a(){
4: console.log("flag2: 使用function关键字定义的函数会整体提升");
5: }
6: console.log(a.toString())
7: var a=function(){
8: console.log("flag3: 使用函数表达式定义的函数");
9: }
10: console.log(a.toString());
3次输出的结果依次是 flag2,flag1,flag3(这里只是简单的根据标记说明),js引擎在编译阶段会把变量/函数声明提升,对于同名变量的声明函数声明优先级高于变量,并且函数声明是包含函数体的整体提升.
所以执行阶段,第一行执行时a实际上是function定义的函数,,所以输出的是flag2
然后执行第2行为a赋值一个字符串,所以第6行输出时为flag1
第7行又为a赋值一个函数体,所以第10行输出为flag3
在chrome控制台var声明变量会添加到window对象上
无修饰符声明的变量为全局变量
无论在任何位置,如果不就修饰符声明一个变量,则该变量为全局变量.
闭包
- 闭包是 JavaScript 中的一个高级特性,它允许一个函数记住并访问它在创建时的作用域链,即使该函数在不同的作用域中执行。
- 闭包通常是在一个函数内部创建另一个函数时形成的。内部函数将保留对外部函数作用域的引用,即使外部函数已经执行完毕。
js闭包可以实现外部访问内部变量或函数.此时的内部声明的变量依然在内存中没有释放,但是外部无法直接访问,需要通过内部函数来间接访问.
注意大量使用闭包可能导致内存占用高
上面的for循环就是一个例子,接下来再来看下面的例子,思考一下输出结果
let name="张三";
let person={
name:"李四",
age:"18",
sayHello:function(){
console.log("hello "+this.name);
},
sayHi:function(){
return function(){
console.log("hi "+this.name);
}
},
sayNihao:function(){
let that=this;
return function(){
console.log("ni hao "+that.name);
}
}
}
person.sayHello();
person.sayHi()();
person.sayNihao()();
sayHello输出'hello 李四' 这个主要看的是this指向,this指向调用者,这里是person
sayHi输出"hi undefined" 首先person.sayHi()会返回一个函数对象,这个函数对象再执行,返回函数并没有指定调用者,这时会使用全局环境.this会指向window(浏览器环境),但是由于name是let修饰,所以window.name并不存在.如果改成var修饰这里的输出结果为"hi 张三"
sanNihao输出结果为'ni hao 李四',这里是一个闭包,在外部访问内部变量that,根据上面同样的分析that指向person对象
PS: 普通函数的this始终指向执行时调用者,箭头函数的this始终指向定义时作用域
作用域链
- 当代码在一个环境中执行时,比如一个函数,它创建了一个作用域链。这个链条的目的是为了提供对变量和函数的访问。
- 作用域链的开始是当前执行环境的局部作用域,包括了该环境中定义的所有变量和函数。
- 然后,作用域链会包含外围函数的作用域,然后是更外围的函数的作用域,一直延伸到全局作用域。
- 当试图访问一个变量时,JavaScript 会先在当前的局部作用域查找。如果没有找到,它会沿着作用域链向上查找,一直到达全局作用域。如果在全局作用域中仍然找不到该变量,会产生一个错误。
this
js中普通函数的this指向执行时的调用者,如果函数作为某个对象的方法来调用则this指向该对象,否则指向全局对象(非严格模式),严格模式下是undefined.
js中的箭头函数指向函数定义时的作用域.
看下面的示例
let fun1=function(){
console.log('fun1 this',this);
}
let fun2=()=>{console.log('fun2 this',this)};
let obj={name:"obj",fun:function(){
console.log('fun this',this);
function innerFun(){
console.log('innerFun this',this);
}
innerFun();
},fun1,fun2}
obj.fun1();
obj.fun2();
obj.fun();
首先obj.fun1(),这里的fun1作为obj的方法来调用,所以this指向obj
obj.fun2(),由于fun2为箭头函数,所以this指向定义时作用域和执行时调用对象无关,所以这里this指向全局,浏览器环境下是window
obj.fun(),obj调用的所以this指向obj
innerFun()这里并没有作为对象的方法来调用,所以非严格模式下this指向全局,浏览器环境下指向window