JS中的 变量提升、作用域、闭包 核心原理解读

<small id="fegbf"></small><colgroup id="fegbf"><figure id="fegbf"></figure></colgroup><dl id="fegbf"><i id="fegbf"></i></dl><dfn id="fegbf"></dfn>
<tfoot id="fegbf"><keygen id="fegbf"></keygen></tfoot><cite id="fegbf"><samp id="fegbf"><select id="fegbf"></select></samp></cite>
<col id="fegbf"></col><button id="fegbf"></button>
        • <select id="fegbf"></select><i id="fegbf"></i>
        • <ul id="fegbf"><legend id="fegbf"></legend></ul>
          <dd id="fegbf"></dd>
        • <i id="fegbf"></i><code id="fegbf"><code id="fegbf"></code><colgroup id="fegbf"></colgroup></code><colgroup id="fegbf"><acronym id="fegbf"></acronym></colgroup>

          JS中的 变量提升、作用域、闭包 核心原理解读

          作者:日期:2018-03-25 19:27:41 点击:450

          数据类型的操作原理

          基本数据类型

          1.var a=12;
          2.var b=a;
          3.b=13;
          4.console.log(a); //=>12

          直接在当前作用域中创建了基本数据类型的值(或者说基本类型值直接存储在当前作用域中),然后把这个值和变量关联起来(一个变量只能关联一个值,关联下一个值后和之前关联的值就没关系了),我们把关联这个操作叫做 变量赋值,基本数据类型是直接 按值操作

          引用数据类型

          1.var o={name:'珠峰培训'};
          2.var p=o;
          3.p.name='中国最权威的前端培训机构';
          4.console.log(o.name);

          引用数据类型不是直接按值操作的(它的结构复杂,要存储很多值,无法直接的创建值),在JS中遇到引用数据类型(对象或者函数),按照如下操作进行:

          1、首先开辟一个新的内存空间(浏览器为其分配一个16进制的地址)
          2、把需要存储的内容存储到内存空间中

          • 对象是把键值对依次存储到空间中
          • 函数是把函数体中的代码当做 ‘字符串’ 存储到内存中

          3、把空间的地址赋值给对应的变量,所以我们也说:引用数据类型是按照空间的引用地址操作

          1.function fn(){
          2. var total=null;
          3. total=1+1;
          4. return total;
          5.}
          6.fn();
          7.fn();
          8....

          函数执行的时候也有属于自己的一系列操作
          1、函数执行首先会形成一个新的私有作用域,目的是为函数体中的代码提供一个执行的环境(而且这个环境是私有的)
          2、把创建时候存储的代码字符串copy一份到自己的私有作用域中,然后把字符串转换为JS表达式,再然后依次自上而下执行

          [总结]
          每一次执行函数都是形成一个新的私有作用域,然后把代码重新自上而下执行(一切都从新开始),所以多次执行函数之间是没有直接联系的,互不干扰;

          函数执行形成的私有作用域,保护了里面的私有变量不受外界的干扰,我们把函数执行的这种保护机制叫做 闭包(如果当前变量是私有的,那么在函数体中出现的所有这个变量都和外界没有任何关系)

          全局作用域

          浏览器渲染页面(渲染页面中的JS)的时候,会提供一个供代码执行的环境 =>全局作用域(window/global)

          Alt text

          堆内存 & 栈内存

          JS中的内存一共两种:堆内存和栈内存

          堆内存

          作用:用来存储引用数据类型值的内存空间叫做堆内存(对象存储的是键值对,函数存储的是代码字符串)

          形成:只要遇到对象/数组/正则/函数等引用类型的值,浏览器首先第一步就是创建一个堆内存…

          释放:如果当前的堆内存被变量(或者函数以及元素事件等)占用了(占用了:堆内存地址赋值给变量了),此时的堆内存是有用的,不能销毁;我们想要手动释放堆内存,只需要让存储地址的变量等于其它值即可(最好等于null,null是空对象指针,本意就是不指向任何的堆内存);

          1.var o={name:'珠峰培训'};//<=> o=AAAFFF000
          2.o=null;//=> 此时堆内存不被占用,浏览器会在空闲的时间,把所有不被占用的堆内存进行自动回收释放(谷歌浏览器的机制,IE浏览器是靠计数器来统计当前堆内存被占用的次数:当计数器统计为零次,说明没有人占用它,浏览器销毁这个堆内存)

          栈内存

          作用:又称为作用域,目的就是提供JS代码执行的环境(供代码执行的),基本数据类型值都是直接的存储在栈内存中

          全局作用域(栈内存):
          形成:浏览器渲染页面,首先就会形成一个全局作用域
          销毁:关闭当前页面(F5刷新页面:先把页面关闭,然后再重新打开)

          私有作用域(栈内存):
          形成:函数执行会形成私有的作用域
          销毁:一般情况下,函数体中的代码执行完成,形成的栈内存会立即释放;当然也有不释放的情况,后面再详细来讲。

          变量提升(预解释)

          当前作用域中,JS代码自上而下执行之前,浏览器首先会把所有带var/function关键字的,进行提前的声明(declare)/定义(defined),这种提前声明变量的机制,我们称之为变量提升

          1、变量提升只对当前作用域下的变量或者函数起作用
          2、带var的在变量提升阶段只是提前的声明(告诉当前作用域有这个变量了而已)
          3、带function关键字的,在变量提升阶段不仅仅是声明,而且还定义了(定义:其实就是赋值的操作)

          1.console.log(total);//=>不会报错:变量提升阶段已经告诉全局有一个total了,只声明没有定义,默认值undefined
          2.//fn();//=>可以执行:变量提升阶段就已经完成了fn的声明和赋值操作,此时的fn已经是一个函数了
          3.
          4.function fn(num1,num2){
          5. var total=null;
          6. total=num1+num2;
          7. return total;
          8.}//->代码执行遇到此处直接跳过:变量提升阶段已经完成过一次了
          9.var total=fn(10,20);//->给total赋值:变量提升阶段只是完成了它的声明,没有赋值,所以代码执行到这一步需要赋值
          10.console.log(total.toFixed(2));

          Alt text

          函数执行会形成一个新的私有作用域,和全局作用域一样,进来的第一步不是代码执行,私有作用域和全局也有一些区别:私有作用域进来的第一步也不是变量提升

          第一步:如果有形参,第一步是形参赋值
          第二步:变量提升
          第三步:代码自上而下执行

          [私有变量]
          形参在私有作用域中声明的变量(函数)都是私有的,和外界的其它变量互不干扰

          作用域链

          函数执行形成私有作用域,在私有作用域中出现的变量可能是私有的,有可能不是自己私有的,私有和非私有我们的操作步骤是不一样的

          [ 私有的 ]
          如果是私有的,和外面的就没有任何的关系了,以后在函数体中操作的当前变量都按照私有的处理

          [ 非私有的 ]
          不是自己私有的,首先向其上级作用域查找,如果也不是上级作用域私有的,则继续向上查找,一直到window全局作用域为止,我们把这种查找机制称为作用域链

          1.var a=10,b=20,x=30,y=40;
          2.function fn(x){
          3. console.log(x,y,a,b);
          4. var a=b=x=y=100;
          5. a=x+y;
          6. b=x-y;
          7. console.log(x,y,a,b);
          8.}
          9.fn(x);
          10.console.log(x,y,a,b);
          11.
          12./*
          13. var a=10,b=20;
          14. <=>
          15. var a=10;
          16. var b=20;
          17.
          18. var a=b=20;
          19. <=>
          20. var a=20;
          21. b=20;
          22.*/

          Alt text

          查找上级作用域

          当前函数的上级作用域和它在哪执行的没有关系,只和它在哪定义的有关系:在哪个作用域下定义的,那么它的上级作用域就是谁

          1.var n=10;
          2.function fn(){
          3. console.log(n);
          4.}
          5.fn();//=>10
          6.
          7.~function(){
          8. var n=100;
          9. fn();//=>10 和在哪执行没关系,FN是在全局下定义的,它的上级作用域是全局作用域,当前自执行函数形成的私有作用域仅是它的‘宿主环境’
          10.}();

          不释放的栈内存

          一般情况下,函数执行完成,形成的私有作用域都会自动释放;

          但是有很多时候,函数执行完,形成的作用域(栈内存)无法释放:当前私有栈内存中的某些东西被栈内存以外的其它内容占用了

          当我们手动把栈内存以外占用其内容的东西清除掉(或者不让其在占用了),之前没有销毁的栈内存会在浏览器空闲的时候自动销毁释放

          1.var obj={
          2. name:'珠峰培训',
          3. fn:(function(){
          4. var i=10;
          5. return function(){
          6. i++;
          7. console.log(i);
          8. }
          9. })()//=>如果传obj.name会报错:因为此时键值对还没有完全存储到堆内存中呢,obj和堆内存还没有关系呢,obj=undefined (错误:Cannot ready property 'name' of undefined)
          10.};
          11.obj.fn();
          12.obj.fn();
          13....
          14.
          15.
          16.//var obj={name:'xxx'};
          17.//obj.fn=(function(){})(obj.name); 这样是可以的,因为此时的obj已经和堆内存关联在一起了

          Alt text

          i++和++i

          i++:先去拿原有的值和别人运算,运算完成后自身累加1
          ++i:先自身累加1,累加后,拿最新的结果和别人运算

          1.var i=4;
          2.console.log(10+(i++)); //=>14
          3.console.log(i); //=>5
          4.
          5.i=4;
          6.console.log(10+(++i)); //=>15
          7.console.log(i); //=>5

          案例练习

          1.var i = 0;
          2.function fn() {
          3. i += 2;
          4. return function (n) {
          5. console.log(n + (++i));
          6. }
          7.}
          8.var f = fn(3);
          9.f(4);
          10.fn(5)(6);
          11.f(7);
          12.fn(8)(9);

          Alt text

          this

          在js中this代表当前函数执行的主体(谁执行的这个函数)

          this是谁和函数在哪定义以及在哪执行的,都没有任何关系

          1.function fn(){
          2. console.log(this);
          3.}
          4.var obj={name:'珠峰培训',fn:fn};
          5.obj.fn();//->this:obj
          6.fn();//->this:window <=> window.fn();

          1、自执行函数中的this一般都是window

          1.var obj={
          2. name:'珠峰培训',
          3. fn:(function(){
          4. console.log(this);//->window(全局对象也可以叫做浏览器对象)
          5. return function(){}
          6. })()
          7.};

          2、给元素的某个事件绑定方法,事件触发方法被执行,此时方法中的this一般都是当前操作元素本身

          1.oBox.onclick=function(){
          2. //->当触发点击事件,执行这个匿名函数的时候,方法中的 this:oBox
          3.}

          3、方法执行看方法名前面是否有点,有点,点前面是谁,方法中的this就是谁,没有点,方法中的this就是window

          1.function fn(){
          2. console.log(this);
          3.}
          4.var obj={name:'珠峰培训',fn:fn};
          5.obj.fn();//->this:obj
          6.fn();//->this:window

          4、以上三条都是限定在非严格模式下,在JS严格模式下,上述部分需要换一下

          不指定执行主体,this是undefined而不是之前非严格模式下默认的window

          1.~function(){
          2. //this->window
          3.}();
          4.fn(); //->fn中的this:window
          1."use strict";//=>放在JS代码第一行(一定要是当前作用域第一行),让整个JS开启严格模式
          2.~function(){
          3. //this->undefined
          4.}();
          5.fn(); //->fn中的this:undefined
          6.window.fn(); //->fn中的this:window

          综合练习

          1.var num = 10;
          2.var obj = {num: 15};
          3.obj.fn = (function (num) {
          4. this.num += 10;
          5. num *= 2;
          6. return function (n) {
          7. this.num += n;
          8. console.log(n + (--num));
          9. }
          10.})(obj.num);
          11.var fn = obj.fn;
          12.fn(10);
          13.obj.fn(15);
          14.console.log(window.num, obj.num);

          Alt text

          闭包汇总

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

          但是现在市面上,99%的IT开发者都认为:函数执行,形成一个不销毁的私有作用域,除了保护私有变量以外,还可以存储一些内容,这样的模式才是闭包

          1.var utils=(function(){
          2.
          3. return {
          4.
          5. }
          6.})();

          闭包作用:
          1、保护
          团队协作开发,每个开发者把自己的代码存放在一个私有的作用域中,防止相互之间的冲突;把需要供别人使用的方法,通过return或者window.xxx暴露在全局下即可;

          jQuery源码中也是利用保护机制实现的

          1.~function(){
          2. var jQuery=function(){
          3. ...
          4. }
          5. ...
          6. window.$=window.jQuery=jQuery;
          7.}();

          2、保存
          选项卡闭包解决办法

          单例模式(JS高阶编程技巧:惰性思想/柯理化函数思想…)

          上一篇: 面向对象深入解读

          下一篇: 正则详解一

          73099威尼斯城73099.com |威尼斯73099.com官方网站登录 |威尼斯手机娱乐官网 | |手机版 | | 澳门威尼斯3544.com,威尼斯3544.com官网登陆,威尼斯3544.com备用网址-0327.com|威尼斯澳门官方6799.com,www.6799.com,威尼斯官方娱乐6799.com登陆-vns566.net|6799.com威尼斯手机版,6799.com威尼斯网址,6799.com威尼斯官网登陆-59859.com|86087威尼斯城86087.com,威尼斯86087.com官方网站登录,威尼斯手机娱乐官网-83855.com|澳门威利斯人手机版,www.86087.com官方入口,澳门威利斯人在线娱乐-944185.com|威尼斯澳门官方4886.com,www.4886.com,威尼斯官方娱乐4886.com登陆-k92988.com|