js作用域

本文详细解释了JavaScript中let、const和var声明的变量作用域差异,强调了块级作用域和函数级作用域的区别,以及闭包的概念和使用。还讨论了this关键字在不同情况下的行为和作用域链的工作原理。
摘要由CSDN通过智能技术生成

声明修饰符

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值