Javascript学习笔记-面向对象编程

类和实例是大多数面向对象编程语言的基本概念,不过在JS中不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。原型是指当我们想要创建xiaoming这个具体的学生时,我们并没有一个Student类型可用。那怎么办?

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

var xiaoming = {
    name: '小明'
};
// 把xiaoming的原型指向了对象Student
xiaoming.__proto__ = Student;
xiaoming.name; // '小明'
xiaoming.run(); // 小明 is running...

JS没有类的概念,所有对象都是实例,所谓继承关系就是把一个对象的原型指向另一个对象而已,如果你把xiaoming的原型指向其他对象:

var Bird = {
    fly: function () {
        console.log(this.name + ' is flying...');
    }
};

xiaoming.__proto__ = Bird;
xiaoming.fly(); // 小明 is flying...

请注意,在JS中不要直接用obj.proto去改变一个对象的原型,并且低版本的IE也无法使用proto。Object.create()方法可以传入一个原型对象,并创建一个基于该原型的新对象,但是新对象什么属性都没有,因此我们可以编写一个函数来创建xiaoming:

// 原型对象:
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

实例对象

JS可以用构造函数的方法来创建对象,它的用法是先定义一个构造函数:

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

// 在JS中用关键字new调用函数返回对象:
var xiaoming = new Student('小明');
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!

这里请注意,如果不写new,这就是一个普通函数并返回undefined。但是如果写了new,它就变成了一个构造函数,它绑定的this指向新创建的对象并默认返回this,也就是说不需要在最后return this。用new创建的对象还从原型上获得了一个constructor属性,它指向函数Student本身:

xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student; // true
Object.getPrototypeOf(xiaoming) === Student.prototype; // true
xiaoming instanceof Student; // true

Student.prototype指向的对象就是xiaoming、xiaohong的原型对象,这个原型对象有个属性constructor指向Student函数本身,但是xiaoming、xiaohong这些对象没有prototype属性,不过可以用proto这个非标准用法来查看,现在我们就认为xiaoming、xiaohong这些对象“继承”自Student。

xiaoming.name; // '小明'
xiaohong.name; // '小红'
xiaoming.hello; // function: Student.hello()
xiaohong.hello; // function: Student.hello()
xiaoming.hello === xiaohong.hello; // false

xiaoming和xiaohong调用的hello是一个函数,但它们是两个不同的函数,虽然函数名称和代码都是相同的。要让创建的对象共享一个hello函数,根据对象的属性查找原则只要把函数移动到对象原型上就可以了,也就是Student.prototype:

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

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

如果创建对象时忘记写new,在strict模式下,this.name = name将报错,因为this绑定为undefined,在非strict模式下,this.name = name不报错,因为this绑定为window,于是无意间创建了全局变量name并返回undefined,这个结果更糟糕。为了区分普通函数和构造函数,按照规定构造函数首字母应当大写,而普通函数首字母小写:

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; // 1

如果创建的对象有很多属性,我们只需要传递需要的某些属性,剩下的属性可以用默认值。

原型继承

在传统的基于类和实例的语言中,继承的本质是类型的拓展,由于JS采用了原型继承,所以不存在类。我们想要在JS中实现继承可以借助于中间函数,用inherits()函数进行封装还可以隐藏中间函数的定义,并简化代码:

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;
};

JS的原型继承实现方式如下:

  1. 定义新的构造函数,并在内部用call()调用希望“继承”的构造函数,并绑定this;
  2. 借助中间函数实现原型链继承,最好通过封装的inherits函数完成;
  3. 继续在新的构造函数的原型上定义新方法;

类继承

我们知道JS的对象模型是基于原型实现的,特点是简单,但是实现继承需要大量代码。ES6引入了新的关键字class来定义类:

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

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

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

用class直接通过extends来实现继承:

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

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

注意,因为不是所有的主流浏览器都支持ES6的class,现在使用还不太方便,不过可以用Babel这个工具把class代码转换为传统的prototype代码。

你可能感兴趣的:(Javascript学习笔记-面向对象编程)