面向对象编程有三个特点: 继承,封装,多态
1.继承
ES6之后,一般就是类的继承。在类之前是构造函数的继承。
构造函数:静态属性和方法,原型对象属性和方法,构造函数内this定义的属性和方法。
对于静态属性的继承。
for(let key in SuperClass) { if(SuperClass.hasOwnProperty(key)) { SubClass[key] = SuperClass[key]; } }
其他的属性和方法的继承方法有如下:
1. 原型链继承-子类原型对象
将父类的实例赋值给子类的原型对象。
优点:
父类的属性和方法都能复用
缺点:
1. 子类实例化时可以向父类构造函数s a z传参进行初始化
2.父类上的引用类型的属性会被子类的实例共享。一个修改,引起其他实例访问的时候是修改后的属性。
语法:
SubClass.prototype = new SuperClass(); // 重新指定构造函数属性,否则它指向SuperClass SubClass.prototype.constructor = SubClass;
示例:
function SuperClass() { this.superValue = [1]; } SuperClass.prototype.getSuperValue = function() { console.log('super'); } function SubClass() { this.subValue = [2]; } // 父类的实例既包含父类构造函数内的属性和方法,也包含原型对象上的属性和方法 SubClass.prototype = new SuperClass(); SubClass.prototype.constructor = SubClass; // 缺点1,公共属性superValue是引用类型,实例访问属性的时候访问的是原型对象上的属性 const instance1 = new SubClass(); instance1.superValue.push('a'); console.log(instance1.superValue); //[1,'a'] instance1.getSuperValue(); //super 可以访问原型链上的方法 //在instance2未进行任何操作时访问superValue const instance2 = new SubClass(); console.log(instance2.superValue); // [1,'a']
2. 构造函数继承-创建即继承
在子类构造函数中调用父类的构造函数。将父类构造函数上的属性和方法复制到子类实例中。
优点:
1. 实例化子类时可以给父类构造函数传参进行初始化
2. 父类上的引用类型的属性不会被子类的实例共享。
缺点:
1. 无法继承原型链上的方法
2.如果想要继承原型链上的方法,必须将原型链上的方法放到构造函数中。违背了“代码共享”的原则。
语法:
function SubClass(props) { SuperClass.call(this, props);// 构造函数继承 }
示例:
function SuperClass(id) { this.id = id; this.books = ['js', 'html']; } SuperClass.prototype.getSuperBooks = function() { console.log(this.books); } function SubClass(props) { SuperClass.call(this, props);// 构造函数继承! } const instance1 = new SubClass(11); instance1.books.push('css'); console.log(instance1.books);// ['js', 'html', 'css'] console.log(instance1.id); // 11 -向父类构造函数传参并初始化 // 未继承原型链上的方法 instance1.getSuperBooks(); // ❌instance1.getSuperBooks is not a function const instance2 = new SubClass(12); console.log(instance2.books); // ['js', 'html'] 不受其他实例影响 console.log(instance2.id); // 12
3. 组合继承-原型链+构造函数
融合了原型链继承和构造函数继承的优点。
缺点:
父类的构造函数调用了两次,浪费性能
语法:
function SubClass(props) { SuperClass.call(this, props); // 第二次调用父类构造函数 } SubClass.prototype = new SuperClass(); // 第一次调用父类构造函数 SubClass.prototype.constructor = SubClass;
示例:
function SuperClass(name) { console.log(`super call->${name}`); } function SubClass(name) { // 将构造函数内部的属性和方法直接绑定到子类的实例上 SuperClass.call(this, name); } SubClass.prototype = new SuperClass(['proto', 'Call']);//第一次 SubClass.prototype.constructor = SubClass; const instance1 = new SubClass(['proto', 'Call']);// 第二次 // 运行结果⚠️字符串模版将数组转为字符串 // super call-> proto,Call // super call-> new,Call
4. 原型式继承
使用Object.create()方法创建以目标对象为原型的实例对象。
语法:
//以目标对象为原型对象创建一个实例对象;实例对象只含原型对象上的方法和属性 const obj = Object.create(targetObject);
5. 寄生式继承
在原型式继承的继承上修改生成的实例对象;
语法:
const obj = Object.create(targetObject);
obj.newProp = 'hello world';
6. 寄生式组合继承
在组合继承的继承上,加入寄生式继承。
优点:
解决了组合继承调用两次父类构造函数的问题。
语法:
function SubClass(props) { SuperClass.call(this, props); } const obj = Object.create(SuperClass.prototype); obj.constructor = SubClass; SubClass.prototype = obj;
示例:
function SuperClass(props) { console.log('super call-->'+props); } SuperClass.prototype.getName =function() { console.log('superClass-prototype-fn'); } function SubClass(props) { SuperClass.call(this, props); } const obj = Object.create(SuperClass.prototype); obj.constructor = SubClass; SubClass.prototype = obj; const instance = new SubClass('newCall'); instance.getName(); // 运行结果 // super call-->newCall 只调用一次 // superClass-prototype-fn 能够访问原型对象上的方法
7. 多重继承(Mixin模式)
即一个构造函数继承多个构造函数。
function Child() {
Father1.call(this);
Father2.call(this);
}
Child.prototype = Object.create(Father1.prototype);
Object.assign(Child.prototype, Father2.prototype);
Object.prototype.constructor = Child;
8. 类的继承
1. 子类的实例this的生成基于父类的实例,所以必须先调用super(),获取父类实例。之后才能使用this。
2. 类的继承还会继承静态属性和方法
class Father {}
class Child extends Father{ constructor(props) { super(props); // =Father.prototype.constructor.call(this,props) } } // super的用法: 1)作为父类构造函数,只能用在构造函数中 2)作为原型对象,在普通函数中:super.xxx(); super相当于父类的原型对象(super.prototype.xxx.call(this)),里面的this指向子类实例。 取不到子类的实例上面的属性和方法!! 3)作为父类,在静态方法中使用super,相当于父类,里面的this指向子类。
类的继承的实现原理:
Object.setPrototypeOf(Child, Father);
Object.serPrototypeOf(Child.prototype, Father.prototype);
可推导出:
Child.__proto__ === Father; Object.getPrototypeOf(Child) === Father; Child.prototype.__proto__ === Father.prototype; Object.getPrototypeOf(Child.prototype) === Father.prototype;
9. 类的多重继承
本质上是将多个被继承类的属性遍历复制到目标。
function mix(...mixins) {// 多个类 class Mix { constructor() { for (let mixin of mixins) { copyProperties(this, new mixin()); // 拷贝实例属性 } } } for (let mixin of mixins) { copyProperties(Mix, mixin); // 拷贝静态属性 copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性 } return Mix; } function copyProperties(target, source) {// 将被继承类的属性和方法复制到this上 for (let key of Reflect.ownKeys(source)) { if ( key !== 'constructor' && key !== 'prototype' && key !== 'name' ) { let desc = Object.getOwnPropertyDescriptor(source, key); Object.defineProperty(target, key, desc); } } }
2. 封装
ES5中没有模块的概念,可以模拟实现将代码封装成模块。
1. ES5封装
将方法和属性封装在构造函数中。
2. ES6封装
将方法和属性封装在类中。
3. 多态
同一个函数的多种调用形式。根据传参不同,实现逻辑不同。
function sum(...args) { const len = args.length; if(len === 1) { return 'no need' } else if(len === 2) { return args.reduce((a,b) => a+b); } else if(len === 3) { return args.reduce((a,b) => a*b) } else { return 'end' } } console.log( ' ', sum(1), ' ', sum(1,2), ' ', sum(1,2,3), ' ', sum(), ); // 结果如下: no need 3 6 end