ECMA-262 把对象定义为git
无序属性的集合,其属性能够包含基本值、对象或者函数。github
即对象是一组没有特定顺序的值。对象的每一个属性或方法都有一个名字,而每一个名字都映射到一个值。正由于这样,咱们能够把 ECMAScript 的对象想象成散列表:无非就是一组名值对,其中值能够是数据或函数。安全
ECMAScript 第 5 版 在定义只有内部采用的特性(attribute)时, 描述了属性(property)的各类特征。ECMAScript 定义这些特性是为了实现 JavaScript 引擎用的,所以在 JavaScript 中不能直接访问它们。为表示特性是内部值,该规范把它们放在两对方括号中,例如 [[Enuermerable]]。app
ECMAScript 中只有两种属性:数据属性和访问器属性。函数
[[value]]:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。this
要修改属性默认的特性,必须用 ECMAScript 5 的 Object.defineProperty()
方法。这个方法接受三个参数`: 属性所在的对象,属性名,和一个描述符对象。其中,描述符(descriptor)对象的属性必须是:configurable、enumerable、writable 和 value。spa
jsvar person = {}; Object.defineProperty(person,"name",{ writable: false, value: "Nicholas", configurable: false }); alert(person.name);//"Nicholas" person.name = "PaddingMe"; alert(person.name);//"Nicholas" delete person.name; alert(person.name);//"Nicholas"
一旦把属性定义为不可配置特性就不能再把它变为可配置。prototype
在调用 Object.defineProperty()
方法时,若是不指定, configurable, enumerable 和 writable 特性的默认值为 false。指针
[[set]]: 在写入属性时调用的函数。默认值为 undefined。code
访问器不能直接定义,必须使用 object.defineProperty()
来定义。
var book = { _year: 2004, edition: 1 }; Object.defineProperty(book, "year", { get: function() { return this._year; }, set: function() { if (newValue > 2004) { this._year = newValue; this.edition += newValue -2004; } } }); book.year = 2005; alert(book.edition); //2
_year 前面的下划线用于表示只能经过对象方法访问的属性。book.edition 变为 2 这是使用访问器属性的常见方式。即设置一个属性的值会致使其余属性发生变化。
Object.definePorperties()
接受两个对象参数:第一个要添加或修改其属性的对象,第二个对象的属性 与第一个对象中要添加或修改的属性一一对应。
Object.getOwnPropertyDescriptor()
方法获取给定属性的描述符。
此方法接受2个参数:属性所在的对象和要读取其描述符的属性名称。
返回值是一个对象。
jsvar book = {}; Object.defineProperties(book,{ _year: {value: 2004}, edition: {value: 1}, year: { get: function() { return this._year; }, set: function() { if (newValue > 2004) { this._year = newValue; this.edition += newValue -2004; } } } }); var descriptor = Object.getOwnPropertyDescriptor(book,"_year"); alert(descriptor.value); //2004 alert(descriptor.configurable);//false alert(typeof descriptor.get); // "undefined" var descriptor = Object.getOwnPropertyDescriptor(book,"year"); alert(descriptor.value); //undefined alert(descriptor.enumerable);//false alert(typeof descriptor.get); //function
最简单的能够用 Object 构造函数,或者对象字面量来建立单个对象,但这样使用一个接口建立不少对象,会产生大量的重复代码。
jsvar paddingme = new Object(); //用 Object 构造函数 建立对象 var paddingme = {}; //对象字面量建立对象
工厂模式是用函数来封装以特定接口建立对象的细节。
jsfunction 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 person = createPerson("PaddingMe",25,"front-end developer");
工厂模式虽然解决了建立多个类似对象的问题,但没有解决对象识别的问题(即怎样知道一个对象的类型)。
jsfunction Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function() { alert(this.name); }; } var person1 = new Person("PaddingMe",25,"front-end developer"); alert(person1.constructor == Person);//true alert(person1 instanceof Person);//true alert(person1 instanceof Object);//true
构造函数模式与工厂模式不一样的是:
- 没有显式地建立对象;
- 没有 return 语句;
- 直接将属性和方法赋给了 this 方法。
另按照惯例,构造函数首字母都应该大写。
建立 Person 新实例,通过了如下4个新步骤:
建立自定义的构造函数意味着未来能够将它的实例标识为一种特殊的类型,而这正是构造函数模式赛过工厂模式的地方。
js//看成构造函数来使用 var person = new Person("paddingme",25,"F2ER"); person.sayName();//"paddingme" //做为普通函数来使用; Person("paddingme",25,"F2ER"); window.sayName();//"paddingme" //**当在全局做用域中调用一个函数时,this 对象老是指向 Global 对象。** //在另外一个对象的做用域中调用 var o = new Object(); Person.call(o,"paddingme",25,"F2ER"); o.sayName();// "paddingme"
构造函数建立对象的问题在于:每一个方法都要在每一个实例上从新建立一遍,会致使不一样的做用域链和标识符解析。不一样实例上的同名函数是不相等的。
咱们建立的每一个函数都有一个 prototype(原型) 属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含能够由特定类型的全部实例共享的属性和方法。 按照字面意思即 prototype 就是调用构造函数而建立的那个对象实例的原型对象。使用原型对象的好处就是可让全部对象实例共享它所包含的属性和方法。换言之,没必要在构造函数中定义实例的信息,而是能够将这些信息直接添加到原型对象中。
jsfunction Person() {} Person.prototype.name = "PaddingMe"; Person.prototype.age = 29; Person.prototype.job = "Front-end Engineer"; Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person(); person1.sayName(); //"PaddingMe" var person2 = new Person(); person2.sayName(); //"PaddingMe"
理解原型对象
不管何时,只要建立一个新函数,就会根据一组特定的规则为该函数建立一个 prototype 属性,这个属性指向函数的原型对象。在默认状况下,全部原型对象都会自动得到一个 constructor 属性。这个属性包含一个指向所在函数的指针。 建立了自定义的构造函数以后,其原型对象默认只会取得 constructor 属性;至于其余方法,都是从 Object 继承而来的。当调用构造函数建立一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。ECMAScript 管此指针叫 [[prototype]]
Person 构造函数、 Person 的原型属性以及 Person 现有的两个实例之间的关系。在此,Person.prototype 指向了原型对象,而 Person.prototype.constructor 又指回了 Person。 原型对象中除了包含 constructor 属性以外,还包括后来添加的其余属性。 Person 的每一个实例都包含一个内部属性,该属性指向了 Person.prototype;换句话说,它们与构造函数没有直接的关系。
jsalert(Person.prototype.isPrototype(person1));//true alert(Person.prototype.isPrototype(person2));//true
ECMASript 5 增长了Object.getPrototypeOf()
,在全部支持的实现中,这个方法返回[[Prototype]] 的值。
jsalert(Object.getPrototypeOf(person1) == Person.prototype); // true alert(Object.getPrototypeOf(person1).name); // "PaddingMe"
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具备给定名字的属性。搜索首先从对象实例自己开始。若在实例中找到了具备给定名字的属性,则返回该属性的值。若没有,则继续搜索指针指向的原型镀锡i昂,在原型对象中查找是否具备给定名字的属性。若在原型对象中找到此属性,则返回该属性的值。
原型最初只包括 constructor 属性,而该属性也是共享的,所以能够经过对象实例访问
当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中同名属性,换句话说,添加这个属性只会阻止咱们访问原型对象中的属性,而不会修改那个属性。
使用 hasOwnProperty()
方法能够检测一个属性是存在于实例中,仍是存在于原型中。
这个方法(它继承于 Object)只在给定属性存在于对象实例中,才会返回 true。
jsfunction Person() {} Person.prototype.name = "PaddingMe"; Person.prototype.age = 29; Person.prototype.job = "Front-end Engineer"; Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person(); var person2 = new Person(); alert(person1.hasOwnProperty("name"));//false person2.name = "hhb"; alert(person2.hasOwnProperty("name"));//true delete person2.name; alert(person2.hasOwnProperty("name"));//false
原型与 in 操做符
在单独使用时,in 操做符会在经过对象可以访问给定属性时返回 true,不管该属性是在实例中仍是原型中。与 hasOwnProperty()
一块儿使用能够肯定该属性究竟是在对象中,仍是存在于原型中。
jsfunction hasPrototypeProperty(){ return !object.hasOwnProperty(name) && (name in object); }
在使用 for-in 循环时, 返回的是全部可以经过对象访问的、可枚举(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性的实例属性也会返回,由于根据规定,全部开发人员定义的属性都是可枚举的——只有在 IE8 以及更早版本中例外。
要去的对象上全部可枚举的实例属性,可以使用 ECMAScript 5 中的 Object.keys() 方法。此方法要接受一个对象做为参数,返回一个包含全部可枚举属性的字符串组。
jsfunction Person() {} Person.prototype.name = "PaddingMe"; Person.prototype.age = 29; Person.prototype.job = "Front-end 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 = "hhb"; p1.age = 25; var p1keys = Object.keys(p1); alert(p1keys);// "name,age"
想要获得全部的实例属性,不管它是否可枚举,可使用 Object.getOwnPropertyNames()
方法。
jsvar keys = Object.getOwnPropertyNames(Person.prototype); alert(keys);// "constructor,name,age,job,sayName"
更简单的原型方法
用对象自变量来重写真哥哥原型对象。
jsfunction Person(){ } Person.prototype = { name : "PaddingMe", age : 25, job : "F2ER", sayName : function() { alert(this.name); } };
注意 constructor 属性再也不指向 Person,而是指向了 Object 构造函数。
jsfunction Person(){ } Person.prototype = { constructor: Person, name : "PaddingMe", age : 25, job : "F2ER", sayName : function() { alert(this.name); } };
原型的动态性
对原型对象所在的任何修改都可以当即从实例上反映出来—— 即便是先建立了实例后修改原型也是同样的。但如果重写整个原型对象,状况就不同了。
调用构造函数时会为实例添加一个指向最初原型的 [[prototype]] 的指针,而把原型修改成另一个对象就等于切断了构造函数于最初原型之间的联系。实例中的指针只指向原型,而不指向构造函数。
jsfunction Person() { } var friend = new Person(); Person.prototype = { constuctor : Person, name : "PaddingMe", age : 25, sayName : function(){ alert(this.name); } }; friend.sayName(); //error
原生对象的原型
jsString.prototype.startsWith = function (text) { return this.indexOf(text) == 0; } var msg = "Hello world!"; alert(msg.startsWith("Hello")); //true
原型对象的问题
全部实例在默认状况下都将取得相同的属性值。原型模式的最大问题是由其共享的本性所致使。
jsfuntion Person(){ } Person.prototype = { constuctor: Person, name: "PaddingMe", age: 26, job: "F2ER", friends: ['hw','wjj'], sayName: function() { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); Person1.friends.push("ex"); alert(person1.friends);//"hw,wjj,ex" alert(person2.friends);//"hw,wjj,ex" alert(person1.friends == person2.friends);//true
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。
jsfunction Person(name,age,job) { this.name = name; this.age = age; this.job = job; this.friends = ["hw","wjj"]; } Person.prototype = { constructor: Person, sayName: function() { alert(this.name); } } var person1 = new Person("winter",59,"Full-Stack Enginner"); var person2 = new Person("PaddingMe",25,"F2ER"); person1.friends.push("ex"); alert(person1.friends); // "hw,wjj,ex" alert(person2.friends); // "hw,wjj" alert(person1.friends == person2.friends); //false alert(person1.sayName == person2.sayName); //true
jsfunction Person(name,age,job) { this.name = name; this.age = age; this.job = job; if(typeof this.sayName != "funtion") { Person.prototype.sayName = function(){ alert(this.name); }; } } var friend = new Person("PaddingMe",25,"F2ER"); friend.sayName();
寄生(parasitic)构造函数模式的基本思想是:建立一个函数,该函数的做用仅仅是封装建立对象的代码,而后再返回新建立的对象;但从表面上看,有很像典型的构造函数。
jsfunction Person(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 friend = new Person("PaddingMe",25,"F2ER"); friend.sayName;// "PaddingMe"
jsfunction SpecialArray() { var values = new Array(); values.push.apply(values,arguments); values.toPipedString = function() { return this.join("|"); } } var colors = new SpecialArray("red","blue","green"); alert(colors.toPipedString());//"red|blue|green"
关于寄生构造函数模式须要说明的是:
返回的对象与构造函数或者与构造函数的原型属性之间没有关系,亦即构造函数返回的都喜好那个与在构造函数外部创建的对象没什么不一样。因此不能依赖 instanceof 操做符来肯定对象模型。
所谓稳妥对象是指没有公共属性,并且其方法也引用 this 的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用 this 和 new),或者在防止数据被其余应用程序(如 Mashup 程序)改动时使用。
稳妥构造函数模式与寄生构造函数模式不同的是:
- 新建立对象的实例方法不引用 this;
- 不使用 new 操做符调用构造函数。
jsfunction Person(name,age,job) { var o = new Object(); o.sayName = function (){ alert(name); }; return o; }