除了使用Object构造函数或者字面量都可以创建对象,但是也有缺点就是使用同一个接口创建很多对象,会产生大量的重复代码。
1. 工厂模式
简单来说就是把Object创建对象使用函数进行封装,然后再返回创建的对象,就可以创建多个相同对象。
function createPerson(name, age) {
var o = new Object();
o.name = name;
o.age = age;
o.sayName = function () {
alert(this.name);
}
return o;
}
var person1 = createPerson('张三', 18);
函数createPerson
根据参数创建了一个包含所有必要信息的Person对象,可以多次调用这个函数,每次调用都能返回一个对象。
工厂模式缺点: 没有解决对象识别问题,就是怎样知道一个对象的类型。
2. 构造函数模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
alert(this.name);
}
}
var person1 = new Person('张三', 18, '前端工程师');
1. 什么是构造函数
构造函数本身跟普通函数一样,也不存在定义构造函数的特殊语法。唯一区别在于调用的方式不同,任何函数只要通过new 操作符来调用,都可以叫做构造函数。默认情况下构造函数的首字母大写,不大写也没有问题,主要是为了与普通函数区分。
2. 为什么有构造函数
为了创建对象而已,本身就是一个函数。
3. 创建一个新实例经历的过程
创建实例,必须使用new操作符调用。
- 创建一个新对象
- 将构造函数的作用域赋给新对象(也就是修改this,将this指向这个新对象)
- 执行构造函数中的代码(给对象新增属性方法)
- 返回新对象
3. 构造函数比其他函数区别
通过new操作符调用就叫做构造函数,如果不通过new操作符调用就是普通函数。一般构造函数默认首字母大写,不大写也没事,主要是为了区分是构造函数还是普通函数。
4. 构造函数比工厂模式的优点
自定义的构造函数将来可以将它的实例标识为一种特定的类型。
5. 构造函数比工厂模式的不同之处
- 没有显式创建对象,也就是new Object
- 直接将属性和方法赋给了this对象
- 没有return语句
6. 构造函数的问题
每个方法都要在每个实例上重新创建一遍
3. 原型模式
function Person() {}
Person.prototype.name = '张三';
Person.prototype.age = 18;
Person.prototype.job = '前端工程师';
Person.prototype.sayName = function () {
alert(this.name);
}
var person1 = new Person();
person1.sayName(); // '前端工程师'
1. 原型对象
创建一个新函数都会有一个prototype
(原型)属性,这个属性指向函数的原型对象。
在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针(也就是构造函数)。创建一个自定义构造函数之后,其原型对象默认只会取得constructor属性,其他方法,则会从Object继承而来的。
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。这个指针(内部属性)叫做[[Prototype]],可以通过_proto_
进行或者。用它来连接实例与构造函数的原型对象。
1. 原型对象的好处
可以让所有对象实例共享它所包含的属性和方法,换句话说,不必在构造函数中定义对象实例的信息,而是直接将这些信息添加到原型对象中。
2. isPrototypeOf()
用来检测实例是否指向某个原型对象,返回布尔类型
Person.prototype.isPrototypeOf(person1); // true
3. Object.getPrototypeOf()
返回原型对象
Object.getPrototypeOf(person1) == Person.prototype; // true
Object.getPrototypeOf(person1.name); // '张三'
4. 如何读取属性值
当对象某个对象属性时,首先先从对象实例本身开始读取,如果存在就返回,不存在就开始向原型对象查找属性。
5. 对象实例保存访问属性注意点
虽然实例对象可以对属性进行添加、编辑等操作,但是不能重写原型中的值。如果在实例中添加一个与原型中属性同名的属性,只是会把原型中对应的属性屏蔽掉。访问值时,会访问实例新创建的这个属性值,如果将实例对象中的这个属性删除,则会访问到原型中该属性的值。
hasOwnProperty()用来检测一个属性是否存在实例中还是原型中,返回布尔类型,若在实例中返回true。
person1.hasOwnProperty('name'); // false
person1.name = '李四';
person1.hasOwnProperty('name'); // true
2. 原型与in操作符
in操作符可以单独使用,也可以在for…in中使用。
**单独使用时:**判断能否访问到某个属性,不管这个属性在实例上还是原型中,返回布尔类型。
'name' is person1; // true
可以与hasOwnProperty()一起使用,来判断某个属性存在对象还是原型中。
function hasPrototypeProperty(object, name) {
return !object.hasOwnProperty(name) && (name in object);
}
hasOwnProperty只有在实例时才回返回true,in只要任意存在都返回true。
若hasOwnProperty为true,则结果返回false,代表在实例中。
若hasOwnProperty为false,in为true, 则结果返回true,代表在原型中。
若都为false,则结果返回false,代表不存在
**for…in: **返回的是所有可枚举的属性,包括实例、原型中存在的。
3. 更简单的原型语法
前面的例子每添加一个属性或方法都要敲一遍Person.prototype。可以用一个包含所有属性和方法的对象字面量来重写整个原型对象。
function Person(){}
Person.prototype = {
name: '张三',
age: 18,
job: '前端工程师',
sayName: function() {
alert(this.name);
}
}
这样看着是简单了,但是其实是用对象字面量又重新创建了一个新对象,这时的constructor属性将不再执向Person了。因为每创建一个函数,都会创建它的prototype对象,这个对象也会自动获得constructor属性。
这里本质重写了默认的prototype对象,因此constructor属性也变成了新对象的constructor属性(也就是Object构造函数), 不再执行Person函数。
解决方法
可以通过修改constructor属性值进行解决。
Person.prototype.constructor = Person;
但是这样修改会使其属性的描述特性[[Enumerable]]
被设置成true,默认原生的属性是不可枚举的,所以可以使用Object.defineProperty()进行设置属性值。
Object.defineProperty(Person.prototype, 'constructor', {
enumerable: false,
value: Person
})
4. 原型的动态性
动态性指的是不管创建实例在设置原型前后,都能获取到原型中的属性和方法。
但是有一种情况则不行,那就是重写了对象原型,重写也是用字面量重写设置了一个新对象(上一个标题3)。
为什么重写了对象原型就不能获取属性和方法了呢?
因为在调用构造函数时就为实例添加了一个指针,指向最初的原型对象,所以说修改原型对象就等于切断了构造函数和最初原型之间的联系。
实例中的指针指向原型,而不指向构造函数。
5. 原生对象的原型
原型模式不仅仅体现在创建自定义类型,就连原生的引用类型也是采用这种模式,都是在其构造函数的原型上定义的方法。
通过原生对象的原型,不仅可以取得所有默认方法的引用,也能定义新方法,也可以修改原生对象的原型就像修改自定义对象的原型。
6. 原型对象的问题
- 它省略了为构造函数传递初始化参数的这一环节,造成所有实例在默认情况下都将取得相同的属性值。
- 最大问题是其共享的本质导致的
不懂,以后编写
4. 组合使用构造函数模式和原型模式
**用法:**构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。
**结果: **每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省内存,并且还支持向构造函数传递参数。
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ['a', 'b'];
}
Person.prototype = {
constructor: Person,
sayName: function () {
alert(this.name);
}
}
var person1 = new Person('张三', 18, '前端工程师');
var person2 = new Person('李四', 10, '后端工程师');
person1.friends.push('c');
console.log(person1.friends); // ['a', 'b', 'c']
console.log(person2.friends); // ['a', 'b']
console.log(person1.friends === person2.friends); // false
console.log(person1.sayName === person2.sayName); // true
5. 动态原型模式
它把所有信息都封装在构造函数中,而通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。
function Person(name, age, job) {
// 属性
this.name = name;
this.age = age;
this.job = job;
// 方法
if (typeof this.sayName != 'function') {
Person.prototype.sayName = function(){
alert(this.name);
}
}
}
不懂?后面添加
6. 寄生构造函数模式
这种模式的基本思路就是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
function Person(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
alert(this.name);
}
return o;
}
var friend = new Person('张三', 18, '前端工程师');
friend.sayName(); // '张三'
在这个例子中,Person函数创建了一个对象,并设置相应的属性和方法初始化这个对象,然后又返回这个对象。
特点:除了使用new操作符并把使用的包装函数叫做构造函数之外,这个模式跟工程模式其实是一模一样的。
构造函数在没有返回值的情况下,默认返回新对象实例,而添加return语句,则可以重写构造函数时的返回值。
说明:返回的对象与构造函数或者构造函数的原型属性之间没有关系,也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。所以不能依赖instanceof操作符来确定对象类型。
7. 稳妥构造函数模式
指的是没有公共属性,而且其方法也不引用this对象。
与寄生构造函数类似的模式,但也有不同,一是新创建对象的实例方法不引用this。二是不使用new操作符调用构造函数。
function Person(name, age, job) {
// 创建要返回的对象
var o = new Object();
// 定义私有变量和函数
// 添加方法
o.sayName = function () {
alert(name);
}
// 返回对象
return o;
}
var friend = Person('张三', 18, '前端工程师');
friend.sayName(); // '张三'