创建对象
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 person1 = createPerson("Nicholas", 29, "Software Engineer");
person1.sayName()//"Nicholas"
var person2 = createPerson("Greg", 27, "Doctor");
person2.sayName()//"Greg"
千万不要对对象直接复制,下面这样做,box1和box指向同一个对象,其一变,则都变
var box = new Object();
box.age = 100; //创建一个age 属性并赋值
box.run = function () { //创建一个run()方法并返回值
return this.name + this.age + '运行中...';
};
var box1=box;
box1.age=1;
console.log(box.age)//1
工厂模式
优点:解决代码重复(重复化实例)
缺点:无法区分是哪个对象的实例
2、构造函数
与工厂模式的不同:
1.构造函数方法没有显示的创建对象(new Object());
2.直接将属性和方法赋值给this 对象;
3.没有renturn 语句。
function Person(name){
this.name=name;
this.sayName=function(){
console.log(this.name);
}
}
var p1=new Person('A');
p1.sayName();//A
p1有一个constructor属性,指向Person
console.log(p1.constructor==Person);//true
对象的constructor属性最初是用来标识对象类型的,但是检测对象类型建议使用instanceof。
console.log(p1 instanceof Person);//true
console.log(p1 instanceof Object);//true
构造函数的规范:1、函数名和实例化构造名相同且大写(非强制)2、通过构造函数创建对象,必须用new运算符
构造函数执行过程:
1、当使用了构造函数,并且new构造函数(),那么后台就执行了new Object();
2、将构造函数的作用域给新对象,而函数体内的this就代表new Object()出来的对象。
3、执行构造函数内的代码
4、返回新对象
构造函数优点;解决代码重复,解决对象类别识别问题
缺点:每个方法都要在实例上创造一遍,因为函数也是对象,每定义一个函数,就实例化了一个对象new Function().因此
console.log(p1.sayName==p2.sayName);//false
解决:把函数定义转移到外部,p1,p2共享全局下的同一个sayName()。但是,这样做毫无封装可言
function Person(name){
this.name=name;
this.sayName=sayName;
}
function sayName(){
console.log(this.name);
}
var p1=new Person('A');
var p2=new Person('b');
console.log(p1.sayName==p2.sayName);//true
3、原型(函数中的一个对象属性)
每个函数都有一个prototype属性,这个属性指向函数的原型对象,该属性包含由特定类型的所有实例共享的属性和方法。不妨理解为,prototype是通过调用构造函数而创建的那个实例对象的原型对象。不必在构造函数中定义对象信息,而是可以直接将这些信息添加到原型中。
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
[[Prototype]]在创建实例时,自动生成,他是一个指针,指向构造函数的原型对象,实例与构造函数没有直接关系。通过
isPrototypeOf()
方法来确定对象之间是否存在这种关系。实例person1/2和原型的constructor均指向构造函数
console.log(Person.prototype.isPrototypeOf(person1))//true
另一个是Object.getPrototypeOf()
获取对象的原型
console.log(Object.getPrototypeOf(person1)==Person.prototype)//true
原型模式的执行流程:
1.先查找构造函数实例里的属性或方法,如果有,立刻返回;
2.如果构造函数实例里没有,则去它的原型对象里找,如果有,就返回;
使用hasOwnProperty()
方法来检测一个属性是存在于实例中还是原型中,
console.log(person1.hasOwnProperty('name'))//false
person1.name='w';
console.log(person1.hasOwnProperty('name'))//true
console.log(person1.name)//w
in
操作符会在通过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原型中.
console.log(person1.hasOwnProperty('name'))//false
console.log('name' in person1);//true
person1.name='w';
console.log(person1.hasOwnProperty('name'))//true
console.log('name' in person1);//true
为了让属性和方法更好的封装,可用字面量方式,但是要注意,字面量创建的方式使用constructor属性不会指向构造函数,而是指向Object
function Person(){
}
Person.prototype={
name:'s',
sayName:function(){
console.log(this.name);
}
}
var p1=new Person();
console.log(p1.constructor==Person);//false
console.log(p1.constructor==Object);//true
字面量方式,需要强制constructor指向构造函数
function Person(){
}
Person.prototype={
constructor:Person,
name:'s',
sayName:function(){
console.log(this.name);
}
}
var p1=new Person();
console.log(p1.constructor==Person);//true
console.log(p1.constructor==Object);//false
字面量方式为什么constructor 会指向Object?因为,字面量其实就是新创建了一个对象,每创建一个函数,同时会创建他的prototype,Person.prototype这个对象也会自动获取constructor 属性。所以,新对象的constructor 重写了原来的constructor,因此会指向新对象,那个新对象没有指定构造函数,那么就默认为Object。
原型的声明是有先后顺序的,所以,重写的原型会切断之前的原型。
function Person(){
}
Person.prototype={
constructor:Person,
name:'s',
sayName:function(){
console.log(this.name);
}
}
var p1=new Person();
Person.prototype={
constructor:Person,
name:'h',
sayName:function(){
console.log(this.name);
}
}
var p2=new Person();
p2.sayName()//h
p1.sayName()//s
原型优缺点:
它省略了构造函数传参初始化这一过程,带来的缺
点就是初始化的值都是一致的。而原型最大的缺点就是它最大的优点,那就是共享。
原型中所有属性是被很多实例共享的,共享对于函数非常合适,对于包含基本值的属性也还可以。但如果属性包含引用类型,就存在一定的问题,一个实例改动,其他实例的该属性也跟着变了
解决:组合构造函数+原型模式
function Person(){
this.name=['s','a'];
}
Person.prototype={
constructor:Person,
sayName:function(){
console.log(this.name);
}
}
var p1=new Person();
p1.name.push('d')
var p2=new Person();
p2.sayName()//['s','a'];p2的name没有随着改变
p1.sayName()// ["s", "a", "d"]
寄生构造函数,其实就是工厂模式+构造函数模式。这种模式比较通用,但不能确定对象关系,所以,在可以使用之前所说的模式时,不建议使用此模式。
在什么情况下使用寄生构造函数比较合适呢?假设要创建一个具有额外方法的引用类型。由于之前说明不建议直接String.prototype.addstring,可以通过寄生构造的方式添加。给js自带对象添加属性和方法
function myString(string) {
var str = new String(string);
str.addstring = function () {
return this + ',被添加了!';
};
return str;
}
var box = new myString('Lee'); //比直接在引用原型添加要繁琐好多
alert(box.addstring());
在一些安全的环境中,比如禁止使用this 和new,这里的this 是构造函数里不使用this,这里的new 是在外部实例化构造函数时不使用new。这种创建方式叫做稳妥构造函数。所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this 的对象。
function Box(name , age) {
var obj = new Object();
obj.run = function () {
return name + age + '运行中...'; //直接打印参数即可
};
return obj;
}
var box = Box('Lee', 100); //直接调用函数
alert(box.run());
//稳妥构造函数和寄生类似。
继承
利用原型链实现继承,所有引用类型默认都继承了Object,而这个继承也是通过原型链实现的。大家要记住,所有函数的默认原型都是Object 的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
};
function SubType(){
this.subproperty = false;
}
//继承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue()); //true
构造函数A,在构造函数A的原型中定义一些属性和方法。在定义一个构造函数B,让B的原型称为A的一个实例,那么B原型会指向A原型,B的实例会继承A原型中的属性和方法,。且指向B原型
构造函数、原型和实例的关系:每个构造函数(prototype属性指向原型函数)都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。
记住一点,实例指向构造函数的原型,实例和构造函数之间没有直接关系。
参考资料:JavaScript高级程序设计(第3版)
网友评论