作用域、this上下文、闭包

news2025/1/14 23:39:33

作用域(静态分析)

作用域是定义变量和访问变量的范围

作用域可以通过静态分析,不需要运行代码,就可以分析出当前作用域。

作用域分为:全局作用域、函数作用域、块级作用域。

  • 全局作用域:在顶层声明的变量和函数可以被访问的范围,通常就是在最外层(非函数体或循环体内)。全局变量中声明的变量和函数在任何地方都可以访问,包括其他作用域内部。在全局作用域中声明的变量,称为全局变量。
  • 函数作用域:函数作用域是在函数体内部声明的变量可以被访问的范围。在函数体外部无法被访问。函数作用域可以创建私有变量,这些被声明的变量只能在函数体中被访问。
  • 块级作用域:块级作用域是在代码块(if条件语句、for循环或大括号{}中的代码)内(let、const)声明的变量可以被访问的范围。在ES6(ECMAScript2015)之前,JavaScript中没有明确的块级作用域。可以使用var声明函数作用域变量。在ES6中引入let和const关键字后,当使用let和const声明变量时会产生块级作用域,变量的可访问范围在当前代码块内。

作用域规定了标识符(变量、函数等)在程序中的可见性生命周期。当变量被引用时,JavaScript引擎会根据作用域找到对应的变量。如果当前作用域中找不到该变量,就会继续往上级作用域中查找,直到找到或者到达全局作用域。这就形成了作用域链(这个过程被称为作用域链)(scope chain)。

作用域链

作用域链是变量和函数的可访问性和查找规则

function outer() {
    const outerVar = 'Outer variable';

    function inner() {
        const innerVar = 'Inner variable';
        console.log(innerVar); // Inner variable
        console.log(outerVar); // Outer variable
        console.log(globalVar); // Global variable
    }

    inner();
}

const globalVar = 'Global variable';
outer();

从上述代码可以看到,内部函数可以访问自身函数体内的变量,又可以访问外部函数体的变量,还能访问全局变量。作用域链会从内层,到外层,逐层查找。

如果不使用var声明变量呢?

console.log(d) //报错:is not defined  // 此时并没有发生变量提升
d=yunyin
// 'yunyin'
window.d
// 'yunyin'
function test() {
    e ='yy'
}
test()
console.log(e) // yy
console.log(window.e) // yy

可以看到,如果不用var进行变量声明,只做赋值操作,此时的变量会被挂在在window对象上。因此只有当变量使用var声明之后才会产生变量提升。 

函数提升

函数提升是默认存在的,目的是为了方便开发者,可以随意放置函数的位置。

变量提升

只有使用var声明的变量才会产生变量提升,只会提升变量定义,不会提升变量赋值。因此在变量声明之前使用var声明的变量会返回undefined(变量提升)。如果使用let、const声明的变量,在变量声明之前访问,会报错  Cannot access 'xx' before initialization。报错提示,无法在变量初始化之前访问。如:

console.log(dog)
let dog = 'wangcai'

 如果是在函数中定义的变量在外部访问,则会报错 Uncaught ReferenceError: dog is not defined。如下:

function funLog() {
    let dog = 'wangcai'
}
console.log(dog)

可以看到报错内容是不一样的。

this上下文(动态分析)

this是在执行时动态读取上下文决定的,而不是创建时决定的

函数直接调用——this指向window 

function foo(){
    console.log('函数内部', this)  //window对象
}
foo()

从上述代码可以看到,在函数体内的this指向window对象。这是因为当前被调用的foo函数,是在全局环境下被调用的。

隐式绑定——this执行调用堆栈的上级(对象、数组等引用关系逻辑)

function fn() {
    console.log('隐式绑定', this) //obj对象
}
const obj = {
    a: 1,
    fn
}
obj.fn = fn;
obj.fn();

从上述代码可以看出,当函数被obj对象引用后,obj对象调用函数时,this执行的是obj对象。说明当前函数作用域被调用时,指向被调用的对象。注意,引用不代表执行,this指向的是执行的对象,而不是引用对象。(当前函数是在哪里执行的)

猜猜this指向

const foo = {
    bar: 10,
    fn: function () {
        console.log(this.bar)
        console.log(this)
    }
}
const fn1 = foo.fn
fn1()

上述代码中,定义了fn1单独取出foo对象中的fn方法,此时定义的函数在windows上,因此this的上下文指向的是window,window上没有bar变量,所以为undefined

const o1 = {
    txt: 'o1',
    fn: function (){
        //直接使用上下文——传统派活
        console.log('o1fn', this)
        return this.txt
    }
}
const o2 = {
    txt: 'o2',
    fn: function (){
        //呼叫领导执行——部门协作
        return o1.fn()
    }
}
const o3 = {
     txt: 'o3',
     fn: function (){
        //直接内部构造——公共人
        let fn = o1.fn
        return fn()
     }
}
console.log('o1fn', o1.fn()); 
console.log('o2fn', o2.fn());
console.log('o3fn', o3.fn());

o1调用fn,此时this指向o1,在o2中只是返回了o1的调用,依然属于o1自身的调用,因此this是o1,o3把o1.fn重新抽出来赋给o3对象中的fn,返回定义的fn函数,此时调用fn与o3之间并无关联,fn是在window上执行的,而window上并没有text的值,因此返回undefined

如果需要将console.log('o2fn', o2.fn()) 的结果改成o2,如下:

//在o2初始化fn方法时,把o1的方法抽出,赋给o2方法
const o1 = {
    txt: 'o1',
    fn: function (){
        //直接使用上下文——传统派活
        console.log('o1fn', this)
        return this.txt
    }
}
const o2 = {
    txt: 'o2',
    fn: o1.fn
}
console.log('o2', o2.fn()) //this指向o2

显示绑定this

function foo() {
    console.log('函数内部', this);
}

foo();

// 使用
foo.call({
    a: 1
});
foo.apply({
    a: 1
});

const bindFoo = foo.bind({
    a: 1
});
bindFoo();

foo()属于函数调用此时this指向window,call、apply、bind调用后都让this指向了对象{a: 1}

call、apply、bind区别

(1)执行时机
  • call接收多个参数,第一个参数是this指向,从第二个参数开始是传递给函数的参数,call会立即执行函数
  • apply接收两个参数,第一个参数是this指向,第二个参数是一个数组,数组中的元素会被展开后传递给函数,apply也会立即执行函数
  • bind接收多个参数,第一个参数是this指向,第二个参数开始是传递给函数的参数。与call和apply不同,bind不会立即执行函数,而是返回一个新的函数,这个新的函数在调用时才会执行。
(2)参数传递方式
  • call和apply都可以传递数组或伪数组对象作为参数
  • apply的第二个参数必须数组或伪数组,而call和bind的参数是独立传递的
(3)使用场景
  • call适用于动态传递参数给函数的场景,特别是在不知道具体参数数量时
  • apply常用于需要传递多个参数给函数的场景,特别是参数较多时,使用apply可以避免手动展开参数的麻烦
  • bind适用于需要预先绑定this和部分参数,但不想立即执行函数的场景。bind返回的新函数可以在需要时调用,且可以保存绑定状态,直到实际需要执行时才使用。

bind实现

//1.需求:手写bind => bind位置(挂载在哪里) => Funtion.prototype 
Function.prototype.newBind = function(){
    const _this = this
    //2.bind是什么?
    const args = Array.prototype.slice.call(arguments)  //把伪数组转成数组
    // const args = [...arguments].slice(1) //把伪数组转成数组,并取出第一项
    // 输入:args特点,第一项是新this,第二项~最后一项函数传参
    const newThis = args.shift() //取出数组中的第一项
    // 返回:返回的是一个函数 =>构造一个函数 =>这个函数返回原函数的结果且继承传参
    return _this.newApply(newThis, args)
}
 
Function.prototype.newApply = function(context){
    //边缘检测
    if (typeof this !== 'function') {
        throw new TypeError('使用正确的函数进行调用') //如果当前调用方不是函数没法往下执行
    }
    //如果传入为空报错,context为新的上下文,如果没传默认在window上执行
    context = context || window
    // 执行函数的替换
    context.fn = this //指向当前执行的对象
    // 临时挂载指向fn => 销毁临时挂载
    let result = arguments[1] 
        ? context.fn(...arguments[1])
        : context.fn()
    
    delete context.fn
    //返回结果
    return result
}

闭包

function mail(){
    let content = 'mail'
    return function(){
        console.log(content)
    }
}
const envelop = mail()
envelop()
//局部变量content逃逸到了外部

闭包的含义:函数内部作用域以函数包裹的形式传递到外部,使内部作用域的变量逃逸到外部。

函数可以使JS产生封闭作用域,这JS模块的基石

闭包可以使模块返回变量,让JS真正实现模块化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2276702.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring Boot 2 学习全攻略

Spring Boot 2 学习资料 Spring Boot 2 学习资料 Spring Boot 2 学习资料 在当今快速发展的 Java 后端开发领域,Spring Boot 2 已然成为一股不可忽视的强大力量。它简化了 Spring 应用的初始搭建以及开发过程,让开发者能够更加专注于业务逻辑的实现&am…

31_搭建Redis分片集群

Redis的主从复制模式和哨兵模式可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:海量数据存储问题、高并发写的问题。由于数据量过大,单个master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展每个复制集只负责存储整个数据集的一部分,这就是Red…

IMX6U Qt 开发环境

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一、交叉编译 1. 安装通用 ARM 交叉编译工具链 2. 安装 Poky 交叉编译工具链 二、编译出厂源码 1. U-boot 2. 内核和模块 3. 编译出厂 Qt GUI 综合 Demo 前言…

数据结构与算法之链表: LeetCode 92. 反转链表 II (Ts版)

反转链表 II https://leetcode.cn/problems/reverse-linked-list-ii/description/ 描述 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反转后的链表 示例 1 输入&…

【Uniapp-Vue3】@import导入css样式及scss变量用法与static目录

一、import导入css样式 在项目文件中创建一个common文件夹&#xff0c;下面创建一个css文件夹&#xff0c;里面放上style.css文件&#xff0c;编写的是公共样式&#xff0c;我们现在要在App.vue中引入该样式。 在App.vue中引入该样式&#xff0c;这样就会使样式全局生效&#…

MySQL中的四种表联结

目录 1、联结、关系表 &#xff08;1&#xff09;关系表 &#xff08;2&#xff09;为什么使用联结 2、如何创建联结 &#xff08;1&#xff09;笛卡尔积&#xff08;叉联结&#xff09;--用逗号分隔 &#xff08;2&#xff09;where子句的重要性 &#xff08;3&#xff…

DVWA靶场CSRF漏洞通关教程及源码审计

目录标题 CSRFlow源码审计 medium源码审计 high源码审计 impossible源码审计 CSRF low 先修改密码 看到地址栏 复制在另一个网页打开 成功登录 源码审计 没有任何过滤措施&#xff0c;很危险&#xff0c;并且采用了不安全的md5加密 <?phpif( isset( $_GET[ Change ] )…

JVM之垃圾回收器G1概述的详细解析

G1(并发) G1 特点 G1&#xff08;Garbage-First&#xff09;是一款面向服务端应用的垃圾收集器&#xff0c;应用于新生代和老年代、采用标记-整理算法、软实时、低延迟、可设定目标&#xff08;最大 STW 停顿时间&#xff09;的垃圾回收器&#xff0c;用于代替 CMS&#xff0…

宝塔面板 申请证书后 仍然提示不安全

证书显示有效&#xff0c;但是网站显示不安全 导致的原因是引入静态文件使用的是HTTP&#xff0c;查看方法为F12打开console控制台 可以看到静态文件全部都是HTTP 网站采用wordpress搭建&#xff0c;基于问题解决&#xff0c;其他方式搭建也是一样&#xff0c;处理掉所有的H…

【1】Word:邀请函

目录 题目 文字解析 流程 题目 文字解析 考生文件夹☞Word.docx☞一定要用ms打开&#xff0c;wps打开作答无效☞作答完毕&#xff0c;F12或者手动另存为&#xff08;考生文件夹&#xff1a;路径文件名&#xff09; 注意&#xff1a;一定要检查&#xff0c;很有可能你前面步…

Docker安装和卸载(centos)

Docker安装和卸载 一&#xff0c;已安装Docker&#xff0c;卸载Docker 1.方法一 sudo yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine​ 如果出现以下提示就证明没卸载…

JVM:ZGC详解(染色指针,内存管理,算法流程,分代ZGC)

1&#xff0c;ZGC&#xff08;JDK21之前&#xff09; ZGC 的核心是一个并发垃圾收集器&#xff0c;所有繁重的工作都在Java 线程继续执行的同时完成。这极大地降低了垃圾收集对应用程序响应时间的影响。 ZGC为了支持太字节&#xff08;TB&#xff09;级内存&#xff0c;设计了基…

ASP.NET Core - 日志记录系统(二)

ASP.NET Core - 日志记录系统&#xff08;二&#xff09; 2.4 日志提供程序2.4.1 内置日志提供程序2.4.2 源码解析 本篇接着上一篇 ASP.NET Core - 日志记录系统(一) 往下讲&#xff0c;所以目录不是从 1 开始的。 2.4 日志提供程序 2.4.1 内置日志提供程序 ASP.NET Core 包括…

无源器件-电容

电容器件的参数 基本概念由中学大学物理或电路分析内容获得&#xff0c;此处不做过多分析。 电容的产量占全球电子元器件产品的40%以上。 单位&#xff1a;法拉 F&#xff1b;1F10^6uF&#xff1b;电路中常见的104电容就是10*10^4pF100nF0.1uF C为电容&#xff0c;Rp为绝缘电…

云平台一键部署【Video-Background-Removal】视频换背景,无任何限制,随意换

Video-Background-Removal 是一款革命性的视频背景替换工具&#xff0c;旨在让用户轻松实现视频背景的快速更换。无论你是专业创作者还是普通用户&#xff0c;这款软件都能让你在几秒钟内改变背景&#xff0c;完全消除限制&#xff0c;随心所欲&#xff0c;随时随地想换就换&am…

HCIP笔记1--IP路由基础回顾、BFD单臂回声、OSPF基础

1. 路由基础回顾 概念 AS(Aotonomous System): 自治系统&#xff0c;由同一机构管理的路由器集合。LAN(Local Area Network): 局域网&#xff0c;用户所使用的网络WAN(Wideless Area Network): 广域网&#xff0c;运营商网络广播域&#xff1a;一个广播帧能在网络中到达的所有…

【Linux网络编程】数据链路层 | MAC帧 | ARP协议

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站 &#x1f308;个人主页&#xff1a; 南桥几晴秋 &#x1f308;C专栏&#xff1a; 南桥谈C &#x1f308;C语言专栏&#xff1a; C语言学习系…

微软与腾讯技术交锋,TRELLIS引领3D生成领域多格式支持新方向

去年 11 月&#xff0c;腾讯推出 Hunyuan3D 生成模型&#xff0c;是业界首个同时支持文字和图像生成 3D 的开源大模型。紧接着不到一个月&#xff0c;微软便发布了全新框架 TRELLIS&#xff0c;加入 3D 资产生成领域的竞争中。TRELLIS 支持多格式输出&#xff0c;包括辐射场、3…

【爬虫】单个网站链接爬取文献数据:标题、摘要、作者等信息

源码链接&#xff1a; https://github.com/Niceeggplant/Single—Site-Crawler.git 一、项目概述 从指定网页中提取文章关键信息的工具。通过输入文章的 URL&#xff0c;程序将自动抓取网页内容 二、技术选型与原理 requests 库&#xff1a;这是 Python 中用于发送 HTTP 请求…

设计模式-结构型-组合模式

1. 什么是组合模式&#xff1f; 组合模式&#xff08;Composite Pattern&#xff09; 是一种结构型设计模式&#xff0c;它允许将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性。换句话说&#xff0c;组合模式允…