1、访问器属性:
var book = { _year: 2004, edition: 1 }; Object.defineProperty(book, "year", { get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }); book.year = 2005; alert(book.edition); //2
非标准方法:
var book = { _year : 2004, edition : 1 }; //定义访问器的旧有方法 book.__defineGetter__("year", function () { return this._year; }); book.__defineSetter__("year", function (newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } }); book.year = 2005; alert(book.edition); //2
2、创建对象
2.1 工厂模式(没有解决对象识别的问题(即怎样知道一个对象的类型))
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; } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor");
2.2 构造函数模式
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function () { alert(this.name); }; } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true
把上面的构造函数当做函数
// 当作构造函数使用 var person = new Person("Nicholas", 29, "Software Engineer"); person.sayName(); //"Nicholas" // 作为普通函数调用 Person("Greg", 27, "Doctor"); // 添加到window window.sayName(); //"Greg" // 在另一个对象的作用域中调用 var o = new Object(); Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //"Kristen"
构造函数的问题:就是每个方法都要在每个实例上重新创建一遍。在前面的例子中,person1 和person2 都有一个名为sayName()的方法,但那两个方法不是同一个Function 的实例
可以把函数定义转移到构造函数外部来解决这个问题:
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.sayName(); //"Nicholas" person2.sayName(); //"Greg" alert(person1 instanceof Object); //true alert(person1 instanceof Person); //true alert(person2 instanceof Object); //true alert(person2 instanceof Person); //true alert(person1.constructor == Person); //true alert(person2.constructor == Person); //true alert(person1.sayName == person2.sayName); //true
但是新的问题又来了:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实。而更让人无法接受的是:如果对象需要定义很多方法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。好在,这些问题可以通过使用原型模式来解决。
2.3 原型模式
function Person() {} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true
可以通过画图分析:非常重要图见书中
下面这个函数可以用于断属性到底是存在对象中还是存在于原型中
function hasPrototypeProperty(object, name) { return !object.hasOwnProperty(name) && (name in object); }
ie BUG :ie不会枚举默认不可枚举的所有属性和方法包括:hasOwnProperty()、propertyIsEnumerable()、toLocaleString()、toString()和valueOf()。
var o = { toString : function () { return "My Object"; } }; for (var prop in o) { if (prop == "toString") { alert("Found toString"); //在IE 中不会显示 } }
枚举对象上的可枚举属性:keys/Object.getOwnPropertyNames
function Person() {} Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function () { alert(this.name); }; var keys = Object.keys(Person.prototype); alert(keys); //"name,age,job,sayName" var p1 = new Person(); p1.name = "Rob"; p1.age = 31; var p1keys = Object.keys(p1); alert(p1keys); //"name,age" var keys = Object.getOwnPropertyNames(Person.prototype); alert(keys); //"constructor,name,age,job,sayName"
使用对象直接量,不用每次都打一遍Person.prototype
function Person() {} Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } };
但是这样上面的代码constructor 属性不再指向Person 了。
解决方法:
function Person() {} Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } };
---------------------------------------------------------------------------------------------------------------------------------------------------- function Person() {} Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } }; Object.defineProperty(Person.prototype, "constructor", { enumerable : false, value : Person });
原型的动态性:
比较下面两段代码的不同:图表分析见书中
var friend = new Person(); Person.prototype.sayHi = function(){ alert("hi"); }; friend.sayHi(); //"hi"(没有问题!)
//注意这里的顺序:
function Person() {} var friend = new Person(); Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } }; friend.sayName(); //error
修改原生对象的原型:如给所有的字符串类型添加一个方法
原型对象的问题:
即两个实例的属性不可能完全相同
如这个例子中两个人的朋友不可能完全相同,
function Person() {} Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job : "Software Engineer", friends : ["Shelby", "Court"], sayName : function () { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court,Van" alert(person1.friends === person2.friends); //true
2.4组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,
最大限度地节省了内存。另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长。下面的代码重写了前面的例子
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; } Person.prototype = { constructor : Person, sayName : function () { alert(this.name); } } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Count,Van" alert(person2.friends); //"Shelby,Count" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
2.5 动态原型模式
可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。来看一个例子。
function Person(name, age, job) { //属性 this.name = name; this.age = age; this.job = job; if (typeof this.sayName != "function") { Person.prototype.sayName = function () { alert(this.name); }; } } var friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName();
这里只在sayName()方法不存在的情况下,才会将它添加到原型中。这段代码只会在初次调用构造函数时才会执行。此后,原型已经完成初始化,不需要再做什么修改了。不过要记住,这里对原型所做的修改,能够立即在所有实例中得到反映。因此,这种方法确实可以说非常完美。其中,if 语句检查的可以是初始化之后应该存在的任何属性或方法——不必用一大堆if 语句检查每个属性和每个方法;只要检查其中一个即可。对于采用这种模式创建的对象,还可以使用instanceof 操作符确定它的类型。但是记住,使用动态原型模式时,不能使用对象字面量,
2.6 寄生构造函数模式
2.7 稳妥构造函数模式