安全基础 --- js的闭包和this属性

news2024/11/18 11:21:48

js闭包

简介

一个函数和对其周围状态(lexical exviroment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)

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

(1)变量作用域:

两种

  • 全局作用域
  • 局部作用域

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

var code = 200;

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

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

function f1() {
    var code = 200;
}
console.log(code);  // 无法读取,在外部未被定义

(2)读取函数内部的局部变量

[1] 在函数内部再定义一个函数

function f1(){
    var code = 200;

    function f2() {
        console.log(code);
    }
}

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

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

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

(3)闭包概念

函数 f2 就是闭包,作用就是将函数内部与外部进行了连接

  • 闭包访问的变量,是每次运行上层函数时重新创建的,是相互独立的
function f1() {
    var obj = {};

    function f2() {
        return obj;
    }
    return f2;
}

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

var result1 = f();
result1.f1(); // 1
result1.f2(); // 2

// 闭包f1和闭包f2共享上层函数中的局部变量num

例:旅行者走路的问题

function factory() {
    var start = 0;
    function walk(step) {
        var new_total = start + step;
        start = new_total;
        return start;
    }
    return walk;
}

var res = factory();
console.info(res(1));
console.info(res(2));
console.info(res(3));

// start 将记录上一次的值

PS:

  1. 闭包使得函数中变量保存在内存中,内存消耗大,不可滥用,否则会造成网页的性能问题,IE中可能导致内存泄露。解决办法:退出函数前,不使用的局部变量全部删除
  2. 闭包会在父函数外部,改变父函数内部变量的值。若把父函数当做对象(object)使用,把闭包当做它的公用方法(Public Method),把内部变量当做它的私有属性(private value),这时不可随意改变父函数内部变量的值

this关键字

(1)关键点:

  1. this始终指向调用该函数的对象;
  2. 没有指明调用的对象,则顺着作用域链向上查找,最顶层为global(window)对象
  3. 箭头函数中的this是定义函数时绑定的,与执行上下文有关;
  4. 简单对象(非函数,非类)没有执行上下文;
  5. 类中的this,始终指向该实例对象;
  6. 箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象

(2)四类调用方式

[1] 作为对象方法使用

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

[2] 纯粹的函数调用

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

var code = 200;
f();// 200
code = 404;
f();// 404

复杂:

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

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

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

[4] 使用apply、call、bind调用

<1> 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指向参数中设置的对象
<2> call

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

function f() {
    console.log(this.code);
}

var obj = {
    code:200
};

f.call(obj);  // 200
<3> 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

(3)箭头函数中的this

[1] 概念

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

[2] 箭头函数在普通对象中

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

[3] 箭头函数在函数中

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);  // 200

[4] 箭头函数在类中

var code = 404;
class Status {
    constructor(code){
        this.code = 200;
    }

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

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

实例解析

(1)例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)例2:

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

console.log(status.getCode()()); // 200

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

(3)例3:复杂

function f() {
    // 宏任务
    setTimeout(() => {
        console.log(">>>" + this); // >>>[object object],语句5
        this.code = 401;
    },0)
    // 同步
    console.log(this.code);
}

let obj = {
    // ">>>" + this
    code:200,
    foo:f
};

var code = 500;

// 1.箭头函数 this 指向问题
// 2.字符串 + this [object object]
obj.foo(); // 200  语句1

console.log("---" + obj.code);  // 200  语句3

// 宏任务
setTimeout(() =>{console.log("---" + obj.code);},0);  //  401  语句4

obj.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):设置一个0延时的定时器,其中的箭头函数在调用

输出:---401

解释:在调用obj.foo()的过程中,foo方法中的setTimeout在当前宏任务结束后执行。由于是箭头函数,this的值保持与父作用域一致(也就是obj对象),所以在箭头函数内部,this.code的值被设为401

setTimeout(() => {console.log(">>>" + this); this.code = 401;},0)  --- (语句5):设置一个0延时的定时器,其中的箭头函数在调用

输出:>>>[object object]

解释:在调用obj.foo()的过程中,foo方法中的setTimeout在当前宏任务结束后执行。箭头函数的this始终指向被他创建时的外部作用域,所以this指向了obj对象,在控制台中打印this时会将其转换为字符串。所以输出为 >>>[object object]

setTimeout()

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

分析 --- :函数setTimeout中传入的函数句柄,由于js是单线程执行,即使延时为0,仍需等到本次执行的所有同步代码执行完毕,才能执行。在两次执行obj.foo()的过程中,其内部的setTimeout的入参函数(语句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],语句3
        this.code = 401;  // 语句4
    },0)
    console.log(this.code);
}

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为函数句柄,作为入参传入函数doFoo,doFoo的调用方为全局变量window,所以,语句2中doFoo对象的code是404,3、4中的this均指向window

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

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

相关文章

计算机毕设 大数据商城人流数据分析与可视化 - python 大数据分析

文章目录 0 前言课题背景分析方法与过程初步分析&#xff1a;总体流程&#xff1a;1.数据探索分析2.数据预处理3.构建模型 总结 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到…

【9月比赛合集】9场可报名的「创新应用」、「数据分析」和「程序设计」大奖赛,任君挑选!

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 创新应用赛&#xff08;2场比赛&#xff09;程序设计赛&#…

openGauss学习笔记-60 openGauss 数据库管理-逻辑存储结构

文章目录 openGauss学习笔记-60 openGauss 数据库管理-逻辑存储结构 openGauss学习笔记-60 openGauss 数据库管理-逻辑存储结构 openGauss的数据库节点负责存储数据&#xff0c;其存储介质也是磁盘&#xff0c;本节主要从逻辑视角介绍数据库节点都有哪些对象&#xff0c;以及这…

无锡布里渊——厘米级分布式光纤-锅炉安全监测解决方案

无锡布里渊——厘米级分布式光纤-锅炉安全监测解决方案 厘米级分布式光纤-锅炉安全监测解决方案 1、方案背景与产品简介&#xff1a; 1.1&#xff1a;背景简介&#xff1a; 锅炉作为一种把煤、石油或天燃气等化石燃料所储藏的化学能转换成水或水蒸气的热能的重要设备&#xff…

音频基础知识

文章目录 前言一、音频基本概念1、音频的基本概念①、声音的三要素②、音量与音调③、几个基本概念④、奈奎斯特采样定律 2、数字音频①、采样②、量化③、编码④、其他相关概念<1>、采样位数<2>、通道数<3>、音频帧<4>、比特率&#xff08;码率&#…

uniapp从零到一的学习商城实战

涵盖的功能&#xff1a; 安装开发工具HBuilder&#xff1a;HBuilderX-高效极客技巧 创建项目步骤&#xff1a; 1.右键-项目&#xff1a; 2.选择vue2和默认模板&#xff1a; 3.完整的项目目录&#xff1a; 微信开发者工具调试&#xff1a; 1.安装微信开发者工具 2.打开…

改变金融贷款市场营销方式 ---- 运营商大数据精准获客

与传统的企业网络营销相比&#xff0c;最常见的是网络推广和硬广告推广。一些企业无法找到可靠准确的数据来源&#xff0c;也无法找到一些未知的总数据。这些数据大多存在持续时间长、准确性差的缺点&#xff0c;企业在将这些数据信息应用于商品在线营销时往往会遇到不足。 在…

什么是RPA机器人?RPA机器人能做什么?RPA机器人的应用场景

什么是RPA机器人&#xff1f; RPA机器人是一种使用软件机器人来模拟和执行人类操作的技术。RPA代表Robotic Process Automation&#xff08;机器人流程自动化&#xff09;。它是一种自动化技术&#xff0c;可以使用预定规则和预定流程来执行重复性、繁琐或规定任务的工作。 RP…

进程的状态与组织

进程的5种状态分别为创建态、就绪态、运行态、阻塞态、终止态。在PCB中&#xff0c;会有一个state变量来记录进程此时所处的状态。 操作系统会根据各种进程的不同状态来组织管理进程&#xff0c;有链接组织方式和索引组织方式。

使用 Linux 相关知识部署博客系统

目录 ​编辑一、认识 Linux 二、如何拥有 Linux 环境 三、常见的 Linux 命令 1、目录相关命令 &#xff08;1&#xff09;ls &#xff08;2&#xff09;pwd &#xff08;3&#xff09;cd 2、文件操作相关命令 &#xff08;1&#xff09;touch &#xff08;2&#xf…

48、springboot 的国际化之让用户在程序界面上弄个下拉框,进行动态选择语言

上一篇是直接改浏览器的支持语言。 在浏览器上面直接改国际化语言 这次要实现的功能是直接在程序界面动态选择语言。 Locale 代表语言、国家。 ★ 在界面上动态改变语言 应用之所以能动态呈现不同的语言界面&#xff0c;其实关键在于如何确定客户端的Locale&#xff08;代…

springboot实战(五)之sql业务日志输出,重要

目录 环境&#xff1a; 一、mybatis-plus之sql分析日志输出 1.配置 2.验证 3.高级输出方式 二、业务日志输出到文件 1.添加log4j2依赖 2.排除logback依赖 3.新增log4j2的配置文件 4.添加配置 5.启动测试 6.给日志请求加个id 6.1、过滤器filter实现 6.2、测试 6.3、…

Linux之基于HTTPS的静态网站

目录 Linux之基于HTTPS的静态网站 定义 SSL协议 使用Apachemod_ssl组件的加密认证网站 mod_ssl模组 安装 配置文件 ssl配置文件的主要参数 案例 案例1 --- 搭建HTTPSSL的加密认证的web服务器 案例2 --- 组建多个子目录的网站www.joker.com&#xff0c;该网站下有2个子…

Android开发中的各种零碎知识点

折叠屏 折叠屏为什么需要适配 折叠屏在视觉效果来说就是&#xff0c;屏幕变大了&#xff0c;手机变平板。这样就需要我们的app在可折叠设备展开时&#xff0c;当前应用页面必须无缝延续到另一个屏幕&#xff0c;并可自动调整大小匹配新的布局&#xff0c;也就是说&#xff0c;应…

护航数字政府建设,美创科技成为“数字政府建设赋能计划”成员单位

近日&#xff0c;“2023软博会-软件驱动数字政府创新发展论坛”顺利召开&#xff0c;本次论坛由中国信息通信研究院、中国通信标准化协会承办&#xff0c;中国通信标准化协会云计算标准和开源推进委员会、数字政府建设赋能计划支持。 天津市工业和信息化局总经济师杨冬梅、中国…

OpenHarmony 使用 ArkUI Inspector 分析布局

● 摘要&#xff1a;视图的嵌套层次会影响应用的性能&#xff0c;开发者应该移除多余的嵌套层次&#xff0c;缩短组件刷新耗时。本文会介绍如何使用 ArkUI Inspector 工具分析布局&#xff0c;提示应用响应性能。 ● 关键字&#xff1a;列举本文相关的关键字&#xff1a;OpenH…

springboot找不到注册的bean

1、错误描述 A component required a bean named ‘fixedAssetsShareMapper’ that could not be found.Action:Consider defining a bean named ‘fixedAssetsShareMapper’ in your configuration.2、问题分析 1、该错误提示表明在你的应用程序中有一个组件&#xff08;可能…

【技术分享】RK Android11系统SD卡启动方法

本文基于Purple Pi OH 3566主板&#xff0c;介绍Android11源码的修改&#xff0c;获得可从SD卡启动的Android11系统镜像。 Purple Pi OH作为一款兼容树莓派的开源主板&#xff0c;采用瑞芯微RK3566 (Cortex-A55) 四核64位超强CPU,主频最高达1.8 GHz,算力高达1Tops&#xff0c;…

stable diffusion实践操作-大模型介绍-SDXL1大模型

系列文章目录 大家移步下面链接中&#xff0c;里面详细介绍了stable diffusion的原理&#xff0c;操作等&#xff08;本文只是下面系列文章的一个写作模板&#xff09;。 stable diffusion实践操作 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生…

Qt鼠标点击事件处理:显示鼠标点击位置(完整示例)

Qt 入门实战教程&#xff08;目录&#xff09; 前驱文章&#xff1a; Qt Creator 创建 Qt 默认窗口程序&#xff08;推荐&#xff09; 什么是事件 事件是对各种应用程序需要知道的由应用程序内部或者外部产生的事情或者动作的通称。 事件&#xff08;event&#xff09;驱动…