Javascript继承
1.原型对象补充:
01 构造函数有一个相关联的原型对象,这个原型对象默认是一个空对象{},该对象拥有一个constructor属性和__proto__
。
02 构造函数的原型对象本身是Object类型,即Person.prototype是Object类型;Person.prototype的构造函数也是Object类型。
03 使用构造函数创建出来的对象会拥有一个构造器属性(constructor),该属性指向创建当前对象的构造函数。
04对象本身并没有构造器属性,该属性是从原型对象中继承来的。
function Person(){
}
var p = new Person();
console.log(Person.prototype); //Object
var o = {};
console.log(o);
//访问的是地址为0x11的那个constructor
console.log(Person.prototype.constructor); //Person
//删除0x11的constructor
delete Person.prototype.constructor;
//访问的是原型对象上面的属性
console.log(Person.prototype.constructor); //Object
#2.属性拷贝和直接赋值的区别:
属性拷贝和直接赋值都会拷贝成员和原型对象。
通常应用属性拷贝 拷贝实例化属性|方法
属性拷贝:只有引用类型的属性会被共享。
直接赋值:所有的属性和方法都会被拷贝。
3.继承的几种实现方法:
- 属性拷贝(浅拷贝)
- 原型式继承(3种)
- 原型链继承
- 借用构造函数
- 组合继承
- 深拷贝+原型式继承
(1)属性拷贝(浅拷贝)
属性拷贝默认情况下,使用for...in循环拷贝时,会把原型属性和原型方法一并拷贝。
通常只要求拷贝实例成员,因此需要增加个判断。
function Person() {
this.name = "默认的名称";
this.friends = ["123","456","567"];
}
Person.prototype.hi = "hi";
var p1 = new Person();
var o = {};
//拷贝属性
for(var k in p1)
{
if(p1.hasOwnProperty(k)) //如果是p1的实例成员
{
o[k] = p1[k];
}
}
console.log(o);
console.log(p1);
(2)原型式继承:
(原型式继承:child.prototype = Parent.prototype;)
-
利用对象的动态特性
eg:Person.prototype.des = "描述信息";
-
字面量直接替换
Person.prototype = { constructor:Person, des:"默认的描述信息" }
-
设置子构造函数的原型对象 = 父构造函数的原型对象
child.prototype = Person.prototype;
问题:第三种方式可以获得父构造函数原型对象上面的属性和方法,但无法获得父构造函数的实例对象属性和方法。
补充:扩展内置对象(Array,Function,Date,Object...)
- 把需要共享的属性和方法写在内置构造函数的原型对象上。
问题:
原型对象上面所有的属性和方法都会被该构造函数创建出来的对象共享,共享的话可能会出现覆盖问题,比较难以维护和管理。
- 安全的扩展内置对象。
-
提供一个自定义的构造函数(eg:myArray)
function MyArray(){}
-
设置该构造函数的原型对象为内置构造函数的一个实例
MyArray.prototype = new Array()
-
在构造函数的原型对象上面添加属性和方法
MyArray.prototype.name = "默认名称";
-
使用自定义的构造函数来创建对象并使用
var myArr = new MyArray();
-
(3)原型链继承:
(原型链继承:child.prototype = new Person();)
原型链结构:
1.js中所有的对象都是由构造函数创建出来的;
2.每个构造函数都有一个原型对象;
3.每个对象都有自己的构造函数;
4.每个构造函数的原型都是一个对象;
5.那么这个构造函数的原型对象也有自己的构造函数;
6.那么这个构造函数的原型对象的构造函数也有自己的原型对象。
原型链的顶端:Object.prototype
JS中,Object.prototype.proto = null;
原型链结构图解
原型链搜索规则:
对象在访问属性的属性时,先查找实例对象的成员,找不到就去它的原型对象上面查找,如果查找到Object.prototype也没有找到,就返回undefined(属性)和报错(方法)。
原型链注意点:
1.原型和实例的关系:
子类的原型对象为父类的一个实例。
2.注意重写原型对象的位置:
- 先重写原型对象,然后再覆盖超类的方法。(eg:children.prototype.constructor = Children)
- 建议在完成原型链继承之后再给原型对象设置属性和方法。
- 在设置完原型链继承之后,不能用字面量的方式来替换当前构造函数的原型对象。
问题:
1. 无法向父构造函数传递参数;
2. 父构造函数的实例属性变成了子构造函数创建出来对象的原型属性。如果父类型的实例属性是引用类型,则会存在共享问题。
补充:Object.Create()
Object.create():创建对象,并指定原型对象
eg:var o = Object.create(obj); //创建一个对象o,并指定这个对象的原型对象为obj
注意:该方法是ES5推出的,所以有兼容性问题。
Object.assign()
Object.assign(target,source1,source2...)
作用:拷贝多个对象的属性。
for...in:会连原型成员也拷贝
assign:不会拷贝对象的原型成员
(4)借用构造函数继承:
借用福构造函数获得父构造函数的实例属性。
(Parent.call(子构造函数,父构造函数的实例属性))
不足:无法获得父构造函数的原型成员。
(5)组合继承:
借用父构造函数,获取父构造函数的实例对象 + 原型式继承
原型式继承:Child.prototype = Parent.prototype;
借用父构造函数获取实例对象的不足:无法获得父构造函数的原型成员;
原型式继承不足:无法获得父构造函数的实例成员。
深拷贝
浅拷贝:for...in 引用类型会共享
深拷贝是对浅拷贝进行了一些改进
分析:浅拷贝只是用了一层for循环,当前拷贝的属性是个引用类型时,就只是把这个引用类型的地址复制了一份。此时就会存在共享问题。
所谓深拷贝就是进行多次浅拷贝,
(0)for in循环遍历当前对象
(1)先查找当前对象是否存在该属性
(2)判断属性是否为引用类型
(3)如果是引用类型,就再次递归,查看引用类型中的属性是否有值类型
(4)有的话就赋值一份值,如果没有就循环(3)
(5)直到找出所有的值,将值拷贝给另一个对象
(6) 深拷贝+原型式继承:
call|apply:借用父构造函数获得父构造函数的实例成员
深拷贝:将父构造函数原型对象上面的属性和方法拷贝给子构造函数的原型对象(原型式继承)
<script>
function deepCopy(obj1, obj2) {
obj1 = obj1 || {};
for (var k in obj2) {
if (obj2.hasOwnProperty(k)) {
if (typeof obj2[k] == "object") {
obj1[k] = Array.isArray(obj2[k]) ? [] : {};
deepCopy(obj1[k], obj2[k]);
} else {
obj1[k] = obj2[k];
}
}
}
}
function Person(name) {
this.name = name;
}
Person.prototype.hi = "hi";
Person.prototype.car = {
type:"奥迪"
};
function Boy(name, lol) {
//构造函数内部会默认创建一个空的对象,并且赋值给this
this.lol = lol;
Person.call(this,name); //Boy借用Person构造函数中的实例属性,
// 当Boy借用Person的实例属性时,Person里的this->Boy构造函数创建的对象,这里的name就是Person中的形参
}
//子构造函数的原型对象 = 父构造函数的原型对象
deepCopy(Boy.prototype, Person.prototype);
var p1 = new Person();
var boy = new Boy("张三","英雄联盟");
console.log(p1.car);
console.log(boy.car);
p1.car.type = "宝马";
console.log(p1.car); //宝马
console.log(boy.car); //奥迪
</script>