为什么要用原型对象
我们学习了构造函数的方式创建对象,你会发现,每一个对象的属性不一样这是一定的,但是它的方法似乎好像是一样的,如果我创建1000个对象,那岂不是内存中就有1000个相同的方法,那要是有10000个,那对内存的浪费可不是一点半点的,我们有没有什么好的办法解决,没错,我们可以把函数抽取出来,作为全局函数, 以了,上代码:
// 使用构造函数来创建对象
function Person(name, age) {
// 设置对象的属性
this.name = name;
this.age = age;
// 设置对象的方法
this.sayName = sayName
}
// 抽取方法为全局函数
function sayName() {
console.log(this.name);
}
但是,在全局作用域中定义函数却不是一个好的办法,为什么呢?因为,如果要是涉及到多人协作开发一个项目,别人也有可能叫sayName这个方法,这样在工程合并的时候就会导致一系列的问题,污染全局作用域,那该怎么办呢?有没有一种方法,我只在Person这个类的全局对象中添加一个函数,然后在类中引用?
// 使用构造函数来创建对象
function Person(name, age) {
// 设置对象的属性
this.name = name;
this.age = age;
}
// 在Person类的原型对象中添加方法
Person.prototype.sayName = function() {
console.log(this.name);
};
原型对象概念
- 我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype,这个属性对应着一个对象,这个对象就是我们所谓的原型对象,即显式原型,原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。
- 以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了。
- 如果函数作为普通函数调用prototype没有任何作用,当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过__proto__(隐式原型)来访问该属性。当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。
原型对象详解
Javascript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象(原型对象prototype)。
这个原型对象的所有属性和方法,都会被构造函数的实例访问(继承)。
这也就意味着,我们可以把所有对象实例需要共享的属性和方法直接定义在 prototype 对象上。
原型链
概念:
- prototype是构造函数都有的属性,称为显示原型
- ___proto__是每个对象都有的属性,又称为隐式原型。
- 但是,___proto__不是一个规范属性,只是部分浏览器实现了此属性,对应的标准属性是[[Prototype]]。
什么是原型链
- 当访问一个对象的某个属性或方法时,会先在这个对象本身上查找,
- 如果没有找到,则会去它的_ proto _([[prototype]])隐式原型上查找,即它的构造函数的prototype
- 如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。
使用prototype实现继承
- 继承了父级原型上的方法
- 实例化多个Student都必须公用name和age
- 需要将构造器的指向更改回正确的指向
// 定义一个构造函数
function Person(name,age){
this.name = name,
this.age = age,
this.say = function(){
console.log('我的名字叫'+name);
}
}
// 给person的原型对象添加一个方法
Person.prototype.eat = function(){
console.log('我是person原型添加的');
}
function Student(sex,state){
this.sex = sex
this.state = state
}
// 继承
Student.prototype = new Person('zjf',12)
Student.prototype.constructor = Student
// 实例化一个stu
let stu = new Student('男','在')
console.log(stu);
console.log('stu.sex:',stu.sex);
console.log('stu.name:',stu.name);
stu.say()
stu.eat()
使用call方法继承
- 实现了继承属性、但值都不相同
- 无法继承父类原型对象上的方法
function Person(name,age){
this.name = name
this.age = age
this.say=function(){
console.log('这是构造函数里面的');
}
}
Person.prototype.like =function(){
console.log('这个是原型对象上的方法');
}
// 让student构造函数里面的this变成
function Student(name,age,score){
// Person.apply(this,arguments)
// Person.call(this,arguments[0],arguments[1],arguments[2])
Person.call(this,name,age,score)
}
let p = new Person('zrs',19)
let stu = new Student('zjf',18,99)
console.log(p); //有link方法
console.log(stu); //没有link方法
使用混合继承
- 混合了两种方法的优点
function Person(name,age){
this.name = name
this.age = age
this.say=function(){
console.log('这是构造函数里面的');
}
}
Person.prototype.like =function(){
console.log('这个是原型对象上的方法');
}
// 让student构造函数里面的this变成
function Student(name,age,score){
this.score = score
Person.call(this,name,age,score)
}
// 改变原型指向
//student.prototype本来是指向的它的原型对象
//现在student.prototype指向person构造函数创建的实例对象
Student.prototype = new Person() //不传值
let stu = new Student('zjf',18,99)
stu.say() //这是构造函数里面的
stu.like() //这个是原型对象上的方法