这篇文章主要介绍了 JavaScript 中的原型继承基础学习教程, 基于原型 prototype 的继承是 JavaScript 中实现面向对象中的继承特性的基本手段, 需要的朋友可以参考下
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
大多数编程语言中,都有类和对象,一个类可以继承其他类。
在 JavaScript 中,继承是基于原型的(prototype-based),这意味着 JavaScript 中没有类,取而代之的是一个对象继承另一个对象。:)
1. 继承, the proto
在 JavaScript 中,当一个对象 rabbit 继承另一了对象 animal 时,这意味着 rabbit 对象中将会有一个特殊的属性:rabbit.__proto__ = animal;
当访问 rabbit 对象时,如果解释器在 rabbit 中不能找到属性,那么它会顺着__proto__链往上在 animal 对象中寻找
栗子中的__proto__属性仅在 Chrome 和 FireFox 中可以访问,请看一个栗子:
- var animal = {
- eats: true
- }
- var rabbit = {
- jumps: true
- }
- rabbit.__proto__ = animal // inherit
- alert(rabbit.eats) // true
eats 属性是从 animal 对象中访问的。
如果在 rabbit 对象中已经发现了属性,那么就不会去检查 proto 属性啦。
再来一个栗子,当子类中也有 eats 属性时,父类中的就不会访问了。
- var animal = {
- eats: true
- }
- var fedUpRabbit = {
- eats: false
- }
- fedUpRabbit.__proto__ = animal alert(fedUpRabbit.eats) // false
你也可以在 animal 中添加一个函数,那么在 rabbit 中也可以访问了。
- var animal = {
- eat: function() {
- alert( "I'm full" )
- this.full = true
- }
- }
- var rabbit = {
- jump: function() { /* something */ }
- }
- rabbit.__proto__ = animal
(1)rabbit.eat():
rabbit.eat() 函数以如下两步执行:
首先,解释器查找 rabbit.eat,rabbit 中没有 eat 函数,那么它就顺着 rabbit.__proto__往上找,在 animal 中找到了。
函数以 this = rabbit 运行。this 值与__proto__属性完全无关。
因此,this.full = true 在 rabbit 中:
看看这里我们有什么新发现,一个对象调用了父类函数,但是 this 还是指向对象本身,这就是继承。
被__proto__引用的对象称作是原型(prototype),animal 是 rabbit 的原型(译者注:这就是 rabbit 的__proto__属性引用了 animal 的 prototype 属性)
(2)读时查找,不是写时
当读取一个对象时,比如 this.prop,解释器会在它的原型中查找属性。
当设置一个属性值时,比如 this.prop = value,那么就没有理由去查找了,这个属性(prop)会被直接添加到这个对象中(这里是 this)。delete obj.prop 也类似,它只删除对象本身的属性,原型中的属性保持原封不动。
(3)关于 proto
如果你在阅读指南,这里我们叫的__proto__,在指南中表示为 [[Prototype]]。双方括号是很重要的,因为有另一个属性叫做 prototype。
2. Object.create, Object.getPrototypeOf
__proto__是一个非标准的属性,由 Chrome/FireFox 提供访问,在其他的浏览器中保持不可见。
所有的现代浏览器除了 Opera(IE > 9)支持两个标准的函数来处理原型问题:
- Object.ceate(prop[,props])
用给定了 proto 创建一个空对象:
- var animal = {
- eats: true
- }
- rabbit = Object.create(animal) alert(rabbit.eats) // true
上面代码创建了一个空 rabbit 对象,并且原型设置为 animal
rabbit 对象创建好以后,我们可以往里添加属性了:
- var animal = { eats: true }
- rabbit = Object.create(animal)
- rabbit.jumps = true
Object.creat 函数的第二个参数 props 是可选的,它允许像新对象设置属性。这里就省略了,因为我们关系的继承。
(1)Object.getPrototypeOf(obj)
返回 obj.__proto__的值。这个函数是标准的,可以在不能直接访问__proto__属性的浏览器中使用了。
- var animal = {
- eats: true
- }
- rabbit = Object.create(animal)
- alert( Object.getPrototypeOf(rabbit) === animal ) // true
现代浏览器允许读取__proto__属性值,但是不能设置。
3. The prototype
有一些好的跨浏览器的方式设置__proto__属性,这将会使用构造器函数(constructor functions)。记住!任何函数创建一个对象都是通过 new 关键字的。
一个栗子:
- function Rabbit(name) {
- this.name = name
- }
- var rabbit = new Rabbit('John')
- alert(rabbit.name) // John
new 操作将原型的属性设置到 rabbit 对象的的__proto__属性中了。
让我们来看看它的原理,例如,new Rabbit 对象,而 Rabbit 是继承 animal 的。
- var animal = {
- eats: true
- }
- function Rabbit(name) {
- this.name = name
- }
- Rabbit.prototype = animal
- var rabbit = new Rabbit('John') alert(rabbit.eats) // true, because rabbit.__proto__ == animal
Rabbit.prototype = animal 字面量意味着:对所有由 new Rabbit 创建的对象设__proto__ = animal
4. 跨浏览器 Object.create(proto)
Object.create(prop) 函数功能的强大的,因为它允许从给定的对象直接继承。它可以由如下代码模拟:
- function inherit(proto) {
- function F() {}
- F.prototype = proto
- return new F
- }
inherit(animal) 与 Object.create(animal) 是完全等同的,返回一个空的对象,并且 object.__proto__ = animal。
一个栗子:
- var animal = {
- eats: true
- }
- var rabbit = inherit(animal) alert(rabbit.eats) // true
- alert(rabbit.hasOwnProperty('eats')) // false, from prototype
来看一下它的原理是什么:
- function inherit(proto) {
- function F() {} // (1)
- F.prototype = proto // (2)
- return new F() // (3)
- }
(1) 创建了一个新函数,函数没有向 this 设置任何属性,以此 `new F` 会创建一个空对象。
(2) `F.prototype` 被设置为 proto
(3) `new` F 创建了一个空对象,对象的 `__proto__ = F.prototype`
(4) Bingo! 我们得到了一个继承 `proto` 的空对象
这个函数广泛适用于各种库和框架之中。
你的函数接受了一个带有 options 的对象
- /* options contains menu settings: width, height etc */
- function Menu(options) {
- // ...
- }
- 你想设置某些options
- function Menu(options) {
- options.width = options.width || 300 // set default value
- // ...
- }
。。。但是改变参数值可能会产生一些错误的结果,因为 options 可能会在外部代码中使用。一个解决办法就是克隆 options 对象,复制所有的属性到一个新的对象中,在新对象中修改,
怎样用继承来解决这个问题呢? P.S. options 可以添加设设置,但是不能被删除。
Solution
你可以继承 options,并且在它的子类的中修改或者添加新的属性。
- function inherit(proto) {
- function F() {}
- F.prototype = proto
- return new F
- }
- function Menu(options) {
- var opts = inherit(options)
- opts.width = opts.width || 300
- // ...
- }
所有的操作只在子对象中有效,当 Menu 方法结束时,外部代码仍然可以使用没有修改的过的 options 对象。delete 操作在这里非常重要,如果 width 是一个 prototype 中的属性,delete opts.width 不会产生任何作用
5. hasOwnProperty
所有的对象都有 hasOwnProperty 函数,它可以用来检测一个属性是否对象自身还是属于原型
一个栗子:
- function Rabbit(name) {
- this.name = name
- }
- Rabbit.prototype = {
- eats: true
- }
- var rabbit = new Rabbit('John') alert(rabbit.hasOwnProperty('eats')) // false, in prototype
- alert(rabbit.hasOwnProperty('name')) // true, in object
6. Looping with/without inherited properties
for..in 循环输出一个对象的所有属性,包括自身的和原型的。
- function Rabbit(name) {
- this.name = name
- }
- Rabbit.prototype = { eats: true }
- var rabbit = new Rabbit('John')
- for(var p in rabbit) {
- alert (p + " = " + rabbit[p]) // outputs both "name" and "eats"
- }
用 hasOwnProperty 可以过滤得到属于对象自己的属性:
- function Rabbit(name) {
- this.name = name
- }
- Rabbit.prototype = { eats: true }
- var rabbit = new Rabbit('John')
- for(var p in rabbit) {
- if (!rabbit.hasOwnProperty(p)) continue // filter out "eats"
- alert (p + " = " + rabbit[p]) // outputs only "name"
- }
7. Summary
JavaScript 是通过一个特殊的属性 proto 来实现继承的
当访问一个对象的属性时,如果解释器不能在对象中找到,它就会去对象的原型中继续寻找 对函数属性来说,this 指向这个对象,而不是它的原型。
赋值 obj.prop = value, 删除 delete obj.prop
管理 proto:
Chrome 和 FireFox 可以直接访问对象的__proto__属性,大多数现代浏览器支持用 Object.getPrototypeOf(obj) 只读访问。
Object.create(proto) 可以用给定的 proto 生成空的子对象,或者通过如下代码达到相同的功能:
- function inherit(proto) {
- function F() {}
- F.prototype = proto
- return new F()
- }
其他方法:
for..in 循环输出一个对象的所有属性(包括自身的和原型的)和对象的原型链。
如果一个属性 prop 属于对象 obj 那么 obj.hasOwnProperty(prop) 返回 true,否则返回 false。
来源: http://www.phperz.com/article/17/0225/265960.html