侧边栏壁纸
  • 累计撰写 218 篇文章
  • 累计创建 59 个标签
  • 累计收到 5 条评论

JS原型链02-原型、构造函数与“类”

barwe
2022-03-25 / 0 评论 / 0 点赞 / 816 阅读 / 2,272 字
温馨提示:
本文最后更新于 2022-04-03,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

在 JavaScript 中,对象 并不需要很清晰的说明,有时候我们往往只关心某个具体的对象,而忽视对这个对象的抽象()。例如,我们定义一个任务:

const task = {
    name: 'blast',
    id: '20220203'
}

我们可以说 task 是一个对象,但我们并没有很清晰的定义出这个对象的抽象概念()。

实际上, 这个概念只有当我们需要赋值大量一模一样的对象时,才会发挥出它的优势: 规定了复制对象时哪些属性和方法可以共用。

JavaScript 中 Object 类是所有类的基类,我们可以从 Object 类显式创建对象:

const user = new Object();
user.name = "barwe";
user.age = 8;
user.sayHi = function() { console.log(`Hi, ${this.name}`) };

这里我们手动创建了一个 user 对象,它以 Object 为模板,并给它新添了两个属性和一个方法。

这看起来很合理,但是如果我们需要创建多个对象,就得把这段代码抄很多遍,这就不合理了。

于是我们考虑用一个普通的函数把创建对象的过程封装起来:

function createUser(name, age) {
    const user = new Object();
    user.name = name;
    user.age = age;
    user.sayHi = function() { console.log(`Hi, ${this.name}`) };
    return user;
}

于是我们能方便的创建多个对象了:

const user1 = createUser('zhangsan', 3)
const user2 = createUser('wangwu', 5)

上面的 createUser 函数虽然可以批量生产出我们需要的对象,但是其问题在于:我们生产的所有对象都是以 Object 为模板的,缺乏对中间对象的抽象,这显然有违常理。因此我们换一种写法:

function User(name, age) {
    this.name = name;
    this.age = age;
    this.sayHi = function() { console.log(`Hi, ${this.name}`) };
}

const user1 = new User('zs', 3)
const user2 = new User('ww', 5)

旧版本的 JavaScript 中通过 function 关键字来声明一个 ,这也是让人很迷惑的地方。

按照约定,PascalCase 的 function 是一个 构造函数,camelCase 的 function 是一个 普通函数

这里的 构造函数 实际上就与经典意义上的 的概念很相似了,它也是通过 new 来创建新的对象。

总结一下,创建对象的两种方式:

  1. 通过字面量创建,这是我们最常使用的方式,例如 {name: "barwe"}
  2. 通过 new 关键字创建,需要先定义一个

批量创建对象的两种方式:

  1. 工厂模式:将创建对象的过程封装在一个普通函数中,不能很好的表示不同类之间的差别
  2. 构造函数:同样是通过 function 定义一个 PascalCase 的与 相似的东西,这个东西可以 new 出对象

不管是哪种批量方式,它们都有一个共同的缺陷:对于值为基本数据类型的属性还行,但是如果值为引用类型,例如上面写到的函数或者其他对象的引用,通过这种方式每创建一个对象,就会把这些对象复制一份,而不是复制它的引用,这显然是不合理的,我们要的是复制它们的引用而不是复制引用的对象。

const user1 = new User('zs', 3)
const user2 = new User('ww', 5)
user1.sayHi === user2.sayHi // false

sayHi 对所有 User 类的对象而言应该是共享的,否则就会浪费许多空间。

JavaScript 提供了一种解决方案:将这中公有属性或者函数写在构造函数外面,单独为它们开辟一块地方,在类上定义一个东西指向它,每次创建对象时都自动建立一个指向它的引用,这样所有对象就能共享这部分属性和方法了,这就是 原型 (prototype)。

当我们通过 function 创建一个普通函数或者构造函数时,该函数引用对象就会自动添加一个 prototype 属性,该属性指向了创建这个函数的原型对象。对于构造函数而言,这个原型对象指向了它所有实例需要共享的内存区域。

所有写在类的原型对象上的属性和方法都会在 new 时自动挂载到对象上去。

我们在类的 prototype 对象上定义共享成员:

function User(name, age) {
    this.name = name;
    this.age = age;
    User.prototype.sayHi = function() {
        console.log(`Hi, ${this.name}`);
    }
}

这样

const user1 = new User('zs', 3);
const user2 = new User('ww', 5);
user1.sayHi === user2.sayHi // true

因为此时 sayHi 并不是 user1user2 的直接成员,它定义在对象的原型对象上,而所有通过 Usernew 出来的对象的原型对象都是一样的:

user1.__proto__.sayHi === user2.__proto__.sayHi // true

通过对象的 .__proto__ 属性我们可以访问到对象的原型对象。

通过类的 prototype 属性我们可以为类的所有对象挂载共享的属性和方法。

通过 原型 方式,所有对象都能串在一起,形成一条以 Object 为根的 原型链

0

评论区