• (珠峰18)JS的变量提升与闭包


    JS的变量提升与闭包

    【JS渲染机制堆栈内存】

      当浏览器去加载界面加载js时,首先会创建提供window全局作用域,然后,代码开始自上而下执行,

    代码:var a = 7;

    ①声明变量a,默认值为undefined

    ②在当前作用域中开辟一个位置存储7这个值

    ③让变量a和值12关联在一起(赋值)

    基本类型和引用类型的区别就是,存储方式的不同。

    基本类型直接在作用域中,引用类型因为相对复杂,所以需要单独开辟的存储空间。

    var ary1 =  [12,23];
    
    var ary2 = ary1;
    
    ary2.push(100);
    
    console.log(ary1); //[12,23,100]

    所有作用域的两个作用:提供代码的执行环境 存储基本类型值。

     

    IE靠的是计数器,引用加一,反之减一。Chrom则是,固定时间查看一下,是否还被引用。

    IE计数器,记混的时候,就发生了内存泄漏。

    想让堆内存销毁,直接赋值为 null ,通过空对象指针 null 可以让 原始变量或者其它 指向空,那么原有被占用的堆内存就没有被占用了,浏览器就会在空闲的时候销毁它。

    【变量提升机制】

    变量提升:当栈内存(作用域)形成,JS代码自上而下执行之前,浏览器受限会把所有带"VAR"/"FUNCTION"关键词的进行提前"声明"或者"定义",这种预先处理机制,叫做"变量提升"。 

    =》 声明(declare): var a (默认值undefined)

    =》 定义(defined): a = 12 (定义其实就是赋值操作)

        [变量提升阶段]

    =》带"VAR"的只声明未定义

    =》带"FUNCTION"的声明和赋值都完成了

    =》变量提升只发生在当前作用域。(例如:开始加载页面的时候只对全局作用域下的进行提升,因为此时函数中存储的都是字符串而已)

    =》在全局作用域声明的函数或者边阿玲是"全局变量",同理,在私有作用域下声明的变量是"私有变量" [带VAR/FUNCTION的才是声明]

    自从学了预解释,从此节操是路人。

    =》浏览器很懒,从不会做相同的事情。当代码执行遇到创建函数这部分代码后,会直接跳过(因为在提升阶段就已经完成了函数的赋值操作了)

    【重名问题】 

    【暂时性死区】

    在ES6中Let和const等方式创建变量或者函数,不存在变量提升机制。

    =》切断了全局变量和window属性的映射机制。

    =》在相同作用域,基于Let 不能声明相同名字的变量。(不管是是什么方式只要声明了,在用let重复声明就会报错,var也一样)

    虽然,没有变量提升机制,但是在当前作用域代码自上而下执行之前,浏览器会做一个重复性检测:自上而下查找当前作用域下所有变量,一旦发现重复的,直接抛出异常,代码停止,不会继续执行。(虽然没有把变量提升,但是浏览器已经记住了,当前作用域下有哪些变量。)

     

     

     

       

    a,b变为私有的,与全局无关,c为全局。 

    【私有变量练习】

     

    【上级作用域查找】

     =》arguments:实参集合

    =》arguments.callee:函数本身

    =》arguments.callee.caller:当前函数在哪执行的,CALLER就是谁(记录的是执行它的宿主环境),在全局下执行的结果是null。(严格模式编程禁止使用这两个属性。)

    =》当函数执行时,形成一个私有作用域A,A的上级作用域是谁,和他在哪执行没有关系,和它在哪创建定义有关系,在哪创建的,它的上级作用域就是谁。

    【堆栈内存销毁机制】 

    =》JS分为堆内存和栈内存

    =》堆内存(存储引用类型值),和代码分开(对象:键值对  函数:代码字符串)

    =》栈内存,提供JS代码的执行环境存储基本类型值

    [堆内存释放] :让所有引用堆内存空间地址的变量赋值为 NULL 即可(没有变量占用这个堆内存,浏览器在空闲的时候就会将其释放)

    [栈内存释放] :一般情况下,当函数执行完成后,所形成的私有作用域(栈内存)都会自动释放掉,(在栈内存占用存储的值也会释放掉),但是也存在特殊情况:

                             ①函数执行完成,当前形成的栈内存中,某些内容被栈内存以外的变量占用。

                             ②全局栈内存,只有当页面关闭后才会被释放掉。 

                              ...

                             如果当前栈内存没有被释放掉,那么之前在栈内存中存储的基本值也不会被释放掉,能够一直保存下来。

     i++ :自身累加1 ,和别人运算的时候 ,先拿原有值和其其它进行运算,运算结束后,本身累加1 。

    ++i  :自身累加1 ,先自身累加1,再和别人运算。

     

     

     6 12 16 8 

    【带不带var的区别】

    =>在全局作用域下声明一个变量,也相当于给Window全局对象设置了一个属性,变量的值就是属性值(私有作用域中声明的私有变量和Window无关)

    用  in 操作符 可以检测某个属性名是否隶属于这个对象。 'AZUKI'  in  BoZai ,rue;

    =》全局变量和window中的属性存在"映射机制" , 双方互相同步。

    =》不加var本质是win的属性,创建变量必需加var,养成良好的编程习惯。

    【闭包】

    函数执行形成一个私有的作用域,保护里面的私有变量不受外界干扰,这种保护机制称之为“闭包”。

    函数执行形成一个不销毁的私有作用域(私有栈内存)才是闭包。

    //=>闭包:柯理化函数 fn执行返回一个堆内存被f占用,所以fn作用域不销毁
    function fn(){
      return function (){
    
      }
    }
    
    var f=fn();
    //=>闭包:惰性函数 自执行匿名函数形成的私有作用域被utils占用
    var utils=(function(){
      return{
      
      }
    })();

    小纸条:()内表示声明一个函数,正常声明函数是不能直接在后面加括号调用的。

    闭包应用举例:
    真实项目为了保证JS性能(堆栈内存的性能变化),应该尽可能减少闭包的使用(不销毁的堆栈内存是消耗性能的)。 1.闭包的保护作用:保护私有变量不受外界干扰 真实项目开发中,尤其是团队协作开发,应尽量减少全局变量的使用,防止冲突,造成全局变量污染,那么此时我们可以把自己这一部分内容封装到一个闭包中,让全局变量转换为私有变量。 (function(){})(); 封装插件的时候,也会把程序都放到闭包中保护起来,防止和用户的程序冲突,这时对于一些需要暴露给用户的方法可以抛到全局。 JQ:把需要暴露的方法抛到全局 Zepto:基于RETURN把需要外面使用的方法暴露出去

    2.闭包的保存作用:形成不销毁的栈内存,把一些值保存下来,方便后面调取使用

    tips:

    进入函数:形参赋值 变量提升 代码自上而下执行

    在传统的ES规范中,只有全局作用域和函数执行产生的私有作用域,判断和循环并不会产生作用域。

    原生JS:闭包,oop,异步编程,promise,async和await,es6新特性,js事件机制

    所有的事件绑定都是异步编程(当前事件没有完成,不再等待,继续执行下面的任务。),同步编程(一件事一件事做,当前事件没完成,下一个任务不能处理。)

    小结:

    有占用,不释放,无论是变量,还是事件什么占用,就是闭包。

    ES6中判断循环都是块级作用域,一般大括号内部都是块级作用域。(对象除外)

    每一轮循环,都会形成一个单独的作用域。

    ES6

    自带严格模式,严格模式下的一些限制:

    常用数组遍历方法:

    -forEach

    -map

    -find(ES6)

    -findIndex(ES6)

    -filter

    -some

    -every

    以上都可以改this

    -reduce(数组去重,对象属性求和,数组元素出现次数,这里主要就是利用reduce第一个参数是迭代,可以通过初始化这个参数的数据类型,达到想实现的效果。)

    -reduceRight

    ES6数组空位统一为undefined处理;

    扩展运算符将一个数组,变为参数序列。

    set Map【小纸条:详情链接】:

    set:

    不存储value。由于key不能重复,所以,在Set中,没有重复的key

    [ ...new Set(arr)] 去重,set 是一个类数组对象;

     set.has(Nan) 判断有无,返回true/false;

     set.clear() 清空,无返回值undefined

     map:

     对象的属性名必是字符串,即是不写字符串也会因默认数据类型而变为字符串。

    Symbol:

    基本数据类型,typeOf 可检测,不能和字符串进行拼接,

    不能进行运算,可以转布尔,

    只要通过Symbol()函数得到的值就是唯一的值,只做自己,和任何人都不一样

    Symbol("参数作为描述,两个Symbel创建的值描述相同,也不会相等")

     对象的属性用 [ ]  中括号括起来,代表着里面是变量。

    如果属性名是Symbol形式,必须通过中括号,不能通过点的形式

     

    Iterator【小纸条:详情链接详情链接2:

     es6中有三类结构生来就具有Iterator接口:数组、类数组对象、Map和Set结构。(Promise.all())

    只要具备Iterator,遍历接口,就可用扩展运算符 [...arr]

    默认的遍历接口:__proto__[Symbol.Iterator] 

    prototype与__proto__【小纸条:详情链接:

    1.在JS里,万物皆对象。方法(Function)是对象,方法的原型(Function.prototype)是对象。因此,它们都会具有对象共有的特点。
    即:对象具有属性__proto__,可称为隐式原型,一个对象的隐式原型指向构造该对象的构造函数的原型,这也保证了实例能够访问在构造函数原型中定义的属性和方法。

    2.方法(Function)
    方法这个特殊的对象,除了和其他对象一样有上述_proto_属性之外,还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象)。原型对象也有一个属性,叫做constructor,这个属性包含了一个指针,指回原构造函数。

    proxy和defineProperty:

    new Proxy(target目标对象,{代理方法});

     

  • 相关阅读:
    阿里代码检查工具
    SpringCloud 服务通信方法
    Apache ab简单使用
    H5 webSocket使用
    Java list去重 取重
    解决 spring boot Failed to decode downloaded font
    Pycharm设置快捷方式
    环境变量与文件的查找
    Linux基础
    VIM中文乱码解决
  • 原文地址:https://www.cnblogs.com/goforxiaobo/p/14201410.html
Copyright © 2020-2023  润新知