写了2年前端从来不用面向对象?_前端用不到面向对象,阿里开发7年大牛

40 阅读6分钟

}

function Login() { this.type = 'edutior'; }

Login.prototype = new UserRole('userName01', [1,2,3]);

console.log(new Login()); /** { "type": "edutior" [[Prototype]]: { "name": "userName01", "routes": [1,2,3] } } */


使用原型链继承时,子类能够访问父类的方法和属性。然而,这种方式存在一个潜在的问题。


举个例子,明明我只改变了`login01`的play属性,为什么`login02`也跟着变了呢?



const login01 = new Login(); const login02 = new Login(); login01.routes.push(4) console.log(login01.routes, login02.routes); // [1,2,3,4] [1,2,3,4]


**如果我们修改了一个实例的属性,其他实例也会受到影响,因为它们共享同一个原型对象**### 💡(ES5+)第三种:将前两种组合


我们可以将前两种继承方式进行组合,解决各自的问题。以下是示例代码:



function UserRole(name, routers) { this.name = name; this.routes = routers; }

function Login() { UserRole.call(this, 'userName01', [1,2,3]); this.type = 'edutior'; }

Login.prototype = new UserRole('userName01', [1,2,3]);

const login01 = new Login(); const login02 = new Login(); login01.routes.push(4) console.log(login01.routes, login02.routes); // [1,2,3,4] [1,2,3]


通过这种组合方式,我们解决了之前的问题。然而,**这种方式会导致父类的构造函数被执行两次**,强迫症非常难受。


### 💡(ES5+)第四种:组合继承的优化


为了优化组合继承的方式,我们可以进行一些改进。以下是示例代码:



function UserRole(name, routers) { this.name = name; this.routes = routers; }

function Login() { UserRole.call(this, 'userName01', [1,2,3]); this.type = 'edutior'; }

Login.prototype = UserRole.prototype;

const login01 = new Login(); console.log(login01) /** { "name": "userName01", "routes": [1,2,3], "type": "edutior" [[Prototype]]: { constructor: UserRole(name, routers) } } */


通过将子类的原型直接指向父类的原型,并修复子类构造函数的引用,我们避免了父类构造函数被执行两次的问题。


但是这个时候又出现了一个**新的**问题:


**子类实例的构造函数是`UserRole`,显然这是不对的,应该是`Login`!**


### 💡(ES5+推荐)第五种:寄生组合继承


最后,我要介绍的是一种最推荐的继承方式:寄生组合继承。以下是示例代码:



function UserRole(name, routers) { this.name = name; this.routes = routers; }

function Login() { UserRole.call(this, 'userName01', [1,2,3]); this.type = 'edutior'; }

Login.prototype = Object.create(UserRole.prototype); Login.prototype.constructor = Login;

const login01 = new Login(); console.log(login01) /** { "name": "userName01", "routes": [1,2,3], "type": "edutior" [[Prototype]]: { constructor: Login() } } */


寄生组合继承是一种接近完美的继承方式。它解决了属性共享和方法继承的问题,并且避免了父类构造函数被执行多次的情况。


### ✨“寄生组合继承”在ES6的编译过程中是怎么运用的


如果我们使用ES6的`extends`关键字进行继承,并编译成ES5的代码,会得到类似以下的JavaScript代码:



function _possibleConstructorReturn(self, call) { // ... return call && (typeof call === 'object' || typeof call === 'function') ? call : self; }

function _inherits(subClass, superClass) { // ... // 注意这里 subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.proto = superClass; }

var Parent = function Parent() { // 验证是否是 Parent 构造出来的 this _classCallCheck(this, Parent); };

var Child = function (_Parent) { _inherits(Child, _Parent);

function Child() {
    _classCallCheck(this, Child);

    return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
}

return Child;

}(Parent);


你可以看到,在编译后的JavaScript代码中,实际上也是采用了寄生组合继承的方式来实现继承。


同时,**它还使用了`Object.setPrototypeOf`来继承父类的静态方法**,又进一步改进了**第五种方法**所忽略的地方。


### ❓只要使用了“继承”,就是好的面向对象设计吗?


先说结论:**不一定!**


假设现在后台管理系统中有N个不同的角色,每个角色都有login,level,content三个方法。



class Role{ constructor(name) { this.name = name; } login(){ console.log("login!"); } level(){ console.log("level!") } content(){ console.log("content!") } } class otherRole extends Role{}


通过继承,我们可以实现角色的基本功能,并在此基础上扩展不同角色的特定功能。看起来似乎一切都很完美,但是现在出现了一个问题:\*\*“游客”\*\*角色,它并没有level(会员等级)。


如果让\*\*“游客”\*\*的类继承自`Role`,那就会我明明只需要`login``content`,但是我却还有一个冗余的`level`。我不需要`level`这个方法,但由于继承的原因,它也被传递给了子类。



> 
> 继承的最大问题在于:无法决定继承哪些属性,所有属性都必须继承。
> 
> 
> 


当然,我们可以再创建一个父类,将`level`方法去掉。


为了满足不同子类的特性,我们需要创建不同的父类,这导致**大量重复**的代码。


而且一旦子类发生变动,父类也需要相应更新,这会导致代码的**耦合性过高,维护性也变得困难**### 📌最好的方法( 函数式编程):用“组合”实现面向对象


“组合”是当今编程语法发展的趋势。`Go`就完全采用的是基于组合的设计方式。



// 函数组合 const compose = (...fns) => (arg) => fns.reduceRight((acc, fn) => fn(acc), arg);

// 函数定义 const addOne = (x) => x + 1; const double = (x) => x * 2; const square = (x) => x * x;

// 组合后的函数 const composedFn = compose( square, double, addOne );

// 使用组合函数 const result = composedFn(3); console.log(result); // 输出 64


代码整洁干净,可以随处复用。面向组合的设计趋势已然到来。


### 结语


本文简要回顾了JavaScript中常见的继承方式,并介绍了它们的优缺点。


每种方式都有自己的特点,可以根据具体需求选择合适的方式。


同时,我们也提到了面向对象设计中继承的问题,即无法选择性地继承属性和方法的困扰。


面向组合的设计方式可以有效地解决这个问题,并成为当今编程语法发展的趋势。


如果对于这些继承方式还有疑问,或者希望深入讨论相关话题,请告诉我。我很愿意和你一起学习和进步。😊


#### **前端面试题库 (**面试必备)****推荐:★★★★★****




### 文末

从转行到现在,差不多两年的时间,虽不能和大佬相比,但也是学了很多东西。我个人在学习的过程中,习惯简单做做笔记,方便自己复习的时候能够快速理解,现在将自己的笔记分享出来,和大家共同学习。



个人将这段时间所学的知识,分为三个阶段:

第一阶段:HTML&CSS&JavaScript基础

![](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/e08a6b41573c493d9a74c8d4f37c25d7~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NTc5MjMwMTY3MDI=:q75.awebp?rk3s=f64ab15b&x-expires=1771407286&x-signature=lx7PD4HLtWKnUnOASySIpLhFbJo%3D)



第二阶段:移动端开发技术

![](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/b36bb08e9f0d40ebbdf7d3fac7426f7b~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NTc5MjMwMTY3MDI=:q75.awebp?rk3s=f64ab15b&x-expires=1771407286&x-signature=sdz51ijHcr2i4OitjblrNTzspGk%3D)

第三阶段:前端常用框架

![](https://p9-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/da347e534b8843c782c260917bd6309c~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg55So5oi3NTc5MjMwMTY3MDI=:q75.awebp?rk3s=f64ab15b&x-expires=1771407286&x-signature=qEEqNVWLKFs3l9fGUMKA%2FeaDCzY%3D)



*   推荐学习方式:针对某个知识点,可以先简单过一下我的笔记,如果理解,那是最好,可以帮助快速解决问题;

*   大厂的面试难在,针对一个基础知识点,比如JS的事件循环机制,不会上来就问概念,而是换个角度,从题目入手,看你是否真正掌握。所以对于概念的理解真的很重要。
**开源分享:https://docs.qq.com/doc/DSmRnRGxvUkxTREhO**