Javascript对象创建的几种方式
本文是Nicholas的《Javascript高级程序设计》6.2章的读书笔记,仅在加强记忆
- 工厂模式。即用一个函数来生成类的实例
function creator(name, age, job){ var o = {}; o.name = name; o.age = age; o.job = job; o.getName = function(){ return this.name; }; return o; } var person1 = creator('siglud', 10, 'Engineer'); var person2 = creator('ethlin', 12, 'Doctor'); console.log(person1.getName == person2.getName);
这种方法的很好理解,就是构造一个能返回一个类实例的函数,但是弊端有两个,一是无法用反射知道一个对象的类型,因为这个对象的类型是Object(例子中我用{}来代替);二是getName作为一个函数本来是可以复用的,但是却其实生成了两个,所以在最后的console log中打印出了false - 生成器模式。原版翻译为构造函数模式,但是这个“构造函数”明显是和C语言中的构造函数不是一个东西,所以我换个说法,其本质就是把函数本身作为一个类,用new关键字来生成一个新的函数(类)对象,因为JS中函数本身也是一个对象
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.getName = function(){ return this.name; } } var person3 = new Person('siglud', 10, 'Engineer'); var person4 = new Person('ethlin', 12, 'Doctor'); console.log(person3 instanceof Person); console.log(person4 instanceof Person); console.log(person3.getName == person4.getName)
这种的算是正统的构造对象的方法了,但是问题就是getName是同一个函数,但是在不同的对象中却是不同的,不是一个可以复用的东西,当然,你可以用一个全局的函数来替代它,但是很明显,这不是一个好主意。 - 原型模式。不在生成器函数中写入任何属性,而是放到prototype中,让new出来的对象直接继承prototype的属性
function Person2(){} Person2.prototype.name = "default name"; Person2.prototype.age = 18; Person2.prototype.job = 'Doctor'; Person2.prototype.getName = function(){ return this.name; }; var person5 = new Person2(); console.log(person5.getName()); var person6 = new Person2(); person6.name = 'Siglud'; person6.age = 19; person6.job = 'Engineer'; console.log(person6.getName()); console.log(person5 instanceof Person2); console.log(person6 instanceof Person2); console.log(person5.getName == person6.getName);
这种方法就是经常用的最多的原型模式了,函数也是公用的原型函数,但是注意到person6的name是person6自己的属性,而person5用的是它的prototype的属性,会被hasOwnProperty判定为false的,person6的prototype其实也有name属性,但是当它也有了自己的name属性的时候,它就不再向prototype请求和使用这个属性了。这里需要注意的是我用的是Person2.prototype.name = "default name"; Person2.prototype.age = 18; Person2.prototype.job = 'Doctor';
这样给原有的原型对象赋值的方式,你也可以用以下方法把原型对象完全替换掉:Person2.prototype = { name: 'default name', age: 18, job: 'Doctor', getName: function(){ return this.name; } };
这样的问题一个就是person5的constructor不再指向Person2了,当然,你也可以这样修正:Person2.prototype = { constructor: Person2, name: 'default name', age: 18, job: 'Doctor', getName: function(){ return this.name; } };
但是这样又会产生一个问题,如果这段代码写在var person5 = new Person2();之前是没问题的,如果写在之后,person5将不会具有新赋值的Person2.prototype的全部属性,因为它两个已经指向两个完全不同的目标。除此之外,原型继承还是有坑的,体现在类似Python的函数参数默认值为可变量(如list)时一样的坑:function Person3(){} Person3.prototype.name = "default name"; Person3.prototype.age = 18; Person3.prototype.job = 'Doctor'; Person3.prototype.friends = ['default friends']; Person3.prototype.getName = function(){ return this.name; }; var person7 = new Person3(); var person8 = new Person3(); person7.friends.push('jack'); console.log(person8.friends);
这时候输出的居然是["default friends", "jack"],当然你也可以这样改造来让它正常function Person3(){} Person3.prototype.name = "default name"; Person3.prototype.age = 18; Person3.prototype.job = 'Doctor'; Person3.prototype.friends = ['default friends']; Person3.prototype.getName = function(){ return this.name; }; var person7 = new Person3(); var person8 = new Person3(); /*person7.friends.push('jack'); console.log(person8.friends);*/ person7.friends = []; person7.friends.push('jack'); console.log(person8.friends); console.log(person7.friends);
但是我们没办法约束客户端程序员每个都这么乖乖的去写,所以这也是独立的使用原型模式比较少的原因。 - 组合模式
其实就是综合使用原型模式和构造函数模式
function ComplexPerson(name, age, job){ this.name = name; this.aget = age; this.job = job; } ComplexPerson.prototype = { constructor: ComplexPerson, sayName : function(){ return this.name; } }; var person_complex_1 = new ComplexPerson('siglud', 11, 'Worker'); var person_complex_2 = new ComplexPerson('ethlin', 12, 'Doctor); console.log(person_complex_1 instanceof ComplexPerson); console.log(person_complex_2 instanceof ComplexPerson); console.log(person_complex_1.sayName()); console.log(person_complex_2.sayName());
这样又做到了函数的复用,又做到了类型统一 - 动态原型模式
这个其实和上面的没啥区别,唯一的区别在于它不会重复的定义函数,它通过首先检查对应的方法是否存在然后再决定是否初始化它
function ComplexPerson(name, age, job){ this.name = name; this.aget = age; this.job = job; } if(typeof ComplexPerson.sayName !== 'function'){ ComplexPerson.prototype = { constructor: ComplexPerson, sayName : function(){ return this.name; } }; }
- 寄生构造函数模式
寄生构造函数其实就是工厂模式,但是Nicolas把它独立出来是因为这货虽然并不怎么好用,但是偶尔却能处理一些很麻烦的问题,比如你想继承系统的Array,并且给它添加一部分功能,当然,你不能随意的去给系统的array的prototype添加功能,这样会给其他人埋坑,所以这个时候工厂模式就有了用武之地了,举例就不用了,其实是工厂模式
- 稳妥构造函数模式
这也是一个为了特殊目的而构造的东西,它的最大目的是隐藏内部变量,不允许直接使用this来访问内部属性,相当于一个非常极端的把属性设置为private的方式,因为js基本上没办法让类的属性变成private
function DurablePerson(name, age, job){ var o = {}; o.sayMyName = function(){ return name; }; return o; } var durablePerson = DurablePerson('siglud', 11); console.log(durablePerson.sayMyName()); console.log(durablePerson.name);
可以看到第二个试图访问的结果被抛出错误了,当然,你也可以给他定义setter,让它可以更改,但是除了你开放的接口,你别人终于无法改动你的变量了
function DurablePerson(name, age, job){ var o = {}; o.sayMyName = function(){ return name; }; o.setMyName = function(myName){ name = myName; }; return o; } var durablePerson = DurablePerson('siglud', 11, 'Worker'); console.log(durablePerson.sayMyName()); durablePerson.setMyName('Ethlin'); console.log(durablePerson.sayMyName());
评论
发表评论