目录
普通的创建对象的方式
(1)用字面量形式创建对象
var person = {
name: "zhangsan",
age: 18,
sayName: function(){
console.log(this.name);
}
}
(2)new Object()创建对象
//先实例化一个对象
var person = new Object();
//为这个实例化的对象添加属性
person.name = "zhangsan";
person.age = 18;
person.sayName = function(){
console.log(this.name)
}
上述两种方法中,字面量创建的对象是一次性的,创建多少个对象就要写多少次,非常繁琐。第二种方式同样是一次只能创建一个对象,且它是先实例化了一个对象,然后再为对象添加属性,整体性弱。
1、工厂模式
优点:可以批量创建对象 提高代码复用率
缺点:方法冗余,无法区分实例对象的种类,创建的实例都是Object的实例
//将创建对象的代码封装在一个函数中
function createPerson(name, age, gender) {
var person = new Object();
person.name = name;
person.age = age;
person.gender = gender;
person.sayName = function () {
console.log(this.name);//这里每创建一个对象就会在堆内占据一个内存,造成内存浪费
}
return person;
}
//利用工厂函数来创建对象
var person1 = createPerson("zhangsan", 18, '男');
var person2 = createPerson("lisi", 20, '男');
console.log(p1.sayName === p2.sayName);//false
var getName=function(){
console.log(this.name);
};//将方法写到函数外面可以避免内存浪费,但是所用实例都可以调用这个方法
function Person(name,age,gender){
return {
name:name,
age:age,
gender:gender,
getName:getName
}
}
var p1=Person('zhangsan',12,'男');
var p2=Person('lisi',13,'男');
console.log(p1,p2);//{ name: 'terry', age: 12, gender: '男', getName: [Function: getName] } { name: 'larry', age: 13, gender: '男', getName: [Function: getName] }无法区分种类,创建的实例都是Object实例
console.log(p1.getName===p2.getName);//true 调用的同一个方法,堆内存地址相同
console.log(p1 instanceof Person);//false无法区分种类
console.log(p1 instanceof Object);//true 创建的实例都是对象实例
2、 函数构造模式
优点:可以批量创建对象,可以区分种类
缺点:方法冗余
var getName=function(){
console.log(this.name);
}
function Person(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
this.getName=getName;
}
var p1=new Person('zhangsan',18,'男');
var p2=new Person('lisi',19,'男');
console.log(p1,p2);// Person {name:'zhangsan',age:18,gender:'男',getName: [Function: getName]} 可以区分实例对象的种类
console.log(p1.getName===p2.getName);// true
构造函数模式相较于工厂模式可以确保实例被标识为特定的类型,可以确定其种类(比如上面的案例就是Person对象的实例)。
注意:
(1)在实例化时,如果不想传参数,构造函数后面的括号可加可不加。只要有 new 操作符,就可以调用相应的构造函数。
var person1 = new Person();
var person2 = new Person;
(2)构造函数也是函数,没有将某个函数定义为特殊函数的语法。任何函数只要使用new操作符调用就是构造函数,不使用new操作符调用的就是普通函数。这时候调用Person(),结果会将属性和方法添加到全局对象。
function Person(name, age) {
this.name = name;
this.age = age;
this.getName = function () {
console.log(this.name);
};
}
// 作为构造函数
var person = new Person("zhangsan", 18);
person.getName(); // zhangsan
// 作为函数调用 且没有明确设置this指向的情况下(即没有作为对象的方法调用或没使用call/bind等改变this指向)this始终指向global全局对象(浏览器中指向window对象)
Person("lisi", 18); // 属性和方法会被添加到全局对象
global.getName(); // lisi
2.1、new操作符
new操作符在new一个对象的过程中做了哪些事情?
1、创建一个新的实例对象
2、这个新对象内部[[[Prototype]]特性指向构造函数的原型对象属性
3、this指向新对象
4、执行构造函数函数体,给新对象添加属性
5、返回创建的新对象
2.2、构造函数的问题
构造函数时,其定义的方法会在每个实例上都创建一遍,如上面的案例,创建了p1、p2两个实例,它们都有一个getName()方法,但是不同实例上的函数虽然同名却不相等。
function Person(name){
this.name = name;
this.getName = function() {
console.log(this.name);
};
}
var p1=new Person('zhangsan');
var p2=new Person('lisi');
console.log(p1.getName===p2.getName);//false
//解决方法 将定义的方法转移到构造函数外部
var getName=function(){
console.log(this.name);
}
function Person(name){
this.name = name;
}
var p1=new Person('zhangsan');
var p2=new Person('lisi');
console.log(p1.getName===p2.getName);//true
这个方法虽然解决了这个问题,但是此时的getName()方法已成为了全局方法,任何对象都可以调用这个方法,全局的作用域会因此混乱,且如果要定义多个函数,这会导致自定义类型引用的代码不能聚集在一起,致使代码结构混乱。这个新的问题可通过原型模式解决。
3、原型模式
原型模式定义的属性和方法是由所有实例共享的。因此 p1 和 p2 访问的都是相同的属性和相同的 getName()函数。
// 将所有的属性和方法全部维护到原型对象中 构造函数中不存放任何属性和方法
// 不单独使用
function Person(){};
Person.prototype={
//设置constructor的值
constructor:Person,// 因为Person.prototype被作为通过字面量形式创建的新对象,完全重写了默认的prototype对象,constructor属性不在指向Person,而是指向新对象(Object构造函数)。解决这个问题,可以在重写原型对象时,设置constructor的值。
name:'terry',
age:12,
friends:[],
getName:function(){
console.log(this.name);
}
}
//创建两个新对象
var p1=new Person();
var p2=new Person();
console.log(p1,p2);//Person {} Person {}
console.log(p1.getName===p2.getName);//true
p1.friends.push('larry');
console.log(p1.friends,p2.friends);//[ 'larry' ] [ 'larry' ]
console.log(p1.name);//terry
//在实例上添加一个name属性
p1.name = 'zhangsan';
console.log(p1.name); //zhangsan原型对象上的name属性被实例对象的name属性遮住了
console.log(p2.name);//teery
在使用案例中的方式设置constructor的值时,会创建一个[[Enumerable]]为true(表示可枚举)的属性。而原生constructor是不可枚举的。可以改用Object.defineProperty()方法恢复constructor属性。
function Person(){};
Person.prototype={
name:'terry',
age:12,
friends:[],
getName:function(){
console.log(this.name);
}
}
Object.defineProperty(Person.prototype, "constructor", {
enumerable: false,
value: Person
});
由上面的案例可知,虽然可以通过实例读取原型对象上的值,但不可能通过实例重写这些值。当实例上添加了一个与原型对象中同名的属性,那就会在实例上创建这个属性,这个属性会遮住原型对象上的属性。
验证:我们可以通过hasOwnProperty()验证p1.name访问的是实例属性还是原型属性。实例属性返回true
console.log(p1.name);//terry
console.log(p1.hasOwnProperty('name'));//false
//添加实例属性
p1.name = 'zhangsan';
console.log(p1.name); //zhangsan
console.log(p1.hasOwnProperty('name'));//true 此时,表明name是一个实例属性,不是原型属性
给对象添加实例属性后,这个属性会遮蔽原型对象上与之同名的属性,只有使用delete操作符才可以完全删除实例上的属性,恢复对象和原型对象上这个属性的联系。
console.log(p1.name);//terry
console.log(p1.hasOwnProperty('name'));//false
//添加实例属性
p1.name = 'zhangsan';
console.log(p1.name); //zhangsan
console.log(p1.hasOwnProperty('name'));//true
//删除实例属性
delete p1.name;
console.log(p1.name);//terry
console.log(p1.hasOwnProperty('name'));//false
图解:
3.1. in操作符
作用:检查一个属性是否在该对象实例上还是在原型上,无论是在实例上还是原型上,都可以检测到
语法:属性名 in 实例对象名 返回值:true/false
console.log('name' in p1); // true
原型模式将所有方法全部维护到原型对象中有好处,但任然存在一些弊端,比如上面的案例中的friends属性,所有实例对象的这个属性都会是同一个值,但实际上不同的实例对象应该有属于自己的私有属性。这也是为什么不单独使用原型模式的原因。
4、组合模式
组合模式:将构造模式和原型模式组合使用
// 将实例的私有的属性和私有方法维护到构造函数中
// 将实例的公共属性和公共方法维护到原型对象中
function Person(name,age,gender){
this.name=name;
this.age=age;
this.gender=gender;
this.friends=[];
}
Person.prototype={
constructor:Person,
getName(){
console.log(this.name);
}
}
var p1=new Person("terry",12,'男');
var p2=new Person("larry",13,'男');
console.log(p1,p2);//
p1.getName();
p2.getName();
console.log(p1.getName===p2.getName);//
console.log(p1 instanceof Person);//
console.log(p1 instanceof Object);//
p1.friends.push("tom");
console.log(p1,p2);//
var arr=new Array();//
console.log(arr instanceof Object);
4.1. instanceof运算符
用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。或者说判断一个对象是某个对象的实例。
运用:前面案例中每个案例既是Object的实例也是Person的实例,下面调用instanceof确定实例p1的对象类型。
console.log(p1 instanceof Person);// true 表示p1的对象类型为Person
console.log(p1 instanceof Object);// true 结果为true表示p1的对象类型是Object