js原型链继承的傻瓜式详解
本文争取用最简单的语言来讲解原型链继承的OOP原理
0.如果对原型继承还没有大致了解,完全一头雾水,请先阅读
《JavaScript高级程序设计》第六章最后部分的寄生组合式继承
或者_廖雪峰js教程里面面向对象部分的原型承部分https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014344997013405abfb7f0e1904a04ba6898a384b1e925000
1.类,构造函数和实例
如果对函数使用 new,那么会默认返回一个对象,这个对象是该函数的实例。
如
var x = new Object();
此时x是一个Object的实例
假如我们有两种构造函数
//情况A
function Person(name,age){
this.name = name;
this.age = age; this.sayHi = function (){
alert("Hi,I'm " + this.name);
}
} var wang = new Person('老王',30);
var li = new Person('老李',22); wang.sayHi == li.sayHi //false //情况B function Person(name,age){
this.name = name;
this.age = age;
} Person.prototype.sayHi = function (){
alert("Hi,I'm " + this.name);
} var wang = new Person('老王',30);
var li = new Person('老李',22); wang.sayHi == li.sayHi //true
wang,li是两个Person类的实例,Person是他们的构造函数。
第一种情况A,方法被重复定义,各实例的方法实际上是被分别定义了一遍。第二种情况B,实现了类的方法的复用。
显然,第一种方法里面,sayHi函数是各实例自有的属性,第二种方法,sayHi被定义在了Person的prototype上面。
要点1:当实例对象本身不存在某个属性时,js会查找该实例的【构造函数】的【prototype】属性
2.如何继承
问题来了,如果定义一个子类,比如Student,如何继承Person类。
按照廖雪峰老师和书上比较推荐的方法
function Student(name, age, grade){
Person.call(this, name, age);
this.grade = grade;
} //--------
function F(){};
F.prototype = Person.prototype;
Student.prototype = new F();
Student.prototype.constructor = Student;
//-------- Student.prototype.sayHello =function(){
alert("Hello, I'm "+ this.name +", I'm in grade " + this.grade + ", nice to meet you.");
}; var wang_son = new Student('老王儿子',8,'二年级');
wang_son.sayHi();//Hi,I'm 老王儿子
wang_son.sayHello();//Hello, I'm 老王儿子, I'm in grade 二年级, nice to meet you.
wang_son.sayHi == wang.sayHi; //结果是true
结果来看确实比较良好的实现了继承,可是中间那四行是什么玩意,那个F又是什么?好像除了中间这四行,其它的都挺容易懂。简单的四行代码,把人绕的云里雾里的。
道格拉斯·克罗克福德在 2006年写了一篇文章,题为 Prototypal Inheritance in JavaScript (JavaScript中的原型式继承)。在这篇文章中,他介绍了这种实现继承的方法
如果不是很熟悉js的细节,这个方法看起来多少有点耍杂技。乍一看很绕,理解之后会发现它的精妙之处。
先来看一些假设:如果不这么搞行不行?
不就是继承父类的方法吗,直接Student.prototype = Person.prototype不就得了,
结果是 wang_son.sayHi()返回结果正确,但是父类wang有了儿子用的sayHello()了,这明显不对。
如果wang_son的sayHi需要重写成alert("叔叔好"),那么老王跟你打招呼也会叫叔叔好,这下彻底乱了。
不就是不重叠不覆盖吗,我把父类的prototype里面有的东西浅复制一份,把那四行代码替换成
Object.getOwnPropertyNames(Person.prototype).forEach(function(key){
Student.prototype[key]=Person.prototype[key]
});
看似没什么问题,改子类代码不会影响父类,但是在后续代码中,如果修改或者添加父类的方法,子类是不会随着变化的。二者的状态只是在复制的一瞬间进行了同步。
那。。。试试把他俩连起来呢? Student.prototype.prototype = Person.prototype 这不就是标准的继承了吗
想得美!!
要点2:只有函数才有prototype属性。普通实例对象没有!!!
从要点1中得知,访问实例对象没有的属性,会向上追溯,去查该对象的【构造函数的prototype】。
那问题来了,prototype里面也没有怎么办?查prototype的【构造函数的prototype】啊。毕竟prototype也是个实例对象啊。
还是上面那些代码,使用typeof Person.prototype 查看类型,返回的是"Object" 也就是说Person.prototype本身是一个Object的实例。
Person.valueOf();
//显示结果
//ƒ Person(name,age){
// this.name = name;
// this.age = age;
//}
Person.valueOf == Object.prototype.valueOf //返回true
你看Person一路向上查,顺利的调用到了Object的prototype的valueOf方法
那么问题就好办了,如果要Student类要继承Person,或者说, Student.prototype里面查不到,能继续沿着这个链条往上查 Person.prototype,最简单的办法是什么?
Student.prototype 是 Person 构造出来的就好了。 就这么简单。。。。。。。
你看看最开始那段代码,有个叫老李的是吧,就是li,他是用Person构造出来的,就先让他当老王儿子的干爹吧( ̄︶ ̄)↗
把中间那四行代码替换一下
function Person(name,age){
this.name = name;
this.age = age;
} Person.prototype.sayHi = function (){
alert("Hi,I'm " + this.name);
} var wang = new Person('老王',30);
var li = new Person('老李',22); function Student(name, age, grade){
Person.call(this, name, age);
this.grade = grade;
} //--------下面四行注释掉
//function F(){};
//F.prototype = Person.prototype;
//Student.prototype = new F();
//Student.prototype.constructor = Student; Student.prototype = li;
//-------- Student.prototype.sayHello =function(){
alert("Hello, I'm "+ this.name +", I'm in grade " + this.grade + ", nice to meet you.");
}; var wang_son = new Student('老王儿子',8,'二年级');
wang_son.sayHi();//Hi,I'm 老王儿子
wang_son.sayHello();//Hello, I'm 老王儿子, I'm in grade 二年级, nice to meet you.
wang_son.sayHi == wang.sayHi; //结果是true
然而最神奇的是这么干竟然成了,这叫啥?干爹继承法?
有点反直觉吧。控制台里面直接打li看输出,干爹老李背负了儿子辈的sayHello方法。
Person {name: "老李", age: 22, sayHello: ƒ}
age: 22
name: "老李"
sayHello: ƒ ()
__proto__: Object
其实没必要非得老李,现场临时生成一个干爹就成了,把老李那句改成。
Student.prototype = new Person();
一样的效果。
问题来了,继承问题好像完美解决了,一句话能搞定,干嘛写四句。原因是这么干还不完美。
主要问题
1.Student.prototype.constructor 应该指向该类的构造函数,既Student。如果只找个干爹,不做处理,那么会沿着原型链向上查找,一直找到有这个属性的prototype为止。
上面的代码构造出来的实例,wang_son.constructor返回的结果是Person。对应这个问题,一句话修正掉,这里不难理解。
Student.prototype.constructor = Student;
2.wang_son构造函数里面调用了父类Person的构造函数来初始化自己的属性name,age,而此时li里面也有老李的name,age。虽然老李是老王儿子构造函数的prototype,但是老王儿子自己有name,age属性了,wang_son是访问不到老李的属性的。同理,如果是new Person()构造一个实例充当prototype,里面也有name和age,只不过值是undefined,也是死活访问不到的。可见,我们对这个干爹的属性是毫不关心的。
这些属性铁定会被子类实例自己的属性覆盖掉,留着没用,那干脆就不要了,同时也省内存开销。
只要这个干爹能引导我们找到Person的prototype就行,其它无所谓。于是有了下面的代码
function F(){};
F.prototype = Person.prototype;
Student.prototype = new F();
把构造函数弄成空的,prototype设成父类的,这样一来生成的中间实例里面干干净净,并没有多余的属性。而且继承了父类的原型链。
看吧,很简单吧。
把这四句单独打包成函数,往后就可以随便继承了
function inherits(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
没了
js原型链继承的傻瓜式详解的更多相关文章
- js原型链+继承 浅析
名称: prototype--原型对象 __proto__--属性 原型链与继承网上搜索定义,看起来挺绕的 .先说继承: 所有的对象实例都可以共享原型对象包含的属性和方法 例如一个实例A ...
- [js高手之路]原型对象(prototype)与原型链相关属性与方法详解
一,instanceof: instanceof检测左侧的__proto__原型链上,是否存在右侧的prototype原型. 我在之前的两篇文章 [js高手之路]构造函数的基本特性与优缺点 [js高手 ...
- [js]js原型链继承小结
这是之前总结的, 发现有很多的毛病,就是重点不突出,重新翻看的时候还是得耗费很长时间去理解这玩意. js中的继承 js中什么是类 1,类是函数数据类型 2.每个类有一个自带prototype属性 pr ...
- JS 原型链 prototypt 和隐式原型 _proto_
prototype(原型) : 对象的一个属性,此属性使您有能力向对象添加属性和方法,当访问对象不存在属性是会自动到 prototype 中找 _proto_(隐式原型): 此对象构造函数(类)的原 ...
- JS原型链继承
继承普通版 继承逻辑上都差不多,普通版调用方式比较繁琐,不利于反复大量的使用: (function (){ //创建一个人员类 function Person(name){ this.name = n ...
- js原型链继承及调用父类方法
方法1: var Parent= function () { }; Parent.prototype.process = function(){ alert('parent method'); }; ...
- PowerDesigner15在win7-64位系统下对MySQL 进行反向工程以及建立物理模型产生SQL语句步骤图文傻瓜式详解
1.安装PowerDesigner15.MySQL5.不详细讲解了.网上一大把.请各位亲参考去. 2.安MyODBC-standard-3.51.0.7-win.msi.mysql-connector ...
- js重点--原型链继承详解
上篇说过了关于原型链继承的问题,这篇详解一下. 1. function animals(){ this.type = "animals"; } animals.prototype. ...
- JS原型链与继承别再被问倒了
原文:详解JS原型链与继承 摘自JavaScript高级程序设计: 继承是OO语言中的一个最为人津津乐道的概念.许多OO语言都支持两种继承方式: 接口继承 和 实现继承 .接口继承只继承方法签名,而实 ...
随机推荐
- Protocol Buffer学习教程之开篇概述(一)
1. Protocol Buffer是什么 Protocol Buffer是google旗下的产品,用于序列化与反序列化数据结构,但是比xml更小.更快.更简单,而且能跨语言.跨平台.你可以把你的数据 ...
- Java TCP通信
1.Socket原理 1)Socket简介 socket通常称作“套接字”,用于描述IP地址和端口号,是一个通信链的句柄.在Internet上的主机一般运行了多个服务软件,同时提供几种服务.每种服务都 ...
- 七、vue中将token存到cookie
使用js-cookie工具: 1.npm i js-cookie //安装2.import Cookies from 'js-cookie' //引用 // 存入cookie:Cookies.set( ...
- VS开发软winform软件的更改用户使用权限
在使用软件的过程中,我们经常需要使用的软件拥有管理员权限,在开发的过程中,本人就遇到了应为权限不足的问题导致软件不能正常使用的状况. 在此我来记录我遇到的问题. 为开发的软件赋予管理员权限 https ...
- Jquery二维码在线生成(不能生成图片文件)
附件地址:http://files.cnblogs.com/files/harxingxing/jQuery%E4%BA%8C%E7%BB%B4%E7%A0%81%E5%9C%A8%E7%BA%BF% ...
- Vmware 安装CentOS7时连不上网问题的解决
在VmWare 上安装Centos7时,装好vmware后还是连不上网,通过查找资料原来是因为有线网卡没有激活,默认centos和redhat7都是不启用有线网卡的,要么手动开启,要么安装时直接启用! ...
- 使用的是html5的canvas将文字转换成图片
当前功能的运用场景是:用户需要传文件给他人,在用户选择文件之后需要显示一个文件图标和所选文件的名称. 当前代码部分是摘自网上,但是已经忘记在什么地方获取的,如有侵权联系小弟后自当删除. 注意:必须在h ...
- Codevs1082 线段树练习 3
题目描述 Description 给你N个数,有两种操作: 1:给区间[a,b]的所有数增加X 2:询问区间[a,b]的数的和. 输入描述 Input Description 第一行一个正整数n,接下 ...
- init0-6(启动级别)
一. init是Linux系统操作中不可缺少的程序之一. 所谓的init进程,它是一个由内核启动的用户级进程. 内核自行启动(已经被载入内存,开始运行,并已初始化所有的设备驱动程序和数据结构等)之后, ...
- centos7安装pgsql及操作命令
1.下载所需要的数据库版本https://yum.postgresql.org/repopackages.php 2.安装数据库版本包 yum install -y https://download. ...