JS中的作用域和this的指向问题

124 阅读7分钟

JS中的作用域

一、类别

  1. 全局作用域

在浏览器环境下,全局作用域是window对象,在Node.js环境下,全局作用域是global对象。 在全局作用域中声明的变量或函数会成为全局变量或全局函数,可以在整个代码的任何地方被访问。

  1. 函数作用域

函数作用域是定义在函数体内部的变量或函数,它们只能在函数体内部被访问。

  1. 块级作用域

ES6引入了letconst声明方式,使得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,声明在当前模块的块级作用域