一、理解对象
- 基于Object对象
var person = new Object();
person.name = "Mike";
person.age = "20";
person.job = "student";
person.sayName = function(){
alert(this.name);
}
- 对象字面量语法
var person = {
name: "Mike",
age: 20,
job: "student",
sayName: function(){
alert(this.name);
}
}
- 可以使用”.”增加其属性,可以通过”delete”将属性值设置为undefined。
person.score = "90";//增加属性
alert(person.score); //90
delete person.score;
alert(person.score);//undefined
二、属性类型
ES中有两种属性:数据属性和访问器属性。
数据属性
- [[Configurable]]: 表示能否通过 delete 删除从而重新定义属性,能否修改属性特性,或能否把属性修改为访问器属性。默认值true。
- [[Enumerable]]: 表示能否通过for-in循环返回属性。默认值true。
- [[Writable]]: 表示能否修改属性的数据值。默认值true。
- [[Value]]: 包含这个属性的数据值。读取写入都在该位置。默认值undefined。
修改默认属性使用defineProperty(),接收三个参数:属性所在对象,属性名和一个描述符对象。
var person = {};
Object.defineProperty(person,"name",{
configurable: false, //不可删除
writable: false, //变为只读
value: "Mike"
});
alert(person.name);//"Mike"
delete person.name;
person.name = "Amy";
alert(person.name);//"Mike"
一旦将configurable设置为false,则无法再使用defineProperty将其修改为true。
访问器属性
- [[Configurable]]: 表示能否通过delete删除属性而重新定义属性,能否修改属性特性,或能否把属性修改为数据属性。对于直接在对象上定义属性,默认值true。
- [[Enumerable]]: 表示能否通过for-in循环返回属性。对于直接在对象上定义属性,默认值true。
- [[Get]]: 读取属性调用的函数。默认undefined。
- [[Set]]: 写入属性调用的函数。默认undefined。
访问器属性不能直接定义,必须使用Object.defineProperty()来定义。
var person = {
_age:20
};
Object.defineProperty(person,"age",{
get: function(){
if(this._age>0){
return true;
}else{
return false;
}
}
});
alert(person.age);//true
在定义getter与setter时不能指定属性的configurable及writable特性。
- ES5定义了一个Object.defineProperties()方法,可以通过描述符一次定义多个属性。接受两个参数:第一个对象是要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应。
var person = {};
Object.defineProperties(person,{
_age:{
writeable:true, //数据属性
value:20
},
year:{ //定义了一个访问器属性
get function(){
if(value>0){
return true;
}else{
return false;
}
}
}
});
- 使用Object.getOwnPropertyDescriptor()方法可以取得给定属性的特性:
var descriptor = Object.getOwnPropertyDescriptor(person,"_age");
alert(descriptor.value);//20
alert(descriptor.configurable);//false
alert(typeof descriptor.year);//undefined
三、创建对象
工厂模式
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("Mike","20","student");
var person2 = createPerson("Amy","22","engineer");
创建对象都是使用Object的原生构造函数来完成的,所以没有解决对象识别问题
构造函数模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name); //第一种写法
};
this.sayName = new Function("alert(this.name)"); //第二种写法
}
function sayName() {
alert(this.name);
} //第三种写法,将方法写在外面
var person1 = new Person("Mike","20","student");
var person2 = new Person("Amy","22","engineer");
构造函数问题
- 上文方法一二,每次创建对象时,每个方法都要在实例上重新创建一遍,创建两个完成同样任务的函数造成浪费,况且有this对象,不用在执行代码前把函数绑定到特定对象上。
- 上文中写的方法三,对象共享了全局作用域的sayName()。但将函数放在全局作用域时,失去了封装性,而且对象需要定义很多方法时,就要定义很多全局函数。
构造函数与工厂模式 不同之处:
- 没有显式地创建对象
- 直接将属性和方法赋给this对象
- 没有return语句
创建Person新实例经历步骤
- 创建一个新对象
- 将构造函数的作用域赋给新对象(因此this就指向这个新对象)
- 执行构造函数中的代码
- 返回新对象
person1和person2分别保存着Person的一个不同的实例。都有constructor属性,该属性指向Person。
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
alert(person1 instanceof Person);//true;
alert(person1 instanceof Object);//true;
alert(person2 instanceof Person);//true;
alert(person2 instanceof Object);//true;
- 构造函数当做函数
- 只要通过new操作符调用,就可以作为构造函数
var person = new Person("Mike",20,"student"); //当做构造函数使用
person.sayName();//"Mike"
Person("Greg",27,"Doctor"); //作为普通函数调用,添加到window
window.sayName(); //"Grey"
var o = new Object();//在另一个对象的作用域中调用
Person.call(o,"Kristen",25,"Nurse");
o.sayName();//"Kristen"
由于构造函数存在的问题,为此引入原型模式
原型模式
原型:我们创建每个函数都有一个prototype(原型)属性。它是一个指针,是构造函数中自动创建的对象实例的原型对象。
用途:包含可以由特定类型的所有实例共享的属性和方法。
好处:可以让所有对象实例共享它包含的属性和方法,也就是说,不必在构造函数中定义对象实例的信息,可以直接将信息添加到原型对象中。
function Person(){} //声明一个空函数
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Endineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName();//"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName);//true
新对象这些属性和方法是有所有实例共享的。person1和person2访问的都是同一组属性和同一个sayName()函数。
理解原型对象
- 只要创建函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象。
- 原型对象自动获得constructor(构造函数)属性是指向prototype属性所在函数的指针。即Person.prototype.constructor指向Person。
- 构造函数创建实例时,实例内部指针指向构造函数原型对象。
由图可见,实例与构造函数无直接关系。实例不包含属性和方法,但仍可调用sayName(),通过查找对象属性过程实现。
为实例添加新的与原型同名属性时,会屏蔽原型中的那个属性。delete恢复原型连接。hasOwnProperty()查看访问的是实例属性还是原型属性。in主要是看对象里的属性存不存在。如下
function Person(){} //声明一个空函数
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Endineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
var person2 = new Person();
console.log(person1.hasOwnProperty("name")); //false 来自原型返回false
console.log("name" in person1); //true in只要通过对象访问到属性就返回true 与上一句结合可以确定属性是原型中的属性
person1.name = "Greg";
alert(person1.name); //"Greg" 来自实例 屏蔽了原型中的同名属性
console.log(person1.hasOwnProperty("name")); //true 来自实例返回true
alert(person2.name);//"Nicholas"来自原型
delete person1.name;
alert(person1.name); //"Nicholas"来自原型 删除原型属性就恢复了对原型的连接
- Object.keys()方法:接受一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。
var keys = Object.keys(Person.prototype);
alert(keys); //"name,age,job,sayName" 原型函数属性
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1); //实例对象属性
alert(p1keys); //"name,age"
- 更简单的原型语法
function Person(){
}
Person.prototype = {
name : "Mike",
age : 29,
job : "student"
sayName : function(){
alert(this.name);
}
};
var friend = new Person();
console.log(friend.constructor == Person); //false
console.log(friend.constructor == Object);//true
1,每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得constructor属性。这里的语法,完全重写了默认的prototype对象,重写之后变成一个Object对象实例,constructor属性也就变成新对象的constructor属性,不再指向Person。
2,如果不想因为重写prototype而改变了constructor的指向,可以在重写prototype时加上:constructor : Person,
不过[[Enumerable]]属性会变为true
3 ,让constructor指向Person的操作会带来问题,为此我们重设构造函数(ES5兼容)
Object.defineProperty(Person.prototype,"constructor",{
enumerable: false,
value: Person
});//重设构造函数
- 原型的动态性
在原型中查找值是一次搜索,因此对原型所做的修改都能立即从实例上反映出来。
var friend = new Person();
Person.prototype.sayHi = function(){
alert("hi");
};
friend.sayHi(); //"hi" 即使friend实例在添加新方法之前创建,也可以访问
- 当我们调用friend.sayHi(),首先会在实例中搜索名为sayHi属性,实例与原型通过指针连接,所以没找到时,可以继续搜索原型。
重写原型对象后,对实例对象通过 [[prototype]] 访问原型对象的属性方法有影响。
- 构造函数的prototype属性是用来指向原型对象的,重写Person.prototype属性意味着它指向了一个新的原型对象,不再指向函数创建时生成的那个原型对象。
调用new Person()创建对象实例时,实例对象会有一个内部属性_ proto _指向原型对象Person.prototype
1) 如果先用new Person()创建实例,后重写Person.prototype,实例中的 _ proto _ 指向的是函数创建时生成的原型对象。
2)如果先重写Person.prototype,后用new person()创建实例,实例中的_ proto _ 指向的是改写后的新原型对象。
//第一种情况:先创建实例,后重写
function Person(){
} //函数创建 同时创建prototype对象,获得constructor属性
var friend = new Person(); //实例中的 _ proto _ 指向上面函数创建时生成的原型对象。
Person.prototype = {
constructor : Person,
name : "Mike",
age : 29,
job : "student"
sayName : function(){
alert(this.name);
}
}; //重写原型,构造函数指向新的原型对象
friend.sayName(); //error 函数创建时生成的原型无sayName属性
//第二种情况:先重写,后创建实例
function Person(){
}
Person.prototype = {
constructor : Person,
name : "Mike",
age : 29,
job : "student",
sayName : function(){
alert(this.name);
}
};
var friend = new Person();
friend.sayName(); //"Mike"
- 原型对象问题
1)对于基本值:使用 实例.属性名 = “” 赋值时,若有该属性,就会隐藏原型中对应属性的值;无该属性时,就会给实例新增属性。不会对原型的值以及属性造成影响。
function Person(){
}
Person.prototype = {
name: "Mike"
};
var person1 = new Person();
var person2 = new Person();
person1.name = "Amy";
console.log(person1.name); //"Amy"
console.log(person2.name); //"Mike"
person1.job = "student";
console.log(person1.job); //"student"
console.log(person2.job); //undefined
2)对于引用值,若有一个对象修改引用值,其他实例会共享这个引用类型,所以会受到影响。由图可知,修改引用值对原型有影响。
function Person(){
}
Person.prototype = {
constructor : Person,
name : "Mike",
age : 29,
job : "student",
friends: ["Amy","Ann"],
sayName : function(){
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
console.log(person1.friends); //"Amy,Ann,Van"
console.log(person2.friends); //"Amy,Ann,Van"
console.log(person1.friends === person2.friends); //true
组合使用构造函数模式和原型模式
构造函数模式用于定义实例属性,原型模式用于定义方法和共享属性。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.freiends = ["Amy","Ann"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
};
var person1 = new Person("Nicholas",29,"Software Engineer");
var person2 = new Person("Greg",27,"Doctor");
person1.friends.push("Van");
console.log(person1.friends); //"Amy,Ann,Van"
console.log(person2.friends); //"Amy,Ann"
console.log(person1.friends == person2.friends);//false 它们引用了不同的数组
console.log(person1.sayName == person2.sayName); //true
动态原型模式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
if(typeOf this.sayName != "function"){ //只会在sayName()方法不存在时才会将其添加到原型 使用动态模型时,不能使用字面量重新写原型
Person.prototype.sayName = function(){
alert(this.name);
};
}
}
var friend = new Person("Mike",20,"student");
friends.sayName();
寄生构造函数模式
基本思想:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。
假设我们要创建一个具有额外方法的特殊数组。由于不能直接修改Array构造数组,因此可以使用这个模式。
function SpecialArray(){
var values = new Array();
values.push.apply(values, arguments);
values.toPipedString = function(){
return this.join("|");
}
return values;
}
var colors = new SpecialArray("red", "blue", "green");
console.log(color.toPipedString());//"red|blue|green"
稳妥构造函数模式
稳妥对象:指没有公共属性,且其方法也不用this对象。适合在安全的环境中,或者防止数据被其他应用程序改动时使用。
与寄生构造模式不同:
1)新创建对象的实例方法不引用this
2)不使用new操作符调用构造函数。
function Person(name, age, job){
//创建要返回的对象
var o = new Object();
//可以在这里定义私有变量和函数
//添加方法
o.sayName = function(){
alert(name);
};
//返回对象
return o;
}
var friend = Person("Mike", 29, "Engineer");
friend.sayName();//"Mike" 除了这个方法,没有其他方法可以访问name值
说明一点:寄生构造函数模式和稳妥构造函数模式所创建的对象与构造函数的原型没有关系,所以不能用instanceof操作符来确定对象。