重新认识JavaScript中的面向对象

本文系读完winter《重学前端》相关章节并查阅相关文档后的学习总结,特此声明和感谢。

误解

以前总觉得JavaScript中的面向对象是一团乱麻,各种原型和类的概念交织错乱,读过的一些书(如《你不知道的JavaScript》)也对JavaScript中类的写法嗤之以鼻以至于不推荐使用新的class关键字。种种现象让我过去很长一段时间都认为JavaScript是一门设计得很差劲的语言,所谓的面向对象都是模拟出来的,虽然我还是得靠它吃饭。

两种面向对象的实现思路

面向对象的思想由于其易理解、可扩展、模块化等优势广为流行,其设计思路可分为两个流派:

  1. 基于类:先定义一个虚拟的分类,如“猫”类,再去实例化具体的猫;分类之间又可形成继承和组合,如继承于“猫”类的“中华田园猫”类,“中华田园猫”类与“公猫”类又可组合为“中华田园公猫”类。
  2. 基于原型:直接定义一只具体的猫,以这只猫为原型,附加或修改一些特性,就出现了一只独具特色的中华田园猫,还可以“照猫画虎”地根据这只猫来定义出各种猫科动物。

很明显的,JavaScript属于第二种基于原型的流派,它本身是一种非常优秀的方案,但由于一些外部原因,JavaScript被额外添加了许多因素用来模拟基于类的流派,这才让它看起来混乱不堪。

基于原型的面向对象

抛开其模拟类的复杂语法,其实JavaScript基于原型的设计非常简单:

  1. 将对象的状态和方法统一为“属性”,它们在Java中被分别称为“属性”和“方法”,在C++中被称为“成员变量”和“成员函数”;
  2. 所有对象都具有私有属性[[prototype]],就是对象的原型;
  3. 访问对象的属性,若对象本身没有,就继续访问其原型、原型的原型等,直到找到此属性或原型为空为止。

ES6中基于原型的语法也非常简明:

  1. Object.create:根据原型创建新对象,原型可以为null;
  2. Object.getPrototypeOf:获取对象的原型;
  3. Object.setPrototypeOf:设置对象的原型。

基于原型的对象示例:

const cat = {
    shitOfficer: 'human',
    say() {
        console.log('meow~');
    }
}

const chineseCat = Object.create(cat);
chineseCat.say = function() {
    console.log('miao~')
}

console.log(chineseCat.shitOfficer);    // 'human',访问到原型cat的属性
chineseCat.say();   // 'miao~'
复制代码

JS早期版本中的模拟类

在JS早期版本中,对于类的支持是相当弱的,主要是通过Function对象和new操作符来模拟类的,主要知识点如下:

  • Function对象的构造器中包含一个prototype属性,它指向从这个构造器构造出的所有对象的原型
  • 使用new操作符时:
    1. 以构造器的prototype指向的对象为原型,创建新对象
    2. 将构造器中的this绑定到上一步创建的对象上,执行构造器(即执行函数)
    3. 若函数没有返回其他对象,则将第一步创建的对象返回
  • 需要实现继承时较为复杂且实现方式多样

示例:

// 使用this:直接在构造器中给this添加属性,使用new操作符后this会绑定到新创建的对象上
function fun1() {
    this.p1 = '1';
    this.p2 = function() {
        console.log(this.p1);
    }
}
var obj1 = new fun1();
obj1.p2();  // '1'

// 使用prototype:给构造器的prototype属性指向的原型对象添加属性
function fun2() {}
fun2.prototype.p1 = '2';
fun2.prototype.p2 = function() {
    console.log(this.p1);
}
var obj2 = new fun2();
obj2.p2();  // '2'
复制代码

ES6中的类

ES6中引入了新特性class,它实际上还是用之前的原型来实现的,使用class定义的类,其每个实例对象具有同一个原型对象,类中定义的属性都会被写在此原型对象上。 class从语言标准上统一了类的实现方法,使function回归原本的函数语意,同时使用extends关键字可轻松实现继承功能,示例:

class Cat {
    constructor(name) {
        this.name = name;
    }
    
    say() {
        console.log(this.name + ': meow~');
    }
}

class chineseCat extends Cat {
    constructor(name) {
        super(name);
    }
    
    say() {
        console.log(this.name + ': miao~');
    }
}

const myCat = new chineseCat('Mimi');
myCat.say();    // 'Mimi: miao~'
复制代码

总结

理解了JavaScript基于原型的设计理念后就会明白,JavaScript中的面向对象并非原先想象中那样混乱不堪。在我看来,相较于基于类的面向对象,基于原型的设计摒弃了所有“虚”的、概念上的东西,一切皆为实体对象,万物皆可追溯至源头,且源头仍是一个实体。 当然,由于一些历史原因,JavaScript被赋予了很多打破自身体系的画蛇添足的东西,就像它的名字中很有误导性的“Java”一样。但既然我们使用的是JavaScript,就应该理解并遵循JavaScript本身的设计理念,发挥它独有的特点,对于一名前端来说尤其如此。

转载于:https://juejin.im/post/5c85bc4a5188257ed47b0e38

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值