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


    js闭包

    简介

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

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

    (1)变量作用域:

    两种

    • 全局作用域
    • 局部作用域
    [1] 函数内部可以读取全局变量
    1. var code = 200;
    2. function f1() {
    3. console.log(code);
    4. }
    5. f1(); //200
    [2] 函数外部无法读取函数内部的局部变量
    1. function f1() {
    2. var code = 200;
    3. }
    4. console.log(code); // 无法读取,在外部未被定义

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

    [1] 在函数内部再定义一个函数
    1. function f1(){
    2. var code = 200;
    3. function f2() {
    4. console.log(code);
    5. }
    6. }

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

    [2] 为外部程序提供访问函数局部变量的入口
    1. function f1() {
    2. var code = 200;
    3. function f2() {
    4. console.log(code);
    5. }
    6. return f2;
    7. }
    8. f1()(); // 200

    (3)闭包概念

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

    • 闭包访问的变量,是每次运行上层函数时重新创建的,是相互独立的
    1. function f1() {
    2. var obj = {};
    3. function f2() {
    4. return obj;
    5. }
    6. return f2;
    7. }
    8. let result1 = f1();
    9. let result2 = f1();
    10. console.log(result1() === result2()); // false
    • 不同的闭包,可共享上层函数中的局部变量
    1. function f() {
    2. var num = 0;
    3. function f1() {
    4. console.log(++num);
    5. }
    6. function f2() {
    7. console.log(++num);
    8. }
    9. return {f1,f2};
    10. }
    11. var result1 = f();
    12. result1.f1(); // 1
    13. result1.f2(); // 2
    14. // 闭包f1和闭包f2共享上层函数中的局部变量num

    例:旅行者走路的问题

    1. function factory() {
    2. var start = 0;
    3. function walk(step) {
    4. var new_total = start + step;
    5. start = new_total;
    6. return start;
    7. }
    8. return walk;
    9. }
    10. var res = factory();
    11. console.info(res(1));
    12. console.info(res(2));
    13. console.info(res(3));
    14. // 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] 作为对象方法使用
    1. function f(){
    2. console.log(this.code);
    3. }
    4. var obj = {
    5. code:200,
    6. f:f
    7. };
    8. obj.f(); // 200
    [2] 纯粹的函数调用
    1. function f() {
    2. console.log(this.code);
    3. }
    4. // 此处,通过var(函数作用域)声明的变量会绑定在windows上,
    5. // 如果使用let(块作用域)声明变量code,则不会绑定在windows上,
    6. // 因此下面的两次函数调用f(),显示为undefined
    7. // let code = 200;
    8. var code = 200;
    9. f();// 200
    10. code = 404;
    11. f();// 404

    复杂:

    1. function doF(fn) {
    2. this.code = 404;
    3. fn();
    4. }
    5. function f() {
    6. console.log(this.code);
    7. }
    8. let obj = {
    9. code:200,
    10. f:f
    11. };
    12. var code = 500;
    13. doF(obj.f); // 404

    结果解析 --- 该例中,为分析出 this 的指向,应找到关键点:哪个对象调用了函数 f()。obj.f 作为doF()的入参,将函数 f 传给了doF,而 doF 是由 window 调用的,所以函数doF中的 this 指向 window ,继而函数 f 中的 this 指向window
    最终执行是 doF,所以 this 指向 doF,结果为404

    [3] 作为[构造函数]调用
    1. code = 404;
    2. function A() {
    3. this.code = 200;
    4. this.callA = function() {
    5. console.log(this.code);
    6. }
    7. }
    8. var a = new A();
    9. a.callA(); // 200,callA在new A返回的对象里
    [4] 使用apply、call、bind调用
    <1> apply

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

    1. var code = 404;
    2. let obj = {
    3. code:200,
    4. f:function() {
    5. console.log(this.code);
    6. }
    7. };
    8. obj.f(); // 200,作为对象的方法调用
    9. obj.f.apply(); // 404,参数为空时,默认使用全局对象global,在此处为对象window
    10. obj.f.apply(obj); // 200,this指向参数中设置的对象
    <2> call

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

    1. function f() {
    2. console.log(this.code);
    3. }
    4. var obj = {
    5. code:200
    6. };
    7. f.call(obj); // 200
    <3> bind

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

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

    (3)箭头函数中的this

    [1] 概念

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

    [2] 箭头函数在普通对象中
    1. var code = 404;
    2. let obj = {
    3. code:200,
    4. getCode:() => {
    5. console.log(this.code);
    6. }
    7. };
    8. obj.getCode(); // 404

    结果解析:

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

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

    [3] 箭头函数在函数中
    1. var code = 404;
    2. function F() {
    3. this.code = 200;
    4. let getCode = () => {
    5. console.log(this.code);
    6. };
    7. getCode();
    8. }
    9. var f = new F(); // 200
    10. var f = F(); // 构造函数没有new调用,成为了一个普通函数
    11. console.log(f);
    12. console.log(code); // 200
    [4] 箭头函数在类中
    1. var code = 404;
    2. class Status {
    3. constructor(code){
    4. this.code = 200;
    5. }
    6. getCode = () => {
    7. console.log(this.code);
    8. };
    9. }
    10. let status = new Status(200);
    11. status.getCode(); // 200

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

    实例解析

    (1)例1:

    1. var code = 404;
    2. let status = {
    3. code:200,
    4. getCode:function(){
    5. return function(){
    6. return this.code;
    7. };
    8. }
    9. };
    10. console.log(status.getCode()()); // 404

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

    (2)例2:

    1. var code = 404;
    2. let status = {
    3. code:200,
    4. getCode:function(){
    5. let that = this;
    6. return function(){
    7. return that.code;
    8. };
    9. }
    10. };
    11. console.log(status.getCode()()); // 200

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

    (3)例3:复杂

    1. function f() {
    2. // 宏任务
    3. setTimeout(() => {
    4. console.log(">>>" + this); // >>>[object object],语句5
    5. this.code = 401;
    6. },0)
    7. // 同步
    8. console.log(this.code);
    9. }
    10. let obj = {
    11. // ">>>" + this
    12. code:200,
    13. foo:f
    14. };
    15. var code = 500;
    16. // 1.箭头函数 this 指向问题
    17. // 2.字符串 + this [object object]
    18. obj.foo(); // 200 语句1
    19. console.log("---" + obj.code); // 200 语句3
    20. // 宏任务
    21. 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)扩展

    1. function doFoo(fn){
    2. this.code = 404;
    3. fn();
    4. }
    5. function f() {
    6. setTimeout(() => {
    7. console.log(">>>" + this); // >>>[object window],语句3
    8. this.code = 401; // 语句4
    9. },0)
    10. console.log(this.code);
    11. }
    12. let obj = {
    13. code:200,
    14. foo:f
    15. };
    16. var code = 500;
    17. doFoo(obj.foo); // 语句1
    18. setTimeout(() => {console.log(obj.code)},0); // 200,语句5
    19. setTimeout(() => {console.log(window.code)},0); // 401,语句6

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

  • 相关阅读:
    谈谈WorkManager线程池的设计
    两年CRUD,二本毕业,备战两个月面试阿里,侥幸拿下offer定级P6
    大数据之Hive(三)
    [JavaScript]面对对象编程,构造函数,原型
    【数字电路与逻辑设计实验】——Multisim仿真实验-xx进制的计数器
    Github高级搜索【指定日期区间,星星数,用户仓库名多条件精确搜索】
    【车载音视频电脑】嵌入式AI分析车载DVR,支持8路1080P
    Python爬虫与数据可视化源码免费领取
    vue3表单参数校验+正则表达式
    北大肖臻老师《区块链技术与应用》系列课程学习笔记[20]以太坊-智能合约-1
  • 原文地址:https://blog.csdn.net/weixin_62443409/article/details/132665629