第一种:工厂模式
根据接收参数返回,包含参数的对象
优点:解决创建多个对象的问题
缺点:没法判断对象的类型
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; }
第二种:构造函数模式
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); } } let p1 = new Person('bzw',20,'stu'); console.log(p1 instanceof Person);//true console.log(p1.hasOwnProperty('name'));//true
构造函数模式与工厂模式区别:
1.没有显式的创建函数
2.直接将属性和方法赋值给this对象
3.没有return语句
构造函数定义:任何函数,只要可以通过new操作符来调用,那它就可以作为构造函数
以这种方式调用构造函数会经理以下几个步骤:
1.创建新对象
2.将作用域赋值给新对象
3.执行构造函数的代码
4.返回新对象
缺点:每个方法都要在实例化的对象上面重新创建一遍
第三种:原型模式(重点)
想要知道原型模式,必须得知道什么叫做原型,每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象包括所有实例可以共享的属性和方法。
此外还需要知道什么叫做原型对象,还是那句话,只要创建一个函数,就会有一个prototype(原型)属性,而这个prototype恰恰指向这个函数的原型对象,这个原型对象包括所有实例可以共享的属性和方法。
每创建一个实例,这个实例会有一个[[prototype]]指针,专门指向构造函数的原型对象,每个原型对象又会有一个constuctor属性,它是一个指向prototype属性所在函数的指针
function Person1(){ } Person1.prototype.name = 'bzw'; Person1.prototype.age = 20; Person1.prototype.job = 'stud'; console.log(Person.prototype);//{constructor: ƒ} console.log(Person.prototype.constructor);//指向Person函数 let p2 = new Person1(); let p3 = new Person1(); console.log(Person1.prototype.isPrototypeOf(p2));//true console.log(Person1.prototype.isPrototypeOf(p3));//true console.log(Object.getPrototypeOf(p2) === Person1.prototype);//true console.log(p2.hasOwnProperty('name'));//false
in与hasOwnProperty的区别:
in会在实例化对象里面找有没有这个属性,如果没有就去它的原型里面去找,hasOwnProperty只会在实例化对象里去找有没有这个属性
console.log('name' in p2);//true console.log(p2.hasOwnProperty('name'));//false
我们可以写个函数专门用来判断某个对象只有原型里面有某个属性
function hasPrototypeProperty(obj,key){ return !obj.hasOwnProperty(key) && (key in obj); } console.log(hasPrototypeProperty(p2,'name'));//true p2.name = 10; console.log(hasPrototypeProperty(p2,'name'));//false
给原型添加方法每次都需要写一个Person.prototype这样写太麻烦,我们可以下面这样写
function Person2(){ } Person2.prototype = { name:'bzw', age:20, job:'stu' }
这样写挺方便但是,有个缺点就是原型对象的constructor会指向Object,这不是我们想看到的
console.log(Person2.prototype.constructor);//Object()
console.log(Object.keys(Person2.prototype))//["name", "age", "job"]
我们可以指定它的constructor,这样就更改过来了,
Person2.prototype = { constructor:Person2,//constructor的enumerable默认为false不可遍历,但是这种方法会让它变为true,即可以遍历 name:'bzw', age:20, job:'stu' }
这里可能大家有个问题,为什么这样要指定constructor了,因为我们前面说过,一个构造函数的constructor需要指向prototype属性的所在的函数
我们可以打印一下的Person2.prototype.constructor
console.log(Person2.prototype.constructor);//person2()
结果如下:
或者可以用下图来表达上述的关系
当然这样写还是有点弊端
//Object.keys()会获取对象中所有可以遍历的属性 console.log(Object.keys(Person2.prototype))//["constructor", "name", "age", "job"]
constructor竟然可以被遍历出来,这是不对的,我们发现这个constructor可以遍历,那么上面的方法就不是特别好,我们可以用下下面的方法,即指定了Person2的constructor指向问题,又解决constructor可以遍历的问题,可谓一举两得
Object.defineProperty(Person2.prototype,'constructor',{ enumerable:false, value:Person2 }); console.log(Object.keys(Person2.prototype))//["name", "age", "job"] console.log(Person2.prototype.constructor);//Person2();
原型的动态性
我们可以利用原型的动态性给原型添加属性和方法
function Person3(){ } let friend = new Person3(); Person3.prototype.a = 1; console.log(friend.a);//1 即使实例已经创建好了, 但是当我给原型添加属性的时候,实例还是原型后面添加的方法 Person3.prototype = { constructor:Person3, name:'bzw' } //实例化对象里面会有一个指针指向原型,而不是指向构造函数,这里把原型修改为另一个对象,就等于切断了最初原型与构造函数之间的关系 console.log(friend instanceof Person3)//false console.log(friend.name)//undefined
下图可以反映上述的情况
原型模式的缺点
function Person4(){ } Person4.prototype={ constructor:Person4, name:[4], } let p4 = new Person4(); console.log(p4.name);//[4] p4.name.push(5); let p5 = new Person4(); console.log(p4.name,p5.name);//[4, 5],[4, 5]
我们发现实例竟然可以对更改原型的值,这个不太好,于是就有其他的对象创建方法
第四种:组合构造函数和原型模式
function Person5(name,age,job){ this.name = name; this.age = age; this.job = job; this.friend = ['tom','jerry']; } Person5.prototype={ constructor:Person5, sayName:function(){ console.log(this.name); } } let p6 = new Person5('bzw',20,'stu'); let p7 = new Person5('bzw1',19,'stu1'); p6.friend.push('bob'); console.log(p6.friend);// ["tom", "jerry", "bob"] console.log(p7.friend);//["tom", "jerry"]
第五种:动态原型
function Person6(name,age,job){ this.name = name; this.age = age; this.job = job; this.friend = []; if(typeof this.sayName != 'function'){ Person6.prototype.sayName = function(){ return this.name; } } } let p8 = new Person6('bzw2',20,'stu'); p8.friend.push('v'); console.log(p8.sayName())//bzw2 let p9 = new Person6('bzw3',21,'stu'); console.log(p8.friend,p9.friend);//["v"] []
使用动态原型不能使用对象字面量来重写原型,否则会切断构造函数与原先原型的关系,也会切断实例与新原型的关系
第六种:寄生构造函数模式(和工厂模式很像)
特点:返回的对象与构造函数或者构造函数的原型属性之间没有关系,也就是说,构造函数返回的对象与在构造函数在外部创建的对象没有什么不同
function Person7(name,age,job){ let o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ return this.name; } return o; } let p10 = new Person7('bzw1',20,'stu'); console.log(p10.sayName());//bzw1
第七种:稳妥构造函数模式(适合于某些安全执行环境下,这些安全环境会禁用this和new)
function Person8(name,age,job){ let o = new Object(); //定义私有变量和函数 let sur = '姓名:'; let sum = function(){ return sur+name; } o.sayName = function(){ return sum(); } return o; } let p11 = Person8('bzw'); console.log(p11.sayName(),sur);//姓名:bzw,sur is not defined