Object 构造函数或对象字面量均可以用来建立单个对象。但这个方法的缺点很是明显:同一个接口建立很可耐多对象会产生大量的重复代码。为了解决这个问题,人们开始使用工厂模式的一种变体。html
这个模式没有解决对象识别的问题(即怎样知道一个对象的类型)。如:数组
具体的建立单个对象:函数
var person = {}; person.name = "Oliver"; person.age = 18; person.sayName = function(){ return this.Name; };
改变成工厂模式:this
function createPerson(name,age){ var obj = {}; obj.name = name; obj.age = age; obj.sayName = function(){ return this.name }; return obj; //注意这里要返回obj 对象,这样才能把obj 对象传给createPerson 变量。 } var newPerson = createPerson("Oliver",18);
构造函数能够建立特定类型的对象。因此,能够建立自定义的构造函数,从而定义自定义对象类型的属性和方法。如:prototype
function Person(name,age){ //注意大小写,构造函数应该把第一个字幕大写化 this.name = name; this.age = age; this.sayName = function (){ return this.name; }; } var newPerson = new Person("Oliver",18); document.write(newPerson.name); //Oliver document.write(newPerson.age); //18 document.write(newPerson.sayName()); //Oliver
确实至关方便设计
这里必定要记住,构造函数都应该以一个大写字母开头,用来区分其余的函数指针
又如:code
function Person(name,age){ //注意大小写,构造函数应该把第一个字幕大写化 this.name = name; this.age = age; this.sayName = function (){ return this.name; }; } var person1 = new Person("Oliver",18); var person2 = new Person("Troy",24); document.write(person1.constructor == Person); //true document.write(person2.constructor == Person); //true
这里的person1 和person2 分别保存着Person 的一个不一样的实例。两个对象都有一个constructor(构造函数)属性,该属性指向Person。htm
在上面这个例子中,person1 和person2 便是Object 的实例,同时也是Person 的实例。能够经过instanceof 操做符来验证:对象
console.log((person1 instanceof Object) && (person2 instanceof Person)); //true
以这种方式建立构造函数是定义在Global 中的(window 对象)
任何函数,只要经过new 操做符来调用,那它就能够座位构造函数;而任何函数,若是不经过new 操做符来调用,那它就跟普通函数没区别。以下面这个构造函数:
function Car(name,color,sound){ this.name = name; this.color = color; this.sound = function(){ return sound; }; console.log(this.name + " " + this.color + " " + this.sound()); }
若是当作构造函数来使用:
var benz = new Car("C200","White","Boom Boom"); //C200 White Boom Boom
若是做为普通函数来调用:
Car("Benz","White","Boom!"); //Benz White Boom! console.log(window.name + window.color + window.sound()); //BenzWhiteBoom!
若是在另外一个对象的做用域中调用:
var cars = {}; Car.call(cars,"Benz","White","Boom Boom!"); document.write(cars.sound()); //Boom Boom!
问题是每一个方法都要在每一个实例是从新建立一遍。可用经过把内部的函数转移到外部来解决这些问题。如:
function Car(name,color){ this.name = name; this.color = color; this.show = show; } function show(){ console.log(this.name + this.color); } var benz = new Car("Benz","white"); benz.show(); //Benzwhite
但这个问题是彻底没有了封装性可言。不过能够经过原型模式来解决。
function Person(){}; Person.prototype.name = "Oliver"; Person.prototype.age = 18; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person(); person1.sayName(); //Oliver var person2 = new Person(); person2.sayName(); //Oliver; console.log(person1.sayName == person2.sayName); //true
与构造函数不一样的是,新对象的这些属性和方法是由全部实例共享的。这里两个新的person 访问的都是同一组属性和同一个sayName() 函数。
以上面的Person 为例,Person 构造函数里面存在一个prototype 属性,这个属性指向原型对象Person Prototype,该Person Prototype 里面包含了constructor 属性,该属性又指向构造函数Person。构造函数的实例包含了一个[[Prototype]]的内部属性,该内部属性则指向Person Prototype。如:
function Person(){}; Person.prototype.name = "Oliver"; Person.prototype.age = 18; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person(); person1.sayName(); //Oliver var person2 = new Person(); person2.sayName(); //Oliver; console.log(Person.prototype); /* age: 18 constructor: function Person() {} name: "Oliver" sayName: function () { __proto__: Object */ console.log(Person.prototype.constructor); //function Person() {} console.log(Object.getPrototypeOf(person1)); /* age: 18 constructor: function Person() {} name: "Oliver" sayName: function () { __proto__: Object */
对于构造函数、原型属性以及实例之间的关系,参见《js高级程序设计》一书中第6.2.3 章节。
两个方法:isPrototypeOf()
和Object.getProtytypeOf()
(ECMAScript 5)。前者是用来肯定[[Prototype]];后者是用来返回[[Prototype]]值。如:
console.log(Person.prototype.isPrototypeOf(person1)); //true console.log(Object.getPrototypeOf(person1).name); //Oliver console.log(Object.getPrototypeOf(person1)); /* age: 18 constructor: function Person() {} name: "Oliver" sayName: function () { __proto__: Object */
为对象添加一个属性时,这个属性会屏蔽原型对象中的同名属性,可是并不会修改那个属性。若是使用delete 删除这个属性,就能够从新访问原型中的属性。如:
function Person(){}; Person.prototype.name = "Oliver"; Person.prototype.age = 18; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person(); person1.sayName(); //Oliver 原型中的Name person1.name = "Troy"; person1.sayName(); //Troy 实例中的Name delete person1.name; person1.sayName(); //Oliver 原型中的Name
每次读取某个对象的某个属性,都会从实例自己开始搜索,若是没有找到给定名字的属性,则会在原型对象中再次搜索。
方法hasOwnProperty()
检测属性若是在对象实例中时,返回true。如:
console.log(person1.hasOwnProperty("age")); //false age属性来自于原型 console.log(person1.hasOwnProperty("name")); //true name属性来自于实例
两种方法使用in 操做符:单独使用和for-in 循环中使用。
单独使用时,in 返回true 说明该属性存在于实例或原型中。如:
function Person(){}; Person.prototype.name = "Oliver"; Person.prototype.age = 18; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person(); person1.name = "Troy"; person1.sayName(); //Troy 实例中的Name console.log("name" in person1); //true name属性在实例或原型中 console.log(person1.hasOwnProperty("name")); //true name属性在实例中 //上面两个就说明name属性必定在实例中
又如:
function Person(){}; Person.prototype.name = "Oliver"; Person.prototype.age = 18; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person(); person1.name = "Troy"; person1.sayName(); //Troy 实例中的Name var person2 = new Person(); console.log("name" in person1); //true name属性在实例或原型中 console.log(person1.hasOwnProperty("name")); //true name属性在实例中 //上面两个就说明name属性必定在实例中 console.log("name" in person2); //true console.log(person2.hasOwnProperty("name")); //false //上面两个说明name属性必定在原型中
自定义一个函数hasPrototypeProperty(object,name)
;即同时使用上面两个方法来肯定属性究竟是不是存在于实例中。如:
function Person(){}; Person.prototype.name = "Oliver"; Person.prototype.age = 18; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person(); person1.name = "Troy"; person1.sayName(); //Troy 实例中的Name var person2 = new Person(); function hasPrototypeProperty(object,name){ console.log((name in object) && !(object.hasOwnProperty(name))) } hasPrototypeProperty(person2,"name"); //true name属性是在原型中 hasPrototypeProperty(person1,"name"); //false name属性是在实例中
用Object.defineProperty()
方法定义的属性:
function Person(){}; Person.prototype.name = "Oliver"; Person.prototype.sayName = function(){ console.log(this.name); }; var person1 = new Person(); Object.defineProperty(person1, "age", { value: 18 }) console.log(person1.hasOwnProperty("age")); //true age属性是实例属性
关于for-in、[[enumerable]]、defineProperty、hasOwnProperty 的例子:
var person1 = { age: 18 }; Object.defineProperty(person1, "name", { value: "Oliver", enumerable: true }) for(var x in person1){ console.log(x); } console.log(person1.hasOwnProperty("name")); //true
又如:
function Person(age){ this.age = age; } var person1 = new Person(18); Object.defineProperty(person1, "name", { value: "Oliver", enumerable: false }) for(var x in person1){ console.log(x); //用defineProperty 定义的name 属性是实例属性,这里不会枚举到 } console.log(person1.hasOwnProperty("name")); //true
又如:
function Person(){}; Person.prototype.age = 18; var person1 = new Person(); Object.defineProperty(person1, "name", { value: "Oliver", enumerable: false }) for(x in person1){ console.log(x); //这里仍然不回枚举到自定义的name 实例属性 }
可是:
function Person(){}; Person.prototype.age = 18; Person.prototype.name = "Oliver"; var person1 = new Person(); Object.defineProperty(person1, "name", { enumerable: false }) for(x in person1){ console.log(x); //这里则返回枚举到自定义的name 原型属性 }
原型属性的[[enumerable]]设置为false,调用for-in 仍然能够被枚举到。
另外,Object.keys()
方法能够返回全部可枚举属性的字符串数组:
function Person(){}; Person.prototype.age = 18; Person.prototype.name = "Oliver"; var person1 = new Person(); Object.defineProperty(person1, "sound", { value: "miao~", enumerable: true //可枚举 }); Object.defineProperty(person1, "sound2", { value: "wang~", enumerable: false //不可枚举 }); console.log(Object.keys(Person.prototype)); //["age", "name"] console.log(Object.keys(person1)); //["sound"]
Object.getOwnPropertyName()
方法,则能够返回不管能否枚举的全部实例属性:
function Person(){}; Person.prototype.age = 18; Person.prototype.name = "Oliver"; var person1 = new Person(); Object.defineProperty(person1, "sound", { value: "miao~", enumerable: true //可枚举 }); Object.defineProperty(person1, "sound2", { value: "wang~", enumerable: false //不可枚举 }); console.log(Object.keys(Person.prototype)); //["age", "name"] console.log(Object.keys(person1)); //["sound"] console.log(Object.getOwnPropertyNames(Person.prototype)); //["constructor", "age", "name"] console.log(Object.getOwnPropertyNames(person1)); //["sound","sound2"]
即字面量方法:
function Person(){}; Person.prototype = { name: "Oliver", age: 18, sayName: function(){ console.log(this.name); } }; var person1 = new Person(); console.log(Person.prototype.constructor); //再也不指向Person()构造函数 function People(){}; People.prototype.name = "Troy"; People.prototype.age = 26; People.prototype.sayName = function(){ console.log(this.name); }; var people1 = new People(); console.log(People.prototype.constructor); //这里则指向People()构造函数
上面第一种就是字面量方法。可是由此带来的问题是,他的原型对象中的constructor 属性将再也不指向上个例子中的Person() 构造函数了。(其实咱们本质上是重写了prototype对象)
若是constructor 值真的很是重要,则只须要把它设置回适当的值就能够了:
function Person(){}; Person.prototype = { constructor: Person, name: "Oliver", age: 18, sayName: function(){ console.log(this.name); } }; var person1 = new Person(); console.log(Person.prototype.constructor); //从新指向Person()构造函数 function People(){}; People.prototype.name = "Troy"; People.prototype.age = 26; People.prototype.sayName = function(){ console.log(this.name); }; var people1 = new People(); console.log(People.prototype.constructor); //这里则指向People()构造函数
然而用字面量的方法致使的问题仍然没有结束,以上面这种方式重设constructor 属性会致使[[Enumerable]]特性被设置为true。所以在支持ECMAScript 5 的js 引擎中能够用Object.defineProperty()
方法把它修改成false:
function Person(){}; Person.prototype = { constructor: Person, name: "Oliver", age: 18, sayName: function(){ console.log(this.name); } }; var person1 = new Person(); console.log(Person.prototype.constructor); for (var x in person1){ console.log(x); //这里会出现constructor,可是咱们实际上不该该让他可以被枚举出 } Object.defineProperty(Person.prototype, "constructor", { enumerable: false }); for (var x in person1){ console.log(x); //这里就不会出现constructor 了,可是这种方法只支持ECMAScript 5的js 引擎 } /* [Log] function Person() {} (repetition.html, line 130) [Log] constructor (repetition.html, line 132) [Log] name (repetition.html, line 132) [Log] age (repetition.html, line 132) [Log] sayName (repetition.html, line 132) [Log] name (repetition.html, line 140) [Log] age (repetition.html, line 140) [Log] sayName (repetition.html, line 140) */
咱们对原型对象所作的任何修改都能当即从实例上反应出来。由于实例与原型之间的连接只不过是一个指针而不是副本:
function Person(){}; var person = new Person(); //person在Person()构造函数修改以前建立的 Person.prototype.name = "Oliver"; console.log(person.name); //仍然会出现实时的变化
可是若是重写了prototype 则就不一样了,由于实例的[[Prototype]]会指向原型对象,若是修改了原来的原型对象,则就是切断了构造函数与最初原型之间的联系:
function Person(){}; var person = new Person(); Person.prototype = { //这里重写了Person.prototype,属于新的Person.prototype constructor: Person, name: "Oliver" } console.log(person.name); //原型对象被修改了,指针仍然指向旧的Person.prototype
从这里就能够很明显看出,Person.prototype={},实际上字面量方法就是重写了原型对象。若是是Person.prototype.name="Oliver",则并非重写而是修改,不会建立“新的原型对象。”
《js高级程序设计》一书中6.2.3 中的图6-3 很清楚的描述了该原理
全部原生的引用类型(Object、Array、String等等)都在其构造函数的原型上定义了方法。同时,咱们也能够给原生对象自定义方法:
var array = new Array(); Array.prototype.name = function(){ console.log("Array") }; array.push("hello ","there"); console.log(array); array.name();
也能够修改:
var array = new Array(); Array.prototype.toString = function(){ return("Array") }; array.push("hello ","there"); console.log(array.toString()); //这样就抹去了toString()方法
强烈不推荐修改和重写原生对象的原型
就是包含引用类型值的属性来讲,问题比较严重。具体体如今原型中的属性被实例共享:
function Person(){}; Person.prototype = { constructor: Person, name: "Oliver", age: 18, friends: ["Troy","Alice"] } var person1 = new Person(); var person2 = new Person(); person1.friends.push("Ellen"); console.log(person1.friends); //["Troy", "Alice", "Ellen"] console.log(person2.friends); //["Troy", "Alice", "Ellen"] //二者彻底相同,由于原型中的该属性被实例所共享,push()方法只是修改了person1.friends,没有重写 person1.friends = ["Troy", "Alice"]; console.log(person1.friends); //["Troy", "Alice"] console.log(person2.friends); //["Troy", "Alice", "Ellen"] //虽然能够经过重写和覆盖来解决该问题,可是仍然很是麻烦 console.log(person1.hasOwnProperty("friends")); //true; console.log(person2.hasOwnProperty("friends")); //false; //这里就能够看到,重写能解决问题是由于重写致使它建立了实例属性"friends"
这里能够看出,若是咱们的初衷像这样只是想建立一个共享的数组,那么固然不会有什么问题;可是实例通常都会有本身的属性,因此不该该单独使用原型模式。而是组合使用构造函数模式和原型模式。
这是一种用来定义引用类型的一种默认模式:
function Person(name,age){ this.name = name; this.age = age; this.friends = []; } //独享的部分 Person.prototype = { constructor: Person, sayName: function(){ return this.name; } } //共享的部分 var person1 = new Person("Oliver",18); var person2 = new Person("Troy",24); person1.friends.push("Alice","Mark"); person2.friends.push("Mac"); console.log(person1.friends.toString()); console.log(person2.friends.toString()); /* [Log] Alice,Mark (repetition.html, line 228) [Log] Mac (repetition.html, line 229) */
能够经过检查某个应该存在的方法是否有效,来决定是否须要初始化原型:
function Person(name,age){ this.name = name; this.age = age; if (typeof this.sayName != "function"){ Person.prototype.sayName = function(){ return (this.name); }; } } var person = new Person("Oliver",18); console.log(person.sayName()); //Oliver
实际上就是把下面代码封装在了构造函数中:
function Person(name,age){ this.name = name; this.age = age; } Person.prototype.sayName = function(){ return(this.name); }; var person = new Person("Troy",24); console.log(person.sayName()); //Troy
世纪撒好难过跟工厂模式同样。建议在可使用其余模式的状况下,不要使用该模式。
稳妥对象,指的是没有公共属性,且其方法也不引用this 的对象如:
function Person(name,age){ var obj = new Object(); obj.sayName = function(){ console.log(name); }; return obj; } var person1 = Person("Oliver",18); person1.sayName(); //Oliver