深入理解JavaScript面向对象:函数即对象与原型机制
本文深入解析JavaScript面向对象编程的核心原理,阐明函数在JS中既是可执行代码又是对象的二重性。通过剖析prototype属性与new关键字的交互,揭示对象创建、属性拷贝及原型继承的真实机制,帮助开发者理解无需类概念的面向对象实现方式。
JavaScript对象分类:普通对象与方法对象
JavaScript 本质上是一种面向对象的语言,但许多开发者对其面向对象特性的理解尚不够全面。
在 JavaScript 中,对象主要可分为两类:一类是常规对象,即我们通常认知的数字、日期以及用户自定义的对象(例如 {});另一类则是方法对象,即我们定义的函数(function)。
你可能会疑惑:函数只是函数,为何会被视为对象?事实上,在 JavaScript 中,函数确实被当作对象来处理。以下是一个简单的示例:
代码如下:
function func() {alert('Hello!');}
alert(func.toString());在上述示例中,func 虽然被定义为方法,但它自身包含一个 toString 方法,这表明 func 在此处被视为对象处理。更准确地说,func 是一个“方法对象”。示例继续如下:
代码如下:
func.name = “I am func.”;
alert(func.name);我们可以为 func 任意设置属性,这进一步证实了 func 就是一个对象。那么,方法对象与普通对象的区别何在?首先,方法对象是可以执行的,在其后添加一对括号即可执行该方法对象。
代码如下:
func();方法对象的二重性与回调机制
因此,方法对象具有二重性:一方面它可以被执行,另一方面它完全可以作为普通对象使用。这意味着方法对象可以独立于其他对象存在。这一点可与 Java 进行对比:在 Java 中,方法必须在某个类中定义,不能单独存在;而在 JavaScript 中则无需如此。
方法对象独立于其他方法,意味着它可以被任意引用和传递。以下是一个示例:
代码如下:
function invoke(f) {
f();
}
invoke(func);将一个方法对象 func 传递给另一个方法对象 invoke,让后者在适当的时候执行 func。这就是所谓的“回调”。此外,方法对象的这种特殊性也使得 this 关键字的使用变得复杂,这方面相关文章较多,此处不再赘述。
原型链与对象创建机制
除了可执行性外,方法对象还有一个特殊功能,即可以通过 new 关键字来创建普通对象。
每个方法对象被创建时,都会自动拥有一个名为 prototype 的属性。该属性并无特殊之处,它与其他属性一样可以访问和赋值。然而,当我们使用 new 关键字创建对象时,prototype 便发挥作用:其值(也是一个对象)所包含的所有属性,都会被复制到新创建的对象中。以下是一个示例:
代码如下:
func.prototype.name=”prototype of func”;
var f = new func();
alert(f.name);执行过程中会弹出两个对话框,后一个对话框表示 f 这个新建的对象从 func.prototype 那里拷贝了 name 属性。而前一个对话框则表示 func 被作为方法执行了一遍。你可能会问,为什么这个时候还要把 func 执行一遍呢?其实这个时候执行 func,就是起“构造函数”的作用。为了形象地说明,我们重新来一遍:
代码如下:
function func() {
this.name=”name has been changed.”
}
func.prototype.name=”prototype of func”;
var f = new func();
alert(f.name);你就会发现 f 的 name 属性不再是"prototype of func",而是被替换成了"name has been changed"。这就是 func 这个对象方法所起到的“构造函数”的作用。因此,在 JavaScript 中,用 new 关键字创建对象是执行了下面三个步骤的:
1.创建一个新的普通对象; 2.将方法对象的 prototype 属性的所有属性复制到新的普通对象中去。 3.以新的普通对象作为上下文来执行方法对象。 对于“new func()”这样的语句,可以描述为“从 func 创建一个新对象”。总之,prototype 这个属性的唯一特殊之处,就是在创建新对象的时候了。
基于原型的继承实现
那么我们就可以利用这一点。比如有两个方法对象 A 和 B,既然从 A 创建的新对象包含了所有 A.prototype 的属性,那么我将它赋给 B.prototype,那么从 B 创建的新对象不也有同样的属性了?写成代码就是这样:
代码如下:
A.prototype.hello = function(){alert('Hello!');}
B.prototype = new A();
new B().hello();这就是 JavaScript 的所谓“继承”了,其实质就是属性的拷贝,这里利用了 prototype 来实现。如果不用 prototype,那就用循环了,效果是一样的。所谓“多重继承”,自然就是到处拷贝了。
无类概念的面向对象实现
JavaScript 中面向对象的原理,就是上面这些了。自始至终我都没提到“类”的概念,因为 JavaScript 本来就没有“类”这个东西。面向对象可以没有类吗?当然可以。先有类,然后再有对象,这本来就不合理,因为类本来是从对象中归纳出来的,先有对象再有类,这才合理。像下面这样的:
代码如下:
var o = {}; // 我发现了一个东西。
o.eat = function(){return "I am eating."} // 我发现它会吃;
o.sleep = function(){return "ZZZzzz..."} // 我发现它会睡;
o.talk = function(){return "Hi!"} // 我发现它会说话;
o.think = function(){return "Hmmm..."} // 我发现它还会思考。
var Human = new Function(); // 我决定给它起名叫“人”。
Human.prototype = o; // 这个东西就代表了所有“人”的概念。
var h = new Human(); // 当我发现其他同它一样的东西,
alert(h.talk()) // 我就知道它也是“人”了!JS中将数组和对象混合组装为JSON字符串
2026-11-05 16:41:42
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
D3.js 与 ECharts 选型对比:灵活性 vs 开发效率
2026-11-05 09:45:01
为什么JS中typeof []返回object:数组类型检测误区解析
2026-11-04 19:50:47
