知识点
- 传统构造函数存在的问题
- 使用原型解决构造函数问题
- 原型相关的概念
- 原型的使用
- 使用原型的注意事项
- __proto__访问原型
- 原型中的构造函数
传统构造函数存在的问题
发现问题
现有构造函数如下:
function Person(name, age){
this.name = name;
this.age = age;
this.sayHi = function(){
console.log("你好");
}
}
调用该构造函数创建对象,并对比创建出来的对象的sayHi方法:
var p = new Person("张三", 18);
var p1 = new Person("李四", 19);
console.log(p.sayHi == p1.sayHi); //输出结果为false
注意:如果构造函数没有参数,那么在调用的时候 ()
可以省略
由于每个对象都是由new Person
创建出来的,因此每创建一个对象,函数sayHi都会被重新创建一次,这个时候,每个对象都拥有一个独立的,但是功能完全相同的方法。
功能相同的函数,完全没有必要再内存中存在这么多份。所以就造成了资源浪费。
解决问题
这里最好的办法就是将函数体放在构造函数之外. 在构造函数中只需要引用该函数即可。
function sayHello(){
console.log("你好");
}
function Person(name, age){
this.name = name;
this.age = age;
this.sayHi = sayHello;
}
//调用该构造函数创建对象,并对比创建出来的对象的sayHi方法
var p = new Person("张三", 18);
var p1 = new Person("李四", 19);
console.log(p.sayHi == p1.sayHi); //输出结果为true
这样写依然存在问题:
- 全局变量增多,会增加引入框架命名冲突的风险
- 代码结构混乱,会变得难以维护
使用原型解决构造函数问题
关键点
- 每一个函数在定义的时候,都会有跟它关联的一个对象被创建出来
- 每一个由构造函数创建出来的对象,都会默认的和构造函数的神秘对象关联
- 当使用一个方法进行属性或者方法访问的时候,会先在当前对象内查找该属性和方法
- 如果当前对象内未找到,就回去跟它关联的神秘对象内进行查找
function Person(name, age){
this.name = name;
this.age = age;
this.sayHi = function(){
console.log("Hello!");
};
}
var p = new Person("张三", 18);
p.sayHi(); //当前对象内有这个方法,所以不会去神秘对象内进行查找
var p1 = new Person("李四", 19);
p1.sayHello(); //当前对象没没有找到这个方法,所以去神秘对象内进行查找
问题来了,如何访问到这个神秘对象呢?
//可以通过 构造函数.prototype 访问这个神秘对象
console.log(Person.prototype);
当尝试给这个对象新增一个方法之后:
Person.prototype.sayHello = function(){
console.log("我是神秘对象中的方法");
};
使用p,p1都可以访问这个方法:
p.sayHello();
p1.sayHello();
总结:
所有对象共享神秘对象(构造函数.prototype)内的属性和方法。
解决方案
既然所有对象共享神秘对象(构造函数.prototype)内的属性和方法。我们只需要将需要共享的东西,也就是重复占用内存的东西,全部都放到 神秘对象(构造函数.prototype)中,那么所有对象就都可以使用,并且内存里面也只有一份了。
改造构造函数
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function(){
console.log("你好");
};
//测试
var p = new Person("张三", 18);
var p1 = new Person("李四", 19);
console.log(p.sayHi == p1.sayHi); //输出true
常见的错误
- 将属性写在神秘对象(构造函数.prototype)内
function Car(name){
this.name = name;
}
function Person() {}
Person.prototype.name = '张三'; //基本类型的属性影响不大
Person.prototype.car = new Car("法拉利"); //引用类型的属性,会被所有的对象共享
var p = new Person();
- 赋值的错误
function Person() {}
Person.prototype.name = '张三';
var p1 = new Person();
var p2 = new Person();
p1.name = '李四';
console.log( p1.name );
console.log( p2.name );
// 如果是访问数据, 当前对象中如果没有该数据就到构造函数的原型属性中去找
// 如果是写数据, 当对象中有该数据的时候, 就是修改值; 如果对象没有该数据, 那么就添加值
原型相关的概念
神秘对象称与构造函数
- 神秘对象就是构造函数的 "原型属性"
- 简称原型 (构造函数的原型)
神秘对象与构造函数所创建出来的对象
- 神秘对象针对构造函数创建出来的对象称为 "原型对象"
-
简称原型 (对象的原型)
原型继承
- 构造函数创建的对象 继承自 构造函数的原型属性
- 构造函数创建的对象 继承自 该对象的原型对象
- 原型中的成员, 可以直接被实例对象所使用
- 实例对象直接 "含有" 原型中的成员
- 因此实例对象 继承自 原型
- 这样的继承就是 "原型继承"
原型的使用
- 使用对象的动态特性
function Person () { }
Person.prototype.func = function () {
console.log( 'something' );
};
var p = new Person();
p.func();
- 直接替换原型对象
function Person () { };
Person.prototype = {
func: function () {
console.log( '22222' );
}
};
var p = new Person();
p.func();
- 直接替换原型会出现的问题
function Person () { }
Person.prototype.func = function () {
console.log( 'something' );
};
var p = new Person();
Person.prototype.func = function () {
console.log( 'something' );
};
var p1 = new Person();
p.func();
p1.func();
替换原型之后,在替换前创建出来的对象和替换后创建出来的对象的原型对象不一致
使用原型的注意事项
- 原型的属性是应用类型
那么所有的对象共享该属性,并且一个对象修改了该引用对象属性,那么其他的对象也会受影响
__proto__访问原型
原型中的构造函数
- 原型对象在创建出来的时候,会默认的有一个
constructor
属性
- 使用新的对象替换掉默认的原型对象之后原型的构造函数