JS作用域+this指向 【JavaScript】

news/2025/2/26 2:55:11

JS作用域:

在JavaScript中,作用域指的是程序中变量、函数和对象可访问的上下文环境。JavaScript主要有三种作用域:全局作用域、函数作用域和块作用域(ES6引入)。

  • 全局作用域:全局作用域中的变量在页面上的所有脚本和函数中都可以访问。这些变量在JavaScript代码的任何地方都可以被访问和修改。全局作用域通常由var关键字在函数外部声明的变量、函数以及window对象的属性构成。
javascript">var globalVar = "I am global";

function globalFunction() {
    console.log(globalVar); // 可以访问全局变量
}

globalFunction(); // 输出: I am global
  • 函数作用域: 函数作用域是指在函数内部声明的变量只能在该函数内部访问,这些变量在函数外部是不可访问的。函数作用域由函数体创建,每次调用函数时都会创建一个新的作用域。
javascript">function myFunction() {
    var functionVar = "I am local to the function";
    console.log(functionVar); // 可以访问函数内部变量
}

myFunction();
console.log(functionVar); // Uncaught ReferenceError: functionVar is not defined
  • 块作用域(ES6引入):块作用域是由{}包围的代码块(如if语句、for循环等)创建的作用域。在块作用域中声明的变量只能在该块内部访问。块作用域由letconst关键字引入。
javascript">if (true) {
    let blockVar = "I am local to the block";
    console.log(blockVar); // 可以访问块内部变量
}

console.log(blockVar); // Uncaught ReferenceError: blockVar is not defined

注意事项:

  • var声明的变量没有块作用域,只有函数作用域或全局作用域。
  • let和const声明的变量具有块作用域。
  • 在函数内部声明变量时,应优先考虑使用let或const而不是var,以避免潜在的作用域问题。
  • 全局变量在整个JavaScript代码中都可以访问和修改,这可能导致命名冲突和意外的行为。因此,应谨慎使用全局变量。

函数调用及this指向:

javascript">function fun(){
    console.log(123);
    return 'abc';
    console.log( fun ); //打印的是函数体
}

返回的是:
f fun(){
console.log(123);
return ‘abc’;
}
且函数体内的代码是不执行的。

javascript">function fun(){
    this.xxx = '123456'
    console.log(this);
    return 'abc';
}
console.log( fun() ); // 调用函数执行

返回的是:window abc 且函数体内的代码都是执行的。此时的this打印的是window。普通函数的this是指向window的,因为执行是在全局进行执行的

javascript">function fun() {
            this.xxx = '123456'
            console.log(this);
            return 'abc';
        }
console.log(new fun());

当你使用new 关键字调用一个函数时,这个函数会被当做构造函数来处理并且会创建一个新的对象实例。而这个this关键字会指向这个新创建的对象实例。

  • new fun()会创建一个新对象,并调用fun函数,其中this指向这个新对象。
  • 在fun函数内部,console.log(this);会打印出这个新对象,包含xxx属性。
  • 然后,new fun()会返回这个新对象实例。
  • 最后,外层的console.log会再次打印这个新对象实例。
    在这里插入图片描述

高频面试题例题一:

javascript"> function Foo() {
     getName = function () {
         console.log(1);
     } //注意是全局的 window.getName
     return this; //所以这里的this是window
}
Foo.getName = function () {
            console.log(2);
        }
Foo.prototype.getName = function () {
            console.log(3);
        }
var getName = function () {
            console.log(4);
        }
function getName() {
            console.log(5);
        }
Foo.getName();
getName();
Foo().getName();
getName(); 
new Foo().getName();

输出:

javascript">Foo.getName(); // 2 由于这里不是Foo().getName 所以我们找的是Foo.getName 
getName(); // 4 普通变量要大于函数,所以永远不可能打印出5
Foo().getName(); // 1 运行Foo()函数,后者覆盖前者,1覆盖了4 所以打印1
getName(); // 1 
new Foo().getName(); // 3
// 对象查找一个属性或查找一个方法:
//  1.先本身找 2.去他的构造函数找 3.去他对象的原型中找 4.去构造函数的原型上找 5.去原型链上找

代码解读与优先级分析:

函数声明与变量声明‌:

  • 在JavaScript中,函数声明会被提升到它们所在作用域的顶部,并且优先于变量声明。但是,如果同一个作用域内既有函数声明又有变量声明,并且它们同名,那么变量声明会覆盖函数声明。
  • 在代码中,有两个全局的getName声明:一个函数声明(输出5那个)和一个变量声明(输出4那个)。由于变量声明(即使它包含一个函数)会覆盖函数声明,所以全局的getName最终指向的是变量声明中的函数(输出4的那个)。但是,这个全局的getName在Foo函数内部被重新赋值了(输出1的那个),所以这个覆盖关系在Foo函数执行后发生了变化。‌

Foo函数内部的getName‌:

在Foo函数内部,getName被重新赋值为一个新的函数(输出1的那个),并且这个新函数被显式地设置为window对象的属性。这意味着,一旦Foo函数被执行,全局的getName就会被改变。‌

Foo的静态方法与原型方法:

  • Foo.getName是Foo函数对象的一个静态方法,它与Foo的实例无关。
  • Foo的原型方法‌:Foo.prototype.getName是Foo实例的原型上的一个方法。当你通过new Foo()创建一个Foo的实例并调用其getName方法时,调用的就是这个原型上的方法。

调用顺序与输出‌:

  • Foo.getName();:调用的是Foo的静态方法,输出2。
  • getName();:在Foo函数执行之前,调用的是全局变量getName所指向的函数,输出4。(变量声明会覆盖函数声明)
  • Foo().getName();:执行Foo函数,该函数改变了全局的getName,然后调用改变后的全局getName,输出1。
  • getName();:由于全局的getName已经被Foo函数改变,所以再次调用时仍然输出1。
  • new Foo().getName();:创建一个Foo的实例,并调用其原型上的getName方法,输出3。

总结:

  • 全局作用域中的函数声明会被变量声明覆盖(如果它们同名)。
  • 在函数内部对全局变量的修改会影响全局作用域。
  • 静态方法属于函数对象本身,与实例无关。
  • 实例方法位于函数的原型上,通过实例来调用。
  • 当通过 new 关键字创建的实例调用一个方法时,查找顺序是:实例本身 -> 实例的原型 -> 构造函数的原型链 -> null
  • 原型链的查找顺序是:实例本身 -> 构造函数 -> 原型对象 -> 构造函数的原型(如果有的话)-> … -> null。

例题二:

javascript"> var o = {
    a: 10,
    b: {
      fn: function () {
          console.log(this.a);
          console.log(this);
      }
    }
}
o.b.fn();

此时fn是o.b调用的,所以这里的this是o.b这个对象。o.b本身是没有a的那么这里的this.a就是undefined。
在这里插入图片描述

javascript"> var o = {
    a: 10,
    b: {
      a:2,
      fn: function () {
          console.log(this.a);
          console.log(this);
      }
    }
}
o.b.fn();

此时fn是b调用的,所以这里的this是o.b这个对象。o.b本身是没有a的那么这里的this.a就是2。
在这里插入图片描述

javascript"> var o = {
    a: 10,
    b: {
      a:2,
      fn: ()=> {
          console.log(this.a);
          console.log(this);
      }
    }
}
o.b.fn();

fn 是一个箭头函数,它是在对象o.b的上下文中定义的,但实际上这个上下文对于this的值并没有影响,因为箭头函数不会从它的封闭执行上下文(在这里是o.b)继承this。相反,它会捕获定义它时的外部函数的this值,或者如果它是在全局上下文中定义的,则捕获全局对象。在浏览器中通常是window
在全局并没有定义a这个变量,所以输出this.a是undefined。
在这里插入图片描述

例题三:

javascript">window.name = 'Kitty';
function A(){
   this.name = 123;
}
A.prototype.getA = function(){
    console.log(this);
    console.log(this.name + 1);
    return this.name + 1 ;
}
let a = new A();
let funcA = a.getA;
funcA();
  • 使用new关键字调用了构造函数A,创建了一个新对象a。此时,a的name属性被设置为123。
  • 从对象a上获取了getA方法的引用,并将其存储在变量funcA中。重要的是要注意,此时funcA只是一个函数引用(函数体),它不再与a对象绑定。
  • 调用了funcA函数。由于funcA是在全局作用域中作为普通函数调用的(而不是作为a的方法调用),因此this在funcA函数内部指向全局对象(在浏览器中通常是window)。
  • 所以这里的 let funcA = a.getA; 为:
javascript">let funcA = function(){
            console.log(this);
            console.log(this.name + 1);
            return this.name + 1 ;
        }
  • console.log(this.name + 1);会尝试访问全局对象的name属性(之前设置为’Kitty’),然后将其与1相加。字符串拼接 为 'Kitty1
    在这里插入图片描述
javascript">window.name = 'Kitty';
function A(){
   this.name = 123;
}
A.prototype.getA = function(){
    console.log(this);
    console.log(this.name + 1);
    return this.name + 1 ;
}
let a = new A();
let funcA = a.getA();
funcA;

如果是上述这样,那么a.getA()就是函数方法的调用,即这里的a的name是 123 ,这里的this代表的是A:

javascript">function A(){
   this.name = 123;
}

那么console.log(this.name + 1);就是123+1 即124在这里插入图片描述

例题四:

javascript">var length = 10;
function fn(){
    return this.length + 1;
}        
var obj = {
    length: 5,
    test1: function(){
        return fn();
    }
}
obj.test2 = fn;
console.log(obj.test1()); // 11
console.log(fn() === obj.test2()); // false
console.log(obj.test1() == obj.test2()); //  false

解析 obj.test2 = fn; fn没有加括号,相当于赋值函数体:

javascript">obj.test2 = function (){
    return this.length + 1;
};

这里obj.test2 相当于在obj对象里有一个test2,即:

javascript">var obj = {
    length: 5,
    test1: function(){
        return fn();
    }
    test2: function(){
    	return this.length + 1;
    }
}

console.log(obj.test1()):
test1里面的return fn(); fn是加括号的,相当于运行的是:

javascript">function fn(){
    return this.length + 1;
}        

这里fn是全局的函数,this是winow,全局的length是10,所以这里的console.log(obj.test1()); // 输出:11 即10+1=11
console.log(fn() === obj.test2()); :
fn是一个普通函数,this指向window,那么fn()的结果是11。

javascript">var obj = {
    length: 5,
    test1: function(){
        return fn();
    }
    test2: function(){
    	return this.length + 1;
    }
}

obj.test2()的this指向obj,length是5,即结果是5+1=6.
11 不等于6 返回false。
console.log(obj.test1() == obj.test2());:
obj.test2()综上所属是6.
obj.test1()是一个闭包返回 11
11 不等于 6 返回false

箭头函数和普通函数的this指向:

普通函数的this指向调用该函数的对象:

  • 独立调用时,指向全局对象(window或global)
  • 被对象调用时,指向调用它的对象
  • 使用call、apply或bind方法调用时,指向他们的第一个参数
  • 在构造函数中使用时,指向实例化该构造函数的对象

箭头函数没有自己的this:

  • 在全局作用域定义时,this指向window,若this.xxx没有则为undefined。
  • 在普通函数中定义时,this继承普通函数的this指向,即继承父级作用域的this。

http://www.niftyadmin.cn/n/5867099.html

相关文章

自由学习记录(38)

python语法 def def print_receipt (store_name, items, total_price, cashier"Self-Checkout", payment_method"Credit Card"): Python 的 函数定义 语法 def print_receipt(...) → 定义了一个名为 print_receipt 的函数。store_name, items, total_…

【ASP .NET Core】ASP .NET Core介绍

最近因为开发小游戏逐渐接触上了ASP .NET Core(后面简称ASP),今天就来简单介绍一下,话不多说直接开始。 什么是ASP ASP是微软开发的Web框架,用于后端服务器开发。ASP可以用于开发 Web应用程序,如网页、网站…

汽车软件︱AUTO TECH China 2025 广州国际汽车软件与安全技术展览会:开启汽车科技新时代

在汽车产业智能化与网联化飞速发展的当下,汽车软件与安全技术已然成为行业变革的核心驱动力。2025年11月20 - 22日,AUTO TECH China 2025 广州国际汽车软件与安全技术展览会将在广州保利世贸博览馆盛大开幕,这场展会将汇聚行业前沿成果&#…

VSCode自定义快捷键和添加自定义快捷键按键到状态栏

VSCode自定义快捷键和添加自定义快捷键按键到状态栏 📄在VSCode中想实现快捷键方式执行与某些指令操作进行绑定,可以通过配置组合式的键盘按键映射来实现,另外一种方式就是将执行某些特定的指令嵌入在面板菜单上,在想要执行的时候…

docker中配置redis

1、常规操作 docker pull redis(默认你的docker中没有redis) 2、查看redis是否拉取成功 docker images redis 3、创建目录,在你的宿主机,(我是在虚机中建的centos7)为了给redis配置文件使用 4、下载redis…

51单片机编程学习笔记——点亮LED

大纲 器件51单片机开发板总结 安装驱动点亮LED烧录 随着最近机器人爆火,之前写的ROS2系列博客《Robot Operating System》也获得了更多的关注。我决定在机器人领域里再走一步,于是想到可以学习单片机。研究了下学习路径,最后还是选择先从51单…

小智AI桌宠机器狗

本文主要介绍如何利用开源小智AI制作桌宠机器狗 1 源码下载 首先下载小智源码,下载地址, 下载源码后,使用vsCode打开,需要在vscode上安装esp-idf,安装方式请自己解决 2 源码修改 2.1添加机器狗控制代码 在目录main/iot/things下添加dog.cc文件,内容如下; #include…

List的模拟实现(2)

前言 上一节我们讲解了list的基本功能,那么本节我们就结合底层代码来分析list是怎么实现的,那么废话不多说,我们正式进入今天的学习:) List的底层结构 我们先来看一下list的底层基本结构: 这里比较奇怪的…