构造函数,原型对象,实例对象的关系
1.首先声明多个对象
//声明三个对象
let p1={
name:'张三',
age:20,
sex:'男
}
let p2={
name:'李四',
age:25,
sex:'男'
}
let p3=[
name:'李华'
age:18,
sex:'女'
}
用以上方式一个个声明对象,代码过于冗余,所有我们需要用函数对他们进行封装,这样我们就引出了工厂函数
2.工厂函数:用于创建多个对象
function createPerson(name,age,sex){
//1.创建对象
let p = {}
//2.给对象赋值
p.name = name
p.age = age
p.sex = sex
//3. 返回对象
return p
}
let p1 = createPerson('张三',20,'男')
let p2 = createPerson('李四',22,'女')
console.log(p1,p2)
封装一个函数后,这样不管我们以后要创建多少个对象,都只需要传入实参调用这个工厂函数,就可以创建很多的对象,可以把它看成一个加工厂,但是我们有更加简洁的方式来实现这个功能,那就是构造函数
3.构造函数:构造函数的作用和工厂函数一致,都是用来创建对象的,但是代码更加简洁,使用new关键字调用一个函数,这个函数称为构造函数
构造函数new工作原理(4个步骤)
(1)创建一个空对象
(2)this指向这个对象
(3)给这个对象赋值
(4)返回这个对象
function Person(name,age,sex){
//(1)创建空对象 {}
//(2)this指向这个对象 this = {}
//(3)对象赋值
this.name = name
this.age = age
this.sex = sex
//(4)返回这个对象 return this
}
let p1 = new Person('张三',20,'男')
console.log(p1)
4.实例对象:用new关键字调用函数返回的对象称为实例对象
let p1 = new Person('张三',20,'男')片
这个对象是用new关键字调用函数返回的对象,称为实例对象,被调用的函数称为构造函数,
我们已经知道实例对象和构造函数,接下来说明一下什么是原型对象
5.原型对象:任何函数在声明的时候,系统都会自动帮你创建一个对象,称为原型对象
那我们为什么要使用原型对象,原型对象的作用又是什么,
首先来解决一下构造函数在使用的时候存在的问题
function Person(name,age){
this.name = name
this.age = age
this.eat = function(){
console.log('eat');
}
}
let p1 = new Person('张三',18)
let p2 = new Person('李四',20)
console.log( p1,p2)
console.log( p1.eat == p2.eat )//false
此时p1和p2都有eat方法,而且函数体相同.但是他们是同一个函数?
答案是否定的,因为每调用一次构造函数,内部都会执行一次function,函数的数据类型是引用类型,它会在堆地址中开辟一个新的空间,虽然代码是一样的,但是地址不同,就会导致每调用一次构造函数,就会多出一个函数堆空间,导致内存资源浪费,此时我们可以使用全局函数来解决内存资源浪费问题
let eat = function() {
console.log("吃东西")
}
let learn = function() {
console.log("学习")
}
function Person(name, age) {
this.name = name
this.age = age
this.eat = eat
this.learn = learn
}
let p1 = new Person("张三", 18)
let p2 = new Person("李四", 20)
console.log( p1.eat == p2.eat )//true
此时p1和p2的eat方法是一样的,因为构造函数在调用的时候,构造函数内部并没有重新创建一个函数,而是拷贝eat的地址赋值,无论我们调用多少次,拷贝的都是地址,而不是在内存中重新开辟空间,这样就解决内存的浪费,但同时又会诞生一个新的问题,那就是在全局中声明太多的函数,变量名太多,可能会导致变量的全局污染,为了解决这个问题, 我们可以将这些函数加到一个新建的对象中
let obj = {
eat: function() {
console.log("吃东西")
},
learn: function() {
console.log("学习")
}
}
function Person(name, age) {
this.name = name
this.age = age
this.eat = obj.eat
this.learn = obj.learn
}
let p1 = new Person("张三", 18)
let p2 = new Person("李四", 20)
console.log(p1, p2)
这样我们就可以引出原型对象,而不需要我们自己去创建一个新对象来保护变量污染
5.1原型对象的作用:解决构造函数造成的内存浪费和变量污染
//构造函数
function Person(name, age) {
this.name = name
this.age = age
}
//原型对象
console.log(Person.prototype)
Person.prototype = {
eat: function() {
console.log("吃东西")
},
learn: function() {
console.log("学习")
}
}
//实例对象
let p1 = new Person('张三',20)
console.log( p1 )
let p2 = new Person('李四',22)
console.log( p1.eat == p2.eat )//true
p1.eat()//p1.__proto__.eat() 控制台打印吃东西
console.log(Person.prototype.constructor)//Person
构造函数在声明的时候,系统会帮我们自动创建一个对象,称为原型对象,构造函数中有一个prototype指向这个原型对象,
本来构造函数里面是没有eat这个方法的,但是p1却可以调用它,这是因为构造函数的原型有这个方法,所以p1可以调用,
实例对象中有一个隐式原型__proto__指向原型对象,而原型对象中有constructor属性指向构造函数,所以我们可以总结一下三者之间的关系
原型对象相关三个属性 : 描述 构造函数、原型对象、实例对象三者关系
prototype : 属于构造函数, 指向原型对象
proto : 属于实例对象,指向原型对象
constructor : 属于原型对象,指向构造函数
验证 构造函数、原型对象、实例对象三者关系
console.log( p1.__proto__.constructor )//Person
console.log( Person.prototype === p1.__proto__ )//true