面向对象
1、类与实例
1.1.类的声明
eg:
1. function Animal(){
this.name = "name";
}
2.ES6中类(class)的声明
class Animal2{
constructor(){
this.name = name;
}
}
1.2.(如何通过类实例化生成对象)生成实例
eg:
//如果构造函数后面没有参数,new后面这个()是可以不要的;
1.console.log(new Animal(),new Animal2());
2、类与继承
2.1.如何实现继承?
2.2.继承的几种方式、各个形式都有什么优点和缺点?
继承的本质(原理)就是原型链。
1.借助构造函数实现继承
(只实现了部分继承,如果父类的属性都在构造函数上,那没有问题;
如果父类的原型对象上还有方法,子类是拿不到这些方法的)
function Parent1(){
this.name = "parent1";
}
function Child(){
//call和apply改变的是函数运行上下文,把父级(Parent1这个构造函数)在子函数里执行的话,
同时修改了父级构造函数this的指向,从而导致了父类执行的时候属性都会挂在Child类实例上去。
//将父级的构造函数的this指向一个子构造函数的实例上去,父级构造函数所有的属性在子类中也有
Parent1.call(this);
this.type="child1";
}
console.log(new Child());
缺点:Parent1原型链上的东西并没有被Child所继承;
没有继承父类原型对象上的方法,导致的并没有真正的实现继承;
function Parent1(){
this.name ="Parent1";
}
Perent1.prototype.say=function(){};
function Child1(){
Parent1.call(this);
this.type="child1";
}
console.log(new Child1(),new Child1().say());//say()不是一个function
2.借助原型链实现继承(弥补构造函数实现继承不足)
function Parent2(){
this.name ="parent2";
}
function Child2(){
this.type="child2";
}
//prototype作用为了让这个构造函数(Child2)的实例能访问到原型对象上
Child2.prototype = new Parent2();
console.log(new Child2()); //Child2{type:"child2",__proto__:Parent2}
console.log(new Child2().__proto__);//.__proto__===Child2.prototype
new Child2.__proto__.name;--->"Parent2"
缺点:
function Parent2(){
this.name ="parent2";
this.play=[1,2,3];
}
function Child2(){
this.type="child2";
}
Child2.prototype = new Parent2();
console.log(new Child2());
var s1= new Child2();
var s2= new Child2();
console.log(s1.play,s2.play);//[1,2,3];[1,2,3]
s1.play.push(4);
console.log(s1.play,s2.play);//[1,2,3,4];[1,2,3,4]
在一个类上实例了两个对象,改第一个对象的属性,第二个对象也跟着改变;
引起这个问题的原因:原型链上的原型对象是共用的;
s1.__proto__===s2.__proto__;//true
3.组合方式(实现继承的通用方式)
function Parent3(){
this.name ="Parent3";
}
function Child3(){
Parent3.call(this);//原型链上执行了1次
this.type="child3";
}
//父类的构造函数执行了2次
Child3.prototype = new Parent3(); //new的时候执行了一次
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play,s4.play);
console.log(s3.constructor);//Parent3
----因为prototype直接拿的父类的实例,它没有自己的constructor,它的constructor是从这个
实例中继承的,也就是原型链上一级拿过来的,它拿过来的肯定是Parent3的constructor;
缺点:父类的构造函数执行了2次
4.(组合继承的优化)
//构造函数体内通过两个构造函数的组合能拿到所有构造函数体内的属性和方法
function Parent4(){
this.name ="Parent4";
}
function Child4(){
Parent4.call(this);//父类只执行了一次
this.type="child4";
}
//对象引用类型(优点:原型对象是只是一个简单的引用,不会执行父级的构造函数)
Child4.prototype = Parent4.prototype;
var s5 = new Child4();
var s6 = new Child4();
s5.play.push(4);
console.log(s5.play,s6.play);
//判断这个对象是不是这个类的实例
console.log(s5 instanceof Child4,s5 instanceof Parent4);//true,true
问题:怎么区分一个对象是直接由它的子类实例化的,还是它的父类实例化的?
(怎么区分s5是是Child4的实例还是Parent4直接实例化的?)
//指向了父类Parent4{this.name="parent4";this.play=[1,2,3]}
console.log(s5.constructor);
---prototype里面有一个属性constructor,而我们的子类原型对象和父类的原型对象是一个对象,这个对象的constructor
就是父类里的constructor,这个父类的constructor指的是Parent4自己;
----因为prototype直接拿的父类的实例,它没有自己的constructor,它的constructor是从这个
实例中继承的,也就是原型链上一级拿过来的,它拿过来的肯定是Parent4的constructor;
5.组合继承优化2(完美版)
function Parent5(){
this.name ="name";
this.play=[1,2,3];
}
function Child5(){
Parent5.call(this);
this.type="Child5";
}
//Object.create创建对象的原理:
//--- Child4.prototype = Parent4.prototype;引用同一个对象,说白是一个对象,缺点:它俩的构造函数指向的是一个
(constructor是一个)无法区分实例是由父类创建的还是子类创建的;
//---Object.create(Parent5.prototype);创建中间对象方法,就把两个原型对象区分开,这个中间对象还具备一个特性就是它的原型
对象是父类的原型对象,这样在原型链上又开始连起来了。
--通过在给Child5原型对象上的constructor做修改,就能正常区分父类和子类的构造函数了。
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;
var s7 = new Child5();
var s8 = new Child5();
s6.play.push(4);
console.log(s7 instanceof Child5,s7 instanceof Parent5);//true true
consoel.log(s7.constructor); //Child5
***为什么通过Object.create方法创建的这个对象就能作为它俩之间的桥梁呢?
Object.create创建的对象有什么特点?
--Object.create它创建的这个对象,原型对象就是参数;
原理:Child5实例的原型对象等于Child5.prototype,Child5.prototype又等于Object.create方法创建的对象,这个中间对象原型对象又是
(prototype)父类的原型对象,所以一级的上一级的上一级,通过这样找实现了继承;而且还达到了父类和子类原型对象的隔离;
对象通过.__proto__ (这个属性往上找它的原型对象的)一级一级往上找;