一、为什么要面向对象?
面向对象(OOP),对象就是一个容器,一般是类,它是面向对象编程的最小单元,面向对象编程的优点是逻辑迁移灵活,代码可复用性高,高度模块化。对象是一个容器,里边封装了方法和属性。方法为对象行为,属性为对象状态
//简单对象
const course = {
teacher: '云隐',
leader: '黄小杨',
startCourse: function(name) {
return `开始${name}课`;
}
}
//函数对象
function course(){
this.teacher='云隐';
this.leader='黄小杨';
this.tartCourse=function(name) {
return `开始${name}课`;
}
}
二、几种生成对象方式
1、通过构造函数来生成对象
生成对象需要一个模版,表征了一类物体的共同特征。类即对象模板,js本质不是基于类,而是基于构造函数和原型链----constructor+prototype
function Course() {
this.teacher = '云隐',
this.leader = '黄小杨',
}
const course = new Course();
1)、Course就是构造函数
2)、函数内部的this表示要生成的实例
3)、生成对象需要new来实例化
4)、可以做初始传参
构造函数不初始化,生成的实例无法使用
以下是兼容没有实例化的调用
function Course() {
if(!(this instanceof Course)){
return new Course();
}
this.teacher = '云隐',
this.leader = '黄小杨',
}
const course = Course();//这样调用这个方法一定是实例化后的对象
2、object.create
const foo = Object.create({})
代表foo对象的原型指向{},foo.proto === {},foo.proto.proto === Object.prototype
3、字面量赋值
const bar = {}
代表bar的原型链指向了Object的原型,bar.proto === Object.prototype
三、原型链
1、new是什么,new做了什么
function Course() {}
const course = new Course();
1)若方法没有返回一个对象,则创建一个空对象作为返回的对象实例。若有则返回这个对象,这个对象是没有构造器的。
2)将当前实例对象赋值给内部this
3)将返回的空对象的原型对象指向了构造函数的prototype属性,即course.proto == Course.prototype。若返回不是空对象,这个对象的原型对象不指向构造函数原型链
4)执行构造函数初始代码
实例属性之间会互相影响吗?-----不会
new 的实现:
function newFunc(Father){
var obj = Object.create(Father.prototype);
const result = Father.apply(obj,Array.prototype.slice.call(arguments, 1));
return result && typeof result === 'object' ? result:obj;
}
用new 的方式模拟实现Object.create
function inherit(p){
function f(){};
f.prototype = p;
f.prototype.constructor = f;
return new f();
}
2、constructor是什么?
1)每个对象创建时会自动拥有一个构造函数属性constructor
2)constructor继承自原型对象,指向构造函数的引用,即它的值就是构造函数
3)构造函数中的方法会存在于每个生成的实例中,重复挂载可能导致资源浪费
3、原型对象
function Course() {}
const course1 = new Course();
const course2 = new Course();
1)构造函数:用来初始化创建对象的函数-Course
:自动给构造函数赋予一个属性prototype,该属性实际等于实例对象的原型对象
2)实例对象:course1就是根据原型创建出来的实例对象
:每个实例对象中都有__proto__属性,即原型对象,
:每个实例对象都有constructor属性
:constructor由继承而来(即原型对象),并指向当前构造函数
3)原型对象:Course.prototye,原型对象包括constructor和_proto_
// 对上篇原型对象做优化
function Course(name) {
this.teacher = '云隐';
this.leader = '黄小杨';
console.log(this.startCourse(name))
}
Course.prototype.startCourse = function(name) {
return `开始${name}课`;
}
const course1 = new Course('es6');
const course2 = new Course('OOP');
实例对象可以直接访问原型链上的方法和属性,但是不能修改和删除,因为删除先删除自己的属性,修改先修改或者添加自己的属性,如果要修改原型对象必须是实例对象.proto.a这样去添加修改。
以上可以看到实例化的对象的原型对象__proto__指向构造函数的prototype属性,prototype属性里又包含__proto__和constructor,里面的__proto__指向Object.prototype属性,Object.prototype指向null。由图像显示
四、继承
1、继承方式
1)原型链继承----在原型对象的所有方法和属性都能被实例共享
// Game类
function Game() {
this.name = 'lol';
}
Game.prototype.getName = function() {
return this.name;
}
//LOL类
function LOL(){
}
//继承
LOL.prorotype = new Game();
LOL.prorotype.constructor = LOL;
const game = new LOL();
//本质是重写原型对象,将父对象的属性方法,作为子对象的原型对象方法属性
原型链继承缺点
function Game() {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
// LOL类
function LOL() {}
// LOL继承Game类
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
const game1 = new LOL();
const game2 = new LOL();
game1.skin.push('ss');//子类1修改会改变父类引用属性
缺点:1、父类属性一旦赋值给给子类的原型属性,此时属性就会被所有子类共享,其中一个通过原型对象改变父类属性,另一个子类也会受到影响
2、实例化子类时,不能向父类传参。
2)构造函数继承----在子类构造函数内部调用父类构造函数
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
//lol类
function LOL(arg){
Game.call(this,arg)
}
// LOL继承Game类
const game3 = new LOL();//这里的父类的属性都是子类自己的,实例化后不影响
它解决了共享性问题和传参问题,但是父类原型链上方法和属性无法读取访问。----组合继承
3)组合继承----构造函数和原型链一起继承
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
// LOL类
function LOL(arg) {
Game.call(this, arg);
}
// LOL继承Game类
LOL.prototype = new Game();
LOL.prototype.constructor = LOL;
const game3 = new LOL();
组合继承缺点就是实例化子类对象时会调用两次父类构造函数
一次是初始化子类原型对象时,一次是子类构造函数执行时
4)寄生组合继承----子类原型对象指向一个新的对象,新的对象的原型对象指向福类的原型对象
function Game(arg) {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
// LOL类
function LOL(arg) {
Game.call(this, arg);
}
LOL.prototype = Object.create(Game.prototype);//生成一个新对象,这个对象的原型对象__proto__指向Game.prototype
//这里也可以直接LOL.prototype = Game.prototype,但是这样做的话修改LOL.prototype属性会直接影响Game.prototype,也不好
LOL.prototype.constructor = LOL;
2、如何实现多重继承----混合父类
unction Game(arg) {
this.name = 'lol';
this.skin = ['s'];
}
Game.prototype.getName = function() {
return this.name;
}
function Store() {
this.shop = 'steam';
}
Store.prototype.getPlatform = function() {
return this.shop;
}
function LOL(arg){
Game.call(this,arg);
Store.call(this,arg);
}
LOL.prototype = Object.create(Game.prototype);
Object.assign(LOL.prototype,Store.prototype);//混合父类原型对象
LOL.prototype.constructor = LOL;
const game3 = new LOL();
3、class继承和组合寄生继承有什么区别?
class继承会继承静态属性,class子类中必须在constructor里使用super,因为子类自己的this对象,必须通过父类的构造函数完成。