JS中的作用域
一、类别
- 全局作用域
在浏览器环境下,全局作用域是window对象,在Node.js环境下,全局作用域是global对象。
在全局作用域中声明的变量或函数会成为全局变量或全局函数,可以在整个代码的任何地方被访问。
- 函数作用域
函数作用域是定义在函数体内部的变量或函数,它们只能在函数体内部被访问。
- 块级作用域
ES6引入了let和const声明方式,使得JavaScript拥有了块级作用域。块级作用域是指变量或函数仅在它们被声明的块({})内部可见。
二、作用域规则
内部作用域可以访问外部作用域的变量,但外部作用域不能访问内部作用域的变量。
function fn(){
var a = 1;
}
fn()
console.log(a); // a在全局作用域中没有声明,报错
三、变量声明
1.var声明
特点: var 声明的变量具有函数作用域或者全局作用域。
var 存在声明提升的特性,这意味着无论 var 声明在函数的哪个位置,它都会被处理成好像是在函数的最顶部声明的一样。,特别注意,仅仅是声明提升
var 可以重复声明变量。
2、let声明
let 是ES6引入的一种新的变量声明方式,let 会和{}形成块级作用域,let声明的变量仅在此作用域内有效。
与 var 不同,let 声明的变量不会被提升,因此它们只能在声明之后被访问,且let 也不可以重复声明变量。
var和let的区别
console.log(age)
var age = 18 //undefined 变量声明提升,声明而未赋值
console.log(name)
let name = 'mike' //报错,出现暂时性死区
3.const声明
基本与let一致,但是用于命名一个常量,即一旦被赋值不可被修改
四、this指向
普通函数
1.普通函数最终指向调用它的对象,也就是说谁调用就指向谁。
2.没有被对象调用的函数默认指向window
const obj = {
name:Mike
bg:function(){
console.log(this.name)
}
}
obj.bg() //Mike
var dbl = obj.bg //相当于把bg的函数体给dbl抄了一份
dbl() //undefined
3.函数是构造函数,但函数内部对this有定义
var age = 99;
var bg=function(){
this.age=1
console.log(++this.age)
}
bg() //两个环境都输出2,此时直接调用构造函数,this指向全局对象window
let a =new bg() //a调用构造函数,this指向a,并在构造过程中进行a.age的自增和打印
console.log(a.age) //输出2
箭头函数
this指向:箭头函数中的this指向,与函数定义位置(定义作用域)的作用域链上第一个有this指向的函数作用域中的this指向一致,注意只有函数的{}构成作用域,对象的{}以及 if(){}都不构成作用域;
var age = 99;
function PersonX() {
this.age = 0;
setTimeout(() => {
this.age++;
console.log(this.age)
}, 1000);
}
PersonX(); //箭头函数中的this与其上一层函数作用域,即与PersonX()的this指向一样,
//所以箭头函数中的this和PersonX()中的this指向一样,
//故箭头函数中的this指向的age和PersonX中this指向的age一样,所以箭头函数中的this.age=0
Node.js和浏览器运行环境的不同
在浏览器环境中,JavaScript 代码通常是在一个全局作用域中执行的,这个全局作用域与浏览器窗口紧密相关,并由 window 对象表示。即window即为全局对象。<br<
而在 Node.js 环境中,JavaScript 代码是在一个模块化的环境中执行的。Node.js 使用CommonJS模块系统,这意味着每个 .js 文件都被视为一个独立的模块,并拥有自己的顶层作用域。
global 对象代表了全局作用域,但它主要用于 Node.js 内部的全局变量和函数,以及跨模块共享的全局状态(尽管通常不推荐这样做)。每个模块顶层作用域中的变量和函数不是 global 对象的属性
//浏览器环境
var age = 99;
function PersonX() {
this.age = 0;
console.log(this.age++)
}
PersonX(); //0
console.log(age) //1
console.log(window.age) //1
例如此段代码,在浏览器环境中,最外层定义的var age就是全局对象window的一个属性,PersonX()调用时this指向全局对象window,对age重新赋值为0,即全局变量中的var age 被赋值为0,此后自增操作也是变化的这个最外层的age
//Node.js环境
var age = 99;
function PersonX() {
this.age = 0;
console.log(++this.age)
}
PersonX(); //1 修改的是global.age
console.log(age) //99
console.log(global.age) //1
而此段代码在Node.js中,最外层声明的var age属于该js模块的顶层作用域,并不属于global全局作用域,而PersonX的this指向全局作用域中的对象global,并不修改最外层的var age。
例题
//浏览器环境
var age = 99; //声明在了全局对象上
function PersonX() {
this.age = 0; //对window全局对象中的age重新赋值,全局对象的age的值由99变为0
setTimeout(() => {
console.log(++this.age); //指向和PersonX相同,均是全局对象window
console.log(age) //
//age沿着函数作用域链一直往外找,
//应该是到var age = 99有声明定义(const,let,var才叫声明定义,this并不是声明)
}, 1000);
}
PersonX();
浏览器环境中,输出1, 1 箭头函数的this和PersonX的指向一样,均是window全局对象,即最外层var age
var age = 99;
function PersonX() {
this.age = 0;
setTimeout(() => {
console.log(++this.age);
console.log(age)
//age沿着函数作用域链一直往外找,
//应该是到var age = 99有声明定义(const,let,var才叫声明定义,this并不是声明)
}, 1000);
}
PersonX();
node.js环境中,this.age对global.age赋值,而最外层的var age属于该模块的顶层作用域,不会被赋值改变,输出 1 99
为了统一浏览器和node.js的差异,多应用let和const声明变量,如下
let age = 99;
function PersonX() {
this.age = 0;
setTimeout(() => {
console.log(++this.age);
console.log(age)
}, 1000);
}
PersonX();
node环境与前文叙述一样,let age定义在该模块顶层作用域中;
而在浏览器环境中,let声明的let变量在全局作用域(即不在任何函数或块内部)中声明时,它实际上是在全局作用域中创建了一个变量,但这个变量不会成为全局对象(在浏览器中是window对象)的可枚举属性。let确实存在了全局对象上,但是并不可枚举,即不可遍历和访问,所以访问window.age时还是PersonX中声明赋值的0,最外层声明的let age并不会被读取,与node.js中的最外层的声明let age被存在当前模块的顶层作用域而不是全局作用域的全局对象global达成的效果一样
因此此段代码在浏览器和node.js环境中均输出 1 99
例题
console.log(window.age) //window和global全局对象的属性,允许不声明直接调用,输出undefined
let age = 99;
function PersonX() {
this.age = 0;
setTimeout(() => {
console.log(++this.age);
console.log(age)
}, 1000);
}
PersonX();
console.log(window.age)
console.log(age)
输出 undefined 0 99 1 99
2025.3.27再总结
三个概念,全局对象和全局作用域以及模块作用域
模块作用域的内容就是在这个模块上声明的变量和函数,全局对象是写在全局作用域上的一个对象, 全局作用域除了全局对象还有window.globalFunction等各种全局方法(此处以浏览器为例)
作用域链:模块内的声明的函数作用域(链)->模块的块级作用域->全局作用域
实际上,在模块上全局的声明通常是声明到该模块的块级作用域(就是该模块的最外层作用域,也可称为模块作用域)。
若是没有对象直接调用方法,默认为是全局对象调用的此方法(window/global)
浏览器环境下var声明,会直接作为全局对象的属性声明在全局对象window上,,而由于nodejs的模块化,会声明在模块的块级作用域上而不是全局对象上。
因此引入了let和const,声明在当前模块的块级作用域上