第四章-面向对象编程

1 面向对象

  一般地, 类是对象的类型模板, 实例是根据类创建的对象

  但是在JavaScript中不区分类和实例, 而是通过原型(prototype)来实现面向对象编程

  JavaScript不区分类和实例的情况就是, 实例是一个对象, 我们想要创建的类也是一个对象, 用

实例对象.__proto__ = 类对象;

  来表明继承, 而且由于是对一个属性进行设定, 因此只能单继承

  使用原型相当于继承

  

  但是如果再给xiaoming绑定一个_proto_, 那原来绑定的东西就没有了(单继承)

  使用__proto__并不是一般的使用方式, 一般的时候方式是编写一个类似于new的方法来创建一个对象

  可以使用

Object.create(类对象);

  来返回一个基于类对象的空的对象, 通过对该对象的属性进行绑定, 可以完成创建一个机遇类对象的对象(实例), 具体如下

// 原型对象(类):
var Student = {
    name: 'Robot',
    height: 1.2,
    run: function () {
        console.log(this.name + ' is running...');
    }
};

function createStudent(name) {
    // 基于Student原型创建一个新对象:
    var s = Object.create(Student);
    // 初始化新对象:
    s.name = name;
    return s;
}

var xiaoming = createStudent('小明');
xiaoming.run(); // 小明 is running...
xiaoming.__proto__ === Student; // true

2 创建对象

  对象访问属性的过程:

    在当前对象查找, 没有找到就找它原型对象有没有, 再没有就找object的原型上查找, 如果都没查找到, 就返回undefined

xiaoming -> xiaoming.prototype -> object.prototype -> null

  constructor

    该属性会获得该对象的prototype的源对象

  关于prototype和constructor的关系

  

  构造函数 

    由于之前都是类是一个对象, 在创建一个函数来生成一个实例

    有没有类似于class类的一步到位既创建了类有可以生成实例的呢, 那就是构造函数了

    构造函数实际上就是一个普通的函数, 函数内容就类似于定义一个类, 在生成对象的时候, 是调用new 函数名来创建

    但是注意的是, 构造函数中的this指的就是新创建的对象, 而且该函数会默认返回this, 所以不要自己单独写return

    由于构造函数与普通函数并没有区别, 因此也可以像普通函数那样直接使用, 但是这样场产生的this会出现异常, 因此为了区分普通函数与构造函数

    一般地:  

      构造函数首字母大写

      普通函数首字母小写

    

  另外, 继承自相同类的不同对象, 得到的方法尽管在功能上是相同的, 但是是不一样的函数, 类似于python中绑定方法

  要使不同的对象共享一个函数, 可以针对父类的prototype来绑定原来放在父类中的函数

function Student(name) {
    this.name = name;
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
};

  在JavaScript中还可以写一个函数来专门封装new, 同时将传入的参数也一并用对象的方式整体传入, 这样就完成了一个封装性更高, 可用性更强的类

function Student(props) {
    this.name = props.name || '匿名'; // 默认值为'匿名'
    this.grade = props.grade || 1; // 默认值为1
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
};

function createStudent(props) {
    return new Student(props || {})
}

var xiaoming = createStudent({
    name: '小明'
});

xiaoming.grade;

  这样设置有一个很大的好处, 那就是传参十分方便, 因为实际上定义的props是一个对象数据类型, 绑定好了就不需要顺序对应, 想什么时候取值就什么时候取值

3 原型继承

  由于JavaScript采用原型继承, 不存在Class这种类型, 因此无法像Java那样使用类的扩展来继承

  要想继承一个类, 需要想办法把原型链修改为

new PrimaryStudent() ----> PrimaryStudent.prototype ----> Student.prototype ----> Object.prototype ----> null

  可以参考道格拉斯的方法, 在中间创建一个空的函数来桥接

// PrimaryStudent构造函数:
function PrimaryStudent(props) {
    Student.call(this, props);
    this.grade = props.grade || 1;
}

// 空函数F:
function F() {
}

// 把F的原型指向Student.prototype:
F.prototype = Student.prototype;

// 把PrimaryStudent的原型指向一个新的F对象,F对象的原型正好指向Student.prototype:
PrimaryStudent.prototype = new F();

// 把PrimaryStudent原型的构造函数修复为PrimaryStudent:
PrimaryStudent.prototype.constructor = PrimaryStudent;

// 继续在PrimaryStudent原型(就是new F()对象)上定义方法:
PrimaryStudent.prototype.getGrade = function () {
    return this.grade;
};

// 创建xiaoming:
var xiaoming = new PrimaryStudent({
    name: '小明',
    grade: 2
});
xiaoming.name; // '小明'
xiaoming.grade; // 2

// 验证原型:
xiaoming.__proto__ === PrimaryStudent.prototype; // true
xiaoming.__proto__.__proto__ === Student.prototype; // true

// 验证继承关系:
xiaoming instanceof PrimaryStudent; // true
xiaoming instanceof Student; // true

  可以简化为

function inherits(Child, Parent) {
    var F = function () {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}

  复用方式为

function Student(props) {
    this.name = props.name || 'Unnamed';
}

Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
}

function PrimaryStudent(props) {
    Student.call(this, props);
    this.grade = props.grade || 1;
}

// 实现原型继承链:
inherits(PrimaryStudent, Student);

// 绑定其他方法到PrimaryStudent原型:
PrimaryStudent.prototype.getGrade = function () {
    return this.grade;
};

4 class继承

  由于原型继承尽管简单, 但是理解起来困难, 因此ES6提供了class继承

  编写类基本格式如下

class 类名{
    constructor(参数列表){
        构造函数函数体;
    }    

    函数名(参数列表){
        一般函数的函数体;
    }    
}

  具体定义和使用如

class Student {
    constructor(name) {
        this.name = name;
    }

    hello() {
        alert('Hello, ' + this.name + '!');
    }
}

var xiaoming = new Student('小明');
xiaoming.hello();

  继承的基本格式如下

class 子类名 extends 父类名{
    constructor(参数列表){
          super(参数列表);
          子类构造方法体;
    }
    
    方法名(参数列表){
        函数体;    
    }

}    

  具体代码为

class PrimaryStudent extends Student {
    constructor(name, grade) {
        super(name); // 记得用super调用父类的构造方法!
        this.grade = grade;
    }

    myGrade() {
        alert('I am at grade ' + this.grade);
    }
}

  由于现在不是全面支持ES6, 因此现在还不普及, 但是如果真的要使用的话, 可以使用Babel工具来将写好的class继承转化为原型继承    

原文地址:https://www.cnblogs.com/weihuchao/p/6912291.html