在js 中创建最简单的对象,然后给它添加属性或者方法 示例如下:
var obj = new Object(); //或者 var obj = {}; obj.name = '张三'; obj.fun = function(){};
上创建的对象 中有 name 属性和一个 fun 方法,也可如下创建
var obj = { name: '张三', fun: function(){} }
这个例子中的 obj 对象与前面例子中的 obj 对象是一样的,都有相同的属性和方法。这些 属性在创建时都带有一些特征值(characteristic),JavaScript 通过这些特征值来定义它们的行为。
1. 属性类型
ECMA-262 第 5 版在定义只有内部才用的特性(attribute)时,描述了属性(property)的各种特征。 ECMA-262 定义这些特性是为了实现 JavaScript 引擎用的,因此在 JavaScript 中不能直接访问它们。为了 表示特性是内部值,该规范把它们放在了两对儿方括号中,例如[[Enumerable]]。在ECMAScript 中有两种属性:数据属性和访问器属性。
(1). 数据属性
数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有 4 个描述其行为的特性,如下:
Configurable // 表示能否通过 delete 删除属性从而重新定义属性 Enumerable // 表示能否通过 for-in 循环及枚举返回属性 Writable // 表示能否修改属性的值 Value // 表示这个属性的数据值,这个特性的默认值为 undefined。
对于像前面例子中那样直接在对象上定义的属性,它们的 Configurable 、Enumerable 和 Writable 特性都被设置为 true,而 Value 特性被设置为指定的值。
要修改属性默认的特性,必须使用 ECMAScript 5 的 Object.defineProperty()方法。这个方法 接收三个参数:属性所在的对象、属性的名字和一个描述符对象。其中,描述符(descriptor)对象的属 性必须是:configurable、enumerable、writable 和 value。设置其中的一或多个值,可以修改 对应的特性值。 如下
var p = {}; Object.defineProperty(p,"name",{ configurable: true, writable: true, enumerable: true, value: "123" })
configurable 属性
var p = {}; Object.defineProperty(p,"name",{ configurable: false, //如果不写该属性或者值为false delete 删除不会生效,在严格模式下会报错 value: "123" }) delete p.name console.log(p); //{p:'123'}
一旦把属性定义为不可配置的, 就不能再把它变回可配置了。此时,再调用 Object.defineProperty()方法修改除 writable 之外 的特性,都会导致错误:
var p = {}; Object.defineProperty(p,"name",{ configurable: false, value: "123" }) Object.defineProperty(p,"name",{ configurable: true, value: "123" }) //跑出错误
也就是说,可以多次调用 Object.defineProperty()方法修改同一个属性,但在把 configurable 特性设置为 false 之后就会有限制了
writable 属性
var p = {}; Object.defineProperty(p,"name",{ writable: false, // 不写该属性 或者值为false 时不能修改值 enumerable: true, value: "123" }) p.name = 321; console.log(p); //{p:'123'}
enumerable 属性
var p = {}; Object.defineProperty(p,"name",{ enumerable: true, // 不写该属性 或者值为false 时不能遍历得到对象属性值 value: "123" }) for(var i in p){ console.log(p[i]); //未获得值 }
在调用 Object.defineProperty()方法时,如果不指定,configurable、enumerable 和 writable 特性的默认值都是 false。多数情况下,可能都没有必要利用 Object.defineProperty() 方法提供的这些高级功能。不过,理解这些概念对理解 JavaScript 对象却非常有用。
(2). 访问器属性
访问器属性不包含数据值;它们包含一对儿 getter 和 setter 函数(不过,这两个函数都不是必需的)。 在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值;在写入访问器属性时,会调用 setter 函数并传入新值,这个函数负责决定如何处理数据。访问器属性有如下 4 个特性。
configurable // 表示能否通过 delete 删除属性从而重新定义属性 Enumerable // 表示能否通过 for-in 循环返回属性 Get // 在读取属性时调用的函数。默认值为 undefined。 Set // 在写入属性时调用的函数。默认值为 undefined。
configurable,Enumerable同数据属性一样的。
var p = { _year: 2000, num : 1 }; Object.defineProperty(p,"year",{ get: function(){ return this._year; }, set: function(newValue){ if(newValue > 2000){ this._year = newValue; this.num += newValue - 2000; } } }) p.year = 2001; console.log(p.num); //2
以上代码创建了一个 p 对象,并给它定义两个默认的属性: _year 和 num。_year 前面 的下划线是一种常用的记号,用于表示只能通过对象方法访问的属性。而访问器属性 year 则包含一个 getter 函数和一个 setter 函数。getter 函数返回_year 的值,setter 函数通过计算来确定正确的版本。因此, 把 year 属性修改为 2001 会导致_year 变成 2001,而 num 变为 2。这是使用访问器属性的常见方 式,即设置一个属性的值会导致其他属性发生变化。
不一定非要同时指定 getter 和 setter。只指定 getter 意味着属性是不能写,尝试写入属性会被忽略。 在严格模式下,尝试写入只指定了 getter 函数的属性会抛出错误。类似地,只指定 setter 函数的属性也 不能读,否则在非严格模式下会返回 undefined,而在严格模式下会抛出错误。
支持 ECMAScript 5 的这个方法的浏览器有 IE9+(IE8 只是部分实现)、Firefox 4+、Safari 5+、Opera 12+和 Chrome。在这个方法之前,要创建访问器属性,一般都使用两个非标准的方法: __defineGetter__()和__defineSetter__()。这两个方法最初是由 Firefox 引入的,后来 Safari 3、 Chrome 1 和 Opera 9.5 也给出了相同的实现。使用这两个遗留的方法,可以像下面这样重写前面的例子。
var p = { _year: 2004, num: 1
}; //定义访问器的旧有方法
p.__defineGetter__("year", function(){ return this._year; }); p.__defineSetter__("year", function(newValue){ if (newValue > 2004) { this._year = newValue; this.num += newValue - 2004; } }); p.year = 2005;
alert(p.num); //2
在 不 支 持 Object.defineProperty() 方 法 的 浏 览 器 中 不 能 修 改 Configurable 和 Enumerable 。
2. 定义属性
Object.defineProperty() //方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
var p = {}; Object.defineProperty(p,"name",{ enumerable: true, value: "123" })
Object.defineProperties()
//
方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。可以定义哥多个属性
var p = {}; Object.defineProperties(p, { name: { value: true, writable: true }, name2: { value: 'Hello', writable: false } });
3 读取属性的特性
使用 ECMAScript 5 的 Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述 符。这个方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。返回值是一个对象,如果 是访问器属性,这个对象的属性有 configurable、enumerable、get 和 set;如果是数据属性,这 个对象的属性有 configurable、enumerable、writable 和 value.
var p = { num : 1 }; Object.defineProperties(p,{ _year: { value : 2000 }, year:{ get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2004) { this._year = newValue; this.num += newValue - 2004; } } } }) var obj = Object.getOwnPropertyDescriptor(p,'_year'); console.log(obj.value); // 2000 console.log(obj.configurable); //false console.log(typeof obj.get); //undefined var obj1 = Object.getOwnPropertyDescriptor(p, "year"); console.log(obj1.value); // 2000 console.log(obj1.enumerable); //false console.log(typeof obj1.get); //"function"
对于数据属性_year,value 等于最初的值,configurable 是 false,而 get 等于 undefined。 对于访问器属性 year,value 等于 undefined,enumerable 是 false,而 get 是一个指向 getter 函数的指针。
在 JavaScript 中,可以针对任何对象——包括 DOM 和 BOM 对象,使用 Object.getOwnPropertyDescriptor()方法。
4.创建对象
4.1 工厂模式
function person(name, sex, age){ var obj = new Object(); obj.name = name; obj.sex = sex; obj.age = age; obj.sayName = function(){ console.log(this.name); } return obj; } var zs = person('zs','M',21); var ls = person('ls','F',22);
函数 person()能够根据接受的参数来构建一个包含所有必要信息的 对象。可以无 数次地调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象。工厂模式虽然解决了创建 多个相似对象的问题,但却没有解决对象识别的问题
4.2构造函数模式
function Person(name, sex, age){ this.name = name; this.sex = sex; this.age = age; this.sayName = function(){ console.log(this.name); } } var zs = new Person('zs','M',21); var ls = new Person('ls','M',22); console.log(zs.constructor === Person);//true
Person()中的代码 除了与 person()中相同的部分外,还存在以下不同之处:
没有显式地创建对象;
直接将属性和方法赋给了 this 对象;
没有 return 语句。
要创建 Person 的新实例,必须使用 new 操作符。以这种方式调用构造函数实际上会经历以下 4 个步骤:
(1) 创建一个新对象;
(2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象);
(3) 执行构造函数中的代码(为这个新对象添加属性);
(4) 返回新对象。
在前面例子的最后,zs 保存着 Person 的一个的实例。这个对象有一个 constructor(构造函数)属性,该属性指向 Person,如下所示。
console.log(zs.constructor === Person);//true
1. 将构造函数当作函数
构造函数与其他函数的唯一区别,就在于调用它们的方式不同。不过,构造函数毕竟也是函数,不 存在定义构造函数的特殊语法。任何函数,只要通过 new 操作符来调用,那它就可以作为构造函数;而 任何函数,如果不通过 new 操作符来调用,那它跟普通函数也不会有什么两样。例如,前面例子中定义 的 Person()函数可以通过下列任何一种方式来调用。
// 当作构造函数使用 var person = new Person("Nicholas", "M", 21); person.sayName(); //"Nicholas" // 作为普通函数调用 Person("Greg", "M", 21); // 添加到window window.sayName(); //"Greg" // 在另一个对象的作用域中调用 var o = new Object(); Person.call(o, "Kristen", 25, "Nurse"); o.sayName(); //"Kristen"
2. 构造函数的问题
构造函数模式虽然好用,但也并非没有缺点。使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍,zs 和 ls 都有一个名为 sayName()的方法,但那 两个方法不是同一个 Function 的实例.
this.sayName = new Function("console.log(this.name)"); // 与声明函数在逻辑上是等价的
以这种方式创建函数,会导致不同的作用域链和标识符解析,但 创建 Function 新实例的机制仍然是相同的。因此,不同实例上的同名函数是不相等的.
console.log(person1.sayName == person2.sayName); //false
创建两个完成同样任务的 Function 实例的确没有必要;况且有 this 对象在,根本不用在 执行代码前就把函数绑定到特定对象上面。因此,大可像下面这样,通过把函数定义转移到构造函数外 部来解决这个问题。
function Person(name, sex, age){ this.name = name; this.sex = sex; this.age = age; this.sayName = sayName; } function sayName(){ console.log(this.name); } var zs = new Person('zs','M',21); var ls = new Person('ls','M',21); console.log( zs.sayName == ls.sayName ); //true
在这个例子中,我们把 sayName()函数的定义转移到了构造函数外部。而在构造函数内部,我们 将 sayName 属性设置成等于全局的 sayName 函数。这样一来,由于 sayName 包含的是一个指向函数 的指针,因此 zs 和 ls 对象就共享了在全局作用域中定义的同一个 sayName()函数。