已复制
全屏展示
复制代码

JavaScript面向对象


· 5 min read

一. 理解原型

JavaScript和其他语言不同,它不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。原型是指当我们想要创建 xiaoming 这个具体的学生时,我们并没有一个 Student 类型可用。此时恰好有这么一个现成的 Student 对象。

// 一个 Student 对象
var Student = {
    name: 'Robot',
    height: 1.6,
    run: function () {
        console.log(this.name + ' is running...');
    }
};

// 创建 xiaoming 对象
var xiaoming = {
    name: '小明'
};

// 把 xiaoming 的原型指向了对象 Student
xiaoming.__proto__ = Student;


// xiaoming 有自己的 name属性,但并没有定义 run() 方法。
// 由于小明是从 Student 继承而来,只要 Student 有 run()方法,xiaoming也可以调用。
xiaoming.name;        // '小明'
xiaoming.run();       // 小明 is running...


// 如果 xiaoming 把自己的原型指向其他对象,那 xiaoming 也就有了其他对象的属性和方法了。

JavaScript的原型链和Java的Class区别就在,它没有“Class”的概念,所有对象都是实例,所谓继承关系不过是把一个对象的原型指向另一个对象而已。

请注意,上述代码仅用于演示目的。在编写JavaScript代码时,不要直接用 obj.__proto__去改变一个对象的原型。Object.create() 方法可以传入一个原型对象,并创建一个基于该原型的新对象,因此,我们可以编写一个函数来创建 xiaoming。

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

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

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

二. 原型链

JavaScript对每个创建的对象都会设置一个原型,指向它的原型对象。当我们用obj.xxx访问一个对象的属性时,JavaScript引擎先在当前对象上查找该属性,如果没有找到,就到其原型对象上找,如果还没有找到,就一直上溯到 Object.prototype 对象,最后,如果还没有找到,就只能返回 undefined。

// 创建一个对象 arr
// 它的原型链是 
// arr -> Array.prototype -> Object.prototype -> null
// Array.prototype定义了indexOf()、shift()等方法
// 因此你可以在所有的 Array 对象上直接调用这些方法
var arr = [1, 2, 3];


// 函数也是一个对象
// 它的原型链是
// foo -> Function.prototype -> Object.prototype -> null
// 由于Function.prototype定义了apply()等方法,因此,所有函数都可以调用apply()方法
function foo() {
    return 0;
}

三. 构造函数

JavaScript中普通的函数可以当做构造函数使用,可以用关键字new来调用这个函数,并返回一个对象。注意,如果不写new,这就是一个普通函数,它返回undefined。如果写了new,它就变成了一个构造函数,它绑定的 this 指向新创建的对象,并默认返回 this

// 创建构造函数,它可以不传参数,也可以传入有限对象参数
function Student(props) {
    this.name = props.name || 'Unnamed'; // 默认值为'Unnamed'
    this.grade = props.grade || 1;       // 默认值为1
}

// 为了让创建的对象共享一个hello函数,节省内存空间
// 把函数移动到 xiaoming、xiaohong 这些对象共同的原型上
Student.prototype.hello = function () {
    alert('Hello, ' + this.name + '!');
}

// 为了避免创建对象时忘记写 new 关键字,把创建对象写到一个函数里面
function createStudent(props) {
    return new Student(props || {})
}



var xiaoming = createStudent({
    name: '小明'
});
xiaoming.name;         // '小明'
xiaoming.hello();      // Hello, 小明!



// xiaoming 的原型链如下
// 也就是说,xiaoming 的原型指向函数 Student 的原型。
// 如果你又创建了 xiaohong、xiaojun,那么这些对象的原型与 xiaoming 是一样的
xiaoming -> Student.prototype -> Object.prototype -> null


// 用 new Student() 创建的对象还从原型上获得了一个 constructor 属性,它指向函数 Student 本身
xiaoming.constructor === Student.prototype.constructor; // true
Student.prototype.constructor === Student;              // true
Object.getPrototypeOf(xiaoming) === Student.prototype;  // true
xiaoming instanceof Student;                            // true

四. 原型继承

https://www.liaoxuefeng.com/wiki/1022910821149312/1023021997355072

五. class 类

JavaScript的对象模型是基于原型实现的,特点是简单,缺点是理解起来比传统的类-实例模型要困难,最大的缺点是继承的实现需要编写大量代码,并且需要正确实现原型链。新的关键字class从ES6开始正式被引入到JavaScript中。class的目的就是让定义类更简单。

// 定义了构造函数 constructor
// 定义在原型对象(Student.__proto__)上的函数 hello()
class Student {
    constructor(name) {
        this.name = name;
    }

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

// 创建对象
var xiaoming = new Student('小明');
xiaoming.hello();

六. class 继承

ES6引入的class和原有的JavaScript原型继承有什么区别呢?实际上它们没有任何区别,class的作用就是让JavaScript引擎去实现原来需要我们自己编写的原型链代码。简而言之,用class的好处就是极大地简化了原型链代码。

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

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


// 创建对象
var xiaohong = new PrimaryStudent('小明');
xiaohong.myGrade();

🔗

文章推荐