在 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
来创建新的对象。
总结一下,创建对象的两种方式:
- 通过字面量创建,这是我们最常使用的方式,例如
{name: "barwe"}
- 通过
new
关键字创建,需要先定义一个 类
批量创建对象的两种方式:
- 工厂模式:将创建对象的过程封装在一个普通函数中,不能很好的表示不同类之间的差别
- 构造函数:同样是通过
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
并不是 user1
和 user2
的直接成员,它定义在对象的原型对象上,而所有通过 User
类 new
出来的对象的原型对象都是一样的:
user1.__proto__.sayHi === user2.__proto__.sayHi // true
通过对象的 .__proto__
属性我们可以访问到对象的原型对象。
通过类的 prototype
属性我们可以为类的所有对象挂载共享的属性和方法。
通过 原型 方式,所有对象都能串在一起,形成一条以 Object
为根的 原型链。
评论区