面向对象编程:继承、封装、多态。
对象的继承:A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。
在经典的面向对象语言中,您可能倾向于定义类对象,然后您可以简单地定义哪些类继承哪些类(参考C++ inheritance里的一些简单的例子),JavaScript使用了另一套实现方式,继承的对象函数并不是通过复制而来,而是通过原型链继承
回顾《再谈javascriptjs原型与原型链及继承相关问题》
什么是原型语言
原型语言创建有两个步骤
所以在JavaScript的世界里,万物皆对象这个概念从一而终。
JavaScript里面没有类这个概念,es6中class虽然很像类,但实际上只是es5上语法糖而已
function People(name){
//属性
this.name = name || Annie
//实例方法
this.sleep=function(){
console.log(this.name + '正在睡觉')
}
}
//原型方法
People.prototype.eat = function(food){
console.log(this.name + '正在吃:' + food);
}
JavaScript的基础方式,首推的就是原型继承
父类的实例作为子类的原型
function Woman(){
this.name= "SubType"; // 子类属性
}
// 如果此处有Woman的原型对象上的方法,由于原型重定向,下面的代码会覆盖此方法
Woman.prototype= new People();// 重写原型对象,代之以一个新类型的实例
// 这里实例化一个 People时, 实际上执行了两步
// 1,新创建的对象复制了父类构造函数内的所有属性及方法
// 2,并将原型 __proto__ 指向了父类的原型对象
Woman.prototype.name = 'haixia';//子原型的属性
Woman.prototype.name = ()=>{};//子原型方法
let womanObj = new Woman();
原型链继承优点:
原型链继承缺点:
解释原型重定向: Woman.prototype= new People();
function Parent () {
this.a = 1;
this.b = [1, 2, this.a];
this.c = {demo: 5};
this.show = function () {
console.log(this.a + ' ' + this.c.demo + ':' + this.b + '\n');
};
}
function Child () {
this.a = 2;
this.change = function () {
this.b.push(this.a);
this.a = this.b.length;
this.c.demo = this.a++;
};
}
Child.prototype = new Parent();
var parent = new Parent();
var child1 = new Child();
var child2 = new Child();
child1.a = 11;
child2.a = 12;
child1.change();
child2.change();
parent.show();
child1.show();
child2.show();
思考原型公用问题
function extendObj(obj) {
if (Object.create) {
return Object.create(obj)
} else {
function F () { }
F.prototype = obj;
return new F()
}
}
var obj = { name: "smd", age: 26, sayHi: function () { } }
var newObj = createObj(obj)
/* Extend Function */
function extend(subClass,superClass){
var Func = function(){} ;
Func.prototype = superClass.prototype ;
subClass.prototype = new Func() ;
subClass.prototype.constructor = subClass ;
} ;
newObj继承了obj的属性和方法,但是同样出现了共享父类中引用类型属性的问题
function Wonman(name){
let instance = new People();
instance.name = name || 'wangxiaoxia';
return instance;
}
let wonmanObj = new Wonman();
// 父类
function People (name) {
this.colors = ["red", "blue", "green"];
this.name = name; // 父类属性
}
People.prototype.sayName = function () { // 父类原型方法
return this.name;
};
/** 第一步 */
// 子类,通过 call 继承父类的实例属性和方法,不能继承原型属性/方法
function Woman (name, subName) {
People.call(this, name); // 调用 People 的构造函数,并向其传参
this.subName = subName;
}
/** 第二步 */
// 解决 call 无法继承父类原型属性/方法的问题
// Object.create 方法接受传入一个作为新创建对象的原型的对象,创建一个拥有指定原型和若干个指定属性的对象
// 通过这种方法指定的任何属性都会覆盖原型对象上的同名属性
Woman.prototype = Object.create(People.prototype, {
constructor: { // 注意指定 Woman.prototype.constructor = Woman
value: Woman,
enumerable: false,
writable: true,
configurable: true
},
run : {
value: function(){ // override
People.prototype.run.apply(this, arguments);
// call super
// ...
},
enumerable: true,
configurable: true,
writable: true
}
})
/** 第三步 */
// 最后:解决 Woman.prototype.constructor === People 的问题
// 这里,在上一步已经指定,这里不需要再操作
//Woman.prototype.constructor = Woman;
var instance = new Woman('An', 'sistenAn')
function Wonman (name) {
let instance = new People();
for (var p in instance) {
Wonman.prototype[p] = instance[p];
}
Wonman.prototype.name=name||'Tom'
}
let wonmanObj = new Wonman();
特点:
缺点:
function Woman(name, age) {
//3行关键代码 此三行用于获取父类的成员及方法
//用子类的this去冒充父类的this,实现继承
//父类People中的this.name、sleep,分别成为了子类的成员、子类的方法
this.method = People;
//接收子类的参数 传给父类
this.method(name);
//删除父类
delete this.method;
//此后的this均指子类
this.age = age;
this.sayWorld = function() {
alert(age);
}
}
因为对象冒充的留下,才有call apply的兴起
复制父类的实例属性给子类
function Woman(name){
//继承了People,子类的this传给父类
People.call(this); //People.call(this,'zhoulujun');
this.name = name || 'andy'
}
let womanObj = new Woman();
通过这种调用,把父类构造函数的this指向为子类实例化对象引用,从而导致父类执行的时候父类里面的属性都会被挂载到子类的实例上去。
但是通过这种方式,父类原型上的东西是没法继承的,因此函数复用也就无从谈起
Woman无法继承Parent的原型对象,并没有真正的实现继承(部分继承)
调用父类构造函数,继承父类的属性,通过将父类实例作为子类原型,实现函数复用
function Woman(name,age){
People.call(this,name,age)
}
Woman.prototype = new People();
Woman.prototype.constructor = Woman;
let wonmanObj = new Woman(ren,27);
wonmanObj.eat();
缺点:
优点:
通过寄生的方式来修复组合式继承的不足,完美的实现继承
function Woman(name,age){
//继承父类属性
People.call(this,name,age)
}
//继承父类方法,可以简化为:Woman.prototype = Object.create(People.prototype);
(function(){
// 创建空类
function Super (){};
Super.prototype = People.prototype;
//父类的实例作为子类的原型
Woman.prototype = new Super();
})();
//修复构造函数指向问题
Woman.prototype.constructor = Woman;
let womanObj = new Woman();
其实还是有两次执行
//class 相当于es5中构造函数
//class中定义方法时,前后不能加function,全部定义在class的protopyte属性中
//class中定义的所有方法是不可枚举的
//class中只能定义方法,不能定义对象,变量等
//class和方法内默认都是严格模式
//es5中constructor为隐式属性
class People{
constructor(name='wang',age='27'){
this.name = name;
this.age = age;
}
eat(){
console.log(`${this.name} ${this.age} eat food`)
}
}
// 继承父类属性,super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。
// 子类必须在constructor方法中调用super方法,否则新建实例时会报错。如果子类没有定义constructor方法,这个方法会被默认添加,不管有没有显式定义,任何一个子类都有constructor方法。
class Woman extends People{
constructor(name = 'ren',age = '27'){
super(name, age);
}
eat(){
//继承父类方法
super.eat()
}
}
let wonmanObj=new Woman('xiaoxiami');
wonmanObj.eat();
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
ES6 的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
/**
* 继承
* @param {*} subClass 子类
* @param {*} superClass 父类
*/
function _inherits(subClass, superClass) {
// 类型检测
if (!superClass || typeof superClass !== "function" ) {
throw new TypeError("Super expression must either be null or a function, not " +typeof superClass);
}
/**
* Object.create 接受两个参数
* 指定原型创建对象
* @param {*} 目标原型
* @param {*} 添加属性
*/
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass, // subClass.prototype.constructor 指向 subClass
enumerable: false, // constructor 不可枚举
writable: true,
configurable: true
}
});
/**
* Object.setPrototypeOf 方法
* 设置子类的 __proto__ 属性指向父类
* @param {*} 子类
* @param {*} 父类
*/
if (superClass) {
// 设置子类的__proto__ 让 Child 能访问父类静态属性
Object.setPrototypeOf
? Object.setPrototypeOf(subClass, superClass)
: (subClass.__proto__ = superClass);
}
}
参考文章:
JavaScript深入之继承的多种方式和优缺点 #16 https://github.com/mqyqingfeng/Blog/issues/16
JavaScript 中的继承 https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Inheritance
JavaScript常见的六种继承方式 https://segmentfault.com/a/1190000016708006
js继承的几种方式 https://zhuanlan.zhihu.com/p/37735247
深入浅出js实现继承的7种方式 https://cloud.tencent.com/developer/article/1536957
前端面试必备之JS继承方式总结 https://www.imooc.com/article/20162
转载本站文章《JavaScript继承的实现方式:原型语言对象继承对象原理剖析》, 请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/js6/2015_0520_8494.html
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。