创建实例对象的方法
1. 工厂模式
类似于工厂内部去加工,一个黑盒过程看不到。
function creatPerson(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('Nich', 29, 'software engineer');
特点:
看不到内部的过程。只需要传入参数即可
2. 构造函数模式
通过js的构造函数来创建对象
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
alert(this.name)
}
}
var person1 = new Person('nich', 29, 'engineer');
alert(person1.constructor == Person) // true
alert(person1 instanceof Object) // true
alert(person1 instanceof Person) // true
特点:
(1)每次new出一个实例都和其他的不是同一个实例
(2)多个实例中的属性和方法都是不同的实例,互相不影响,起不到共用的作用。
3. 原型模式
function Person() {}
Person.prototype.name = 'Nich';
Person.prototype.age = 29;
Person.prototype.sayName = function() {};
特点:
定义在prototype上的属性和方法可以共用,修改后所有实例的方法和属性都会改变。
4. 原型对象
Person.prototype一个指针指向一个原型对象。原型对象会获得一个constructor指向构造函数Person
Person.prototype = person._proto_
person.constructor = Person.prototype.constructor
(1)判断是否为实例的原型对象
alert(Person.prototype.isPrototypeOf(person1)) //true
(2)获取实例的原型对象
alert(Object.getPrototypeOf(person) == Person.prototype) // true
(3)检测一个属性是否存在于实例中
alert(person.hasOwnProperty('name')) // false
(4)获取所有实例属性,包括不可枚举属性
alert(Object.getOwnPropertyNames(Person.prototype)); // 'constructor, name'
在实例中可以重写原型中的方法,调用方法会先从自身顺着原型链找直到找到。
如果重写了原型方法后,即便设置为null,也只会在实例中设置,不会恢复指向原型的连接,只有通过使用delete操作符可以完全删除实例属性,从而访问到原型的属性。
如果需要重写原型?
function Person() {}
Person.prototype = {
constructor : Person,
// 如果重写了原型,则constructor不会自动添加也不会默认指会Person,需要手动添加
name : 'Nich',
age : 29
}
重写原型的方式有两个缺点
(1)没有办法传参来实例化不同的实例
(2)如果原型属性有引用值的话,修改了会改变其他的。因为他是指针指向,原型修改后所有实例会同步,所以很适合函数和基本类型的属性。不适合引用类型,而new构造函数的方式,则会脱离引用关系。自己单独实例化。修改自己实例中的值不会影响其他实例中的值。
function Person();
Person.prototype = {
arr:[1,2]
}
let person = new Person();
person.arr.push(3) // 其他都会改变
修复缺点最好的方式是使用构造函数和原型混合的模式
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ["shelby", "court"];
}
Person.prototype = {
constructor : Person,
sayName : function() {
alert(this.name);
}
}
var person1 = new Person("Nich", 29, "engineer");
var person2 = new Person("greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); // shelby court van
alert(person2.friends); // shelby court
alert(person1.sayName === person2.sayName); // true
5. 继承
原型链:让实例的原型对象等于另一个类型的实例,这个实例的原型再指向另一个实例。层层递进就构成原型链。所有的对象又继承于Object
<1> 原型链继承方式
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType(); // 关键语句 SubType继承于SuperType
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
let instance = new SubType();
alert(instance.getSuperValue()); // true
创建SuperType的实例,并赋给SubType.prototype,重写原型对象,原来存在于SuperType的实例中的属性方法,现在也存在于SubType.prototype中了(因为是实例,会将构造函数中的属性直接拿来一份,而原型方法则还在原型中)instance.constructor现在指向的是SuperType,因为原型被重写了,constructor也被重写到了SuperType
原型链继承方式的问题
1:如果有引用类型-- 原型被一个实例替代,实例如果从构造函数中继承的属性则变成了原型属性,原型属性是所有实例共用的,一个实例修改,所有都会改变
2: 创建子实例时,没办法向超类中传参
<2> 组合继承
function SuperType(name) {
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function() {
alert(this.name);
}
function SubType(name, age) {
// 执行一下继承的构造函数,可以继承属性
// 让自己的构造函数中也有一份属性,这样实例中的属性,修改就不会相互影响了。
SuperType.call(this, name);
// 在执行完构造函数后,再添加自己的属性。这样不会被覆盖。
this.age = age;
}
SubType.prototype = new SuperType();
// 其中还是有超类的构造函数中的属性,不过已经被覆盖
SubType.prototype.sayAge = function() {
alert(this.age);
}
// 可以自己传参给超构造函数
let instance1 = new SubType("Nich", 29);
instance1.colors.push("black");
alert(instance1.colors); // "red, blue, green, black"
instance1.sayName(); // "Nich"
instance1.sayAge(); // 29
let instance2 = new SubType("greg", 27);
alert(instance2.colors); // "red, blue, green"
instance2.sayName(); // "greg"
instance2.sayAge(); // 27
组合继承的缺点
都会调用两次超类型构造函数, 原型中仍然还有超类构造函数中的属性,不过已经被覆盖。但是会在新对象上创建多余的实例属性
<3> 寄生组合继承
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); // 创建对象副本
prototype.constructor = subType; // 重写了构造函数,需要重新指向构造函数
subType.prototype = prototype; // 指定对象
}
function SuperType(name) {
this.name = name;
this.colors = ["red","blue","green"];
}
SuperType.prototype.sayName = function() {
alert(this.name);
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
// 相当于只把超原型中的方法拷贝一份,不拷贝超构造函数中的属性。
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
alert(this.age);
}
拷贝原型上的方法上述例子中使用inheritPrototype(),有更常用的两种方法。
- SubType.prototype = Object.create(SuperType.prototype);
Object.create()内部实现:
function(o) {
let F = new Function();
F.prototype = o;
return new F()
}
实际上是进行了浅拷贝。创建了一个新对象,让他的原型等于传入的对象。和他的构造函数就脱离了关系,只拷贝原型方法,继承其原型方法,构造函数指向Object
- Object.setPrototypeOf(subType.prototype, superType.prototype)
Object.create内部需要new一个中介函数,再用垃圾回收机制回收。性能不高。Object.setPrototypeOf(subType.prototype, superType.prototype)可以和object.create一样的效果,性能更好
这样只调用一次SuperType的构造函数,避免在SubType.prototype上面创建多余的属性,原型链保持不变还可以正常使用instanceof和isPrototypeOf()
小结
创建对象方式:
1 工厂模式:使用简单的函数创建对象,添加属性方法,返回对象。
2 构造函数模式: 可以创建自定义引用类型,可以使用new,缺点:每个成员没办法复用,尤其函数不可以共享。new出实例后各自实例不会互相影响
3 原型模式:使用构造函数的prototype属性指定共享的方法和属性(一般是方法);
继承的方式:
1 原型链继承方式:可以达到继承父类的方法效果,但是没办法像父类传承。并且如果父类构造函数中有引用类型,就会被当成原型属性共用。修改都会改变
2 组合继承方式: 相比原型链继承可以向父类传参。但是构造函数需要执行两次,并且有多余的属性
3 寄生组合继承方式: 只是去做原型的复制。不会多次去执行父类的构造函数。推荐使用
网友评论