9、对象的继承有哪些?

前置知识:14、原型与原型链 - 掘金

继承:可以访问属性/方法

普通继承
原理:将父实例赋值到子构造函数的原型上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 父类
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 李四

优点:没啥优点,最基本的继承而已
缺点:父类是共享的;父类实例创建没法传参

经典继承
原理:在子构造函数内调用父构造函数,将值绑到子实例上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 父类
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.*

组合继承
结合普通继承,补齐经典继承的缺点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 父类
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":["游泳","足球"]}

优点:补齐了普通、经典继承的缺点
缺点:父构造函数调用了两次

寄生组合继承
原理:解决组合继承的第二次父构造函数的调用,不让它调用呗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 父类
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就解决了

多重继承
功能:一子可以访问多父属性/方法
原理:基于寄生组件继承,多次调用父构造函数即可

1
2
3
4
5
6
7
8
// 只写关键代码:
function Child() {
Parent1.call(this)
Parent2.call(this)
}

Child.prototype = Object.create(Object.assgin({}, Parent1.prototype, Parent2.prototype))
Child.prototype.constructor = Child; // constructor 修正

9、对象的继承有哪些?
https://mrhzq.github.io/职业上一二事/前端面试/每日一题/9、对象的继承有哪些?/
作者
黄智强
发布于
2024年1月23日
许可协议