前言
在JavaScript中,我们有三种创建对象的方式,分别是:对象字面量,class关键字和function函数对象,本文将详细解析三者之间的关系特点以及优缺点。
对象字面量
对象字面量是创建对象最简单,直接的方式,只需要一个花括号{},它允许开发者直接定义和初始化一个对象,而不用通过构造函数或类来实例化。例如:
const Person = {
name:奶龙
age:18
}
这样一个Person对象就创建完成了,非常简单,但是它有一个很明显的缺点就是,你每每创建一个新的对象所有属性都要自己重新添加,随意且不灵活。
class
class关键字是在es6时引入的,这使得 JavaScript 开发更加接近传统面向对象语言的概念。使用 class 关键字,你可以定义一个类,然后通过 new 关键字来创建这个类的实例。例如:
class person {
constructor(name,age){
this.name = name
this.age = age
}
sayHi(){
console.log('hi');
}
}
let ii = new person('大奶龙',18)
这就是通过传统的面向对象的思想来开发JS,通过类的封装将属性和方法做成一个模板,但是在es5时并不支持class,这时候就要请出我们的今天主角了。
构造函数
要理解何为构造函数?我们首先来看一串代码:
function Person(name,age){
console.log(this);
this.name = name;
this.age = age;
}
如果这时我们在其下方传入参数调用:
Person('凯总',19)
这时这个函数就是以普通函数的方式运行,它的this指向的是window
但是如果我们要以构造函数的方式运行,便需要用new运算符创建一个实例对象出来,这时,this指针指向的是new出来的实例化对象,此时这个函数就是构造函数:
const k = new Person('yx',22); //this指向实例对象k
所以是不是一个构造函数是由其调用方式决定的,如果你需要的函数是一个构造函数,在命名时需要首字母大写,这并不是什么规则,而是一种编程风格,是一种约定俗成的格式。
那我们如果想要给对象增加一个方法该如何实现呢,当然我们可以直接在构造函数中添加方法,但是这并不是我们今天要说的,我们将用另一个种,也是JavaScript 面向对象编程的一个核心概念原型(prototype)来解决。
原型(prototype)
prototype是每一个函数创建时都拥有的一个属性,prototype属性是一个对象,即使你没有显示设置,JavaScript引擎也会自动为每个函数提供一个prototype对象。这个默认的 prototype 对象包含一个特殊的属性 constructor,该属性指向函数本身。此外,prototype 对象最初是空的,不包含任何其他属性或方法。
那么它是用来做什么的呢?
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype = {
eat:function(){
console.log(`${this.name}爱吃饭`);
}
}
const xx = new Person('xx',18)
xx.eat() // xx爱吃饭
你可以通过修改prototype来添加新的属性和方法,它可以让所有这个函数的实例化对象共享其中的属性和方法。
const owen = {
name:'owen',
playBasketball:function (){
console.log(`打篮球`);
}
}
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype = owen;
const kai = new Person('凯',18);
kai.playBasketball(); //打篮球
还可以将prototype的值等于一个对象,这样其他实例化对象也可以共享其中的方法。
任何一个实例都会有一个私有属性._proto_表达它构造函数的原型对象是谁,当一个对象调用一个属性或者方法时,它在自己内部没有找到,便会沿着这个私有属性去到原型对象中查找,这就是prototype的原理。
console.log(kai.__proto__ === owen);
当你没有在构造函数中添加this的指向也就是实例属性时(this.name=name)时,对象会自动去往原型对象中寻找:
function Person(name,age){
}
Person.prototype.name = '孔子'
let Person1 = new Person();
let Person2 = new Person();
console.log(Person1.name,Person2.name);
但是此时如果你打印一个console.log(Person1 === Person2);你会发现输出的值是False,这就是我们之前文章中讲到的,因为===会判断你的值和类型是否都相等,都相等才会返回True。在这里因为它们是两个不同的对象,在堆内存中所处的位置不同,即它们的引用(内存地址不同) 所以会输出False。
如果在构造函数中添加实例属性后,这些属性会被实例自身的属性覆盖掉,即便你没有给他添加值,它也只会返回undefined,并不会到原型对象中查找。
function Person(name,age){
this.name = name;
this.age = age;
}
Person.prototype.name = '孔子'
Person.prototype.hometown = '山东'
let Person1 = new Person('x',18);
let Person2 = new Person('k',17);
console.log(Person1.name,Person2.name);
到这里为止差不多就已经结束啦,但是 那么总结一下:在es5的js中类的构建就是 = 构造函数(属性,对象的)+ 原型(方法,所有实例共享)
那这样做有什么好处呢?我们为什么方法不直接放在实例上,而是放在原型上呢?主要有两个原因:
- 性能优化:如果方法直接放在每个实例上,那么每次创建新的函数时都会创建一个新的执行上下文,这会占用更多的内存。而将方法放在原型上,所有实例都可以共享同一个方法,从而节省内存。
- 动态修改:通过原型,我们可以在运行时动态地添加、删除或修改方法,这对于某些需要高度灵活性的应用场景非常有用。
三者的关系
- 构造函数:用来创建特定类型的对象。它定义了对象的基本结构(属性和方法)。
- 原型对象:每个构造函数都有一个原型对象,原型对象包含了可以被该构造函数所有实例共享的属性和方法。
- 实例:通过
new关键字调用构造函数创建的对象。每个实例都会有一个内部链接指向其构造函数的原型对象,以便能够访问原型上的属性和方法。
小结
JavaScript 的面向对象编程提供了多种创建对象的方式,每种方式都有其独特的优势和适用场景。对象字面量适合创建简单的、一次性使用的对象;构造函数和原型机制提供了更强大的功能,适用于创建多个具有相同属性和方法的对象;ES6 的 class 关键字则使 JavaScript 的面向对象编程更加符合传统面向对象语言的编程习惯。
理解这些概念及其相互关系,有助于开发者更好地利用 JavaScript 的面向对象特性,编写出高效、可维护的代码。无论是前端开发还是后端开发,掌握这些基础知识都是必不可少的。
希望本文能帮助你更深入地了解 JavaScript 的面向对象编程,让你在开发过程中更加得心应手。