ECMA-262把对象定义为:无序属性的集合,其属性可以包含基本值、对象或者函数。
内部属性
数据属性
- [[Configurable]]:能否通过delete删除属性;能否修改属性的特性;能否把属性修改为访问器属性,默认为true。
- [[Enumerable]]:能否通过for-in循环返回属性,默认为true。
- [[Writable]]:能否修改属性值,默认为true。
- [[Value]]:此属性的数据值,默认为undefined。
var person = {};
Object.defineProperty(person, "name", {
configurable:false,
value:"Lou"
});
alert(person.name); //"Lou"
delete person.name;
alert(person.name); //"Lou"
可以多次调用Object.defineProperty()方法修改同一个属性,但在把configurable特性设置为false之后就不能再将其变为可配置的了。
访问器属性
- [[Configurable]]:能否通过delete删除属性;能否修改属性的特性;能否把属性修改为数据属性,默认为true。
- [[Enumerable]]:能否通过for-in循环返回属性,默认为true。
- [[Get]]:在读取属性时调用的函数,默认为undefined。(非必需)
- [[Set]]:在写入属性时调用的函数,默认为undefined。(非必需)
在不支持 Object.defineProperty()的浏览器中不能修改 [[Configurable]]、[[Enumerable]]
内部属性的定义&获取
var book = {};
Object.defineProperties(book, {
_year: {
value: 2004
}, //_开头的属性表示只能通过对象方法访问的属性
edition: {
value: 1
},
year: {
get: function() {
return this._year;
},
set: function(newValue) {
if(newValue > 2004){
this.edithion += newValue - this._year;
this._year = newValue;
}
}
}
});
var descriptor = Object.getOwnPropertyDescriptor(book, "_year"); //数据属性
alert(descriptor.value); //2004
alert(descriptor.configurable); //false
alert(typeof descriptor.get); //undefined
descriptor = Object.getOwnPropertyDescriptor(book, "year"); //访问器属性
alert(descriptor.value); //undefined
alert(descriptor.enumerable); //false
alert(typeof descriptor.get); //function
创建对象
使用Object构造函数或对象字面量都可以创建单个对象,但使用同一接口创建大量对象时会产生冗余。因此,产生了许多创建对象的模式。
工厂模式
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 0;
}
var bb = createPerson("Lou", 20, "nurse");
没有解决对象识别问题
构造函数模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
// 当作构造函数使用
var bb = new Person("Lou", 20, "nurse");
alert(bb.constructor == Person); //true
alert(bb instanceof Object); //true
alert(bb instanceof Person); //true,定义了对象类别
person.sayName(); //"Lou"
// 当作普通函数调用
Person("Greg", 27, "doctor");
window.sayName(); //"Greg"
// 在另一个对象的作用域调用
var o = new Object();
Person.call(o, "Kati", 25, "writer");
o.sayName(); //"Kati"
使用构造函数创建对象时,每个方法都要在每个实例上创建一次,会导致不同的作用域链和标识符解析
原型模式
-
组合使用构造函数模式和原型模式【默认模式】
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。
-
动态原型模式
通过条件判断,(仅在必要条件下)在构造函数中初始化原型。
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; //这段代码仅在初次调用构造函数时才会执行 if(typeof this.sayName != "function"){ Person.prototype.sayName = function(){ alert(this.name); }; } }
-
寄生构造函数模式
构造函数返回的对象与在构造函数外部创建的对象没有不同,但可以使用这种方式构造特殊对象以达到扩展或修改原生对象方法的效果。
function 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 bb = new Person("Lou", 20, "nurse"); alert(bb instanceof Object); //true alert(bb instanceof Person); //false
-
稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。
该模式与寄生构造函数模式类似,但有两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。适合在一些安全的环境中或在防止数据被其他应用程序修改时使用。
继承
ECMAScript只支持实现继承,主要依靠原型链来实现。原型链的构建是通过将一个类型的实例赋值给另一个构造函数的原型实现的。
使用原型链
仅使用原型链来实现继承存在两个问题:
-
原型实际上是另一个类型的实例,共享所有继承的属性和方法;
-
创建子实例时,无法在不影响所有对象实例的情况下向超类型的构造函数中传递参数。
class SuperType {
constructor() {
this.property = true;
this.colors = ["red", "yellow", "blue"];
}
}
function SubType(){
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("white");
console.log(instance1.colors); //['red', 'yellow', 'blue', 'white']
var instance2 = new SubType();
console.log(instance2.colors); //['red', 'yellow', 'blue', 'white']
构造函数继承
优点:可以在子类型构造函数中向超类型构造函数传递参数
缺点:函数复用性差
function SuperType(name){
this.name = name;
this.colors = ["red", "yellow", "blue"];
}
function SubType(){
SuperType.call(this, "Lou"); //只能继承属性,不能继承方法
this.age = 20;
}
var instance1 = new SubType();
console.log(instance1.name); //"Lou"
console.log(instance1.age); //20
console.log(instance1.colors); //['red', 'yellow', 'blue']
组合继承
融合了原型链继承和构造函数继承的优点
function SuperType(name){
this.name = name;
this.colors = ["red", "yellow", "blue"];
}
SuperType.prototype.sayName = function(){
console.log(this.name);
}
function SubType(name, age){
//继承属性
SuperType.call(this, name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
console.log(this.age);
}
var instance1 = new SubType("Lou", 20);
instance1.colors.push("black");
instance1.sayName(); //"Lou"
instance1.sayAge(); //20
console.log(instance1.colors); //['red', 'yellow', 'blue', 'black']
var instance2 = new SubType("Barb", 17);
instance2.sayName(); //"Barb"
instance2.sayAge(); //17
console.log(instance2.colors); //['red', 'yellow', 'blue']
原型式继承
object()对传入其中的对象执行浅拷贝,类似于复制一个对象
缺点:所有实例的属性和方法共享;无法实现复用
var person = {
name: "Lou",
friends: ["Abby", "Bob", "Cindy"]
};
var person1 = Object.create(person);
person1.name = "David";
person1.friends.push("Elsa");
var person2 = Object.create(person);
person2.name = "Frank";
person2.friends.push("Greg");
console.log(person.friends === person1.friends); //true
console.log(person1.friends === person2.friends); //true
寄生式继承
创建了一个仅用于封装继承过程的函数,无法复用
function createAnother(o){
var clone = Object(o);
clone.sayHi = function(){
console.log("Hi");
};
return clone;
}
var person3 = createAnother(person);
person3.sayHi();
寄生组合式继承【最常用】
借助构造函数继承属性,通过原型链的混合形式继承方法。避免了组合继承方式中两次调用原型对象构造函数带来的冗余。
将组合继承中继承方法的语句封装起来
function inheritPrototype(subType, superType){
var prototype = Object(superType.prototype); //创建对象
prototype.constructor = subType; //弥补重写原型失去的constructor属性
subType.prototype = prototype; //指定对象
}