一、属性类型(两种)
1.数据属性
数据属性有 4个 描述其行为的特性:
[[Configurable]]:默认值 true。表示能否通过 delete 删除属性,能否修改属性的特
性,或者能否把属性修改为访问器属性。
[[Enumerable]]:默认值 true。表示能否通过 for-in 循环返回属性。
[[Writable]]:默认值 true。表示能否修改属性的值。
[[Value]]:默认值undefined。包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。
修改特性值:Object.defineProperty(),Configurable、Enumerable、Writable均默认为false。
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
person.name = "Greg";//严格模式下,这样赋值会抛出错误。
alert(person.name); //"Nicholas" writable设为false,name修改失败
2.访问器属性
访问器属性也有 4个 特性:
[[Configurable]]:默认值为true。表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特
性,或者能否把属性修改为数据属性。
[[Enumerable]]:默认值为 true。表示能否通过 for-in 循环返回属性。
[[Get]]:默认值为 undefined。在读取属性时调用的函数。
[[Set]]:默认值为 undefined。在写入属性时调用的函数。
【注】
访问器属性不能直接定义,必须使用 Object.defineProperty()来定义。
3.定义多个属性
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;
}
}
}
});
4.读取属性的特性
Object.getOwnPropertyDescriptor():
返回一个对象,对象中存储的是所访问属性的特性值。
注:只能用于实例属性,要取得原型属性的描述符:Object.getOwnPropertyDescriptor(Person.prototype)。
接收两个参数:
(1)obj:属性所在对象
(2)propertyName:要读取其描述符(特性值)的属性名称
二、创建对象(7种方式)
1.工厂模式
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 o;
}
var person1 = createPerson("Sherry",23,"Software Engineer");
var person2 = createPerson("Greg",25,"Doctor");
【缺点】无法知道一个对象的类型
2.构造函数模式
function Person(name,age,job){
this.name = name;
this.age = age;this.job = job;
this.sayName = function(){
alert(this.name);
};
}var person1 = new Person("Sherry",23,"Software Engineer");
var person2 = new Person("Greg",25,"Doctor");
【new做了什么?】
(1)创建一个新对象
(2)将构造函数的作用域赋给新对象(因此this指向了这个新对象)
(3)执行构造函数中的代码(为这个新对象添加属性)
(4)返回新对象
【关于constructor】
person1.constructor == Person; //true
person2.constructor == Person; //true
【缺点】
每个方法都要在每个实例上重新创建一遍。person1和person2创建了两个名为sayName的函数实例。
person1.sayName == person2.sayName; //false
【改进】
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}var person1 = new Person("Sherry",23,"Software Engineer");
var person2 = new Person("Greg",25,"Doctor");
【新缺点】
全局作用域定义的函数,实际上只能被某个对象调用。这点与全局作用域不符。同时,如果对象需要定义很多方法,就要定义很多个全局函数,这使得我们自定义的引用类型丝毫没有封装性可言了。
3.原型模式
function Person(){ }
Person.prototype.name = "Sherry";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
(1)【达到效果】
person1.sayName == person2.sayName; //true
(2)【实例对象、原型对象与constructor的关系】
person1.__proto__ === person2.__proto__ === Person.prototype
Person.prototype.constructor === Person
(3)【原型检测】
Person.prototype.isPrototypeOf(person1); //true
Object.getPrototypeOf(person1) === Person.prototype; //true
(4)【in操作符——实例属性+原型属性】
"name" in person1; //true 实例属性、原型属性均可以访问
for-in:返回所有能通过对象访问的可枚举的属性,实例属性、原型属性均可以访问。
注:所有的实例属性都是可枚举的。
(5)【Object.keys()——实例属性】
获取对象上所有可枚举的实例属性。
(6)【Object.getOwnPropertyNames()——实例属性】
获取所有实例属性,无论是否可枚举。
(7)【原型对象切断与构造函数联系的情况】
//改变定义原型对象的方式
Person.prototype = {
name:"Sherry",
age:23,
job:"Software Engineer",
sayName:function(){
alert(this.name);
}
}
!!注!!:
这种定义原型对象的写法,相当于完全重写了原有的原型对象,且会切断其与构造函数之间的联系。
即:相当于创建了一个新的普通对象,并把Person.prototype指向了这个新对象。所以,这时候的Person.prototype也就是一个普通对象,只不过Person的实例可以读取它里面的属性和方法。
因为这种写法完全重写了默认的prototype对象,相当于创建了一个新的对象,所以constructor属性就变成了新对象的constructor属性,指向Object。
但,instanceof依然可以检测Person类型。
var friend = new Person();
friend instanceof Person; //true
friend.constructor == Person; //false
friend.constructor == Object; //true
【显式设置constructor】
Person.prototype = {
constructor:Person,
name:"Sherry",
age:23,
job:"Software Engineer",
sayName:function(){
alert(this.name);
}
}
注:这样设置会导致constructor的[[Enumerable]]被置为true。
(这样设置constructor,js会认为它是新对象的实例属性,而实例属性都可枚举)
——解决:使用defineProperty重置constructor的特性。
【原型的动态性】
function Person(){ }
var friend = new Person();
Person.prototype = {
constructor:Person,
name:"Sherry",
age:23,
job:"Software Engineer",
sayName:function(){
alert(this.name);
}
};
friend.sayName(); //error
解析:
创建friend对象时,获取到调用构造函数时创建的原型对象。
经过Person.prototype = { xxx }重写,Person的原型对象相当于被更改为新创建的对象,之后再new实例,均会指向新的原型对象。而已经创建过的实例对象friend,仍指向旧的原型对象。
【原型模式缺点】
(1)省略了为构造函数传递初始化参数的环节,结果所有实例在默认情况下都会获得相同的属性值。
(2)最大问题:共享属性的问题。尤其是,共享引用类型属性的问题。
function Person(){ }
Person.prototype = {
constructor:Person,
name:"Sherry",
age:23,
job:"Software Engineer",
friends:["Greg","Court"],
sayName:function(){
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
person1.friends; //["Greg","Court","Van"]
person2.friends; //["Greg","Court","Van"] 非理想效果
4.构造函数模式+原型模式(最常见方式,定义引用类型的默认模式)*暂无缺陷^_^
function Person(name,age,job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Greg","Court"];
}
Person.prototype = {
constructor:Person,
sayName:function(){
alert(this.name);
}
}
var person1 = new Person("Sherry",23,"Software Engineer");
var person2 = new Person("Greg",25,"Doctor");
person1.friends.push("Van");
person1.friends; //["Greg","Court","Van"]
person2.friends; //["Greg","Court"]
person1.friends === person2.friends; //false
person1.sayName === person2.sayName; //true
5.动态原型模式(为了提高模式4的封装性)*暂无缺陷^_^
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);
}
}
}
var friend = new Person("Sherry",23,"Software Engineer");
friend.sayName();
6.寄生构造函数模式(可以使用其他模式的情况下,不要使用这种模式)
//与工厂模式形式相同
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 friend = new Person("Sherry",23,"Software Engineer");
firend.sayName(); //"Sherry"
【与工厂模式的区别】
(1)使用new调用
(2)把Person称作构造函数,不是普通函数
【适用场景】
为特定类型的对象创建构造函数,增加特殊功能。如为Array构造函数添加功能。
eg:创建一个具有额外方法的特殊数组
function SpecialArray(){
var values = new Array();values.push.apply(values,arguments);
values.toPipedString = function(){
return this.join("|");
}
return values;
}var colors = new SpecialArray("red","green","blue");
alert(colors.toPipedString()); //"red|green|blue"
【缺点】
不能用instanceof确定对象类型。
7.稳妥构造函数模式(安全性高)
【稳妥对象】
没有公共属性,而且其方法也不引用this的对象。
function Person(name,age,job){
var o = new Object();
o.sayName = function(){
alert(name);
};
return o;
}
这里,除了使用sayName()方法之外,没有其他办法访问name的值。
【与寄生构造函数模式的区别】
(1)新创建对象的实例方法不引用this
(2)不使用new操作符调用构造函数
【使用场景】
需要高安全性的场景。
【缺点】
不能用instanceof确定对象类型。
三、继承(6种方式)
1.原型链
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function (){
return this.property;
};
function SubType(){
this.subproperty = false;
}
SubType.prototype = new SuperType(); //重写原型对象,和字面量方式定义原型对象相同
SubType.prototype.getSubValue = function(){
return this.subproperty;
};
var instance = new SubType();
instance.getSuperValue(); //true
【原型链的问题】
(1)最大问题来自,包含引用类型值的原型。
子类的原型对象,是父类的实例。所以,父类的实例属性,会变成子类实例的原型属性,从而造成一些,不希望共享的属性、被共享了的问题。
(2)创建子类的实例时,不能向超类型的构造函数中传递参数。或者说,没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。
【问题一】eg:
function SuperType(){
this.colors = ["red","green","blue"];
}
function SubType(){
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,green,blue,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,green,blue,black" 期望colors数组不改变
2.借用构造函数(在子类构造函数内部调用超类的构造函数)
function SuperType(){
this.colors = ["red","green","blue"];
}
function SubType(){
SuperType.call(this); //改变父类的this指向
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);//"red,green,blue,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,green,blue"
【优势】
在子类构造函数中给父类构造函数传递参数。
function SuperType(name){
this.name = name;
}
function SubType(){
SuperType.call(this,"Sherry");
this.age = 23;
}
var instance = new SubType();
alert(instance.name); //"Sherry"
alert(instance.age); //23
【问题】
(同创建对象的构造函数模式)如果方法都在构造函数中定义,每个实例都会创建一个新的方法,但方法执行相同的功能,不具备函数复用性。
3.组合继承(原型链+借用构造函数,最常用的继承模式)
function SuperType(name){
this.name = name;
this.colors = ["red","green","blue"];
}
SuperType.prototype.sayName = function(){
alert(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(){
alert(this.age);
}
var instance1 = new SubType("Sherry",23);
instance1.colors.push("black");
alert(instance1.colors); //"red,green,blue,black"
instance1.sayName(); //"Sherry"
instance1.sayAge(); //23
var instance2 = new SubType("Greg",25);
alert(instance2.colors); //"red,green,blue"
instance2.sayName(); //"Greg"
instance2.sayAge(); //25
【缺点】
SubType.prototype = new SuperType();把父类所有实例属性都赋给了子类的原型对象。
SuperType.call(this,name);又把父类所有的实例属性都赋给了子类的实例。
两次调用父类构造函数,子类实例上挂载的父类实例属性,一定会屏蔽子类原型对象上挂载的父类实例属性。造成浪费。
4.原型式继承
function object(o){
function F(){ }F.prototype = o;
return new F();
}
【注】
传入的参数o中如果包含引用类型,则所有新创建的对象都会共享这个引用类型的值。
【规范化】
Object.create()实现原型式继承。传入一个参数时,行为与上述object方法相同。
两个参数:
(1)用作新对象原型的对象
(2)(可选)为新对象定义额外属性的对象。格式与Object.defineProperties()第二个参数相同。
eg:
var person = {
name:"Sherry",
friends:["Kaven","Greg"]
}
var anotherPerson = Object.create(person,{
name:{
value:"Lucy"
}
});
anotherPerson.name; //"Lucy"
【适用场景】
只是想让一个对象与另一个对象保持类似的情况。
没必要兴师动众创建构造函数,原型式继承便可以胜任。
5.寄生式继承(与工厂模式、寄生构造函数模式类似)
function createAnother(original){
var clone = Object.create(original);
clone.sayHi = function(){
alert("hi");
};
return clone;
}
【适用场景】
主要考虑对象,而不是自定义类型和构造函数的情况。
【缺点】
sayHi函数不能被复用,降低效率。同构造函数模式创建对象的缺点。
6.寄生组合式继承(最理想的继承模式)*无缺陷^_^
function SuperType(name){
this.name = name;
this.colors = ["red","green","blue"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name,age){
SuperType.call(this,name);
this.age = age;
}
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};