本文深入解析JavaScript闭包原理,通过匿名函数与命名函数的对比示例,阐明作用域链与变量副本机制。进一步探讨如何利用闭包在构造函数中封装私有变量,实现特权方法访问,并分析该模式在内存消耗与继承方面的局限性。最后介绍通过立即执行函数封装静态私有成员的改进方案,以优化性能并限制实例数量。
JavaScript闭包原理与私有成员封装实战
本文通过具体实例解析JavaScript中闭包的核心原理及其实际应用场景,旨在帮助读者深入理解并掌握这一重要特性。
闭包基础示例与机制解析
首先,通过一个基础示例来直观展示闭包的工作机制。
function foo() {
var a = 10;
function bar() {
a *= 2;
return a;
}
return bar;
}
var baz = foo(); // baz is now a reference to function bar.
console.log(baz()); // returns 20.
console.log(baz()); // returns 40.
console.log(baz()); // returns 80.
var blat = foo(); // blat is another reference to bar.
console.log(blat()); // returns 20, because a new copy of a is being used.
可使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun对示例代码进行测试,观察到的运行结果如下:

过去常认为只有匿名函数才能构成闭包,但实际上,只要普通函数被包含在另一个函数内部并被返回,即可形成闭包,无需局限于匿名函数。
当foo函数执行返回后,其内部作用域并未被销毁,而是被保留下来。此时,只有foo返回的那个内部函数(如bar)能够访问该作用域中的变量。在前面的示例中,baz和blat各自拥有独立的作用域及变量a的一个副本,且只有它们自身能修改对应的a值。
这意味着,对foo函数引用的多次调用(如baz和blat)互不干扰,每个引用都维护着独立的变量状态。
利用闭包封装与隐藏信息
二、利用闭包封装与隐藏信息
基于上述原理,我们可以利用匿名函数来封装私有变量,从而实现信息的隐藏。
var Book = function(newIsbn, newTitle, newAuthor) { // implements Publication
// Private attributes.
var isbn, title, author;
// Private method.
function checkIsbn(isbn) {
//...
return true;
}
// Privileged methods.
this.getIsbn = function() {
return isbn;
};
this.setIsbn = function(newIsbn) {
if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.');
isbn = newIsbn;
};
this.getTitle = function() {
return title;
};
this.setTitle = function(newTitle) {
title = newTitle || 'No title specified';
};
this.getAuthor = function() {
return author;
};
this.setAuthor = function(newAuthor) {
author = newAuthor || 'No author specified';
};
// Constructor code.
this.setIsbn(newIsbn);
this.setTitle(newTitle);
this.setAuthor(newAuthor);
};
// Public, non-privileged methods.
Book.prototype = {
display: function() {
//...
}
};
var mybook=new Book("myisbtn","mytittle","myauthor");
console.log(mybook.getAuthor());
使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试相关代码,运行结果如下:

在构造函数中,通过var声明变量(如isbn、title、author)及checkIsbn函数,使其成为私有属性。若需访问这些私有变量或函数,必须在构造函数内部定义相应的访问方法,这些方法被称为特权方法。而对于无需访问私有属性的方法(如display),则应在Book.prototype中声明。需要注意的是,这种模式会为每个新对象实例生成私有方法和特权方法的新副本,导致较高的内存消耗,因此仅适用于真正需要私有成员的场合。此外,由于子类无法访问父类的私有属性和方法,这种基于闭包的私有成员实现方式会导致“继承破坏封装”的问题。
静态私有成员与构造器优化方案
三、方案改进
此方案与前一种类似,但存在关键区别。私有成员和特权成员仍声明在构造函数中,但构造函数本身被封装在一个立即执行函数(IIFE)内,并将该构造函数作为返回值赋给变量Book。这创建了一个闭包,允许在此闭包内声明静态的私有成员函数。
checkIsbn函数被设置为静态方法,因为无需为每个实例生成该方法的副本。此外,通过静态属性numBooks(或numOfBooks)可以限制构造器的总调用次数。
var Book = (function() {
// Private static attributes.
var numOfBooks = 0;
// Private static method.
function checkIsbn(isbn) {
// ...
return true;
}
// Return the constructor.
return function(newIsbn, newTitle, newAuthor) { // implements Publication
// Private attributes.
var isbn, title, author;
// Privileged methods.
this.getIsbn = function() {
return isbn;
};
this.setIsbn = function(newIsbn) {
if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.');
isbn = newIsbn;
};
this.getTitle = function() {
return title;
};
this.setTitle = function(newTitle) {
title = newTitle || 'No title specified';
};
this.getAuthor = function() {
return author;
};
this.setAuthor = function(newAuthor) {
author = newAuthor || 'No author specified';
};
// Constructor code.
numOfBooks++; // Keep track of how many Books have been instantiated
// with the private static attribute.
if(numOfBooks > 1) throw new Error('Book: Only 1 instances of Book can be '
+ 'created.');
this.setIsbn(newIsbn);
this.setTitle(newTitle);
this.setAuthor(newAuthor);
}
})();
// Public static method.
Book.convertToTitleCase = function(inputString) {
//...
console.log("convertToTitleCase");
};
// Public, non-privileged methods.
Book.prototype = {
display: function() {
//...
console.log("display");
}
};
var mybook=new Book("myisbtn","mytittle","myauthor");
console.log(mybook.getAuthor()); //myauthor
mybook.display(); //display
//mybook.convertToTitleCase(); //mybook.convertToTitleCase is not a function
var mybook2= new Book("my2","tittle2","myauthor2");
console.log(mybook2.getAuthor()); //Only 1 instances of Book can be created.
使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试改进后的代码,运行结果如下:

更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》
希望本文所述的闭包原理与应用示例能对JavaScript程序设计有所帮助。