本文深入解析 JavaScript 面向对象编程的核心概念,重点介绍通过闭包实现数据封装的两种常见模式,并对比分析类式继承与原型继承的原理及适用场景,帮助开发者掌握对象创建与继承的最佳实践。
JavaScript 封装:闭包的应用与模式
在 JavaScript 创建对象的各种模式中,通过闭包实现的数据隐藏被视为真正意义上的封装。为了清晰阐述这一概念,我们首先简要回顾闭包的基本用法,参考以下示例代码。
代码如下:
<script type="text/javascript">
function myInfo(){
var name ="老鱼",age =27;
var myInfo = "my name is" + name + "i am" + age +"years old";
function showInfo(){
alert(myInfo);
}
return showInfo;
}
var oldFish = myInfo();
oldFish();
</script>这段代码展示了典型的闭包应用。其核心逻辑在于:函数 myInfo 内部定义的变量,在其嵌套函数 showInfo 中是可以访问的。当我们将 showInfo 的引用赋值给外部变量 oldFish,并在 myInfo 的作用域之外调用 oldFish 时,它依然能够访问到定义在 myInfo 内部的变量。
闭包的核心原理在于:函数运行于其定义时的作用域,而非调用时的作用域。返回内部函数是构建闭包最常规的手段。
若觉得上述解释较为抽象,我们可以通过重构函数来使结构更加清晰,具体实现如下。
代码如下:
<script type="text/javascript">
var ioldFish = function(name,age){
var name = name,age = age;
var myInfo = "my name is" + name + "i am" + age +"years old";
return{
showInfo:function(){
alert(myInfo);
}
}
}
ioldFish("老鱼",27).showInfo();
</script>上述代码风格常见于 Ext 和 YUI 等框架,特点是公私分明、结构清晰。利用闭包,我们可以将不希望被外部直接访问的数据隐藏起来。外部无法直接读取函数内部变量,必须通过特定的公开方法才能访问,这正是封装的本质。
若转换为 jQuery 风格的封装模式,代码结构会有所不同。这种模式允许外部直接访问内部定义的变量(例如实例化对象后,在外部读取 name 或 age 属性)。为了规范团队协作,通常约定在私有变量或方法名前添加下划线“_”作为警示标识,以此实现逻辑上的“封装”。
代码如下:
<script type="text/javascript">
var ioldFish = function(name,age){
return ioldFish.func.init(name,age);
};
ioldFish.func = ioldFish.prototype ={
init:function(name,age){
this.name = name;
this.age = age;
return this;
},
showInfo:function(){
var info = "my name is" + this.name +"i am " +this.age+"years old";
alert(info);
}
};
ioldFish.func.init.prototype = ioldFish.func;
ioldFish(" 老 鱼",27).showInfo();
//var oldFish = new ioldFish("老鱼",27);
//alert(oldFish.name);
</script>关于哪种模式更优的问题,两者各有优劣,建议结合具体场景使用。核心原则是:对于绝不允许被外部直接访问的数据,必须使用闭包进行封装。这一原则需要在实践中不断体会。
JavaScript 继承:类式与原型继承原理
接下来讨论继承。需要指出的是,闭包封装的一个缺点是不利于子类的派生,因此在使用闭包进行封装时需慎重。为了直观展示继承原理,以下示例采用“门户大开型”模式创建对象。
JavaScript 中的继承通常分为三种方式:“类式继承”、“原型继承”和“掺元类”。下面简要介绍前两种继承方式的原理。
A. 类式继承:这是当前主流框架中常用的继承方式,参考以下示例。
代码如下:
<script type="text/javascript">
var Name = function(name){
this.name = name;
};
Name.prototype.getName = function(){
alert(this.name);
};
var Fish = function(name,age){
Name.call(this,name);
this.age = age;
};
Fish.prototype = new Name();
Fish.prototype.constructor = Fish;
Fish.prototype.showInfo = function(){
alert(this.age);
}
var ioldFish = new Fish("老鱼",27);
ioldFish.getName();
</script>在上述示例中,子类 Fish 并未定义 getName 方法,但其实例 ioldFish 仍能调用该方法,这是因为子类继承自超类 Name。具体机制是:子类的 prototype 指向超类的一个实例。根据原型链原理,当在子类中查找未定义的方法时,会向上搜索至 prototype 指向的对象,直至找到该方法或到达原型链顶端。特别需要注意的是语句 `Fish.prototype.constructor = Fish;`,因为默认情况下子类的 prototype 应指向自身,但此处将其指向了超类实例,因此需要手动将 constructor 重置为 Fish。此外,相关逻辑可通过函数封装以模拟 extend 行为。
B. 原型继承:从内存性能角度来看,这种方式优于类式继承。
代码如下:
<script type="text/javascript">
function clone(object){
var F = function(){};
F.prototype = object;
return new F();
};
var Name = {
name:"who's name",
showInfo:function(){
alert(this.name);
}
};
var Fish = clone(Name);
//Fish.name = "老鱼";
Fish.showInfo();
lt;/script>
原型继承的核心在于 clone 函数。它同样基于原型链原理,但直接克隆超类对象,使得子类继承超类的所有属性和方法。这种继承方式无需创建构造函数,只需定义一个对象字面量并指定相应属性与方法,子类即可通过点号“.”直接引用这些属性和方法。
2下一页阅读全文