简单总结《JavaScript高级程序设计》第三版第六章:面向对象的程序设计
理解对象属性
- 面向对象语言都有类的概念,通过类可以创建任意多个具有相同属性和方法的对象,但是ECMAScript中没有类的概念,因此它的对象也与基于类的语言中的对象有所不同
- 可以把ECMAScript的对象想象成散列表,相当于一组名-值对,其中的值可以是数据或者函数
- ECMAScript中有两种属性:数据属性和访问器属性
创建对象
-
工厂模式
function Person(name,age,job){ var o =new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.sayName); } return o; } var friend = Person("Nicholas", 29, "Software Engineer");
-
构造函数模式:
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); } } var person1 = new Person("Grey",27,"Doctor"); var person2 = new Person("Grey",29,"Software Engineer"); alert(person1.sayName == person2.sayName); // false //不同实例上的同名函数是不相等的
-
原型模式:
我们创建的每一个函数都有一个prototype
(原型)属性,它是一个指向对象的指针。如果按照字面意思理解,那么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(); alert(person1.sayName == person2.sayName); // true // 与构造函数不同的是,新对象的属性和方法是由所有实例共享的
1.理解原型对象:
在我们调用person1.sayName()
的时候,会先后执行两次搜索。首先搜索实例person1
是否有sayName
属性,如果没有就搜索person1
的原型
虽然可以通过对象实例访问保存在原型中的值,但不能通过对象实例重写原型中的值。如果在实例中添加一个与实例原型属性同名的属性,那就会在实例中创建该属性,并屏蔽原型中的属性person1.name = "Greg"; alert(person1.name); // "Grey"---来自实例 delete person.name; alert(person1.name); // "Nicholas"---来自原型 // 使用delete操作符删除了person1.name
2.原型与
in
操作符:
单独使用alert(person1.hasOwnProperty("name")); // false // 使用hasOwnProperty()方法,该属性存在实例中才返回true alert("name" in person1); // true //单独使用时,in操作符在通过对象能够访问给定属性时返回true function hasPrototypeProperty(object, name){ return !object.hasOwnProperty(name) && (name in object); } //两者同时使用就可知道该属性是存在对象中还是原型中
在
for-in
循环中使用:
Object.keys()
和Object.getOwnPropertyName()
方法都可以用来替代for-in
循环3.更简单的原型语法
function Person(){} Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function(){ alert(this.name); } }; var friend = new Person(); alert(friend.constructor == Person); // false alert(friend.constructor == Object); // true
这里本质上是重写了默认的
prototype
对象,construtor
属性不再指向Person
函数。对象的constructor
属性最初是用来标识对象类型的,但是提到检测对象类型,还是instanceof
操作符更可靠一些。4.原型的动态性
function Person(){} var friend = new Person(); // 创建Person实例 //重写了原型对象 Person.prototype = { name : "Nicholas", age : 29, job : "Software Engineer", sayName : function(){ alert(this.name); } }; friend.sayName(); // error
调用构造函数时会为实例添加一个指向最初原型的
Prototype
指针,而重写原型对象会切断了现有原型与任何之前已经存在的对象实例之间的联系。实例中的指针仅指向原型,不指向构造函数。5.原生对象的原型
原生模式的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式创建的。所有原生引用类型(Object、Array、String
等等)都在其构造函数的原型上定义方法。6.原型对象的问题
首先,原型模式省略了为构造函数传递初始化参数这一环节,在某种程度上带来了一些不方便。
最大的问题是由其共享的本性所导致。原型中所有属性是被很多实例共享的,这种共享对于函数非常合适,对于包含基本值的属性也说得过去,但是对于包含引用类型值的属性却有较大问题,比如我们并不想在所有实例中共享一个数组,那么修改一个实例引用的数组,这个修改也会通过另一个实例反映出来。实例一般都是要有属于自己的全部属性的。所以,很少看到有人单独使用原型模式。 -
组合使用构造函数模式和原型模式:
构造函数用于定义实例属性,原型模式用于定义方法和共享的属性。该模式使用广泛,认同度高。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); } }
-
动态原型模式:
该模式把所有信息都封装在构造函数中,而通过在构造函数中初始化原型,保持了同时使用构造函数和原型的优点。function Person(name,age,job){ this.name = name; this.age = age; this.job = job; //只在sayName()方法不存在的时候才将它添加到原型 if(typeof this.sayName != "function"){ Person.prototype.sayName = function(){ alert(this.name); }; } } var friend = new Person("Nicholas", 29, "Software Engineer");
-
寄生构造函数模式
这个模式可以在特殊情况下用来为对象创建构造函数。function Person(name,age,job){ var o =new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.sayName); } return o; } var friend = new Person("Nicholas", 29, "Software Engineer");
-
稳妥构造函数模式
适合在一些安全环境中使用。稳妥构造函数遵循与寄生构造函数类似的模式,但是新创建对象的实例方法不引用this
,不使用new
操作符调用构造函数