在 JavaScript 中,每个对象都有一个“隐藏的靠山”,这个靠山就是它的原型(prototype)。你可以把原型想象成一个模板,上面存放了一些属性和方法,供其他对象共享使用。
举个例子:
这里的 person
是一个对象,它有一个隐藏的 __proto__
属性,指向它的原型 Object.prototype
。这个原型就像一个工具箱,里面有一些默认的工具(比如 toString
方法),person
可以直接用。
关键点:每个对象都有一个 __proto__
,它指向自己的原型对象。
原型链是 JavaScript 的核心机制之一。当你访问一个对象的属性或方法时,JavaScript 会按以下步骤查找:
__proto__
指向的对象)上找。Object.prototype
的 __proto__
是 null
)。来看个例子:
这里我们用 Object.setPrototypeOf
把 dog
的原型设为 animal
。当调用 dog.eat()
时,JavaScript 发现 dog
自身没有 eat
方法,就去它的原型 animal
上找,找到了就执行。
关键点:原型链就像一个家谱,属性和方法可以从“祖先”那里继承过来。
prototype
和__proto__
:别搞混了!这两个概念听起来很像,但作用完全不同:
prototype
:是函数专有的属性,指向一个对象。这个对象会成为通过该函数(作为构造函数)创建的新对象的原型。__proto__
:是每个对象都有的属性,指向它的原型对象。来看代码:
Person.prototype
是 Person
函数的一个属性,定义了所有 Person
实例的共享方法(比如 sayHello
)。xiaoming.__proto__
指向 Person.prototype
,这就是 xiaoming
能调用 sayHello
的原因。关键点:prototype
是给构造函数用的,__proto__
是给实例对象用的。
new
到底干了啥?当你用 new
关键字调用一个函数时,JavaScript 在幕后做了几件事:
{}
。__proto__
设为构造函数的 prototype
。this
绑定到新对象上,执行构造函数代码。来看个例子:
这里,husky
的 __proto__
指向 Dog.prototype
,所以它能调用 bark
方法。
关键点:new
是一个魔法操作,它把构造函数和原型链连了起来。
JavaScript 的继承主要靠原型链实现。子类可以通过原型链访问父类的属性和方法。来看一个完整的继承例子:
解释一下:
Animal.call(this, name)
:调用 Animal
的构造函数,确保 Dog
实例有 name
属性。Object.create(Animal.prototype)
:创建了一个新对象,继承了 Animal.prototype
的属性和方法。Dog.prototype.constructor = Dog
:修复 constructor
,因为 Object.create
会让 constructor
指向错误的对象。关键点:通过 call
继承属性,通过原型链继承方法。
想知道一个对象是不是另一个对象的原型?用 isPrototypeOf
:
想知道属性是来自对象自身还是原型链?用 hasOwnProperty
:
你可能会想给内置对象(比如 Array
)加点“新功能”:
虽然这很方便,但不推荐!因为:
建议:如果需要扩展功能,考虑写独立的工具函数。
JavaScript 的原型和继承机制的核心在于原型链。通过 __proto__
和 prototype
,对象可以共享属性和方法;通过构造函数和原型链,可以实现灵活的继承。理解这些机制后,你就能写出更高效、更清晰的代码。
几点建议:
Object.create
或 class
(ES6 提供的更现代的方式)来实现继承。__proto__
,因为它在现代代码中不推荐使用。