JavaScript面向对象
一. 理解原型
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();