这些方式有明显的缺点:使用同一个接口建立不少对象,会产生大量的重复代码。为了解决这个问题,出现了工厂模式。 数组
考虑在ES中没法建立类(ES6前),开发人员发明了一种函数,用函数来封装以特定接口建立对象的细节。(实现起来是在一个函数内建立好对象,而后把对象返回)。 bash
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 0;
}
var person1=createPerson("Nicholas",29,"Software Engineer");
var person2=createPerson("Greg",27,"Doctor");
复制代码
像Object和Array这样的原生构造函数,在运行时会自动出如今执行环境。此外,也能够建立自定义的构造函数,从而定义自定义对象类型的属性和方法。app
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
alert(this.name);
};
}
var person1=new Person(...);
var person2=new Person(...);
复制代码
与工厂模式相比,具备如下特色: 函数
没有显式建立对象;测试
直接将属性和方法赋给了this对象;ui
没有return语句;this
要建立新实例,必须使用new操做符;(不然属性和方法将会被添加到window对象)spa
可使用instanceof操做符检测对象类型(instanceof运算符用于测试构造函数的prototype属性是否出如今对象的原型链中的任何位置)prototype
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}
var auto = new Car('Honda', 'Accord', 1998);
console.log(auto instanceof Car);
// expected output: true
console.log(auto instanceof Object);
// expected output: true
复制代码
构造函数的问题:指针
构造函数内部的方法会被重复建立,不一样实例内的同名函数是不相等的。可经过将方法移到构造函数外部解决这一问题,但面临新问题:封装性很差。
咱们建立的每一个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含能够由特定类型的全部实例共享的属性和方法。(prototype就是经过调用构造函数而建立的那个对象实例的原型对象)。
使用原型对象的好处是可让全部对象实例共享它所包含的属性和方法。换句话说,没必要在构造函数中定义对象实例的信息,而是能够将这些信息直接添加到原型对象中。
function Person(){
}
Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="...";
Person.prototype.sayName=function(){
...
};
var person1=new Person();
person1.sayName();//"Nicholas"
复制代码
更常见的作法是用一个包含全部属性和方法的对象字面量来重写整个原型对象,并重设constructor属性。
function Person(){
}
Person.prototype={
name:"...",
age:29,
job:"...",
sayName:function(){
...
}
};
Object.defineProperty(Person.prototype,"constructor",{
enumerable:false,
value:Person,
});
复制代码
原型对象的问题:
这是建立自定义类型的最多见的方式。
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。因此每一个实例都会有本身的一份实例属性的副本,但同时共享着对方法的引用,最大限度的节省了内存。同时支持向构造函数传递参数。
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=["S","C"];
}
Person.prototype={
constructor:Person,
sayName:function(){
alert(this.name);
}
};
var person1=new Person(...);
复制代码
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);
};
}
}
复制代码
这里只有sayName()不存在的状况下,才会将它添加到原型中,这段代码只会在初次调用构造函数时才执行。这里对原型所作的修改,可以马上在全部实例中获得反映。
ES5定义了一个名为Object.create()的方法,它建立一个新对象,其中第一个参数是这个对象的原型,第二个参数对对象的属性进行进一步描述。
Object.create()介绍
Object.create(null) 建立的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法,例如:toString(), hasOwnProperty()等方法
Object.create()方法接受两个参数:Object.create(obj,propertiesObject) ;
obj:一个对象,应该是新建立的对象的原型。
propertiesObject:可选。该参数对象是一组属性与值,该对象的属性名称将是新建立的对象的属性名称,值是属性描述符(这些属性描述符的结构与Object.defineProperties()的第二个参数同样)。注意:该参数对象不能是 undefined,另外只有该对象中自身拥有的可枚举的属性才有效,也就是说该对象的原型链上属性是无效的。
var o = Object.create(Object.prototype, {
// foo会成为所建立对象的数据属性
foo: {
writable:true,
configurable:true,
value: "hello"
},
// bar会成为所建立对象的访问器属性
bar: {
configurable: false,
get: function() { return 10 },
set: function(value) {
console.log("Setting `o.bar` to", value);
}
}
});
console.log(o);//{foo:'hello'}
var test1 = Object.create(null) ;
console.log(test1);// {} No Properties
由于在bar中设置了configurable 使用set,get方法默认都是不起做用,因此bar值没法赋值或者获取
这里的o对象继承了 Object.prototype Object上的原型方法
咱们能够 对象的 __proto__属性,来获取对象原型链上的方法 如:
console.log(o.__proto__);//{__defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, __lookupSetter__: ƒ, …}
console.log(test1.__proto__);//undefined
复制代码
经过打印发现, 将{}点开,显示的是 No Properties ,也就是在对象自己不存在属性跟方法,原型链上也不存在属性和方法,
new object()
var test1 = {x:1};
var test2 = new Object(test1);
var test3 = Object.create(test1);
console.log(test3);//{}
//test3等价于test5
var test4 = function(){
  
}
test4.prototype = test1;
var test5 = new test4();
console.log(test5);
console.log(test5.__proto__ === test3.__proto__);//true
console.log(test2);//{x:1}
复制代码
var test1 = {};
var test2 = new Object();
var test3 = Object.create(Object.prototype);
var test4 = Object.create(null);//console.log(test4.__proto__)=>undefined 没有继承原型属性和方法
console.log(test1.__proto__ === test2.__proto__);//true
console.log(test1.__proto__ === test3.__proto__);//true
console.log(test2.__proto__ === test3.__proto__);//true
console.log(test1.__proto__ === test4.__proto__);//false
console.log(test2.__proto__ === test4.__proto__);//false
console.log(test3.__proto__ === test4.__proto__);//false
复制代码
总结:使用Object.create()是将对象继承到__proto__属性上
var test = Object.create({x:123,y:345});
console.log(test);//{}
console.log(test.x);//123
console.log(test.__proto__.x);//3
console.log(test.__proto__.x === test.x);//true
var test1 = new Object({x:123,y:345});
console.log(test1);//{x:123,y:345}
console.log(test1.x);//123
console.log(test1.__proto__.x);//undefined
console.log(test1.__proto__.x === test1.x);//false
var test2 = {x:123,y:345};
console.log(test2);//{x:123,y:345};
console.log(test2.x);//123
console.log(test2.__proto__.x);//undefined
console.log(test2.__proto__.x === test2.x);//false
复制代码
我这里就介绍一种吧,剩下的能够去权威指南里看去
ECMAScript 中描述了原型链的概念,并将原型链做为实现继承的主要方法。其基本思想是利用原 型让一个引用类型继承另外一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每 个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型 对象的内部指针。那么,假如咱们让原型对象等于另外一个类型的实例,结果会怎么样呢?显然,此时的 原型对象将包含一个指向另外一个原型的指针,相应地,另外一个原型中也包含着一个指向另外一个构造函数 的指针。假如另外一个原型又是另外一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实 例与原型的链条。这就是所谓原型链的基本概念。
实现原型链有一种基本模式,其代码大体以下。
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
复制代码
以上代码定义了两个类型:SuperType 和 SubType。每一个类型分别有一个属性和一个方法。它们 的主要区别是 SubType 继承了 SuperType,而继承是经过建立 SuperType 的实例,并将该实例赋给 SubType.prototype 实现的。实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原 来存在于 SuperType 的实例中的全部属性和方法,如今也存在于 SubType.prototype 中了。在确立了 继承关系以后,咱们给 SubType.prototype 添加了一个方法,这样就在继承了 SuperType 的属性和方 法的基础上又添加了一个新方法。这个例子中的实例以及构造函数和原型之间的关系如图 6-4 所示。
原型链虽然很强大,能够用它来实现继承,但它也存在一些问题。其中,最主要的问题来自包含引 用类型值的原型。想必你们还记得,咱们前面介绍过包含引用类型值的原型属性会被全部实例共享;而 这也正是为何要在构造函数中,而不是在原型对象中定义属性的缘由。在经过原型来实现继承时,原 型实际上会变成另外一个类型的实例。因而,原先的实例属性也就瓜熟蒂落地变成了如今的原型属性了。 下列代码能够用来讲明这个问题
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
}
//继承了 SuperType
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green,black"
复制代码
这个例子中的 SuperType 构造函数定义了一个 colors 属性,该属性包含一个数组(引用类型值)。 SuperType 的每一个实例都会有各自包含本身数组的 colors 属性。当 SubType 经过原型链继承了 SuperType 以后,SubType.prototype 就变成了 SuperType 的一个实例,所以它也拥有了一个它自 己的 colors 属性——就跟专门建立了一个 SubType.prototype.colors 属性同样。但结果是什么 呢?结果是 SubType 的全部实例都会共享这一个 colors 属性。而咱们对 instance1.colors 的修改 可以经过 instance2.colors 反映出来,就已经充分证明了这一点。
原型链的第二个问题是:在建立子类型的实例时,不能向超类型的构造函数中传递参数。实际上, 应该说是没有办法在不影响全部对象实例的状况下,给超类型的构造函数传递参数。有鉴于此,再加上 前面刚刚讨论过的因为原型中包含引用类型值所带来的问题,实践中不多会单独使用原型链。
在解决原型中包含引用类型值所带来问题的过程当中,开发人员开始使用一种叫作借用构造函数 (constructor stealing)的技术(有时候也叫作伪造对象或经典继承)。这种技术的基本思想至关简单,即 在子类型构造函数的内部调用超类型构造函数。别忘了,函数只不过是在特定环境中执行代码的对象, 所以经过使用 apply()和 call()方法也能够在(未来)新建立的对象上执行构造函数,以下所示:
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(){
//继承了 SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
复制代码
代码中加粗的那一行代码“借调”了超类型的构造函数。经过使用 call()方法(或 apply()方法 也能够),咱们其实是在(将来将要)新建立的 SubType 实例的环境下调用了 SuperType 构造函数。 这样一来,就会在新 SubType 对象上执行 SuperType()函数中定义的全部对象初始化代码。结果, SubType 的每一个实例就都会具备本身的 colors 属性的副本了。
1. 传递参数
相对于原型链而言,借用构造函数有一个很大的优点,便可以在子类型构造函数中向超类型构造函 数传递参数。看下面这个例子。
function SuperType(name){
this.name = name;
}
function SubType(){
//继承了 SuperType,同时还传递了参数 SuperType.call(this, "Nicholas");
//实例属性
this.age = 29;
}
var instance = new SubType();
alert(instance.name); //"Nicholas";
alert(instance.age); //29
复制代码
以上代码中的 SuperType 只接受一个参数 name,该参数会直接赋给一个属性。在 SubType 构造 函数内部调用 SuperType 构造函数时,其实是为 SubType 的实例设置了 name 属性。为了确保 SuperType 构造函数不会重写子类型的属性,能够在调用超类型构造函数后,再添加应该在子类型中 定义的属性。
2. 借用构造函数的问题
若是仅仅是借用构造函数,那么也将没法避免构造函数模式存在的问题——方法都在构造函数中定 义,所以函数复用就无从谈起了。并且,在超类型的原型中定义的方法,对子类型而言也是不可见的,结 果全部类型都只能使用构造函数模式。考虑到这些问题,借用构造函数的技术也是不多单独使用的。
组合继承(combination inheritance),有时候也叫作伪经典继承,指的是将原型链和借用构造函数的 技术组合到一块,从而发挥两者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方 法的继承,而经过借用构造函数来实现对实例属性的继承。这样,既经过在原型上定义方法实现了函数 复用,又可以保证每一个实例都有它本身的属性。下面来看一个例子。
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.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);
instance1.sayName();
instance1.sayAge();
//"red,blue,green,black"
//"Nicholas";
//29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors);
instance2.sayName();
instance2.sayAge();
//"red,blue,green"
//"Greg";
//27
复制代码
在这个例子中,SuperType 构造函数定义了两个属性:name 和 colors。SuperType 的原型定义 了一个方法 sayName()。SubType 构造函数在调用 SuperType 构造函数时传入了 name 参数,紧接着 又定义了它本身的属性 age。而后,将 SuperType 的实例赋值给 SubType 的原型,而后又在该新原型 上定义了方法 sayAge()。这样一来,就可让两个不一样的 SubType 实例既分别拥有本身属性——包 括 colors 属性,又可使用相同的方法了
组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优势,成为 JavaScript 中最经常使用的继 承模式。并且,instanceof 和 isPrototypeOf()也可以用于识别基于组合继承建立的对象。 9
能够在没必要预先定义构造函数的状况下实现继承,其本质是执行对给定对象的浅 复制。而复制获得的副本还能够获得进一步改造。
与原型式继承很是类似,也是基于某个对象或某些信息建立一个对象,而后加强 对象,最后返回对象。为了解决组合继承模式因为屡次调用超类型构造函数而致使的低效率问 题,能够将这个模式与组合继承一块儿使用。
集寄生式继承和组合继承的优势与一身,是实现基于类型继承的最有效方式。