面向对象语言都有类的概念,可是ECMAScript没有类的概念,因此它的对象与基于类的语言中的对象有所不一样。app
var person = { name:'Zhangsan', age:20, gender: 'male', sayName: function(){ console.log(this.name); } }
var person = new Object(); person.name = 'Zhangsan'; person.age = 20; person.gender = 'male'; person.sayName = function(){ console.log(this.name); }
缺点:使用同一个接口建立不少对象,会产生大量的重复代码函数
由于ECMAScript没有类的概念,因此用函数来封装建立对象的细节this
function createPerson(name,age,gender){ var o = new Object(); o.name = name; o.age = age; o.gender = gender; o.sayName = function(){ console.log(this.name); } return o; } var person1 = createPerson('Zhangsan',20,'male'); var person2 = createPerson('Lisi',24,'male'); console.log(person1 instanceof Object); //true
缺点:工厂模式解决了建立多个类似对象的重复代码问题,但没有解决对象类型识别的问题spa
function Person(name, age, gender){ this.name = name; this.age = age; this.gender = gender; this.sayName = function(){ console.log(this.name); } } var person1 = new Person('Zhangsan',20,'male'); var person2 = new Person('Lisi',24,'male'); console.log(person1 instanceof Person); //true console.log(person1 instanceof Object); //true console.log(person1.constructor == Person); //true console.log(person1.sayName == person2.sayName); //false
以这种方式调用构造函数会经历如下四步:prototype
任何函数,只要经过new操做符来调用,那它就能够做为构造函数;若是不经过new操做符来调用,那它就是普通函数指针
var person = new Person('Zhangsan',20,'male'); person.sayName(); //'Zhangsan' Person('Lisi',24,'male'); window.sayName(); //'Lisi' var o = new Object(); Person.call(o,'Wangwu',22,'female'); o.sayName(); //'Wangwu'
缺点:每一个方法都要在每一个实例上从新建立一遍,上述例子中的sayName方法就会在每一个实例中从新建立一遍code
function Person(name, age, gender){ this.name = name; this.age = age; this.gender = gender; this.sayName = sayName; } function sayName(){ console.log(this.name); } var person1 = new Person('Zhangsan',20,'male'); var person2 = new Person('Lisi',24,'male');
上述代码将sayName()函数的定义转移到构造函数外部,这样构造函数中的的sayName是一个指向函数的指针,所以person一、person2就共享了在全局做用域定义的同一个sayName()函数。这么作虽然解决了问题但却带来了新问题:若是对象须要定义不少方法那么就须要定义不少全局函数,那么自定义的引用类型就丝毫没有封装性可言了对象
每一个函数都有一个prototype属性,这个属性是一个指针指向一个对象,而这个对象包含能够由特定类型的全部实例共享的属性和方法。使用原型对象的好处就是让全部对象实例共享它所包含的属性和方法blog
function Person(){ } Person.prototype.name = 'Nicholas'; Person.prototype.age = 29; Person.prototype.gender = 'male'; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person(); person1.sayName(); //Nicholas var person2 = new Person(); person2.sayName(); //Nicholas console.log(Person.prototype.isPrototypeOf(person1));//true console.log(Person.prototype.isPrototypeOf(person2));//true console.log(Object.getPrototypeOf(person1) == Person.prototype);//true console.log(Object.getPrototypeOf(person1).name);//Nicholas
当代码读取某个对象的某个属性时,先从对象实例自己开始搜索,若是找到给定名称的属性则返回该属性的值;若是没找到则搜索其指针指向的原型。当为对象实例添加一个属性时,则这个属性就会屏蔽原型对象中保存的同名属性继承
person1.name = 'Zhangsan'; console.log(person1.name); //Zhangsan console.log(person1.hasOwnProperty('name')); //true console.log(person2.hasOwnProperty('name')); //false delete person1.name; console.log(person1.name); //Nicholas
function Person(){ } Person.prototype = { name:'Nicholas', age:29, gender:'male', sayName:function(){ console.log(this.name); } } var person = new Person(); console.log(person instanceof Object); //true console.log(person instanceof Person); //true console.log(person.constructor == Person); //false console.log(person.constructor == Object); //true Person.prototype.constructor = Person; console.log(person.constructor == Person); //true
使用原型对象赋值操做是会覆盖原型对象中的constructor属性,就会切断原型与构造函数之间的关联
function Person(){ } var person = new Person(); Person.prototype = { name:'Nicholas', age:29, gender:'male', sayName:function(){ console.log(this.name); } } person.sayName(); //error
缺点:因为原型中的全部属性和方法都是共享的,因此对于引用类型属性问题就比较突出
function Person(){ } Person.prototype = { constructor:Person, name:'Nicholas', age:29, gender:'male', love:['swimming','running'], sayName:function(){ console.log(this.name); } } var person1 = new Person(); var person2 = new Person(); person1.love.push('playing games'); console.log(person1.love); //["swimming", "running", "playing games"] console.log(person2.love); //["swimming", "running", "playing games"] console.log(person1.love == person2.love); //true
function Person(name,age,gender){ this.name = name; this.age = age; this.gender = gender; this.love = ['swimming','running']; } Person.prototype = { constructor:Person, sayName:function(){ console.log(this.name); } } var person1 = new Person('Zhangsan',20,'male'); var person2 = new Person('Lisi',24,'male'); person1.love.push('playing games'); console.log(person1.love); //["swimming", "running", "playing games"] console.log(person2.love); //["swimming", "running"] console.log(person1.love == person2.love); //false
这种模式是使用最普遍、认同度最高的一种建立自定义类型的方法
function SpecialArray(name,age,gender){ var array = new Array(); array.push.apply(array,arguments); array.toPipedString = function(){ return this.join('|'); } return array; }
这种模式能够用来为原生引用类型作扩展,寄生构造函数模式返回的对象与构造函数或者与构造函数原型之间没有关系,所以不能依赖instanceof操做符来肯定对象类型
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function (){ return this.property; } function SubType(){ this.subproperty = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function (){ return this.subproperty; } var instance = new SubType(); console.log(instance.getSuperValue()); //true
原型链虽然很强大,能够用它来实现继承,但它也存在一些问题,其中,最主要的问题来自包含引用类型值的原型;第二个问题是建立子类型的实例时不能向超类的构造函数中传递参数。
function SuperType(){ this.colors = ['red','yellow','blue']; } function SubType(){ } SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push('green'); console.log(instance1.colors); //["red", "yellow", "blue", "green"] var instance2 = new SubType(); console.log(instance2.colors); //["red", "yellow", "blue", "green"]
借用构造函数用于解决原型链中包含引用类型值所带来的问题
function SuperType(){ this.colors = ['red','yellow','blue']; } function SubType(){ SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push('green'); console.log(instance1.colors); //["red", "yellow", "blue", "green"] var instance2 = new SubType(); console.log(instance2.colors); //["red", "yellow", "blue"]
问题:方法都在构造函数中定义,所以函数复用无从谈起,并且在超类原型中定义的方法对子类而言也是不可见的,结果全部类型都只能使用构造函数模式
function SuperType(name){ this.name = name; this.colors = ["red", "yellow", "blue"]; } SuperType.prototype.sayName = function(){ console.log(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(){ console.log(this.age); } var instance1 = new SubType('Zhangsan',20); instance1.colors.push('green'); console.log(instance1.colors); //["red", "yellow", "blue", "green"] instance1.sayName(); //Zhangsan instance1.sayAge(); //20 var instance2 = new SubType('Lisi', 24); console.log(instance2.colors); //["red", "yellow", "blue"] instance2.sayName(); //Lisi instance2.sayAge(); //24
组合继承避免了原型链和借用构造函数的缺陷,融合了他们的优势,成为JavaScript中最经常使用的继承模式
var person = { name : 'Zhangsan', colors : ["red", "yellow", "blue"] } var anotherPerson = Object.create(person); anotherPerson.name = "Lisi"; anotherPerson.colors.push("green"); var otherPerson = Object.create(person); otherPerson.name = "Wangwu"; otherPerson.colors.push("black"); console.log(person.colors); //["red", "yellow", "blue", "green", "black"] console.log(person.name); //Zhangsan
这种继承方式在想让一个对象与另外一个对象保持相似的状况下是彻底能够胜任的
var person = { name : 'Zhangsan', colors : ["red", "yellow", "blue"] } var anotherPerson = Object.create(person); anotherPerson.sayHi = function(){ console.log("hi"); } anotherPerson.sayHi();
使用寄生式继承不能作到函数复用而下降效率
组合继承最大的问题就在于不管什么状况下都会调用两次超类型构造函数:一次是在建立子类型原型的时候,另外一次是在子类型构造函数内部。子类型最终回报寒潮类型对象的所有实例属性,可是咱们不得不在调用子类型构造函数时重写这些属性(处理引用类型共用问题)
组合式继承代码以下:
function SuperType(name){ this.name = name; this.colors = ["red", "yellow", "blue"]; } SuperType.prototype.sayName = function(){ console.log(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(){ console.log(this.age); }
寄生组合式继承代码以下:
function SuperType(name){ this.name = name; this.colors = ["red", "yellow", "blue"]; } SuperType.prototype.sayName = function(){ console.log(this.name); } function SubType(name, age){ SuperType.call(this,name); this.age = age; } SubType.prototype = Object.create(SuperType.prototype); SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ console.log(this.age); }