面对对象的语言有一个标志,那就是他们都有类的概念,而经过类能够建立任意多具备相同属性和方法的对象。js语言没有类的概念,所以他的对象也与基于类的语言中对象有所不一样,esma-262中定义js对象为:javascript
无序属性的集合,其属性能够包含基本值、对象和函数java
ECMAScript中有两种属性:数据属性和访问器属性编程
数据属性包含一个数据值的位置,在这个位置能够读取和写入值。数据值有四个描述其行为的特性:数组
修改属性值必须经过Object.defineProperty()方法,该方法接受三个参数,属性所在的对象,属性的名称,和一个描述符对象描述符对象的属性必须是上面四个特性。安全
访问器属性不包含数据值,它包含一对setter和getter函数。读取调用getter,写入调用setter,访问器属性包含四个特性:函数
访问器属性不能直接定义,必须经过Object.defineProperty()方法。性能
因为定义多个属性的几率很大,es5又定义了一个Object.defineProperties()方法
该方法接受两个参数,第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应,eg:this
var book = {} Object.defineProperties(book,{ _year:{ value:2004 }, edition:{ value:1 } year: { get:function(){ return this._year; } set:function(newValue){ if(newValue > 2004){ this._year = newValue; this.edition += newValue - 2004; } } } })
function createPerson(){ var obj = new Object(); obj.name = 'abc'; obj.age = 30; obj.sex = 'M'; return obj; }
工厂模式虽然解决了建立多个像是对象的问题,可是没有解决对象识别的问题,即怎么知道一个对象的类型。es5
function Person(){ this.name = 'abc'; this.age = 30; this.sex = 'M' }
构造函数解决了对象识别的问题,也很好用,可是也有缺点,这种方式的缺点会在每个实例中保存一份属性的副本,会对性能形成影响。spa
function Person(){ } Person.prototype.name = 'Nicholas'; Person.prototype.age = 29; Person.prototype.job = 'Software Engineer'; Person.prototype.sayName = function(){ alert(this.name) }; var p1 = new Person(); p1.sayName(); // Nicholas var p2 = new Person(); p2.sayName();// Nicholas alert(p1.sayName == p2.sayName) //true
咱们建立的每一个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途包含能够由特定类型的全部实例共享的属性和方法。用这个对象的好处是可让全部对象实例共享它所包含的属性和方法。
上图描述了Person构造函数、Person的原型对象以及Person现有的两个实例之间的关系。此外,Person.prototype指向了原型对象,而Person.prototype.constructor又指回了Person原型对象中除了包含constructor属性以外,还包括后来添加的其余属性。
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具备给定名字的属性。搜索首先从对象实例自己开始。找到则返回,没找到则继续搜索指针指向的原型对象,也就是说若是原型对象和实例的属性名相同的话,则优先执行实例的属性。hasOwnProperty()能够判断属性值是存在于实例仍是原型。p1.hasOwnProperty("name") 返回true。
单独使用in仍是在for-in循环中使用,in操做符都会访问到实例属性和原型属性。若是判断属性是否存在于原型中,能够经过下面这种方式实现:
function hasPrototypeProperty(object, name){ return !object.hasOwnProperty(name) && (name in object); }
固然也能够经过 Object.keys(Person.prototype) 来获取,方法返回一个可枚举属性的字符串数组。或者Object.getOwnPropertyNames(Person.prototype)返回一个可枚举属性的字符串数组。可是上面两种方法对不可枚举的属性失效,只能用for-in更简单的原型语法:
function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
可是问题是,原型的constructor指针再也不只想Person了。本质上这种写法是重写了prototype对象能够增长一个从constructor并只想Person来解决:
function Person(){ } Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
可是也有问题,这样作会把constructor的enumerable特性被改为true,最好的解决方案是以下:
Object.defineProperty(Person.prototype, "constructor", { enumerable: false, value: Person });
原型对象的缺点是初始化参数共享,对函数共享是很是合适,可是基本值的属性却会引起安全性问题。综合构造函数模式的缺点和原型对象的缺点,经过组合使用构造函数模式与原型对象模式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每一个实例都会有本身的一份实例属性副本,但同时又共享着对方法的引用,最大限度地节省了内存。
组合模式以下:
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
上文为《JavaScript高级编程》的读书笔记,关于JavaScript面对对象编程的章节。