前置知识
绝大多数的程序语言,他们的内存生命周期基本一致:
对于所有的编程语言,第二部分都是明确的。而第一和第三部分在底层语言中是明确的。
但在像JavaScript这些高级语言中,大部分都是隐含的,因为JavaScript具有自动垃圾回收机制(Garbage collected)。
因此在做JavaScript开发时,不需要关心内存的使用问题,所需内存分配和无用内存回收,都完全实现自动管理。
1.概述
像C语言这样的高级语言一般都有底层的内存管理接口,比如 malloc()和free()。另一方面,JavaScript创建变量(对象,字符串等)时分配内存,并且在不再使用它们时“自动”释放。 后一个过程称为垃圾回收。这个“自动”是混乱的根源,并让JavaScript(和其他高级语言)开发者感觉他们可以不关心内存管理。 这是错误的。 ——《MDN JavaScript 内存管理》
MDN中的介绍告诉我们,作为JavaScript开发者,还是需要去了解内存管理,虽然JavaScript已经给我们做好自动管理。
2.JavaScript内存生命周期
2.1 分配内存
在做JavaScript开发时,我们定义变量的时候,JavaScript便为我们完成了内存分配:
var num=100; // 为数值变量分配内存 var str='pingan'; // 为字符串变量分配内存 var obj={ name : 'pingan' }; // 为对象变量及其包含的值分配内存 var arr=[1, null, 'hi']; // 为数组变量及其包含的值分配内存 function fun(num){ return num + 2; }; // 为函数(可调用的对象)分配内存 // 函数表达式也能分配一个对象 someElement.addEventListener('click', function(){ someElement.style.backgroundColor='blue'; }, false);
另外,通过调用函数,也会分配内存:
// 类型1. 分配对象内存 var date=new Date(); // 分配一个Date对象 var elem=document.createElement('div'); // 分配一个DOM元素 // 类型2. 分配新变量或者新对象 var str1="pingan"; var str2=str1.substr(0, 3); // str2 是一个新的字符串 var arr1=["hi", "pingan"]; var arr2=["hi", "leo"]; var arr3=arr1.concat(arr2); // arr3 是一个新的数组(arr1和arr2连接的结果)
2.2 使用内存
使用内存的过程实际上是对分配的内存进行读取与写入的操作。
通常表现就是使用定义的值。
读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。
var num=1; num ++; // 使用已经定义的变量,做递增操作
2.3 释放内存
当我们前面定义好的变量或函数(分配的内存)已经不需要使用的时候,便需要释放掉这些内存。这也是内存管理中最难的任务,因为我们不知道什么时候这些内存不使用。
很好的是,在高级语言解释器中,已经嵌入“垃圾回收器”,用来跟踪内存的分配和使用,以便在内存不使用时自动释放(这并不是百分百跟踪到,只是个近似过程)。
3.垃圾回收机制
就像前面提到的,“垃圾回收器”只能解决一般情况,接下来我们需要了解主要的垃圾回收算法和它们局限性。
3.1 引用
垃圾回收算法主要依赖于引用的概念。
即在内存管理环境中,一个对象如果有权限访问另一个对象,不论显式还是隐式,称为一个对象引用另一个对象。
例如:一个JS对象具有对它原型的引用(隐式引用)和对它属性的引用(显式引用)。 注意:
这里的对象,不仅包含JS对象,也包含函数作用域(或全局词法作用域)。
3.2 引用计数垃圾收集
这个算法,把“对象是否不再需要”定义为:当一个对象没有被其他对象所引用的时候,回收该对象。这是最初级的垃圾收集算法。
var obj={ leo : { age : 18 }; };
这里创建2个对象,一个作为leo的属性被引用,另一个被分配给变量obj。
// 省略上面的代码 /* 我们将前面的 { leo : { age : 18 }; }; 称为“这个对象” */ var obj2=obj; // obj2变量是第二个对“这个对象”的引用 obj='pingan'; // 将“这个对象”的原始是引用obj换成obj2 var leo2=obj2.leo; // 引用“这个对象”的leo属性
可以看出,现在的“这个对象”已经有2个引用,一个是obj2,另一个是leo2。
obj2='hi'; // 将obj2变成零引用,因此,obj2可以被垃圾回收 // 但是它的属性leo还在被leo2对象引用,所以还不能回收 leo2=null; // 将leo变成零引用,这样obj2和leo2都可以被垃圾回收
这个算法有个限制:
无法处理循环引用。即两个对象创建时相互引用形成一个循环。
function fun(){ var obj1={}, obj2={}; obj1.leo=obj2; // obj1引用obj2 obj2.leo=obj1; // obj2引用obj1 return 'hi pingan'; } fun();
可以看出,它们被调用之后,会离开函数作用域,已经没有用了可以被回收,然而引用计数算法考虑到它们之间相互至少引用一次,所以它们不会被回收。
实际案例:
在IE6,7中,使用引用计数方式对DOM对象进行垃圾回收,常常造成对象被循环引用导致内存泄露:
var obj; window.onload=function(){ obj=document.getElementById('myId'); obj.leo=obj; obj.data=new Array(100000).join(''); };
可以看出,DOM元素obj中的leo属性引用了自己obj,造成循环引用,若该属性(leo)没有移除或设置为null,垃圾回收器总是且至少有一个引用,并一直占用内存,即使从DOM树删除,如果这个DOM元素含大量数据(如data属性)则会导致占用内存永远无法释放,出现内存泄露。
3.3 标记清除算法
这个算法,将“对象是否不再需要”定义为:对象是否可以获得。
标记清除算法,是假定设置一个根对象(root),在JS中是全局对象。垃圾回收器定时找所有从根开始引用的对象,然后再找这些对象引用的对象...直到找到所有可以获得的对象和搜集所有不能获得的对象。
它比引用计数垃圾收集更好,因为“有零引用的对象”总是不可获得的,但是相反却不一定,参考“循环引用”。
循环引用不再是问题:
function fun(){ var obj1={}, obj2={}; obj1.leo=obj2; // obj1引用obj2 obj2.leo=obj1; // obj2引用obj1 return 'hi pingan'; } fun();
还是这个代码,可以看出,使用标记清除算法来看,函数调用之后,两个对象无法从全局对象获取,因此将被回收。相同的,下面案例,一旦 obj 和其事件处理无法从根获取到,他们将会被垃圾回收器回收。
var obj; window.onload=function(){ obj=document.getElementById('myId'); obj.leo=obj; obj.data=new Array(100000).join(''); };
注意: 那些无法从根对象查询到的对象都将被清除。
3.4 个人小结
在日常开发中,应该注意及时切断需要回收对象与根的联系,虽然标记清除算法已经足够强壮,就像下面代码:
var obj,ele=document.getElementById('myId'); obj.div=document.createElement('div'); ele.appendChild(obj.div); // 删除DOM元素 ele.removeChild(obj.div);
如果我们只是做小型项目开发,JS用的比较少的话,内存管理可以不用太在意,但是如果是大项目(SPA,服务器或桌面应用),那就需要考虑好内存管理问题了。
#
4.内存泄露(Memory Leak)
#
4.1 内存泄露概念
在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。 ——维基百科
其实简单理解:一些不再使用的内存无法被释放。
当内存占用越来越多,不仅影响系统性能,严重的还会导致进程奔溃。
4.2 内存泄露案例
未定义的变量,会被定义到全局,当页面关闭才会销毁,这样就造成内存泄露。如下:
function fun(){ name='pingan'; };
var data={}; setInterval(function(){ var render=document.getElementById('myId'); if(render){ render.innderHTML=JSON.stringify(data); } }, 1000);
var str=null; var fun=function(){ var str2=str; var unused=function(){ if(str2) console.log('is unused'); }; str={ my_str=new Array(100000).join('--'); my_fun=function(){ console.log('is my_fun'); }; }; }; setInterval(fun, 1000);
定时器中每次调用fun,str都会获得一个包含巨大的数组和一个对于新闭包my_fun的对象,并且unused是一个引用了str2的闭包。
整个案例中,闭包之间共享作用域,尽管unused可能一直没有调用,但my_fun可能被调用,就会导致内存无法回收,内存增长导致泄露。
var ele={ img : document.getElementById('my_img') }; function fun(){ ele.img.src="http://www.baidu.com/1.png"; }; function foo(){ document.body.removeChild(document.getElementById('my_img')); };
即使foo方法将my_img元素移除,但fun仍有引用,无法回收。
4.3 内存泄露识别方法
通过Chrome浏览器查看内存占用:
步骤如下:
如果内存占用基本平稳,接近水平,就说明不存在内存泄漏。
反之,就是内存泄漏了。
命令行可以使用 Node 提供的process.memoryUsage方法。
console.log(process.memoryUsage()); // { rss: 27709440, // heapTotal: 5685248, // heapUsed: 3449392, // external: 8772 }
process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。该对象包含四个字段,单位是字节,含义如下。
判断内存泄漏,以heapUsed字段为准。
公众号:前端自习课
eb服务是.net中让人激动的部分,几乎所有你能叫出名字的服务都有一些执行服务器端代码的机制:正巧每种语言都一个类库,因此在HTTP中生成一个GET请求变得很简单,解析出XML也有了些捷径。
这种方案给你提供了一种跨平台,跨语言,跨厂商乃至一切的方法,只要它们都在INTERNET上或是以其他的方式相连,我们就可以在某个程序的代码中调用另外一个完全不同的机器上的代码。
这就是隐藏在WEB服务背后的基本观念。使用类似于WEB服务描述语言(说 wizdle会更酷一些)开发有一定的标准,它们涵盖了这些技术细节。
如果你用Visual Studio.NET创建一个WEB服务,它将满足这些标准。如果你只是需要WEB服务,而不管它是如何创建的,通过Visual Studio.NET,你会发现借用他人的代码是如此简单。
编写一个WEB服务
为了编写一个WEB服务,你至少要用一种方法写一个类。这个类必须有WebService属性,方法也要有WebMethod属性。WEB方法能够接受和返回任何可用的类型,包括你定义的对象实例。它们能做任何事情:维护数据库数据的内外一致性,做任何形式的运算,甚至调用另外一个WEB方法来完成任务。
在Visual Studio.NET中创建一个新工程。在Visual C++工程模板中,选择可管理的WEB服务。修改后是:
<@ WebService Class=Calculator.CalculatorService %>
我获得了一个叫做HelloWorld()的方法,把它改成Add()很简单——我仅仅更改了.cpp文件和.h文件的名称,改变了签名以便它能够接受浮点数,然后加了些代码以返回和。
类声明的结束部分:
using <System.Web.Services.dll>
using namespace System;
using namespace System::Web;
using namespace System::Web::Services;
namespace Calculator
{
public __gc
class CalculatorService : public WebService
{
public:
[System::Web::Services::WebMethod]
double Add(double x, double y);
};
}
实现的部分:
#include "stdafx.h"
#include "Calculator.h"
#include "Global.asax.h"
namespace Calculator
{
double CalculatorService::Add(double x, double y)
{
return x + y;
}
}
#include <iostream>using namespace std;
int main (){
cout << "Content-type:text/html\r\n\r\n";
cout << "<html>\n";
cout << "<head>\n";
cout << "<title>Hello World - 第一个 CGI 程序</title>\n";
cout << "</head>\n";
cout << "<body>\n";
cout << "<h2>Hello World! 这是我的第一个 CGI 程序</h2>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;}
使用WEB服务
写一个WEB服务相当简单:你只需要一个类属性,一个方法属性和calculator.asmx文件,而这三个都由Visual Studio生成。
最简单的方法是键入URL到Calculator.asmx然后按回车。你会看到和以前运行WEB服务工程时同样的文件。点击添加参数结束这个过程。
参数一旦添加,调用WEB服务就像调用任何C++类一样。添加参数建立一个头文件,这个文件在任何你想使用WEB服务时都可以包括进去。
近一直在开源社区浏览一些开源的后台管理框架,从中找到一个自己中意的项目很难,不是后期开发太繁琐,要不就是界面不友好,想了想还是自己搭建一个后台管理模板,以后在开发过程中直接拿过来开发就可以了。
此项目是基于一个HEYUI搭建的一款后台管理模板。
HeyUI 是一套基于 Vue2.0 的开源 UI 组件库,主要服务于一些中后台产品。
HeyUI提供的是一整套解决方案,所有的组件提供全局的可配置模式。真正的数据驱动、全局的配置模式、数据字典化
后台布局通常采用的上左右布局方式
第一部分:登录页面
登录页面主要是靠设计,设计做的好,前端怎么实现出来都好看,这里的登录界面也没有经过专业的设计,反正还能看的过眼吧(请忽略开发的审美)。
功能点:选择记住登录账号时,在登录成功后会将账号保存到本地存储,下次进入页面直接从本地存储获取登录账号,同时在点击登录时会将两个输入框属性设置为只读,按钮出现加载效果。
第二部分:管理后台头部
相对于整个系统来说,这部分是最简单的一部分,其中大多数要写的只是css样式,唯一一点就是登录用户这块,用到了vuex状态管理,如果你在某一处修改了用户信息,只需要执行
this.$store.dispatch('userStore/setUserInfo', {name: '超级管理员'})
就可以修改页面上显示的登录用户的用户名。
第三部分:左边菜单管理
使用Menu组件即可,通过key匹配当前选中的菜单,执行
this.$refs.menu.select(this.$route.name)
选中当前菜单。
第四部分:内容区域
采用子路由加载方式 <router-view />
第五部分:模板主题配置
这里使用的是HEYUI的自定义主题,需要自己定义一个less文件做引用,在less文件中定义主题变量,覆盖框架默认的主题,本模板就是采用自定义less变量更换主题的。
主题文件位置:src\style\themes.less
原始全局变量文件你可以在github上查看。
https://github.com/heyui/heyui/blob/master/themes/var.less
如果你还需要一些更细节化的调整,官方建议可以新建overwrite.less对已有的class进行覆盖修改。
项目地址:https://gitee.com/yangon/vue-admin-frame
使用
git clone https://gitee.com/yangon/vue-admin-frame
cd vue-admin-frame
npm install
npm run dev
本项目引用到的框架KEYUI:https://www.heyui.top
如果本文对您有所帮助, 请关注科技男给与支持,后期为您带来更多的技术文章。
*请认真填写需求信息,我们会在24小时内与您取得联系。