什么是继承
大多数使用继承不外乎是为了获得两点好处,代码的抽象和代码的复用。
代码的抽象不用说了,交通工具和汽车这类的例子数不胜数,在传统的语言中(java)代码的抽象更多是使用接口来实现,而试验继承更多的是为了代码的复用。
怎么复用的?打个比方,classa继承了classb,classa便拥有了classb的公有和私有的变量和方法,便是classb将这些属性和方法直接copy给classa。这样便实现了继承。因此我们可以这样说,继承实际上是一种类与类之间的copy行为。从另一个方面来说,js的数据类型分文基本数据类型和引用数据类型,我们在对数据进行复制的时候,如果这个数据是基本数据类型那很好办,直接赋值就好,如果在使用js对对象或数组进行操作的时候,经常要将数组进行备份,事实证明如果只是简单的将它赋值其他变量,那么我们只要更改其中的任何一个,其他的也会跟着改变,这就导致了问题的发生。
js中的继承
在js中没有类的概念,只有对象。虽然,现在人们经常使用class关键字,这让js看起来似乎是拥有了“类”,可表面看到的不一定是本质,class只是一块糖,嚼碎了才知道里面其实还是原型链那一套。因此,js中的继承只是对象与对象之间的继承。反观继承的本质,继承便是让子类拥有父类的一些属性和方法,那么在js中便是让一个对象拥有另一个对象的属性和方法。
所以,这给我们一条十分清晰的思路。js中如何实现继承?只需让一个对象拥有另一个对象的属性和方法,这就实现了。
浅拷贝
既然让一个对象拥有另一个对象的属性和方法,首先想到的便是粗暴的方式,直接将对象的属性和方法强制copy到另一个对象。
var person={
name:'cj';
age:22;
}
var programer={
language:'js'
}
function extend(p,c){
var c={};
for(var prop in p){
c[prop]=p[prop]
}
}
局限性:如果person
加上一个adress
对象即address{home:'home address'}
当programer.address.home='home'
即改变时,person.address.home
也跟着改变,因为两个对象指向的是同一个地址,拷贝的只是引用,也就是浅拷贝。
深拷贝
var person={
name:'cj';
age:22;
adderss{name:'home address'}
}
var programer={
language:'js'
}
function extend(p,c){
var c={};
for(var prop in p){
if(typeof p[prop] ==='object'){
c[prop]=p[prop].constructor===Array?[]:{};
extend(p[prop],c[prop])
}else{c[prop]=p[prop]}
}
}
深拷贝用递归的方式来复制子对象里面的所有属性和方法,直到子子属性为基本数据类型。
原型链继承
如何使用原型链来实现继承呢?这要归功于js中的委托机制。
当我们获取一个对象的某个属性时。比如a.b,会默认调用一个内置的方法,这个内置的方法就是:
在当前对象中查找,找不到则委托给当前对象的proto。再找不到则委托给proto的proto直到object.prototype中也没有找到,则返回undefind。
因此,我们想让对象a拥有对象b的属性和方法,即对象a继承对象b,只需要把b赋值给a的proto。利用属性的查找的委托机制,实现了a也拥有了b的属性和方法。
前面提到,proto是个内置隐藏属性,设计的本意是不可被读取和修改的,那么我们如何利用原型链来建立继承关系?js提供了new关键字。new关键字实现了a.proto=A.prototype这样只需把a的构造函数的prototype连接到b 就行了。
function A(){
}
var b={
show:function(){
console.log('这是来自b的方法')
}
}
A.prototype=b
A.prototype.constructor=A
var a=new A()
a.show//这是来自b的方法
核心:原型链继承子类只是继承了父类的原型链上的属性,并且在创建子类的时候不能向父类的构造函数传递参数。
call继承
function Parent(){
this.x=100;
this.y=199
}
Parent.prototype,fn=function(){}
function Child(){
this.a=100;
Parent.call(this)
}
var p=new Parent();
var c=new Child();
console.log(p)//Parent{x:100,y:199}
console.log(c)//Child{d:100,x:100,y:199}
这很好理解,在子类的构造函数中,改变父类的this指向,改变为子类的实例,同时运行父类方法,这样父类中的this.x就变成了子类的实例.x。这样只能继承父类的私有属性和方法。
call继承和原型继承集合在一起。
===》无论是私有还是公有的属性都拿到了。
function Parent(){
this.x=100;
}
Parent.prototype.getx=function(){}
function Child(){
Parent.call(this)
}
Child.prototype=new Parent();
Child.prototype.constructor=Child;
var p=new Parent();
var c=new Child();
console.log(c)
create继承
var b={c:1}
var a=Object.create(b)
console.log(a.c)//1
那么create继承是怎样实现地呢?
var p={name:'cj'}
function create(p){
var ins;
function F(){
F.prototype=p;
ins=new F();
return ins;
}
}
var c=create(p)
c.name==>'cj'
上例中ins.__proto__=F.prototype
又因为F.prototype=p
所以c.name==>'cj'
继承
-
继承的作用
继承可以使一个对象直接使用另一个对象的方法和属性。
-
代码的区别
//方法1
function People(name, sex){
this.name = name;
this.sex = sex;
this.printName = function(){
console.log(this.name);
}
}
var p1 = new People('饥人谷', 2)
//方法2
function Person(name, sex){
this.name = name;
this.sex = sex;
}
Person.prototype.printName = function(){
console.log(this.name);
}
var p1 = new Person('若愚', 27);
区别: 第二种方法将公有的属性放在Person的prototype上,当再创建一个对象实例的时候就不用像第一种方法那样把printName再创建一遍,而是从原型中调用就可以了。
-
Object.create的作用
Object.create() 方法会使用指定的原型对象及其属性去创建一个新的对象。
兼容性:
-
hasOwnProperty
作用:hasOwnPerperty是Object.prototype的一个方法,可以判断一个对象是否包含自定义属性而不是原型链上的属性,hasOwnProperty是JavaScript中唯一一个处理属性但是不查找原型链的函数。
语法:obj.hasOwnProperty(prop)(prop为要检测的属性名称)
-
call的作用
function Person(name, sex){
this.name = name;
this.sex = sex;
}
function Male(name, sex, age){
Person.call(this, name, sex); //在子类的构造函数中,改变父类的this指向,改变为子类的实例,同时运行父类方法,这样父类中的this.x就变成了子类的实例.x。这样只能继承父类的私有属性和方法。
this.age = age;
}
-
补全代码
function Person(name, sex){
this.name=name;
this.sex=sex;
}
Person.prototype.getName = function(){
console.log(this.name)
};
function Male(name, sex, age){
Person.call(this,name,sex)
this.age=age
}
Male.prototype=Object.create(Person.prototype)
Male.constructor=Male;
Male.prototype.getAge = function(){
console.log(this.age)
};
var ruoyu = new Male('若愚', '男', 27);
ruoyu.printName();