第6章我一共写了3篇总结,下面是相关连接:
读《javaScript高级程序设计-第6章》之理解对象
读《javaScript高级程序设计-第6章》之继承java
所谓的工厂模式就是,把建立具体对象的过程抽象成了一个函数,每次调用这个函数都会返回一个类似的对象。chrome
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"); person1.sayName(); //"Nicholas" person2.sayName(); //"Greg"
工厂模式虽然解决了建立多个类似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。segmentfault
js里常常如此写var obj=new Object();var arr=new Array();
,Object
和Array
就是构造函数,使用new
操做符能够建立相应类型的对象,使用instanceof
能够验证对象的类型,例如:alert(arr instance Array); //true
构造函数模式就是,自定义像Array
和Object
等这样的构造函数,并使用new操做符调用它来建立自定义类型对象的方法。
例如:数组
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"); person1.sayName(); //"Nicholas" person2.sayName(); //“Greg”
使用new操做符调用,Person就是一个构造函数
要建立Person的新实例,必须使用new操做符。以这种方式调用构造函数实际上会经历一下4个步骤:
(1)建立一个新对象
(2)将构造函数的做用域赋给新对象,即把构造函数的this指向这个新对象
(3)执行构造函数中的代码(为这个新对象添加属性)
(4)返回新对象浏览器
若是不使用new,Person就是一个普通的函数,能够正常调用。例如:函数
//做为普通函数在全局做用域下调用 Person("Greg", 27, "Doctor"); //adds to window window.sayName(); //“Greg" //做为普通函数在另外一个对象中调用 var o = new Object(); Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //"Kristen"
alert(person1 instanceof Object); //true
alert(person1 instanceof Person);//true
this
综上,建立自定义的构造函数,意味着未来能够将它的实例标识为一种特定的类型(相似于Array类型,Number类型);而这正是构造函数模式赛过工厂模式的地方。可是构造函数模式也存在缺点。spa
使用构造函数的主要问题就是,每一个方法都要在每一个实例上从新建立一遍(实例化一次Function对象),浪费内存。例如,person1和person2都有一个sayName()的方法,但建立person1和person2时候,定义sayName这个方法时都实例化了一个函数对象,所以person1.sayName和person2.sayName是不相等的,而事实上它们又是作的一样的事情。或者也能够这么说,person1和person2的sayName()方法作一样的事情,但却在建立对象时被实例化了两次,也就占用了两倍内存。
虽然能够解决,但并不完美,例如:firefox
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”); alert(person1.sayName == person2.sayName); //true
可是若是共享方法有不少,就须要定义不少个全局函数,那么咱们的自定义的引用类型就丝毫没有封装性可言了。好在,这些问题能够经过使用原型模式解决。prototype
不管何时,只要建立了一个新函数,就会根据一组特定的规则为该函数建立一个prototype
属性,这个属性就是该函数的原型对象。每一个函数都有一个原型对象,全部原型对象都会自动得到constructor
属性,constructor
指向该函数(拥有该prototype
属性的函数)。
例如,Person.prototype.constructor
指向Person
。
建立构造函数后,其原型对象默认只会取得constructor
属性;至于其余的方法都是从Object
继承来的(__proto__
)。当调用构造函数建立一个新实例后,该实例内部将包含一个指针(__proto__
),指向构造函数的原型对象。(ECMA-262第5版中管这个指针叫[[Prototype]]
,但在脚本中没有标准的方式访问它。在chrome,safari和firefox中都支持一个属性__proto__
,但在其余实现中__proto__
对脚本是不可见的)。因此和实例有直接关系的是构造函数的原型对象,而不是构造函数。
上图展现了Person构造函数、Person的原型对象和Person现有的两个实例之间的关系。
原型属性即构造函数的原型对象的属性;实例属性即在实例对象上直接添加的属性。
例如:person1.name=“Jone”。
经过点运算符能够访问到实例的实例属性和原型属性。实例访问属性时,脚本会先搜索实例属性,若是找到了,则中止搜索返回实例属性的值;若是没找到就继续搜索原型属性。因此若是实例属性和原型属性同名,那么原型属性就会被屏蔽掉,没法访问到。
须要注意的是:实例没法修改他的原型属性的值,也没法修改原型对象(即不能修改、删除和增长一个原型属性)
(注意:实例不能修改的是原型属性的值,可是若是原型属性指向一个引用类型,原型属性的值是存储这个引用类型的地址,即不能修改原型属性指向另外一个对象,但却能修改原型属性指向的对象里的属性。下面原型对象的问题里还会再讲到)。
若是person1.name=“Jone”
这样写,脚本只会在实例属性里建立或修改一个name=“Jone”
的属性,delete person1.name
只会删除person1
的实例属性name
(就算实例没有name
的实例属性,也不会删除实例的原型属性)。
alert(Person.prototype.isPrototypeOf(person1)); //true
若是person1
的[[prototype]]
(即__proto__
)指向调用isPrototypeOf
的对象即Person.prototype
就会返回true
。
即判断Person.prototype
是不是person1
的[[prototype]]
alert(Object.getPrototypeOf(person1)==Person.prototype); //true
返回person1
这个对象的原型[[prototype]]
person1.hasOwnProperty(“name”);
若是person1.name
是来自于person1
的实例属性,返回true
;若是来自于person1
的原型属性,则返回false
。
有两种方式使用in操做符:
单独使用in:alert(“name” in person1); //true
在经过person1可以访问给定属性是返回true,不管属性是实例属性仍是原型属性。
在for-in循环中使用:返回的是全部可以经过对象访问的、可枚举的属性,其中包括实例属性也包括原型属性。
接受一个对象做为参数,返回一个包含对象的全部可枚举属性的字符串数组。
若是对象是一个实例,则只返回实例的实例属性而不包含原型属性
var keys = Object.getOwnPropertyNames(Person.prototype); alert(keys); //"constructor,name,age,job,sayName”
获得对象的全部实例属性,不管它是否可枚举
所谓的更简单的原型写法就是用字面量的形式来定义构造函数的原型对象,以下:
function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } }; var friend = new Person(); alert(friend instanceof Object); //true alert(friend instanceof Person); //true alert(friend.constructor == Person); //false alert(friend.constructor == Object); //true
这样定义完了以后,Person.prototype
这个对象就被重写了,致使它的constructor
这个属性的指向变成了Object
,而不是Person
(解释:Person.prototype
是Object
的一个实例,因此它有一个原型属性constructor
指向Object
。Person
被建立时,它的原型对象Person.prototype
自动得到了一个constructor
的属性,指向Person
,这个属性是对象的实例的实例属性,因此会屏蔽掉对象的原型属性,因此说Person.prototype.constructor
是指向Person
的。可是用字面量重写了Person.prototype
后,Person.prototype
还是Object
的一个实例,因此它有一个原型属性constructor
指向Object
,但它没有了指向Person
的实例属性constructor
,因此在访问Person.prototype.constructor
时,就是访问了Person.prototype
对象的原型属性,指向了Object
)。
但咱们能够再把它定义进这个对象字面量里手动指向Person
,即给Person.prototype
这个对象的实例加一个实例属性constructor
,指向Person
。以下:
function Person(){ } Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
咱们知道如此定义对象,对象的属性的[[enumerable]]
特性默认是true
。而默认状况下,原声的原型对象的constructor
属性是不可枚举的,所以若是你使用兼容ES5的javaScript引擎,可使用Object.defineProperty()
来设置constructor
属性。以下:
//重设构造函数,只适用于ES5兼容的浏览器 Object.difineProperty(Person.prototype,”constructor”,{ enumerable:false, value:Person });
简单点来讲,就是实例的[[prototype]]
是指向构造函数的原型对象,而不是构造函数。只要你明白这一点,原型的动态性就好理解了。
第一种状况:Person.prototype
能够在任意地方增长修改或删除属性,实例能够实时的访问最新的原型属性。由于每次实例访问属性,都是一次搜索的过程,搜索原型属性时是到实例的[[prototype]]
指向的对象里查找。实例的[[prototype]]
是一个指针,Person.prototype
也是一个指针,指向的是同一个地址,也就是说修改和查找都在同一个地方,那么查找到的值天然就是最新实时的了。
function Person(){ } var friend = new Person(); Person.prototype.sayHi = function(){ alert("hi"); }; friend.sayHi(); //"hi"
第二种状况:在实例被建立以后,Person.prototype
被重写了
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
这种状况是由于:实例一旦被建立,实例的[[prototype]]
存储的地址就肯定了,指向的对象地址就肯定了,若是你改变这个地址里的对象,实例均可以访问的到。可是若是在实例被建立以后,重写Person.prototype
,就至关因而把Person.prototype
指向了一个新的对象,而实例的[[prototype]]
仍是指向原来的对象,因此实例访问的原型属性仍是要在原来的对象里查找,原来的对象里并无sayName
这个方法,所以会报错。
咱们用原型模式建立自定义类型,让自定义类型和原生类型同样使用。其实全部的原生的对象(Object、Array、String,等等)也是采用的原型模式建立的。全部原生的引用类型都在其构造函数的原型上定义了方法。
例如,在Array.prototype
中能够找到sort()
方法,而在String.prototype
中能够找到substring()
方法。
经过原生对象的原型,不只能够取得全部默认方法的引用,也能够定义新的方法。能够像修改自定义对象的原型同样修改原生对象的原型,所以能够随时添加方法。可是不建议如此作(在支持该方法的实现中运行代码时会致使命名冲突,或者意外重写了原生方法)。
**首先,原型模式省略了为构造函数传递参数,初始化实例的环节,使得全部实例默认时都是同样的。
其次,原型模式的共享本性使得全部的实例都能共享它的属性。**
若是属性值是函数或者是基本值时,实例不能修改原型属性的值,只会为该实例增长一个同名属性,而后屏蔽掉同名原型属性,这样其它的实例都不会受到影响,使用的仍然是原型属性原来的值。
若是属性值是引用类型,实例虽不能修改原型属性的值(这个值就是指向的对象的地址),即实例不能让这个原型属性从新指向另外一个对象,可是却能够修改指向的对象的属性,这就会致使其它实例再访问这个对象时,对象已被修改了。
例如:
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
这样就违反了咱们但愿实例拥有属于本身的所有属性的初衷
综合前面所说的,咱们发现构造函数模式优势在于能向构造函数传递,定义属于实例本身的实例属性。原型模式优势在于共享着对方法的引用,原型属性是全部实例所共享的。
因此建立自定义类型的最多见方式,就是组合使用构造函数模式与原型模式
例如:
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,Court,Van" alert(person2.friends); //"Shelby,Court" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
这一小节,私觉得了解了解就好,只要你理解了上面所说的构造函数模式和原型模式的原理,那么原型属性的定义你能够为所欲为,只要符合你的预期就好。你高兴就好,代码高兴就好。
与工厂模式的区别是使用new 调用。不使用new调用,它就是工厂模式。
这一小节,私觉得了解了解就好。
与工厂模式的区别是对象定义的方法不使用this,构造函数传进来的参数不向外直接暴露。
这一小节,私觉得了解了解就好。
好了,封装类的几种方式已经介绍完了。个人观点是理解了对象和构造函数模式以及原型模式,就能够随机应变了。不须要记住什么什么各类模式的,无非就是使用对象的场景不一样。要理解对象和构造函数以及原型对象,灵活变换,无招胜有招才好。
这是我读《javaScript高级程序设计》这本书的第6章面向对象的程序设计,作的笔记,在本篇以前还有一篇理解对象的笔记,后面还有一篇继承的笔记。发现问题的小伙伴欢迎指出。