对象
可以想象成散列表,键值对,值可以是数据或函数
创建对象的方式
1.工厂模式
1 function createPerson(name, age, job){ 2 var o = new Object(); 3 o.name = name; 4 o.age = age; 5 o.job = job; 6 o.sayName = function(){ 7 alert(this.name); 8 }; 9 return o; 10 } 11 var person1 = createPerson("Nicholas", 29, "Software Engineer"); 12 var person2 = createPerson("Greg", 27, "Doctor");
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");
经历了4个步骤:
(1)创建一个新对象;
(2)将构造函数的作用域赋给新对象(因此this指向了这个新对象)
(3)执行构造函数中的代码(为这个新对象添加属性)
(4)返回新对象
验证构造函数模式创造出的实例
alert(person1 instanceof Object); //true alert(person1 instanceof Person); //true alert(person2 instanceof Object); //true alert(person2 instanceof Person); //true
任何函数,只要通过new操作符调用,就可以作为构造函数;而任何函数,如果不通过new操作符调用,跟普通函数不会有什么两样
// 当作构造函数使用 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()的方法,这2个方法不是同一个Function实例,每定义一个对象,就是实例化了一个对象。
其实这是为了引申出JavaScript的原型模式,但就面向对象编程来说,各自的实例拥有各自的域本身是符合自然规律的,而JavaScript是通过原型来实现的,类似C#通过类方法的override倒是更符合正常的世界观。不过在OOP在各种语言中怎么实现的方式上,增加这种计算机域的解决方法也是可行的啊,有些语言不仅实例拥有自己独立的域,甚至不可更改,类似Scala就有这种机制,看在现在宿主环境的份上,解放开发者就是胜利,至于JavaScript就是苦了老搞不清原型的同志
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");
通过全局函数来实现实例公用方法,可是这样对象就没有封装性了。
3.原型模式
创建的每个函数都有一个prototype属性,是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中
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
只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。默认情况下,所有原型对象都会自动获得一个constructor属性,这个属性包含一个指向prototype属性所在函数的指针。
每当代码读取某个对象实例的某个属性,都会执行一次搜索,目标是给定名字的属性。搜索首先从对象实例本身开始,如果找到则返回;如果没找到则继续搜索原型对象。
如果在实例中添加了一个属性,而该属性与实例原型中一个属性同名,就会在实例中创建该属性,该属性将会屏蔽原型中的同名属性。
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(); var person2 = new Person(); person1.name = "Greg"; alert(person1.name); //"Greg"——来自实例 alert(person2.name); //"Nicholas"——来自原型
通过使用delete操作符可以完全删除实例属性,从而让我们能够重新访问原型中的属性
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(); var person2 = new Person(); person1.name = "Greg"; alert(person1.name); //"Greg"——来自实例 alert(person2.name); //"Nicholas"——来自原型 delete person1.name; alert(person1.name); //"Nicholas"——来自原型
使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这方法只在给定属性存在于对象实例中时,才会返回true
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(); var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false person1.name = "Greg"; alert(person1.name); //"Greg"——来自实例 alert(person1.hasOwnProperty("name")); //true alert(person2.name); //"Nicholas"——来自原型 alert(person2.hasOwnProperty("name")); //false delete person1.name; alert(person1.name); //"Nicholas"——来自原型 alert(person1.hasOwnProperty("name")); //false
in操作符单独使用时,通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中
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(); var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false alert("name" in person1); //true person1.name = "Greg"; alert(person1.name); //"Greg" ——来自实例 alert(person1.hasOwnProperty("name")); //true alert("name" in person1); //true alert(person2.name); //"Nicholas" ——来自原型 alert(person2.hasOwnProperty("name")); //false alert("name" in person2); //true delete person1.name; alert(person1.name); //"Nicholas" ——来自原型 alert(person1.hasOwnProperty("name")); //false alert("name" in person1); //true
判断属性是原型中的属性只要in返回true而hasOwnProperty()返回false
简洁的原型写法
function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
每创建一个函数,就同时创建它的prototype对象,这个对象也会自动获得constructor属性,这种简洁的写法完全重写了默认的prototype对象,因此constructor属性就变成了新对象的constructor属性(指向Object构造函数)
var friend = new Person(); alert(friend instanceof Object); //true alert(friend instanceof Person); //true alert(friend.constructor == Person); //false alert(friend.constructor == Object); //true
可以特意指定
function Person(){ } Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
由于在原型中查找值的过程是一次搜索,因此对原型对象所做的任何修改都能立即从实例上反映出来,即使是先创建了实例后修改原型也是如此
但这样也会遇到一些问题
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
friend实例指向的原型中不包含sayName方法
重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的联系;它们引用的仍然是最初的原型
原型对象的问题
原型中所有属性是被多个实例共享,对于函数非常合适。但对于引用类型的属性来说,就是灾难。
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
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
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); }; } }
引用:
《JavaScript高级程序设计中文版》