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


Posted in Javascript onMay 06, 2016

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

Javascript 相关文章推荐
filemanage功能中用到的lib.js
Apr 08 Javascript
Extjs Ajax 乱码问题解决方案
Apr 15 Javascript
网页和浏览器兼容性问题汇总(draft1)
Jun 01 Javascript
ExtJS PropertyGrid中使用Combobox选择值问题
Jun 13 Javascript
JavaScript高级程序设计 客户端存储学习笔记
Sep 10 Javascript
js判断横竖屏及禁止浏览器滑动条示例
Apr 29 Javascript
JavaScript编写检测用户所使用的浏览器的代码示例
May 05 Javascript
ES6中module模块化开发实例浅析
Apr 06 Javascript
微信小程序实战之顶部导航栏(选项卡)(1)
Jun 19 Javascript
VUE实现表单元素双向绑定(总结)
Aug 08 Javascript
使用vue的transition完成滑动过渡的示例代码
Jun 25 Javascript
vue-cli中实现响应式布局的方法
Mar 02 Vue.js
jquery实现下拉框功能效果【实例代码】
May 06 #Javascript
浅析在javascript中创建对象的各种模式
May 06 #Javascript
jquery自定义插件——window的实现【示例代码】
May 06 #Javascript
javascript创建对象的几种模式介绍
May 06 #Javascript
前端性能优化及技巧
May 06 #Javascript
jquery自定义插件开发之window的实现过程
May 06 #Javascript
快速使用Bootstrap搭建传送带
May 06 #Javascript
You might like
如何取得中文字符串中出现次数最多的子串
2013/08/08 PHP
php中ob函数缓冲机制深入理解
2015/08/03 PHP
微信支付的开发流程详解
2016/09/13 PHP
RR vs IO BO3 第一场2.13
2021/03/10 DOTA
javascript 基础篇2 数据类型,语句,函数
2012/03/14 Javascript
简述JavaScript的正则表达式中test()方法的使用
2015/06/16 Javascript
javascript中的Function.prototye.bind
2015/06/25 Javascript
js无提示关闭浏览器窗口的两种方法分析
2016/11/06 Javascript
jQuery中弹出iframe内嵌页面元素到父页面并全屏化的实例代码
2016/12/27 Javascript
JS+CSS实现下拉刷新/上拉加载插件
2017/03/31 Javascript
vue 框架下自定义滚动条(easyscroll)实现方法
2019/08/29 Javascript
layui之table checkbox初始化时选中对应选项的方法
2019/09/02 Javascript
vue实现图片上传预览功能
2019/12/23 Javascript
对于Python异常处理慎用“except:pass”建议
2015/04/02 Python
Python利用IPython提高开发效率
2016/08/10 Python
python3.5实现socket通讯示例(TCP)
2017/02/07 Python
Linux下python与C++使用dlib实现人脸检测
2018/06/29 Python
程序员写Python时的5个坏习惯,你有几条?
2018/11/26 Python
Python可迭代对象操作示例
2019/05/07 Python
Python实现基于SVM的分类器的方法
2019/07/19 Python
Django如何将URL映射到视图
2019/07/29 Python
Python代码注释规范代码实例解析
2020/08/14 Python
Python中常用的os操作汇总
2020/11/05 Python
详解Css3新特性应用之过渡与动画
2017/01/10 HTML / CSS
使用HTML5 Canvas绘制直线或折线等线条的方法讲解
2016/03/14 HTML / CSS
H5 canvas中width、height和style的宽高区别详解
2018/11/02 HTML / CSS
Uber Eats台湾:寻找附近提供送餐服务的餐厅
2018/05/07 全球购物
wedgwood加拿大官网:1759年成立的英国国宝级陶瓷餐具品牌
2018/07/17 全球购物
PHP面试题附答案
2015/11/28 面试题
以太网Ethernet IEEE802.3
2013/08/05 面试题
日语系毕业生推荐信
2013/11/11 职场文书
给物业的表扬信
2014/01/21 职场文书
老同学聚会感言
2014/02/23 职场文书
绩效管理实施方案
2014/03/19 职场文书
Django实现在线无水印抖音视频下载(附源码及地址)
2021/05/06 Python
解析在浏览器地址栏输入一个URL后发生了什么
2021/06/21 Servers