前置知识:14、原型与原型链 - 掘金
继承:子可以访问父的属性/方法
普通继承
原理:将父实例赋值到子构造函数的原型上
// 父类
function Parent(args = {}) {
this.like = args.like || ["游泳", "足球"];
this.sayHello = function () {
console.log("你好呀~");
};
}
Parent.prototype.sayName = function () {
console.log(`my name is ${this.name}`);
};
// 子类
function Child(name, address) {
this.name = name;
this.address = address;
}
// ⭐️ 继承实现 - start:
Child.prototype = new Parent();
Child.prototype.constructor = Child; // constructor 修正
// ⭐️ 继承实现 - end:
const c1 = new Child("张三", "四川", { like: ["吃饭", "睡觉"] });
const c2 = new Child("李四", "深圳");
console.log(JSON.stringify(c1)); // {"name":"张三","address":"四川"}
console.log(JSON.stringify(c2)); // {"name":"李四","address":"深圳"}
// ❗️ 缺点:父类是共享的
c1.like.push("喝酒");
c2.like.push("跑步");
console.log(JSON.stringify(c1.like)); // ["游泳","足球","喝酒","跑步"]
console.log(JSON.stringify(c2.like)); // ["游泳","足球","喝酒","跑步"]
c1.sayHello(); // '你好呀~'
c2.sayHello(); // '你好呀~'
c1.sayName(); // my name is 张三
c2.sayName(); // my name is 李四
优点:没啥优点,最基本的继承而已
缺点:父类是共享的;父类实例创建没法传参
经典继承
原理:在子构造函数内调用父构造函数,将值绑到子实例上
// 父类
function Parent(args = {}) {
this.like = args.like || ["游泳", "足球"];
this.sayHello = function () {
console.log("你好呀~");
};
}
Parent.prototype.sayName = function () {
console.log(`my name is ${this.name}`);
};
// 子类
function Child(name, address, parentArgs) {
// ⭐️ 继承实现 - start:
Parent.call(this, parentArgs);
// ⭐️ 继承实现 - end:
this.name = name;
this.address = address;
}
const c1 = new Child("张三", "四川", { like: ["吃饭", "睡觉"] });
const c2 = new Child("李四", "深圳");
c1.like.push("喝酒");
c2.like.push("跑步");
console.log(JSON.stringify(c1)); // {"like":["吃饭","睡觉","喝酒"],"name":"张三","address":"四川"}
console.log(JSON.stringify(c2)); // {"like":["游泳","足球","跑步"],"name":"李四","address":"深圳"}
c1.sayHello(); // '你好呀~'
c2.sayHello(); // '你好呀~'
// ❗️ 缺点:没法继承父构造函数原型链上的
c1.sayName(); // 报错: c1.sayName is not a function
c2.sayName(); // 报错: c2.sayName is not a function
优点:父的属性/方法将直属于子实例,并且支持父构造函数的传参
缺点:没法继承父构造函数原型链上的属性Parent.prototype.*
组合继承
结合普通继承,补齐经典继承的缺点
// 父类
function Parent(args = {}) {
this.like = args.like || ["游泳", "足球"];
this.sayHello = function () {
console.log("你好呀~");
};
}
Parent.prototype.sayName = function () {
console.log(`my name is ${this.name}`);
};
// 子类
function Child(name, address, parentArgs) {
// ⭐️ 继承实现(1) - start:
Parent.call(this, parentArgs);
// ⭐️ 继承实现(1) - end:
this.name = name;
this.address = address;
}
// ⭐️ 继承实现(2) - start:
Child.prototype = new Parent();
Child.prototype.constructor = Child; // constructor 修正
// ⭐️ 继承实现(2) - end:
const c1 = new Child("张三", "四川", { like: ["吃饭", "睡觉"] });
const c2 = new Child("李四", "深圳");
c1.like.push("喝酒");
c2.like.push("跑步");
console.log(JSON.stringify(c1)); // {"like":["吃饭","睡觉","喝酒"],"name":"张三","address":"四川"}
console.log(JSON.stringify(c2)); // {"like":["游泳","足球","跑步"],"name":"李四","address":"深圳"}
c1.sayHello(); // '你好呀~'
c2.sayHello(); // '你好呀~'
c1.sayName(); // my name is 张三
c2.sayName(); // my name is 李四
// ❗️ 缺点:父构造函数调用了两次,导致出现重复属性,直属子实例与子实例原型链上都有该属性
console.log(JSON.stringify(Object.getPrototypeOf(c1))); // {"like":["游泳","足球"]}
优点:补齐了普通、经典继承的缺点
缺点:父构造函数调用了两次
寄生组合继承
原理:解决组合继承的第二次父构造函数的调用,不让它调用呗
// 父类
function Parent(args = {}) {
this.like = args.like || ["游泳", "足球"];
this.sayHello = function () {
console.log("你好呀~");
};
}
Parent.prototype.sayName = function () {
console.log(`my name is ${this.name}`);
};
// 子类
function Child(name, address, parentArgs) {
// ⭐️ 继承实现(1) - start:
Parent.call(this, parentArgs);
// ⭐️ 继承实现(1) - end:
this.name = name;
this.address = address;
}
// ⭐️ 继承实现(2) - start:
Child.prototype = Object.create(Parent.prototype); // ⭐️ 再多一层原型链
Child.prototype.constructor = Child; // constructor 修正
// ⭐️ 继承实现(2) - end:
const c1 = new Child("张三", "四川", { like: ["吃饭", "睡觉"] });
const c2 = new Child("李四", "深圳");
c1.like.push("喝酒");
c2.like.push("跑步");
console.log(JSON.stringify(c1)); // {"like":["吃饭","睡觉","喝酒"],"name":"张三","address":"四川"}
console.log(JSON.stringify(c2)); // {"like":["游泳","足球","跑步"],"name":"李四","address":"深圳"}
c1.sayHello(); // '你好呀~'
c2.sayHello(); // '你好呀~'
c1.sayName(); // my name is 张三
c2.sayName(); // my name is 李四
// ❗️ 缺点:父构造函数调用了两次,导致出现重复属性,直属子实例与子实例原型链上都有该属性
console.log(JSON.stringify(Object.getPrototypeOf(c1))); // {"like":["游泳","足球"]}
特殊说明:Parent.call(this, parentArgs)这段代码其实就是 ES6 里面,子类继承时调用的super(args)的实现逻辑
额外补充:现在有了 ES6 的class后,继承就很简单了,一个关键词extends就解决了
多重继承
功能:一子可以访问多父的属性/方法
原理:基于寄生组件继承,多次调用父构造函数即可
// 只写关键代码:
function Child() {
Parent1.call(this)
Parent2.call(this)
}
Child.prototype = Object.create(Object.assgin({}, Parent1.prototype, Parent2.prototype))
Child.prototype.constructor = Child; // constructor 修正