大纲
- 类
- 继承
类
1.1.类的声明
// 类的声明
function Animal(name) {
this.name = name;
}
// ES6的class的声明
class Animal2 {
// 构造函数
constructor(name) {
this.name = name;
}
}
1.2.通过类来实例化一个对象
// 通过new关键字实例化一个对象
var animal1 = new Animal('dog');
var animal2 = new Aniaml2('pig');
二、继承
2.1.借助构造函数实现继承
// 借助构造函数实现继承
function Parent1() {
this.name = 'parent1';
this.say = function () {
console.log('hello');
}
}
Parent1.prototype.getName = function () {
return this.name;
}
function Child1() {
// call, apply: 改变函数运行的上下文
Parent1.call(this);
this.type = 'child1';
}
console.log(new Child1()); // Child1 {name: "parent1", say: ƒ, type: "child1"}
- 原理:将父级的构造函数的this执行子类构造函数的实例上
- 优点:可以继承父类构造函数的属性与方法
- 缺点:但是没有继承到Parent1原型对象上的方法
2.2.借助原型链实现继承
// 借助原型链实现继承
function Parent2() {
this.name = 'parent2';
this.arr = [1, 2, 3];
this.say = function () {
console.log('hello');
}
}
Parent2.prototype.getName = function () {
return this.name;
}
function Child2() {
this.type = 'child2';
}
Child2.prototype = new Parent2();
Child2.prototype.getChildName = function () {
return 'childName'
}
let c1 = new Child2();
let c2 = new Child2();
console.log(c1.__proto__ === Child2.prototype); // true
// 优点是能继承原型的属性和方法
// 缺点:因为通过子类构造出来的实例的__proto__隐形原型都相同,即:
console.log(c1.__proto__ === c2.__proto__); // true
// 导致了如果有一个实例修改了属性,那么所有通过实例出来的对象都会跟着改变
console.log(c1.arr); // [1, 2, 3]
console.log(c2.arr); // [1, 2, 3]
// 只是在c1对象中添加4
c1.arr.push(4);
// 然而c1,c2对象都已经跟着改变了
console.log(c1.arr); // [1, 2, 3, 4]
console.log(c2.arr); // [1, 2, 3, 4]
- 原理:在子类的原型链上面继承父类的实例对象,到达了在原型链上面能查询到父类属性和方法
- 缺点:因为通过子类构造出来的实例的__proto__隐形原型都相同即,导致了如果有一个实例修改了属性,那么所有通过实例出来的对象都会跟着改变
2.3.构造函数与原型组合方式继承
// 组合方式
function Parent3() {
this.name = 'parent3';
this.arr = [1, 2, 3];
this.say = function () {
console.log('hello');
}
}
function Child3() {
// 继承父类的构造函数的属性和方法
Parent3.call(this);
this.type = 'child3';
}
// 继承父类的原型对象的属性和方法
Child3.prototype = new Parent3();
let c1 = new Child3();
let c2 = new Child3();
console.log(c1.arr); // [1, 2, 3]
console.log(c2.arr); // [1, 2, 3]
// 这种继承方法通常叫组合方式继承,弥补了原型模式修改对象属性影响其他的实例问题
c1.arr.push(4);
console.log(c1.arr); // [1, 2, 3, 4]
console.log(c2.arr); // [1, 2, 3]
- 优点:弥补了原型模式修改对象属性影响其他的实例问题
- 缺点:每一次new调用都需要调用一次父类的调用:Parent3.call(this); 还有Child3.prototype = new Parent3();实际实例1次子类就实例化了2次父类。
2.4.构造函数与原型组合方式继承的优化方法1
- 优化方法1:在子类的原型对象上引用父类的原型对象
// 组合方式
function Parent4() {
this.name = 'parent3';
this.arr = [1, 2, 3];
}
function Child4() {
// 继承父类的构造函数的属性和方法
Parent4.call(this);
this.type = 'child3';
}
// 继承父类的原型对象的属性和方法
Child4.prototype = Parent4.prototype;
let c1 = new Child4();
let c2 = new Child4();
console.log(c1.arr); // [1, 2, 3]
console.log(c2.arr); // [1, 2, 3]
c1.arr.push(4);
console.log(c1.arr); // [1, 2, 3, 4]
console.log(c2.arr); // [1, 2, 3]
console.log(c1 instanceof Child4); // true
console.log(c1 instanceof Parent4); // true
console.log(c1.__proto__ === Child4.prototype); // true
console.log(c1.__proto__ === Parent4.prototype); // true
// 如何区分c1到底是子类实例化还是父类实例化?
console.log(c1.constructor); // 指向的是Parent4
// 理想上c1实例化的构造函数应该是指向Child4,实际上指向的是Parent4
- 原理:当通过new关键字实例化Child4时,已经生成了Parent4实例,让子类的原型对象指向父类的原型对象即可
- 优点:弥补了构造函数与原型组合方式继承多次调用父类实例的问题
- 缺点:无法识别通过子类实例化出来的对象的构造函数是由谁实例化的
2.5.构造函数与原型组合方式继承的优化方法2
- 优化方法:使用Object.create()方法创建新的对象,使子类的显式原型对象(prototype)等于通过Object.create()传入父类的原型对象新建出来的对象的隐式原型(__proto__)关联起来,然后再重新再把子类的(constructor)原型对象指引回它的构造函数。
- Object.create();
- 使用现有的对象来提供新创建的对象的__proto__
// 组合方式优化2
function Parent5() {
this.name = 'parent3';
this.arr = [1, 2, 3];
}
function Child5() {
// 继承父类的构造函数的属性和方法
Parent5.call(this);
this.type = 'child3';
}
// 继承父类的原型对象的属性和方法
// Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;
let c1 = new Child5();
let c2 = new Child5();
console.log(c1.arr); // [1, 2, 3]
console.log(c2.arr); // [1, 2, 3]
c1.arr.push(4);
console.log(c1.arr); // [1, 2, 3, 4]
console.log(c2.arr); // [1, 2, 3]
console.log(c1 instanceof Child5); // true
console.log(c1 instanceof Parent5); // true
console.log(c1.constructor); // 指向的是Child5
- 原理:使Object.create()传入了父类原型对象,使子类的prototype与新创建的对象的__proto__又重新关联起来了,那么再通过修改子类的prototype.constructor重新指向回它的构造函数,实现继承。
console.log(c1.__proto__ === Child5.prototype); // true
console.log(c1.__proto__ === Parent5.prototype); // false
- 优点:优化了构造函数和原型方式的缺点
三、封装DOM查询的原型链继承的例子
function Elem(id) {
this.elem = document.getElementById(id)
}
Elem.prototype.html = function (val) {
var elem = this.elem;
if (val) {
elem.innerHTML = val;
return this; // 链式操作
} else {
return elem.innerHTML;
}
}
Elem.prototype.on = function (type, fn) {
var elem = this.elem;
elem.addEventListener(type, fn);
return this;
}
// 用法
var box = new Elem('box');
console.log(box.html())
box.html("<p>hello world</p>").on('click', function () {
console.log('click');
}).html("<p>hello javascript</p>")