7.内存泄漏
什么是内存泄露?
由于ie9之前的版本的垃圾回收机制不同,因此如果在ie9之前的版本应用闭包可能导致一些问题,具体来说如果该闭包包含对一个HTML元素的引用,那么就意味着该元素永远都不会被销毁,例如下面的例子:
function assignHander()
{
var element = document.getElementById("sonmeElement");
element.onclick = function()
{
alert(element.id)
}
}
但是我们可以用下面的改动来避免占用内存,例如:
function assignHander()
{
var element = document.getElementById("sonmeElement");
var id = element.id;
element.onclick = function()
{
alert(id)
};
element = null;
}
8.模仿块级作用域
我么知道在JavaScript里面是没有块级作用域的概念的,也就是说一个变量一旦在语句中声明,那么他将属于整个函数作用域,而不像Java或c++等,在语句里声明,那么变量就属于该语句,执行完该语句,变量就会被销毁,例如下面的例子:
function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
alert(i); //count
}
outputNumbers(5);
i 是在for语句中定义的,但是出了for循环后i值仍然存在,并可以在函数的任何地方使用,即使我们错误的重新定义了i,i 依然不会受影响,Javascript从来不关心你声明了几次变量,只会对后来声明的同名变量视而不见,不过他会执行后续声明的变量初始化。例如:
function outputNumbers(count){
for (var i=0; i < count; i++){
alert(i);
}
var i;
alert(i); //
}
为了让JavaScript也能有块级作用域的概念,我们可以利用匿名函数模仿块级作用域,例如:
function outputNumbers(count){
(function () {
for (var i=0; i < count; i++){
alert(i);
}
})();
alert(i); //会引发错误;
}
outputNumbers(5);
在这里我们把内部的循环放在了一个函数里,形成一个闭包函数,那么闭包之外的作用域将不能访问闭包内的变量。
关于立即执行的问题,我们声明一个函数后如果想让他立即执行,可以用圆括号将函数包起,然后在尾部再加上括号。给函数声明套上圆括号是为了将函数声明变成函数表达式,因为JavaScript中函数声明后面不能跟圆括号,但是函数表达式却可以,例如:
(function () {
for (var i=0; i < count; i++){
alert(i);
}
})();
它实际上相当于:
function A(){
Alert(“A函数”)
}
A()
这个过程。当然我们也可以这样写,即直接写函数表达式:
function outputNumbers(count){
var A=function () {
for (var i=0; i < count; i++){
alert(i);
}
}();
// alert(i); //causes an error
}
outputNumbers(5)//将依次弹出0到4;
这种方式可以保证外部环境或全局环境访问不到闭包内的变量,有利于在大型项目中避免向全局环境添加过多的变量而导致命名冲突等问题,而且这种方式还可以减少闭包占用的内存,因为没有指针指向闭包的引用,因此闭包执行完后即可被销毁。
9.私有变量
严格上说JavaScript中没有私有成员这种说法,所有对象属性都是公有的,但倒是有个私有变量的概念。任何在函数内部定义的变量都可以认为是私有变量。因为不能再函数外部访问这些变量,所以私有变量包括函数的参数,局部变量和函数内部的其他函数。
我们把有权访问私有变量和私有函数的公有方法称为特权方法,有两种在对象上创建特权方法的方式,第一种是在构造函数中定义特权方法,如下例:
function Person(name){
this.getName = function(){
return name;
};
this.setName = function (value) {
name = value;
};
}
var person = new Person("Nicholas");
alert(person.getName()); //"Nicholas"
person.setName("Greg");
alert(person.getName()); //"Greg"
所有的实例都可以通过getName和setName方法访问name属性,本质上来说特权方法就是闭包函数,用this声明的属性都是实例属性,所以name值在任何一个实例中都不相同。因为闭包可以访问外层函数的作用域中的变量,所以getName和setName都可以访问name属性。但是由于构造函数本身的缺点,就是没创建一个实例就会创建创建同样的一组方法,浪费内存,我们可以使用静态私有变量来解决这个问题。其基本模式入下:
(function(){
var name = "";
Person = function(value){
name = value;
};
Person.prototype.getName = function(){
return name;
};
Person.prototype.setName = function (value){
name = value;
};
})();
var person1 = new Person("Nicholas");
alert(person1.getName()); //"Nicholas"
person1.setName("Greg");
alert(person1.getName()); //"Greg"
var person2 = new Person("Michael");
alert(person1.getName()); //"Michael"
alert(person2.getName()); //"Michael"
简单说来person是个闭包函数,该闭包函数有权访问name属性,但是person同时又会产生一个函数原型,name还有getName和setName都是这个原型的原型属性和方法,因此在创建实例时,每个实例都指向这个原型,因此name属性值会被不断的更改,这个name就是静态私有变量,因为每个实例都没有自己的私有变量,因此到底是使用实例变量还是静态私有变量要是需求而定。
10.模块模式
模块模式是用于为自定义类型创建私有变量和特权方法的。而JavaScript里的模块模式则是为单例创建私有变量和特权方法。所谓单例就是只有一个实例的对象。按照惯例,JavaScript是以对象字面量的方式创建单例对象的。例如一下就是一个单例:
var singleton={
name: value,
method:function()
{
//这里是方法代码
}
};
模块模式通过为单例添加私有变量和特权方法使其得到增强,其语法如下:
var singleton=function()
{
//私有变量和私有函数
var privateVariable = 10;
function privateFunction()
{
return false;
}
//特权/公有方法和属性
return
{
publicPropertys: true,
publicMethods : function()
{
privateVariable++;
return privateFunction();
}
};
}();
这个模块模式使用了一个返回对象的匿名函数,在这个匿名函数内部首先定义了私有变量和函数,然后将一个对象字面量作为函数的值返回。返回的对象字面量中只包含可以公开的属性和方法,由于这个对象是在匿名函数内部定义的,因此它的公有方法有权访问私有变量和函数。从本质上讲,这个对象字面量定义的是单例的公共接口。这种模式在对单例进行某些初始化,同时又需要维护其私有变量时是非常有用的。例如:
function BaseComponent(){
}
function OtherComponent(){
}
var application = function(){
//private variables and functions
var components = new Array();
//initialization
components.push(new BaseComponent());
//public interface
return {
getComponentCount : function(){
return components.length;
},
registerComponent : function(component){
if (typeof component == "object"){
components.push(component);
}
}
};
}();
application.registerComponent(new OtherComponent());
alert(application.getComponentCount()); //2
上面的例子简单说来,getComponentCount()和registerComponent()是有权访问components数组的特权方法,简而言之,如果想创建一个对象并对其初始化,同时还需要公开一些能够访问这些私有数据的方法,那么就可以使用模块模式。以这种模式创建的每个单例都是object的实例。(所以没有必要检验其instanceof了)
11.增强的模块模式
function BaseComponent(){
}
function OtherComponent(){
}
var application = function(){
//private variables and functions
var components = new Array();
//initialization
components.push(new BaseComponent());
//create a local copy of application
var app = new BaseComponent();
//public interface
app.getComponentCount = function(){
return components.length;
};
app.registerComponent = function(component){
if (typeof component == "object"){
components.push(component);
}
};
//return it
return app;
}();
alert(application instanceof BaseComponent);
application.registerComponent(new OtherComponent());
alert(application.getComponentCount()); //2
在这里application得到了立即执行,那么app对象将赋值给application,那么就可以直接采用application.getComponentCount这种形式调用。因为app在application内部定义,因此app在外部无法访问到,不能用app去调用。
注:以上内容节选自疯狂的JavaScript,经过本人加工整理而成,如有侵权可联系删除,转载请注明出处。