程序de生命周期

2018-11-25  本文已影响9人  Zszen

个人开发总结,不喜勿喷。

简单聊一下程序中对象的生命周期。生命周期是一个对象/变量存在的有效时间端或区域中。任何对象包括静态对象都被约束在一定范围和周期内,只是静态变量本身存在于整个程序的运行周期中,不特别处理是不会被回收的。如果你想访问这个对象/变量,你就需要在它的周期内去访问,而不要在其外部或等它死掉了再去访问。

1. 块内

//函数方法内
function t(){
  let a = 1;
}
//循环条件内
for(let k in dic){
  let a = 1;
}
//空块中
{
  let a = 1;
}

当一个变量在函数、条件循环甚至空块中,它的生命周期也只在这一范围存在。

你会发现这套逻辑其实是面向过程开发的。 :P

2. 块嵌套

{
  let b = 2;
  {
    let a = 1;
  }
  let c = 3;
}

在上面这样的嵌套中,变量遵循创建之后和块结束之前的访问。在b点不可以访问a和c的数值,在a点不可以访问c的数值。在c点不可以访问a的数值。在子括号结束后a会先于b被回收,整个代码块结束时,b和c都会被回收。

发现了么,你在计算机课程中学到的入盏,出盏在这里体现了。最先被表达的块在追后才被回收。遵循了先入后出的原则。 :P

3. 对象和对象中的子变量

class A{
  public type:number = 123;
  public static NUM = 1;
}

let a = new A();
console.log(a.type);
console.log(A.NUM);

类的构建要先于其对象的创建,类只是描述对象如何构造的一个工厂。类中静态对象可在构造后随时调用,且不需要创建对象。

神说要有类,于是就有了面向对象。

4. 数组和字典

let pool = [obj1, obj2];
let dic = {a:1,pot:{x:0,y:9}};

当被包含在数组/词典中,这个对象会有活过块限制的特权。比如

let pool = [];
{
  let a = new A;
  pool.push(a);
}

在块中的对象a虽然被回收了(你在块外是无法调取到的)。但这个对象本身被pool带出了块区域,只要在pool生命周期中,它就能保护a不会死去。

在解释型语言中,指针功能被弱化的消失不见了,但它并不是不存在。你创建的每个对象,其实都不是对象内存本身,而是一个指向此对象的一个引用/指针/连结。在上面这个代码中,系统在你看不到的后台创建了new A的内存对象地址p,然后将p连结给了a,a又把p的地址告诉了pool,a的使命完结挂掉了,但pool还是知道p的信息,知道它在哪里,所以它是安全的,直到pool死去,整个世界再也没人知道p的存在,然后就等待着消亡。

5. 奇怪的时空跳跃 - 匿名函数与lamda表达式

function getData(params){
    let request = new NetWork("http://urlxxxxx");//瞎写的
    request.send("post",params).then(()=>{
        console.log(params);
    }).cache(()=>{
        console.log(params);
    })
}

你会发现奇怪的现象,params竟然活到了send接口沟通结束以后,太神奇了,为什么它能持续这么久,明明这个getData函数早已执行完毕,但它的生命周期被延续了。

那是因为lamda表达式对对象的引用做了连结,其实原本params已经死去了,只是在lamda式子中它被复制了一份连结,而且每个lamda的复制是单独的,他们虽然指向相同的参数,但是从原理上最后两个输出是不一样的(结果是一样的)。

6. 函数为什么要指明对象

class A{
  function getP():number{ return 0;}
}

function laterCall(obj, func){
  func.call(obj); // or apply(obj);
}

一般来讲函数是调用时直接执行的,有时候我们需要操作一些其他函数处理结果完毕后再调用此函数,有时候需要等待一定时间来响应他,有时候需要网络的反馈来决定是否要激活这个函数。那为什么要写函数和对象呢?

首相非直接调用函数并非必须使用对象,全局函数就不需要。其次函数本身的编号是对应对象的,而不是全局,这也是为了索引效率(其实全局变量也是这么设计的,把全局看成一个对象)。 :p

7. 打印函数所出现的有趣奇特现象 (h5开发)

let a = {p:{c:1}}
console.log(a);
a.p = null;

当你想打印a的整个对象的时候,你会发现,控制台的输出变成了p为null的结果。这是因为输出的时候记录的是对a这个对象的连结,它是动态的,尤其内部是有对象变化的情况。

有时候你会发现这样让你输出很混乱,这样的时候你就做个遍历对象输出就好了,注意避免代码影响发布。

8. 斩断生命

let a = {p:{c:1}}
delete a.p;

创建一个a对象,它能保护p对象不会死掉,那么怎么能斩断这种保护.

delete可以去掉对象之间的连结,但无法去掉类对象和内部变量的连结。(少用delete,他并不能多帮你回收多少内存,但可能会给你埋很多雷)

8. 死亡和回收

当各种对象,变量等不再被任何程序运行时引用,他就处于休眠状态。这时的对象并没有完全死掉。也就是说如果这个对象和外部没有沟通,但是内部还在运转,那么也有可能影响到程序的运行,比如用观察者模式的时候,删除对象但是忘了移除notification事件,由于观察者灵活度高耦合度低的特点,这个对象可能会产生诈尸或者干扰游戏正常运行等各种问题。
等待一定时间系统就会自动回收这些垃圾,但是虽然说是回收,但并不等同于内存的完美释放。

当对象被移除,有时候他连结了其他活跃的对象,虽然死了,但是还挣扎着不想被消灭。这样的对象积累多了,你的内存就慢慢的漫出来了,然后系统/软件就会崩溃掉。

9. 尽量做到重复利用

提倡以下几点:

  1. 值不变就用常量const
  2. 尽量用单例来代替静态类函数。
  3. 能不创建就不创建,能少创建就少创建对象。
  4. 重复利用已有的对象来反复发挥效率。
  5. 在循环外创建局部变量

题外话,关于效率再加两句:能用乘法就少用除法,能用加减乘除就少用三角函数开方操作等,应该再开一个坑单独写一下,以后再说吧。

创建是要耗费内存,cpu和时间的,节省下来的话,你也可以增快你的程序运行速度。

请大家多多提意见

上一篇下一篇

猜你喜欢

热点阅读