创建对象工厂模式示例:functioncreatePerson(name,age,job){varo=newObject();o.name=名称;o.age=年龄;o.job=工作;o.sayName=function(){console.log(this.name);};返回o;}varperson1=createPerson('张三',20,'演员');控制台日志(person1);person1.sayName();varperson2=createPerson('李四',21,'老师');person2.sayName();return语句:当createPerson()函数中没有return语句时,person1指向createPerson{},会被调用报错UncaughtTypeError:person1.sayNameisnotafunction功能:解决创建多个的问题相似对象缺点:无法确定对象的类型构建函数模式示例:functionPerson(name,age,job){this.name=name;这个。年龄=年龄;这个。工作=工作;this.sayName=function(){console.log(this.name);};}varperson1=newPerson('张三',20,'演员');person1.sayName();与工厂函数的区别:不显式创建对象,直接给这个对象赋属性和方法,不带return语句new操作符,调用构造函数分4步:创建一个新对象;constructing函数作用域被赋值给新对象(所以this指向新对象)执行构造函数中的代码(给新对象添加属性property)返回一个新对象functionNew(f){returnfunction(){varo={__proto__:f.prototype,};f.apply(o,参数);返回o;};}varperson1=New(Person)('张三',20,'演员');console.log('per',person1);person1.sayName();把构造函数当作一个函数varperson1=newPerson('张三',20,'actor');person1.sayName();//作为普通函数调用:Person('Lisi',21,'teacher');//添加到窗口window.sayName();//函数在另一个对象的字段中调用varo=newObject();Person.call(o,'小白',21,'医生');o.sayName();使用构造函数的主要问题每个方法必须在每个实例中重新创建一遍,如果将sayName函数转移到构造函数外部,在构造函数内部,设置sayName属性等于全局sayName函数,可以解决两个函数做同样事情的问题,但是新的问题是:在全局作用域中定义的函数实际上只能被一个对象调用,这使得全局作用域有点名不副实。更让人无法接受的是:如果对象需要定义很多方法,那么就必须定义很多全局函数,所以我们自定义的引用类型根本就没有封装。这些问题可以通过使用原型模式来解决。原型模式操作符和方法instanceof:判断原型与实例的关系].getPrototypeOf(person1)==Person.prototypehasOwnProperty():检查属性是否存在于实例或原型中。当可以通过对象访问给定属性时,inin运算符返回true,无论该属性存在于实例中还是存在于原型中。同时使用hasOwnProperty()方法和in运算符,可以判断该属性是否存在于对象中,或者存在于原型函数中hasPrototypeProperty(object,name){return!object.hasOwnProperty(name)&&(nameinobject);}使用for-in循环时,返回所有可以通过对象访问的可枚举属性(实例属性+原型属性)Object.keys():获取对象上所有可枚举的实例属性Object.getOwnPropertyNames():获取所有实例属性,无论是否可枚举。原型使用注意事项functionPerson(){}Person.prototype={name:'哈',age:29,sayName:function(){}}/*该方法覆盖了默认的原型对象,constructor属性没有Then指向Person*///重置构造函数Object.defineProperty(Person.prototype,"constructor",{eumerable:false,value:Person})组合模式构造函数模式用于定义实例属性,原型模式用于定义方法和共享属性。因此,每个实例都将拥有自己的实例属性副本,但同时共享对方法的引用,从而最大限度地节省内存。此外,该模式还支持向构造函数传递参数。示例函数Person(name){this.name=name;this.friends=["Shelby","Court"]}Person.prototype={constructor:Person,sayName:function(){console.log(this.name)}}varperson1=newPerson('Ha');varperson2=newPerson('习');person1.friends.push("Van")console.log(person1.friends)//"Shelby,Court,Van"console.log(person2.friends)//"Shelby,Court"console.log(person1.friends===person2.friends)//falseconsole.log(person1.sayName===person2.sayName)//true动态原型模式动态原型模式将所有信息封装在构造函数中,通过在构造函数中初始化原型(仅必要时),它保持了同时使用构造函数和原型的优点。换句话说,你可以通过检查一个应该存在的方法是否有效来决定是否需要初始化原型。示例函数Person(name){this.name=name;if(typeofthis.sayName!="function"){Person.prototype.sayName=function(){console.log(this.name)}}}varperson1=newPerson("Ha")person1.sayName()//ha寄生构造器模式该模式的基本思想是创建一个简单封装创建对象代码的函数,然后返回新创建的对象示例functionPerson(name){varo=newObject()o.name=名称;o.sayName=function(){console.log(this.name)}returno}varperson1=newPerson('Ha')person1.sayName()//'Ha'特性:除了使用new运算符和调用使用的包装函数是一个构造函数,这种模式其实和工厂模式是一模一样的。当构造函数没有返回值时,默认返回一个新的对象实例,通过在构造函数末尾添加return语句,可以覆盖调用构造函数时返回的值。在特殊情况下可以使用此模式来为对象创建构造函数。比如用额外的方法创建一个特殊的数组,由于不能直接修改Array的构造函数,可以使用这种模式。关于寄生构造器模式,返回的对象与构造器或构造器的prototype属性没有任何关系;不能依赖instanceof运算符来确定对象类型安全构造器模式安全对象最适用于一些安全环境(这些环境禁止使用this和new),或者防止数据被其他应用程序访问。安全构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例不引用this,二是构造函数不是使用new操作符调用ExamplefunctionPerson(名称){varo=newObject()o.name=名称;o.sayName=function(){console.log(name)}returno}varperson1=Person('Ha')person1.sayName()//'Ha'继承了很多OO语言都支持两种继承方式:接口继承和实现继承。接口继承只继承方法签名,而实现继承继承实际的方法。ECMAScript只支持实现继承,其实现继承主要通过原型链实现。基本思想是使用原型让一个引用类型继承另一个引用类型的属性和方法。原型链原型链是通过将一个类型的实例赋值给另一个构造函数的原型来构建的。示例functionSuperType(){this.colors=["red","blue","green"]}functionSubType(){}SubType.prototype=newSuperType()varinstance1=newSubType()instance1.colors.push(“黑色的”);console.log(instance1.colors)//"red,blue,green,black"varinstance2=newSubType()console.log(instance2.colors)// "red,blue,Green,black"原型链问题:主要问题来自包含引用类型值的原型。当通过原型实现继承时,原型实际上会变成另一个类型的实例,所以原来的实例属性理所当然的变成了当前的原型属性。借用构造函数(也称为假对象或经典继承)解决了由原型中包含的引用类型值引起的问题。借用构造函数的基本思想是在子类型构造函数内部调用超类型构造函数。函数只是在特定环境中执行代码的对象,因为通过使用apply()和call()方法,构造函数也可以在(未来)新创建的对象上执行示例functionSuperType(){this.colors=["red","blue","green"]}functionSubType(){SuperType.call(this)}varinstance1=newSubType()instance1.colors.push("black")console.log(instance1.colors)//"red,blue,green,black"varinstance2=newSubType()console.log(instance2.colors)特点传参:与原型链相比,借用的构造函数有一个很大的优势,就是可以在子类型函数将参数传递给超类型构造函数。构造函数借用的问题如果只借用构造函数,那么就无法避免构造函数模式的问题——方法都定义在构造函数中,因为函数重用是谈不上的,而在supertype定义的方法对于子类型也是不可见的,因此所有类型都只能使用构造函数模式。鉴于这些问题,借用构造函数的技术也很少单独使用。组合继承(也称为伪类继承)是指将原型链和借用构造函数的技术结合起来,充分发挥两者优势的一种继承模式。其背后的思想是利用原型链来实现原型属性和方法的继承,通过借用构造函数实现实例属性的继承。这样通过在原型上定义方法实现函数复用,可以保证每个实例都有自己的属性ExamplefunctionSuperType(name){this.name=name;this.colors=["red","blue","green"]}SuperType.prototype.sayName=function(){console.log(this.name)}functionSubType(name,age){SuperType.call(this,name)//第二次调用SuperType()this.age=age}SubType.prototype=newSuperType()//第一次调用SuperType()SubType.prototype.sayAge=function(){console.log(this.age)}varinstance1=newSubType("Ha",29)instance1.colors.push("black")console.log(instance1.colors)//"red,blue,green,black"instance1.sayName()//"Ha"instance1.sayAge()//29varinstance2=newSubType("Xi",27)console.log(instance2.colors)//"red,blue,green"console.log(instance2.sayName)//"Xi"console.log(instance2.sayAge)缺点组合继承最大的问题是无论在什么情况下,超类型的构造函数都会被调用两次:创建子类型原型时调用一次;另一次是在子类型构造函数中。子类型最终会包含超类型对象的所有实例属性,但我们必须在调用子类型构造函数时重写这些属性。原型继承ECMAScript5通过Object.create()方法标准化了原型继承。此方法有两个参数:一个用作新对象原型的对象和(可选)一个为新对象定义附加属性的对象。在传入参数的情况下,Object.create()与object()方法具有相同的行为。在函数内部,首先创建一个临时构造函数,然后将传入的对象作为构造函数的原型,最后返回一个创建这个临时类型的新实例。本质上,object()对传递给它的对象进行了浅表复制。返回新的F();}寄生继承Parasitic继承的思想类似于寄生构造函数和工厂模式,即创建一个函数,只用于封装继承过程,在内部以某种方式增强对象,最终返回object示例函数createAnother(original){varclone=object(original);clone.sayHi=function(){console.log('hi');};返回克隆;}寄生组合继承解决了组合继承两次调用超类型构造函数的问题。寄生组合继承通过借用构造函数继承属性,通过原型链接的混合形式继承方法。它背后的基本思想是:我们不需要调用超类型的构造函数来指定子类型的原型,我们所需要的只是超类型原型的副本。本质上,寄生继承是用来继承超类型的原型,然后将结果赋值给子类型的原型。示例函数inheritPrototype(subType,superType){varprototype=object(superType.prototype);prototype.constructor=subType;subType.prototype=原型;}/*在函数内部,第一步是创建超类型原型的副本,第二步是将构造函数属性添加到创建的副本中,从而弥补通过重写原型丢失的默认构造函数属性。作为最后一步,将新创建的对象(即副本)分配给子类型的原型。*/functionSuperType(name){this.name=name;thissis.colors=["red","blue","green"]}SuperType.prototype.sayName=function(){console.log(this.name)}functionSubType(name,age){SuperType.call(this,name)this.age=age}inheritPrototype(SubType,SuperType)SubType.prototype.sayAge=function(){console.log(this.age)}
