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 相关文章推荐
JavaScript判断两种格式的输入日期的正确性的代码
Mar 25 Javascript
二级域名或跨域共享Cookies的实现方法
Aug 07 Javascript
基于Jquery的淡入淡出的特效基础练习
Dec 13 Javascript
js 触发select onchange事件代码
Mar 20 Javascript
Javascript中设置默认参数值示例
Sep 11 Javascript
JS操作JSON方法总结(推荐)
Jun 14 Javascript
Angularjs实现下拉框联动的示例代码
Aug 22 Javascript
angularjs实现对表单输入改变的监控(ng-change和watch两种方式)
Aug 29 Javascript
thinkjs微信中控之微信鉴权登陆的实现代码
Aug 08 Javascript
vue+vant-UI框架实现购物车的复选框全选和反选功能
Nov 05 Javascript
利用Vue的v-for和v-bind实现列表颜色切换
Jul 17 Javascript
vue图片裁剪插件vue-cropper使用方法详解
Dec 16 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
PHP 执行系统外部命令 system() exec() passthru()
2009/08/11 PHP
PHP中基于ts与nts版本- vc6和vc9编译版本的区别详解
2013/04/26 PHP
php版淘宝网查询商品接口代码示例
2014/06/17 PHP
thinkphp自带验证码全面解析
2016/09/18 PHP
php常用经典函数集锦【数组、字符串、栈、队列、排序等】
2019/08/23 PHP
jQuery.autocomplete 支持中文输入(firefox)修正方法
2011/03/10 Javascript
js计算两个时间之间天数差的实例代码
2013/11/19 Javascript
js中自定义方法实现停留几秒sleep
2014/07/11 Javascript
jQuery实现checkbox全选的方法
2015/06/10 Javascript
javascript数组去重小结
2016/03/07 Javascript
浅谈js的url解析函数封装
2016/06/28 Javascript
JavaScript数组方法大全(推荐)
2016/07/05 Javascript
angular基于路由控制ui-router实现系统权限控制
2016/09/27 Javascript
Bootstrap 下拉多选框插件Bootstrap Multiselect
2017/01/22 Javascript
详解Node.js中的Async和Await函数
2018/02/22 Javascript
JS实现textarea通过换行或者回车把多行数字分割成数组并且去掉数组中空的值
2018/10/29 Javascript
详解如何快速配置webpack多入口脚手架
2018/12/28 Javascript
jquery分页优化操作实例分析
2019/08/23 jQuery
基于JS实现简单滑块拼图游戏
2019/10/12 Javascript
[03:33]TI9战队采访 - Infamous
2019/08/20 DOTA
Python 命令行非阻塞输入的小例子
2013/09/27 Python
selenium3+python3环境搭建教程图解
2018/12/07 Python
pyqt5之将textBrowser的内容写入txt文档的方法
2019/06/21 Python
Python实现图片添加文字
2019/11/26 Python
python学生信息管理系统实现代码
2019/12/17 Python
keras实现theano和tensorflow训练的模型相互转换
2020/06/19 Python
一款CSS3实现多功能下拉菜单(带分享按)的教程
2014/11/05 HTML / CSS
联想法国官方网站:Lenovo法国
2018/10/18 全球购物
德国BA保镖药房中文网:Bodyguard Apotheke
2021/03/09 全球购物
资金主管岗位职责范本
2014/03/04 职场文书
学校2014重阳节活动策划方案
2014/09/16 职场文书
民主评议党员自我鉴定
2014/10/21 职场文书
计算机专业自荐信范文
2015/03/26 职场文书
vue2的 router在使用过程中遇到的一些问题
2022/04/13 Vue.js
springcloud整合seata
2022/05/20 Java/Android
NoSQL优缺点与MongoDB数据库简介
2022/06/05 MongoDB