最近正在学习前端知识,所以想通过写写博客来总结一下,加深自己的理解。
关于对象
程序员怎么可能会有对象!
对象是某个特定引用类型的实例,在JavaScript中就是Object类型。
创建Object实例的方法有以下两种:
//第一种:使用new操作符后跟Object构造函数
var person = new Object();
person.name = "Mike";
person.age = 21;
//第二种:使用对象字面量表示法
var person = {
name : "Mike",
age : 21
}
程序员们大部分时候都使用对象字面量表示法,因为其代码量更少,当向函数传递大量可选参数时也更加方便。
上面两种方法可以创建对象,但当使用同一接口创建很多对象的时候,就会产生很多的重复代码。下面是创建对象的几种方式及其比较。
1.工厂模式
function createPerson(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 person_one = createPerson("Mike",21,"Student");
var person_two = createPerson("Jack",34,"Engineer");
person_one.sayName(); //Mike
工厂模式没有解决对象识别的问题,即怎么知道一个对象的类型。
2.构造函数模式
function Person(name,age,job){ //构造函数以大写字母开头
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person_one = new Person("Mike",21,"Student"); //要使用new操作符
var person_two = new Person("Jack",34,"Engineer");
alert(person_one instanceof Object); //true
alert(person_one instanceof Person); //true
构造模式和工厂模式相比:
- 并没有显式的创建对象;
- 直接将属性和方法赋给this对象;
- 没有使用到return语句。
此方式调用构造函数会经历4个步骤:
- 创建一个新对象;
- 将构造函数的作用域赋给新对象(使this指向新对象);
- 为新对象添加属性;
- 返回新对象。
构造函数也有其自身的不足,主要问题就是每个方法都要在实例上重新创建一遍,例如上面的sayName()方法每个对象都是一样的,但是上例就重复的创建了方法,浪费了空间。创建两个完成同样任务的Function实例没有必要,所以可以像下面这样:
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
}
function sayName(){ //将函数定义转移到构造函数的外部
alert(this.name);
}
var person_one = new Person("Mike",21,"Student");
var person_two = new Person("Jack",34,"Engineer");
上面的方法实现了person_one和person_two对象共享同一个sayName()函数,但是如果对象需要定义很多方法,那么就要定义很多全局函数,自定义的引用类型也没有封装可言了。为了解决上述问题,我们就引入了原型模式。
3.原型模式 (重点难点)
原型对象:我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。prototype是通过调用构造函数而创建的那个对象实例的对象原型,使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。
function Person(){
}
Person.prototype.name = "Mike";
Person.prototype.age = 21;
Person.prototype.job = "Student";
Person.prototype.sayName = function(){
alert(this.name);
};
var person_one = new Person();
person_one.sayName(); //Mike
var person_two = new Person();
person_two.sayName(); //Mike
alert(person_one.sayName == person_two.sayName); //true
当代码读取某对象的某属性时,会执行一次搜索,首先从对象实例本身开始,如果找到了对应属性,则返回该属性的值,否则继续搜索指针指向的原型对象,如果找到了则返回属性的值。当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。注意:虽然能够通过对象实例访问保存在原型的值,但是不能通过对象实例重写原型中的值。
通过isPrototypeOf()方法,可以确定对象之间是否存在实例和原型的关系:
alert(Person.prototype.isPrototypeOf(person_one)); //true
alert(Person.prototype.isPrototypeOf(person_two)); //true
通过hasOwnProperty()方法,可以检测一个属性是存在于实例中,还是存在于原型中,只有给定属性存在于实例中,才会返回true。
alert(person_one.hasOwnProperty("name")); //false
上面的原型模式的语法稍显繁琐,下面用对象字面量的方式来重写整个原型对象,更加方便:
function Person(){
}
Person.prototype = {
name : "Mike",
age : 21,
job : "Student",
sayName : function(){
alert(this.name);
}
};
var person_one = new Person();
alert(person_one instanceof Person); //true
alert(person_one.constructor == Person); //false
alert(person_one.constructor == Object); //true
可以发现用对象字面量的方式创建会使constructor属性等于Object而不是Person,所以可以特意将其设置回适当的值。
function Person(){
}
Person.prototype = {
constructor : Person, //设置constructor的值
name : "Mike",
age : 21,
job : "Student",
sayName : function(){
alert(this.name);
}
};
原型模式的问题由其共享的本性所导致,共享属性就带来了两个问题:
- 不能传参
- 共享了原型,没有特有的属性
所以原型模式很少会单独使用。
4.组合使用构造函数模式和原型模式
创建自定义类型的最常见方式,就是组合使用构造函数模式和原型模式。构造函数模式用于定义实例属性,原型模式用于定义方法和共享属性。这样,每个实例都存在自己的实例属性副本,同时共享对方法的引用,最大限度地节省了内存。
function Person(name,age,job){
this.name = name ;
this.age = age ;
this.job = job ;
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person_one = new Person("Mike",21,"Student");
var person_two = new Person("Jack",34,"Engineer");
alert(person_one.job); //Student
person_two.sayName(); //Jack
除了以上几种方式外,还有动态原型模式,寄生构造函数模式,稳妥构造函数模式等,但是使用场景特殊,用的比较少,这里不再赘述。