对象是面向对象编程中封装的主要元素。所以在面向对象编程过程中需要经常使用。掌握定义对象也就必不可少。
使用JavaScript构建对象的方法有几种方式:
- 字面量创建方式
- Object.create()
- class
- 工厂模式
字面量创建方式
我们需要区分数据结构和面向对象的对象。数据结构具有公共数据,没有任何行为。那意味着他们没有方法。
我们可以使用对象文字语法创建此类对象像这样:
const product = {
name: 'apple',
category: 'fruits',
price: 1.99
}
console.log(product);
JavaScript中的对象是键值对的动态集合。键 始终是字符串,并且在集合中必须唯一。该值可以是基元(String,Number和Boolean),对象甚至函数。
我们可以使用点 “ · ” 或方括号 “ [] ” 符号访问属性。
console.log(product.name);
//"apple"
console.log(product["name"]);
//"apple"
这是一个值是另一个对象。
const product = {
name: 'apple',
category: 'fruits',
price: 1.99,
nutrients : {
carbs: 0.95,
fats: 0.3,
protein: 0.2
}
}
这是carbs
属性的值是一个新对象。这是我们访问carbs
的方法。
console.log(product.nutrients.carbs);
//0.95
简写属性名称
这是将属性值存储在变量中的情况。
const name = 'apple';
const category = 'fruits';
const price = 1.99;
const product = {
name: name,
category: category,
price: price
}
JavaScript支持所谓的速记属性名称。它允许我们仅使用变量名来创建对象。它将创建一个具有相同名称的属性。下一个对象文字与上一个相同。
const name = 'apple';
const category = 'fruits';
const price = 1.99;
const product = {
name,
category,
price
}
Object.create()
现在我们看看如何使用面向对象的方式来实现对象。
JavaScript具有原型链,该原型链允许对象之间共享行为。主要思想是创建一个称为原型的对象,然后在创建新对象时使用它。
原型链可以使我们能够创建从其他对象继承行为的对象。
让我们创建一个原型对象,使我们可以添加产品并从购物车中获得总价。
const cartPrototype = {
addProduct: function(product){
if(!this.products){
this.products = [product]
} else {
this.products.push(product);
}
},
getTotalPrice: function(){
return this.products.reduce(function(total, p){
return total + p.price
}, 0);
}
}
请注意,这次属性值addProduct
是一个函数。我们还可以使用一种称为“简写方法”语法的较短形式写先前的对象。
const cartPrototype = {
addProduct(product){
if(!this.products){
this.products = [product]
} else {
this.products.push(product);
}
},
getTotalPrice(){
return this.products.reduce(function(total, p){
return total + p.price
}, 0);
}
}
的cartPrototype
是原型对象,保持通过两种方法表示的共同行为,addProduct
和getTotalPrice
。它可用于构建继承此行为的其他对象。
const cart = Object.create(cartPrototype);
cart.addProduct({name: 'orange', price: 1.25});
cart.addProduct({name: 'lemon', price: 1.75});
console.log(cart.getTotalPrice());
//3
该cart
对象具有cartPrototype
其原型。它从中继承了方法。cart
具有指向原型对象的隐藏属性。
当我们在对象上使用方法时,首先在对象本身而不是其原型上搜索该方法。
this
我们使用一个特殊的关键字this
来访问和修改对象上的数据。
函数是JavaScript中行为的独立单元。它们不一定是对象的一部分。当它们存在时,我们需要有一个引用,该引用允许该函数访问同一对象上的其他成员。this
是函数上下文。它提供对其他属性的访问。
数据
现在有个疑问,为什么没有把products定义
在原型对象本。
因为原型应该用于共享行为,而不是共享数据。共享数据将导致在多个购物车对象上拥有相同的产品。考虑下面的代码:
const cartPrototype = {
products:[],
addProduct: function(product){
this.products.push(product);
},
getTotalPrice: function(){}
}
const cart1 = Object.create(cartPrototype);
cart1.addProduct({name: 'orange', price: 1.25});
cart1.addProduct({name: 'lemon', price: 1.75});
console.log(cart1.getTotalPrice());
//3
const cart2 = Object.create(cartPrototype);
console.log(cart2.getTotalPrice());
//3
从cart1
和cart2
继承相同行为的对象和cartPrototype
也共享相同的数据。这样的话就会出现错误的情况。原型应该用于共享行为,而不是数据。
类
原型链不是构建对象的常用方法。我们应该更熟悉使用类来构建对象。
类语法允许创建共享方法的对象。它仍然会默默的创建一个相同的原型,这样写语法更清晰,并且我们还避免了以前的数据相关问题。该类提供了一个特定的位置来定义每个对象不同的数据。
这是使用类的方式创建的对象:
class Cart{
constructor(){
this.products = [];
}
addProduct(product){
this.products.push(product);
}
getTotalPrice(){
return this.products.reduce((total, p) => total + p.price, 0);
}
}
const cart = new Cart();
cart.addProduct({name: 'orange', price: 1.25});
cart.addProduct({name: 'lemon', price: 1.75});
console.log(cart.getTotalPrice());
//3
const cart2 = new Cart();
console.log(cart2.getTotalPrice());
//0
注意,该类具有一个构造函数方法,该方法针对每个新对象初始化该数据。实例之间不共享构造函数中的数据。为了创建一个新实例,我们使用new
关键字。
我认为对于大多数开发人员而言,类语法更加清晰和熟悉。但是,它做类似的事情,它使用所有方法创建一个原型,并使用它来定义新对象。可以使用访问原型Cart.prototype
。
事实证明,原型链是多么的灵活,可以使用类语法。接下来我们使用原型链来模拟一个私有属性的创建。
私有属性
唯一的事情是,products
默认情况下,新对象上的属性是公有属性。
console.log(cart.products);
//[{name: "orange", price: 1.25}
// {name: "lemon", price: 1.75}]
我们可以使用哈希#
前缀将其设为私有。
私有属性使用#name
语法声明。#
是属性名称本身的一部分,应用于声明和访问属性。这是一个声明products
为私有财产的示例:
class Cart{
#products
constructor(){
this.#products = [];
}
addProduct(product){
this.#products.push(product);
}
getTotalPrice(){
return this.#products.reduce((total, p) => total + p.price, 0);
}
}
console.log(cart.#products);
//Uncaught SyntaxError: Private field '#products' must be declared in an enclosing class
工厂功能
另一种选择是将对象创建为闭包的集合。
闭包是一个函数即使在外部函数执行后也可以从另一个函数访问变量和参数的能力。看一下cart
使用工厂函数构建的对象。
function Cart() {
const products = [];
function addProduct(product){
products.push(product);
}
function getTotalPrice(){
return products.reduce((total, p) => total + p.price, 0);
}
return {
addProduct,
getTotalPrice
}
}
const cart = Cart();
cart.addProduct({name: 'orange', price: 1.25});
cart.addProduct({name: 'lemon', price: 1.75});
console.log(cart.getTotalPrice());
//3
addProduct
和getTotalPrice
是两个内部函数products
从其父级访问变量。products
父级Cart
执行后,他们可以访问变量事件。addProduct
和getTotalPrice
两个关闭共享同一个私有变量。
Cart
是工厂功能。
cart
使用工厂函数创建的新对象具有products
变量private。不能从外部访问它。
console.log(cart.products);
//undefined
工厂函数不需要new
关键字,但是您可以根据需要使用它。无论是否使用它,它都会返回相同的对象。
回顾
通常,我们使用两种类型的对象:具有公共数据但没有行为的数据结构和具有私有数据和公共行为的面向对象的对象。
使用对象文字语法可以轻松构建数据结构。
JavaScript提供了两种创新的方式来创建面向对象的对象。第一种是使用原型对象共享常见行为。对象从其他对象继承。类提供了一种很好的糖语法来创建此类对象。
另一种选择是将对象定义为闭包的集合。