作用域与闭包

本文深入探讨JavaScript中的闭包概念及其应用场景,包括闭包如何帮助处理循环与定时器问题,以及如何通过闭包实现模块化编程。同时,介绍了简单的现代模块机制,包括定义加载器和依赖注入的基本实现。

今天总结的这个章节内容有点多,有点复杂,如果我有说不清或者说错的地方,欢迎大家交流与指导

在前面我已经给大家介绍过词法作用域的概念了(即在编译阶段就被放在固定位置读取的作用域),所以在说闭包之前我们先来说说函数、变量提前与块作用域的概念

一、块作用域

大家可以将块作用域理解为局部变量所在的词法作用域内,形象的给个demo就是↓

function zoom(){
    var me='我是zoom作用域下的局部变量me';
    console.log(me);   //'我是zoom作用域下的局部变量me'
}

console.log(me);  //Reference

这个时候zoom函数就是一个块作用域了;

那么for与if呢?是否也算是块作用域呢?

for(var i=0;i<10;i++){
    console.log(i);  //1 2 3 .....
}

console.log(i);  //10

if(true){
    var a=5;
}

console.log(a);//5

我们可以看出在ES5的写法下for与if是不算块作用域的,他们的词法作用域都在全局之下,而我们如何可以让他们也变成块作用域呢,这个时候我们有种解决方法;

利用let声明变量,let会将变量绑定在for循环的块中,并且在每次迭代中重新进行赋值:

for(let i=0;i<10;i++){
    console.log(i)  //1 2 3 .....
}                                      

console.log(i);   //Reference   
//if同理

if(true){
    let a=5;
    console.log(a);  //5 
}    
console.log(a); //Reference

{
    var a=5;
    console.log(a);  //5
}      
console.log(a);  //Reference

当然let只是一种替代方案,可以让你的变量绑定在一个{}中,但实际上for是不会创建块作用域的。

二、函数与变量的提升

1、变量的提升

console.log(a);  //undefined
var a=5;
console.log(a);  //5

上述代码中,第一个控制台日志中a的报错是undefined而不是未进行声明所报的Reference,这就证明了变量a的声明是在编译阶段被提升至最前了,否则应会被报错为a未定义;

2、函数的提升

top(); //'我被提升了'

function top(){
    console.log('我被提升了');
}

这里与1中同理;

3、那么当函数与变量同时出现时,哪个的提升优先呢?show me my code~

console.log(top);//function top(){...}

var top='我是变量top';

function top(){
    console.log('我是top函数')
}

由上面的demo我们已经显然得出了结论,当变量与函数相冲时,编译器会优先提前函数而不是变量,如果优先为变量那么此处log的结果应该为undefined,如果先声明top函数再声明top变量,结果是一样的,大家可以试试,所以得出结论:函数提前优先

三、简单的闭包理解

先来一个简单的闭包来方便大家理解:

function foo(){
    var a=2;
    function bar(){
        console.log(a);
    }
    return bar;
}

var baz=foo();
baz();//2

1、什么时候会产生闭包?
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行。(引用至书中)

2、解释其中的机制
在demo中我们先执行了外部函数foo,我们知道引擎具有垃圾回收器的职能,当一个函数执行后,该函数的内部作用域会被销毁,以此来释放内存,当下次再次调用则又不会进行创建对应作用域;

那么在这里我们先执行了foo()后,在foo中return出来的是function bar(){…}并赋值给了baz,紧接着我们执行了baz()输出2,此时我们是在外部执行的bar函数而不是在foo的内部,但是按照回收器的原理,此时foo函数执行完毕后,不再有需要时,内部作用域应该是会被回收的,而此处就体现了出闭包的神奇,foo内部没有被回收,引擎记住了foo的内部作用域,那么我们知道函数一旦不被需要时就会被回收,那么是谁在需要者foo的内部作用域呢?

在此我们分析一下baz=foo(),此时foo()执行后返回了bar并由baz接住了返回值bar方法,此时bar是存在的,并且在其后会在外部的词法作用域中被调用,但是正是由于bar的存在,bar是存在于foo的内部作用域之中的,那么简单的说就是bar需要foo内部作用域的存在,那么引擎这里就知道了:’哦原来bar你还要用啊,那我就先不回收了,你先用着先’,此时foo的内部作用域是一直存在并被记忆在了内存之中,我们反复使用时就不需要再次创建新的foo内部作用域了,因为它从来都没有被回收!这就提高了内部作用域中的执行效率,而其中的var a=2;的变量a就可以说被foo的内部作用域保护了起来,因为我们从来都是在利用闭包内bar在改变变量值;

四、循环和闭包

恩,下面我会套用书上的例子来说明,什么?你问这里怎么不自己写demo说明了,那么我只能说我太菜了,实在想不到比书上还好的例子了……

for(var i=0;i<5;i++){
    setTimeout(function(){
        console.log(i);
    },i*1000)
}

看完这个书中的demo之后,首先一开始其实我也是认为是每隔1s输出一个i(i由0-4)的,但执行后可以发现结果是每秒都是输出5,下次依次解释引擎执行的过程;

1、首先进入循环后确实是执行了一次性定时器,并且根据i的值递增延迟执行,但此时一次性定时器的内部函数只有一个作用域且其中使用的都是全局变量的i,记住由于一次性定时器内的函数是延迟执行的,这里内部函数还没执行;
2、那么问题来了,在一次性定时器中的函数开始执行时,for循环已经循环完毕了,此时i=5,for完毕后一次性定时器中的函数才开始执行,退出循环时i=5,接下来一共执行5次,此时只有一个全局i,且i=5,所以会log出5个5,而不是依次0-4。

所以这里最根本的原因就是一次性定时器内从始至终都共享了 ‘一个’全局变量i,那么我们如何改进它才能使它实现我们想要的结果一次延迟输出0-4呢?

这就回到了我们上面所讲的简单闭包中的知识点了,利用闭包就可以解决了,在for循环中只要能依次生成循环次数的闭包并且在闭包中将i保护起来,那么在执行内部函数时就是不同的闭包,不同的i了,因为有了’循环次数’个作用域,所以在内部可以实现;

那么接下来我们尝试通过5个闭包来创建5个作用域来解决问题:

for(var i=0;i<5;i++){
    (function(){
        setTimeout(function(){
            console.log(i);
        },i*1000);
    })();
}

我们在这里已经创建了5个作用域了,但是问题解决了吗?你可以试试~

结果你发现:’妈蛋,还是输出了5个5 哈哈哈哈哈哈哈哈哈哈哈哈哈哈’;

为什么不行呢?
哈哈哈哈哈哈哈,因为你即使创建了5个空作用域,但是还是只右查询了一个全局i,虽然在你的5个作用域内都有了i,但是引擎只用了右查询,并没有左查询在各自的作用域中保存下来,哈哈哈哈哈~

所以改造一下,只要不是空作用域,去保存每次的i就行了:

for(var i=0;i<5;i++){
    (function(){
        var j=i;
        setTimeout(function(){
            console.log(j)
        },j*1000)
    })();
}

恩,这次就行了,5个闭包,5个局部变量j来保存每次进入的全局i,然后延迟执行的函数依次有查询j,嘻嘻~

等等我们再来升级一下再结束这段

for(var i=0;i<5;i++){
    (function(j){
        setTimeout(function(){
            console.log(j)
        },j*1000)
    })(i);
}

恩,立即执行函数这样写就行了,不需要这么麻烦。

最后告诉你一个悲剧的方法

for(let i=0;i<5;i++){
    setTimeout(function(){
        console.log(i);
    },i*1000)
}

这个方法与上面的立即执行函数出来的结果是一样的,依次输出0-4,为什么可以呢,是因为let在for的{…}中创立了类似于块作用域的概念,并且let会使i每次迭代赋值 等价于 {var j=i;i++},每次在块作用域中i都会迭代赋值,即是每次增加1,这里let已经满足了我们开始需要的2个条件~

1、利用5个闭包分别创建5个不同的作用域;
2、将每次的i赋值给局部变量保护起来;

let已经在上面讲过了,这里就不多说了,别想死,我最后看到这个方法心里也是X了狗。

五、模块化与简单的现代模块机制(定义加载器/管理器与依赖注入的简单实现)

首先我们来写一个最基本的模块来说明这个小章节

var modules=(function(name){
    function change(){
        public.foo=foo1;
    }

    function foo(){
        console.log('我的名字叫:'+name);
    }

    function foo1(){
        console.log('我的名字大写叫:'+name.toUpperCase());
    }

    var public={
        foo:foo,
        change:change
    };

    return public;

})('MyModules');

modules.foo();  //'我的名字叫:MyModules'
modules.change();  //public.foo=>foo1
modules.foo();   //'我的名字大写叫:MYMODULES'

在上面的demo中我们利用模块化的思想,在构造模块时我们将'MyModules' 作为参数传入并构造了2个API,一个是foo一个是change,代码也很简单,相信大家自己就能看懂吧~

接下来我们创建一个加载器或者叫管理器,用于部分需要依赖注入的模块,仿造书中的简单核心逻辑来说明

//首先建立一个管理器
var MyModules=(function(){
        var modules={};
        function define(name,deps,func){
            for(var i=0;i<deps.length;i++){
                deps[i]=modules[deps[i]];
            }
            modules[name]=func.apply(func,deps);//核心
        }

        function get(name){
            return modules[name];
        }

        return{
            define:define,
            get:get
        }
    })();

//注册foo模块
MyModules.define('foo',[],function(){
    function add(a,b){
        console.log('和为:'+(a+b));
        return a+b;
    }
    return {
        add:add
    }

//注册bar模块
MyModules.define('bar',['foo'],function(foo){
    function sumIsEven(a,b){
        console.log(foo.add(a,b)%2 ===0 ?'和为一个偶数':'和为一个奇数');
    }
    return {
        sumIsEven:sumIsEven
    }
});

var foo=MyModules.get('foo');
var bar=MyModules.get('bar');

bar.sumIsEven(5,3);  //和为8  和为一个偶数

大家也看到了结果,我独立使用了bar 内的sumIsEven来判断和是否为一个偶数,关键点在于我在bar模块内使用了foo模块内的add方法,如果使用angular的童鞋应该知道这样的依赖注入的方式,因为angular的路由就是这样注入的,当然我并没有说完全与其一致,但是思路的核心逻辑是一样的,现在来让我们分析一下这个过程。

1、建立管理器
在其中我们声明了2个函数,一个为定义模块(define),一个为获取模块(get),最后在返回为对象,使其能封装为高内聚的API,正如我上一个小模块demo一样
重点说明:define内的一段循环与其后的赋值操作,其实一般的小伙伴应该看的明白~但是我还是解释一下吧,首先在循环内我们将定义模块的第二个参数(模块名建立的数组)为条件进行的循环,将数组deps内的模块名分别在modules初始化对象内找到对应的模块函数去重新赋值给deps数组,循环结束后则重新将本次定义的模块名利用apply内置API将数组作为参数传给本次模块的声明函数,其中apply并没有改变this的指向,最后将对应赋值给modules对象,在我们的例子中是以下情况:
第一次循环=>数组长度为0=>循环执行0次=>modules[‘foo’]=func.apply(func,[])=>return add;

modules={
    foo:{
        add:function(a,b){...}
    }
}

第二次循环=>数组长度为1=>循环执行一次=>存在依赖foo=>deps[0]=modules[‘foo’]
(注意此时deps[0]为整个foo的函数式)
=>modules[‘bar’]=func.apply(func,deps) ,这里将整个foo函数作为参数传递入了function(bar){…}内执行并赋值给modules[‘bar’],也就是说在bar的作用域内已经可以调用foo内的方法了。但需要注意的是你不适用get是不会返回bar函数的~,所以注册了使用时一定要记得先获取对应模块。注意定义管理器时的get方法。

modules={
    foo:{
        add:function(a,b){...}  
    },
    bar:{
        sumIsEven:function(a,b){...}
    }
}

在bar定义的模块内,foo实际上是以这种形式存在的

foo=(function(){
    function add(a,b){
        console.log('和为:'+(a+b));
        return a+b;
    }
    return {
        add:add
    }
})()

apply时已经执行了,所以你可以想象成上述的情况方便理解,当然这里只有一个依赖,如果有2个,便有2个这样的函数执行,而且你可以很明显的看出,没错,foo在bar内是一个闭包= =…因为是在sumIsEven作用域内调用的foo作用域中的add方法,这很词法作用域~

其实我说这么多还不如自己多看几遍来的实在~

这次的内容有点多,特别是闭包与模块这里会比较复杂,其实我也分了两天来写这块,如果其中有错误或者不清楚的地方,欢迎大家指导~

【电力系统】单机无穷大电力系统短路故障暂态稳定Simulink仿真(带说明文档)内容概要:本文档围绕“单机无穷大电力系统短路故障暂态稳定Simulink仿真”展开,提供了完整的仿真模型说明文档,重点研究电力系统在发生短路故障后的暂态稳定性问题。通过Simulink搭建单机无穷大系统模型,模拟不同类型的短路故障(如三相短路),分析系统在故障期间及切除后的动态响应,包括发电机转子角度、转速、电压和功率等关键参数的变化,进而评估系统的暂态稳定能力。该仿真有助于理解电力系统稳定性机理,掌握暂态过程分析方法。; 适合人群:电气工程及相关专业的本科生、研究生,以及从事电力系统分析、运行控制工作的科研人员和工程师。; 使用场景及目标:①学习电力系统暂态稳定的基本概念分析方法;②掌握利用Simulink进行电力系统建模仿真的技能;③研究短路故障对系统稳定性的影响及提高稳定性的措施(如故障清除时间优化);④辅助课程设计、毕业设计或科研项目中的系统仿真验证。; 阅读建议:建议结合电力系统稳定性理论知识进行学习,先理解仿真模型各模块的功能参数设置,再运行仿真并仔细分析输出结果,尝试改变故障类型或系统参数以观察其对稳定性的影响,从而深化对暂态稳定问题的理解。
本研究聚焦于运用MATLAB平台,将支持向量机(SVM)应用于数据预测任务,并引入粒子群优化(PSO)算法对模型的关键参数进行自动调优。该研究属于机器学习领域的典型实践,其核心在于利用SVM构建分类模型,同时借助PSO的全局搜索能力,高效确定SVM的最优超参数配置,从而显著增强模型的整体预测效能。 支持向量机作为一种经典的监督学习方法,其基本原理是通过在高维特征空间中构造一个具有最大间隔的决策边界,以实现对样本数据的分类或回归分析。该算法擅长处理小规模样本集、非线性关系以及高维度特征识别问题,其有效性源于通过核函数将原始数据映射至更高维的空间,使得原本复杂的分类问题变得线性可分。 粒子群优化算法是一种模拟鸟群社会行为的群体智能优化技术。在该算法框架下,每个潜在解被视作一个“粒子”,粒子群在解空间中协同搜索,通过不断迭代更新自身速度位置,并参考个体历史最优解和群体全局最优解的信息,逐步逼近问题的最优解。在本应用中,PSO被专门用于搜寻SVM中影响模型性能的两个关键参数——正则化参数C核函数参数γ的最优组合。 项目所提供的实现代码涵盖了从数据加载、预处理(如标准化处理)、基础SVM模型构建到PSO优化流程的完整步骤。优化过程会针对不同的核函数(例如线性核、多项式核及径向基函数核等)进行参数寻优,并系统评估优化前后模型性能的差异。性能对比通常基于准确率、精确率、召回率及F1分数等多项分类指标展开,从而定量验证PSO算法在提升SVM模型分类能力方面的实际效果。 本研究通过一个具体的MATLAB实现案例,旨在演示如何将全局优化算法机器学习模型相结合,以解决模型参数选择这一关键问题。通过此实践,研究者不仅能够深入理解SVM的工作原理,还能掌握利用智能优化技术提升模型泛化性能的有效方法,这对于机器学习在实际问题中的应用具有重要的参考价值。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值