JavaScript闭包和this

目录

JavaScript闭包和this

1 闭包

1.1 变量作用域

        1)函数内部可以读取全局变量

        2)函数外部无法读取函数内部的局部变量

1.2 读取函数内部的局部变量

        1)在函数内部再定义一个函数

        2)为外部程序提供访问函数局部变量的入口

1.3 闭包概念

2 this

2.1 关键点:

2.2 四类调用方式

1)作为对象方法的调用

2)纯粹的函数调用

复杂一点:

3)作为[构造函数]调用

4)使用apply、call、bind调用

2.3 箭头函数中的this

        1)箭头函数在普通对象中

        2)箭头函数在函数中

        3)箭头函数在类中

3 样例详解

1)

2)

3)更复杂的例子

4)由上面的例子继续扩展


JavaScript闭包和this

1 闭包

        一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来

在JS中,通俗来讲,闭包就是能够读取外层函数内部变量的函数

1.1 变量作用域

        变量的作用域为两种:全局作用域和局部作用域

1)函数内部可以读取全局变量

let code = 200;
​
function f1() {console.log(code);
}
​
f1(); // 200

2)函数外部无法读取函数内部的局部变量

function f1() {let code = 200;
}
console.log(code); // Uncaught ReferenceError: code is not defined

1.2 读取函数内部的局部变量

1)在函数内部再定义一个函数

function f1() {let code = 200;
​function f2() {console.log(code);}
}

        函数f1内部的函数f2可以读取f1中所有的局部变量。因此,若想在外部访问函数f1中的局部变量code,可通过函数f2间接访问

2)为外部程序提供访问函数局部变量的入口

function f1() {let code = 200;
​function f2() {console.log(code);}
​return f2;
}
​
f1()();  // 200

1.3 闭包概念

1.2中的函数f2,就是闭包,其作用就是将函数内部与函数外部进行连接

  • 闭包访问的变量,是每次运行上层函数时重新创建的,是相互独立

function f1() {let obj = {};
​function f2() {return obj;}
​return f2;
}
​
let result1 = f1();
let result2 = f1();
console.log(result1() === result2()); // false
  • 不同的闭包,可以共享上层函数中的局部变量

function f() {let num = 0;
​function f1() {console.log(++num);}
​function f2() {console.log(++num);}
​return {f1,f2};
}
​
let result = f();
result.f1(); // 1
result.f2(); // 2
​
从结果可以看出,闭包f1和闭包f2共享上层函数中的局部变量num。

        旅行者走路的问题

function factory() {var start = 0function walk(step) {var new_total = start + stepstart = new_totalreturn start}return walk
}
var res = factory()
console.info(res(1))
console.info(res(2))
console.info(res(3))start会记录上次一的值

使用闭包的注意点:

        1)由于闭包会使得函数中的变量都被保存在内存中内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除

        2)闭包会在父函数外部改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

2 this

2.1 关键点:

  1. this始终指向调用该函数的对象

  2. 若没有指明调用的对象,则顺着作用域链向上查找,最顶层为global(window)对象

  3. 箭头函数中的this是定义函数时绑定的,与执行上下文有关

  4. 简单对象(非函数、非类)没有执行上下文;

  5. 类中的this,始终指向该实例对象

  6. 箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。

2.2 四类调用方式

1)作为对象方法的调用

function f() {console.log( this.code );
}
let obj = {code: 200,f: f
};
obj.f(); // 200

2)纯粹的函数调用

function f() {console.log( this.code );
}
// 此处,通过var(函数作用域)声明的变量code会绑定到window上;如果使用let(块作用域)声明变量code,则不会绑定到window上,因此下面的2次函数调用f(),会输出undefined
// let code = 200;
var code = 200;
f(); // 200
code = 404;
f(); // 404

注意:

        通过var(函数作用域)声明的变量code会绑定到window上;如果使用let(块作用域)声明变量code,则不会绑定到window

复杂一点:

function doF(fn) {this.code = 404;fn();
}
​
function f() {console.log(this.code);
}
​
let obj = {code: 200,f: f
};
​
var code = 500;
doF(obj.f); // 404

        该列子中,为分析出this的指向,应找到关键点,哪个对象调用了函数f()。obj.f作为doF()的入参,将函数f传给了doF,而doF是由window对象调用的,所以函数doF中的this指向window,继而函数f中的this也指向window。

但最终执行是doF所以this指向doF,结果404

function doF(fn) {this.code = 404;fn();
}
​
function f() {this.code = 401;console.log(this.code);
}
​
let obj = {code: 200,f: f
};
​
var code = 500;
doF(obj.f); // 401

3)作为[构造函数]调用

code = 404
function A() {this.code = 200this.callA = function() {console.log(this.code)}
}
​
var a = new A()
a.callA() // 200, callA在new A返回的对象里

4)使用apply、call、bind调用

apply

        apply方法的作用与call方法类似,也是改变this指向,然后再调用该函数。唯一的区别就是,它接收一个数组作为函数执行时的参数

var code = 404;
let obj = {code: 200,f: function() {console.log(this.code);}
}
​
obj.f(); // 200, 实际上是作为对象的方法调用
obj.f.apply(); // 404,参数为空时,默认使用全局对象global,在此处为对象window
obj.f.apply(obj); //200,this指向参数中设置的对象

call

        函数实例的call方法,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在所指定的作用域中,调用该函数

function f() {console.log( this.code );
}
var obj = {code: 200
};
f.call( obj ); // 200

bind

        bind()方法用于将函数体内的this绑定到某个对象,然后返回一个新函数

// bind返回一个新的函数
function f(b) {console.log(this.a, b);return this.a + b;
}
var obj = {a: 2
};
var newF = f.bind(obj);
var result = newF(3); // 2 3
console.log(result); // 5

2.3 箭头函数中的this

        箭头函数中的this是定义函数时绑定的,而不是在执行函数时绑定。若箭头函数在简单对象中,由于简单对象没有执行上下文,所以this指向上层的执行上下文;若箭头函数在函数、类等有执行上下文的环境中,则this指向当前函数、类

1)箭头函数在普通对象中

var code = 404;
let obj = {code: 200,getCode: () => {console.log(this.code);}
}
obj.getCode(); // 404

        在箭头函数中,this 的值是在定义函数时确定的,而不是在运行时确定的。在这个例子中,箭头函数 getCode 是在对象 obj 定义时创建的,而不是在调用 obj.getCode() 的时候

        箭头函数中的 this 指向的是外层的语法作用域的 this 值,而不是指向调用它的对象。在全局作用域中,this 指向的是全局对象(在浏览器环境中通常是 window 对象)。所以,当箭头函数中使用 this.code 时,它实际上是引用全局作用域中的 code 变量,其值为 404

2)箭头函数在函数中

var code = 404;
function F() {this.code = 200;let getCode = () => {console.log(this.code);};getCode();
}
var f = new F() //200
var f = F() //构造函数没有new调用,就成为了一个普通函数
console.log(f)
console.log(code)

3)箭头函数在类中

var code = 404;
class Status {constructor(code) {this.code = code;}getCode = () => {console.log(this.code);};
}
let status = new Status(200);
status.getCode(); // 200

注意:

        不管是箭头函数还是普通函数,只要是类中,this就指向实例对象

3 样例详解

1)

var code = 404;let status = {code : 200,getCode : function() {return function(){return this.code;};}
};console.log(status.getCode()()); // 404

       执行status.getCode()时,返回函数,status.getCode()()表示执行当前返回的函数,其调用者为全局变量window,所以this.code为绑定在window中的code,值为404。

2)

var code = 404;let status = {code : 200,getCode : function() {let that = this;return function(){return that.coe;};}
};console.log(status.getCode()()); // 200

        执行status.getCode()时,this指向status,并通过局部变量that保存this的值,最后返回值为函数。status.getCode()()表示执行返回的函数,其that指向的status,所以返回值为200。

3)更复杂的例子

function f() {//宏任务setTimeout(() => {console.log(">>>" + this); // >>>[object object],语句5this.code = 401;}, 0)//同步console.log( this.code );
}let obj = {">>>" + thiscode: 200,foo: f
};var code = 500;同步和异步 关键词说的很好
宏任务 微任务
1.宏任务 微任务 同步异步
2.箭头函数this指向问题
3.字符串+ this  [object object]
obj.foo(); // 200,语句1  console.log("--" + obj.code); // --200,语句3//宏任务
setTimeout(()=>{console.log("---" + obj.code);}, 0); // ---401,语句4obj.foo(); (语句1):调用 obj 对象的 foo 方法。
输出:200
解释:在 foo 方法内部的 console.log(this.code) 打印出 obj 对象的 code 属性,其值为 200。console.log("--" + obj.code); (语句3):打印 obj 对象的 code 属性。
输出:--200
解释:在全局作用域中,code 被赋值为 500,但这里的 obj.code 指向的是 obj 对象的 code 属性,其值仍然是 200。setTimeout(() => { console.log("---" + obj.code); }, 0); (语句4):设置一个零延迟的定时器,其中的箭头函数在调用。
输出:---401
解释:在调用 obj.foo() 的过程中,foo 方法中的 setTimeout 在当前宏任务结束后执行。由于是箭头函数,this 的值保持与父作用域一致(也就是 obj 对象)。所以在箭头函数内部,this.code 被设置为 401。setTimeout(() => { console.log(">>>" + this); this.code = 401; }, 0) (语句5):设置一个零延迟的定时器,其中的箭头函数在调用。
输出:>>>[object Object]
解释:在调用 obj.foo() 的过程中,foo 方法中的 setTimeout 在当前宏任务结束后执行。箭头函数的 this 始终指向它被创建时的外部作用域,所以 this 指向了 obj 对象,而在控制台中打印 this 时会将其转换为字符串。所以输出为 >>>[object Object]。

        知识补充 --- 函数setTimeout用于创建一个定时器同一个对象,各个定时器使用一个编号池,不同的对象使用独立的编号池,同一个对象上的多个定时器有不同的定时器编号。所以,setTimeout到了执行时间点时,其内部的this指向定时器所绑定的对象

        结果分析 --- 函数setTimeout中传入的函数句柄,由于js是单线程执行即使延时为0,仍需等到本次执行的所有同步代码执行完毕,才能执行。所以在两次执行obj.foo()的过程中,其内部的setTimeout的入参函数(也就是语句5)都未执行。同理,执行语句3时,语句5同样未执行。直到执行语句4,当前同步代码块执行完毕,语句5执行(并且执行了2次,因为语句1和语句2分别执行1次),obj上绑定的code被更新为401。最终,语句4的入参函数执行,输出obj.code的值为401

4)由上面的例子继续扩展

function doFoo(fn) {this.code = 404;fn();
}function f() {setTimeout(() => {console.log(">>>" + this); // >>>[object window],语句3this.code = 401; // 语句4}, 0)console.log( this.code ); // 404,语句2
}let obj = {code: 200,foo: f
};var code = 500;doFoo( obj.foo ); // 语句1
setTimeout(()=>{console.log(obj.code)}, 0); // 200,语句5
setTimeout(()=>{console.log(window.code)}, 0); // 401,语句6

结果分析obj.foo为函数句柄,作为入参传入函数doFoodoFoo的调用房为全局变量window,所以,语句2中doFoo对象的code是404、3、4中的this均指向window

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

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

相关文章

Github 创建自己的博客网站

参考pku大佬视频制作,附上B站视频:【GitHub Pages 个人网站构建与发布】 同时还参考了:【Python版宝藏级静态站点生成器Material for MkDocs】 GitHub Pages 介绍 内容参考:GitHub Pages - 杨希杰的个人网站 (yang-xijie.githu…

【CSS】ios上fixed固定定位的input输入框兼容问题

需求 : 实现一个简单的需求,上方是搜索框并且固定顶部,下方是滚动的内容list 问题 : 若如图上方使用固定定位, 下方用scroll-view, 在安卓上是没有问题的, 但是发现ios上会出现兼容问题 : 问题1: 当content list滚动到中间时再去搜索, 展…

c语言每日一练(1)

前言: 每日一练系列,每一期都包含5道选择题,2道编程题,博主会尽可能详细地进行讲解,令初学者也能听的清晰。每日一练系列会持续更新,暑假时三天之内必有一更,到了开学之后,将看学业情…

解决Springboot+VUE项目部署出现的跨域问题

自己写了一个项目,写好了,发现不会部署,然后到处查资料,最终终于部署好自己写的系统,系统为前后端分离项目。需要分别部署在同一个服务器docker中,配置不同得端口隐射,部署得过程中主要是跨域问…

你值得拥有——流星雨下的告白(Python实现)

目录 1 前言 2 霍金说移民外太空 3 浪漫的流星雨展示 4 Python代码 1 前言 我们先给个小故事,提一下大家兴趣;然后我给出论据,得出结论。最后再浪漫的流星雨表白代码奉上,还有我自创的一首诗。开始啦: 2 霍金说…

Oracle锁的学习

Oracle数据库中的锁机制 数据库是一个多用户使用的共享资源。当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。 在数据库中有两种基本的锁类…

spark history网络流量占用高问题记录

生产环境遇到一台机器网络流量占用高告警 由于监控只有机器总的网络流量,没有具体进程的 于是只能登陆服务器,安装nethogs:yum install nethogs 然后执行nethogs命令查看进程流量 观察到主要是spark history server这个进程占用流量高(最高…

docker-compose搭建redis服务

docker-compose搭建redis服务 1.首先准备所需文件 mkdir data touch redis.conf touch docker-compose.yaml # 这个结构 [rootiZbp16ukkrjo2m3jyyo3tfZ redis]# ls data docker-compose.yaml redis.conf2.编辑redis.conf bind 0.0.0.0 port 6379 # tcp-backlog 511 timeou…

puppeteer监听response并封装为express服务调用

const express require(express); const puppeteer require(puppeteer); const app express(); let browser; // 声明一个全局变量来存储浏览器实例app.get(/getInfo, async (req, res) > {try {const page_param req.query.page; // 获取名为"page"的查询参数…

openCV图像读取和显示

文章目录 一、imread二、namedWindow三、imshow #include <opencv2/opencv.hpp> #include <iostream>using namespace std; using namespace cv;int main(int argc,char** argv) {cv::Mat img imread("./sun.png"); //3通道 24位if (img.empty()) {std:…

bitbucket ssh登录提示 port 22: Operation timed out

bitbucket ssh登录失败 执行命令 ssh -T -vvv gitbitbucket.org结果提示&#xff1a; ssh: connect to host bitbucket.org port 22: Operation timed out原因&#xff1a;使用了22端口其实并不稳定。配置的其实如果连接到443端口更稳定。修改 ~/.ssh/config &#xff0c;增…

Alchemy Catalyst 2023 crack

Alchemy Catalyst 2023 crack Alchemy CATALYST是一个可视化本地化环境&#xff0c;支持本地化工作流程的各个方面。它帮助组织加快本地化进程&#xff0c;比竞争对手更快地进入新市场&#xff0c;并为他们创造新的收入机会。 创建全球影响力 高质量的产品和服务翻译对跨国组织…

【980. 不同路径 III】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 在二维网格 grid 上&#xff0c;有 4 种类型的方格&#xff1a; 1 表示起始方格。且只有一个起始方格。2 表示结束方格&#xff0c;且只有一个结束方格。0 表示我们可以走过的空方格。-1 表示我们无…

windows创建占用特定端口程序

默认情况下&#xff0c;远程桌面使用的是3389端口。如果您想将远程桌面端口更改为8005&#xff0c;以达到模拟程序占用端口8005的情况&#xff0c;可以执行以下操作&#xff1a; 如执行以下命令&#xff0c;则1&#xff0c;2&#xff0c;3步相同操作可以跳过&#xff0c;直接往…

二进制安装K8S(单Master集群架构)

目录 一&#xff1a;操作系统初始化配置 1、项目拓扑图 2、服务器 3、初始化操作 二&#xff1a; 部署 etcd 集群 1、etcd 介绍 2、准备签发证书环境 3、master01 节点上操作 &#xff08;1&#xff09;生成Etcd证书 &#xff08;2&#xff09;创建用于存放 etcd 配置文…

在VUE中使用websocket

websocket概念 1、WebSocket是HTML5下一种新的协议&#xff0c;在单个TCP连接上进行全双工通信&#xff1b; 2、Websocket是一个持久化的协议&#xff0c;浏览器和服务器只需要完成一次握手&#xff0c;两者之间就直接可以创建持久性的连接&#xff0c;并进行双向数据传输&…

传球游戏

题目描述 上体育课的时候&#xff0c;小蛮的老师经常带着同学们一起做游戏。这次&#xff0c;老师带着同学们一起做传球游戏。 游戏规则是这样的&#xff1a;n个同学站成一个圆圈&#xff0c;其中的一个同学手里拿着一个球&#xff0c;当老师吹哨子时开始传球&#xff0c;每个…

链表OJ题讲解2

&#x1f493;博主个人主页:不是笨小孩&#x1f440; ⏩专栏分类:数据结构与算法&#x1f440; &#x1f69a;代码仓库:笨小孩的代码库&#x1f440; ⏩社区&#xff1a;不是笨小孩&#x1f440; &#x1f339;欢迎大家三连关注&#xff0c;一起学习&#xff0c;一起进步&#…

策略模式(C++)

定义 定义一系列算法&#xff0c;把它们一个个封装起来&#xff0c;并且使它们可互相替换((变化)。该模式使得算法可独立手使用它的客户程序稳定)而变化(扩展&#xff0c;子类化)。 ——《设计模式》GoF 使用场景 在软件构建过程中&#xff0c;某些对象使用的算法可能多种多…

深入理解Streamlit中的按钮行为(四):示例与最佳实践

文章目录 1 前言&#x1f680;2 Streamlit中if st.button()的使用时机 &#x1f3af;&#x1f4a1;3 按钮的逻辑3.1 通过按钮显示临时消息的常用逻辑 &#x1f4e2;&#x1f4a1;3.2 状态保留按钮 &#x1fa84;&#x1f518;3.3 切换按钮 &#x1f504;&#x1f518;3.4 控制流…