作用域、this上下文、闭包

news/2025/1/14 20:29:28/

作用域(静态分析)

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

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

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

  • 全局作用域:在顶层声明的变量和函数可以被访问的范围,通常就是在最外层(非函数体或循环体内)。全局变量中声明的变量和函数在任何地方都可以访问,包括其他作用域内部。在全局作用域中声明的变量,称为全局变量。
  • 函数作用域:函数作用域是在函数体内部声明的变量可以被访问的范围。在函数体外部无法被访问。函数作用域可以创建私有变量,这些被声明的变量只能在函数体中被访问。
  • 块级作用域:块级作用域是在代码块(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 variableconsole.log(outerVar); // Outer variableconsole.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.fnreturn 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.ppmy.cn/news/1563119.html

相关文章

一个使用 Golang 编写的新一代网络爬虫框架,支持JS动态内容爬取

大家好,今天给大家分享一个由ProjectDiscovery组织开发的开源“下一代爬虫框架”Katana,旨在提供高效、灵活且功能丰富的网络爬取体验,适用于各种自动化管道和数据收集任务。 项目介绍 Katana 是 ProjectDiscovery 精心打造的命令行界面&…

Swagger学习⑰——@Link注解

介绍 Link 是 Swagger/OpenAPI 3.0 注解库中的一个注解,用于在 OpenAPI 文档中定义链接(Link)。链接是一种在 API 响应中提供相关操作或资源引用的机制,通常用于描述操作之间的关系或提供额外的操作提示。 Link 注解的作用 Link…

计算机网络 (39)TCP的运输连接管理

前言 TCP(传输控制协议)是一种面向连接的、可靠的传输协议,它在计算机网络中扮演着至关重要的角色。TCP的运输连接管理涉及连接建立、数据传送和连接释放三个阶段。 一、TCP的连接建立 TCP的连接建立采用三次握手机制,其过程如下&…

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

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

深度学习camp-第J7周:对于ResNeXt-50算法的思考

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 📌你需要解决的疑问:这个代码是否有错?对错与否都请给出你的思考 📌打卡要求:请查找相关资料、逐步…

基于element UI el-dropdown打造表格操作列的“更多⌵”上下文关联菜单

<template><div :class"$options.name"><el-table :data"tableData"><el-table-column type"index" label"序号" width"60" /><!-- 主要列 BEGIN---------------------------------------- --&g…

【2025 Rust学习 --- 15 迭代器的消耗】

消耗迭代器 使用带有 for 循环的迭代器&#xff0c;也可以显式调用 next&#xff0c;但有许多常见任务不必一遍又一遍地写出来。Iterator 特型提供了一大组可选方法来涵盖其中的许多任务。 简单累加&#xff1a;count、sum 和 product count&#xff08;计数&#xff09;方法…

[C#] 调用matlab 类型初始值设定项引发异常

我的环境/开发工具&#xff1a;Matlab2016b&#xff08;64位&#xff09;vs2017 报的异常&#xff1a;System.TypeInitializationException:““MathWorks.MATLAB.NET.Arrays.MWNumericArray”的类型初始值设定项引发异常。”。 异常原因&#xff1a;解决方案平台是AnyCPU&am…