对象构建方法:Object构造函数和对象字面量
//Object构造函数创建 const obj = new Object(); obj.name = 'Anna'; obj.age = 24; // 对象字面量创建 const obj = { name: 'Anna', age: 24 }
对象属性分为:数据属性、访问器属性
数据属性描述符包含:configurable(属性是否可delete、修改为访问器属性)、enumerable(是否可用for-in循环返回该属性)、writable(是否可写)、value
访问器属性包含: configurable(属性是否可delete删除该属性、修改为数据属性)、enumerable、get(读取属性调用的函数,默认undifined)、set(写入属性调用的函数,默认undifined)。访问器属性可通过Object.difineProperty(obj, attrName, descripter)或Object.difineProperties(obj, attrObj)
// 数据属性 Object.difineProperty(person, 'name', { writable: false, // 通过构造函数或对象字面量创建的,除了value,其他几个默认为为true,通过本方法设置的,其他几个属性默认为false value: 'Anna' // name属性为只读 }) console.log(person.name) // Anna person.name = 'Lily'; // 在非严格模式下,会忽略该操作;在严格模式下会报错 //访问器属性 Object.difineProperty(person, 'newAge', { get: function() { // 通过本方法创建的访问器其他属性值默认为false return this.age }, set: function(newval) { if (newval > 30) { this.age = newval; } } }) person.newAge = 40; console.log(person.newAge) // 40,如果没有set属性,为只读,返回24
工厂模式
用以解决Object构造函数和对象字面量创建包含同样属性、方法的对象时产生的大量重复代码,缺点是无法区分对象。
function createPerson(name, age) { const o = new Object(); o.name = name; o.age = age; o.sayname = function() { alert(this.name); } return o; } const person1 = createPerson('Anna', 23); const person2 = createPerson('Jophy', 24); console.log(person1.name); console.log(person2.name)
构造函数模式
用以解决工厂模式无法区分创建对象的问题
function Person(name, age) { this.name = name; this.age = age; this.sayname = function() { alert(this.name); } } const person1 = new Person('Anna', 23); const person2 = new Person('Jophy', 24); console.log(person1.name); console.log(person2.name)
构造函数模式不足是同名称的函数实际是不同的函数实例,若函数较少可通过函数定义转移的方式解决该问题,但若函数较多都挂到全局对象上,则都挂到全局对象上了,从而使自定义的引用类型失去了封装性。
// 通过构造函数模式创建的同一名称的函数实际是不同函数实例 console.log(person1.sayname == person2.sayname); // false //函数定义转移,sayName挂载到全局对象window上 function Person(name, age) { this.name = name; this.age = age; this.sayname = sayName } function sayName() { alert(this.name); } const person1 = new Person('Anna', 23); const person2 = new Person('Jophy', 27);
原型模式
每个函数都包含prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享和方法。字面意思即prototype就是通过调用构造函数而创建的那个对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。
function Person() { // protoype是Person的原型属性 } Person.prototype.name = 'Anna'; Person.prototype.age = 23; Person.prototype.sayname = function() { alert(this.name); } const person1 = new Person(); // prototype是person1/person2的原型对象 const person2 = new Person(); person1.sayname() // Anna person2.sayname() // Anna console.log(person1.sayname === person2.sayname) // true,两个对象实例的sayname方法是同一个函数实例
判断一个属性在对象实例中还是原型对象中
function propertyInPrototype(obj, propertyName) { // hasOwnProperty方法查找属性是否在对象实例中,有返回true,无返回false // in 方法只要查到该属性,无论在对象实例还是原型对象上都会返回true,都没得返回false return !obj.hasOwnProperty(propertyName) && propertyName in obj; }
for-in只会列出可枚举的属性,由于IE8中,如果原型对象上某属性不可枚举,即使对象实例重写了该属性,该属性依然显示不出来,所以推荐用Object.keys()方法,该方法会返回对象上所有可枚举的实例属性。
function Person() { } Person.prototype.name = 'Anna'; Person.prototype.age = 23; Person.prototype.sayname = function() { alert(this.name); } const keys = Object.keys(Person.prototype); console.log(keys); // ["name", "age", "sayname"] const person1 = new Person(); console.log(keys1); // [] const person2 = new Person(); const keys1 = Object.keys(person1); person2.gender = 'female'; person2.job = 'programer'; const keys2 = Object.keys(person2); console.log(keys2); // ["gender", "job"]
Object.getOwnPropertyNames(obj) 获取对象实例所有属性,无论是否可枚举
组合使用构造函数模式+原型模式
鉴于原型模式的缺点有:
1.引用类型值的修改——某个对象实例修改了该引用类型的值,其他对象实例对应的值也会跟着修改;
2.对象实例无法传参。
这两个不足结合构造函数模式可完美解决,该组合方式也是使用最广泛的。
function Person(name, age) { this.name = name; this.age = age; this.friends = ['Jack', 'Tony']; } Person.prototype = { constructor: Person, sayname: function() { alert(this.name); } } const person1 = new Person('Anna', 23); const person2 = new Person('Jophy', 27); person1.friends.push('Lily'); console.log(person1.friends); // ["Jack", "Tony", "Lily"] console.log(person2.friends); // ["Jack", "Tony"]