JavaScript中的原型继承基础学习教程

大多数编程语言中,都有类和对象,一个类可以继承其他类。
在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。

(0)

相关推荐

  • 关于JavaScript中原型继承中的一点思考

    我们先看一段传统的继承代码: 复制代码 代码如下: //定义超类 function Father(){ this.name = "父亲"; } Father.prototype.theSuperValue = ["NO1","NO2"]; //定义子类 function Child(){ } //实现继承 Child.prototype = new Father(); //修改共享数组 Child.prototype.theSuperValue.

  • javascript原型继承工作原理和实例详解

    先为大家分享JS原型继承实例,供大家参考,具体内容如下 一.JS原型继承 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JS原型继承</title> </head> <body> <!--原型继承--> <script type="text/javascript"> //cl

  • 浅析JS原型继承与类的继承

    我们先看JS类的继承 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JS类的继承</title> </head> <body> /* -- 类式继承 -- */ <script type="text/javascript"> //先声明一个超类 var Animal = function

  • 详谈js原型继承的一些问题

    当我们使用原型链继承时,需要谨慎的定义原型上的方法和属性,因为这可能带来意外的结果. 一.谨慎的定义原型上的方法. 当我们想为一个构造函数的原型上定义一个方法时,一定要在更改原型后再定义,否则新的原型对象上不会有定义的这个方法,导致与我们预期的结果不同.例: function superObj(){} superObj.prototype.sayHi=function sayHi(){ console.log('hi'); }; superObj.prototype={ name:'Poly'

  • JavaScript的原型继承详解

    JavaScript是一门面向对象的语言.在JavaScript中有一句很经典的话,万物皆对象.既然是面向对象的,那就有面向对象的三大特征:封装.继承.多态.这里讲的是JavaScript的继承,其他两个容后再讲. JavaScript的继承和C++的继承不大一样,C++的继承是基于类的,而JavaScript的继承是基于原型的. 现在问题来了. 原型是什么?原型我们可以参照C++里的类,同样的保存了对象的属性和方法.例如我们写一个简单的对象 复制代码 代码如下: function Animal

  • 浅析JavaScript原型继承的陷阱

    JavaScript默认采用原型继承.虽然没有类(class)的概念,它的函数(function)可以充当构造器(constructor).构造器结合this,new可以构建出类似Java的类.因此,JavaScript通过扩展自身能模拟类式(class-based)继承. JavaScript和其它面向对象语言一样,对象类型采用引用方式.持有对象的变量只是一个地址,而基本类型数据是值.当原型上存储对象时,就可能有一些陷阱. 先看第一个例子 复制代码 代码如下: var create = fun

  • JavaScript 原型继承

    Object.prototype JavaScript是基于原型继承的,任何对象都有一个prototype属性.Object.prototype是所有对象的根,并且不可改变. 复制代码 代码如下: Object.prototype=null; alert(Object.prototype);//[object Object] Object与Object.prototype Object继承于Object.prototype,增加一个属性给Object.prototype上,同时也会反应到Obje

  • 浅谈javascript的原型继承

    请看源码: 复制代码 代码如下: function clone(o) { var F = function(){}; F.prototype = o; return new F(); } 首先看ext(4.1的1896行开始)的原型式继承. 复制代码 代码如下: var TemplateClass = function(){}; var ExtObject = Ext.Object = { chain: function (object) { TemplateClass.prototype =

  • 再谈javascript原型继承

    真正意义上来说Javascript并不是一门面向对象的语言,没有提供传统的继承方式,但是它提供了一种原型继承的方式,利用自身提供的原型属性来实现继承. 原型与原型链 说原型继承之前还是要先说说原型和原型链,毕竟这是实现原型继承的基础. 在Javascript中,每个函数都有一个原型属性prototype指向自身的原型,而由这个函数创建的对象也有一个__proto__属性指向这个原型,而函数的原型是一个对象,所以这个对象也会有一个__proto__指向自己的原型,这样逐层深入直到Object对象的

  • js原型继承的两种方法对比介绍

    在实际的项目中,我们通常都是用构造函数来创建一个对象,再将一些常用的方法添加到其原型对象上.最后要么直接实例化该对象,要么将它作为父类,再申明一个对象,继承该父类. 而在继承的时候有两种常用方式,今天我们就来稍作探讨 复制代码 代码如下: //父类 function Person(name){    this.name = name;}; // 子类 function Student(sex){  Person.apply(this,arguments); //继承父类的构造函数  this.s

  • JavaScript原型继承_动力节点Java学院整理

    在传统的基于Class的语言如Java.C++中,继承的本质是扩展一个已有的Class,并生成新的Subclass. 由于这类语言严格区分类和实例,继承实际上是类型的扩展.但是,JavaScript由于采用原型继承,我们无法直接扩展一个Class,因为根本不存在Class这种类型. 但是办法还是有的.我们先回顾Student构造函数: function Student(props) { this.name = props.name || 'Unnamed'; } Student.prototyp

随机推荐