This article was wrote in Chinese. Simple conslusion about methods of creating object in Javascript
Object 创建
修改数据属性
方法:Object.defineProperty()
接受参数:属性所在对象,属性名称,描述符对象,其中描述符对象的属性必须是:configurable,enumerable,writable和value中的一个或者多个值1
2
3
4
5
6
7var person = {};
Object.defineProperty(person,'name',{
writable:false;
value: 'Yi'
});
alert(person.name); // Yi
person.name = "test"; // 严格模式下会报错,非严格模式下会被忽略
如果不指定configurable,enumberable,writable,默认值都是false
创建对象
工厂模式
1 | function createPerson(name, age, job){ |
工厂模式没有解决对象识别问题,即怎么样知道一个对象的类型
构造函数模式
1 | function Person(age, name, job){ |
将构造函数当做函数
1 | // 当做构造函数使用 |
构造函数的问题
每个方法都要在每个实例上重新创建一遍,person1和person2两个实例中的sayName()方法并不是同一个Funciton实例
可以把函数转移到构造函数外部来解决问题1
2
3
4
5
6
7
8
9
10function Person(age, name, job){
this.age = age;
this.name = name;
this.job = job;
this.sayName = sayName;
}
function sayName(){
alert(this.name);
}
新的问题,全局作用域中会出现一些function只能被Person调用,而且如果Person中方法特别多,那么全局作用域中的方法
就会变得很多
原型模式
使用原型对象的好处是可以让所有实例共享一些方法和属性,换句话说,可以不在构造函数中
定义对象实例的信息,而是可以将这些信息直接添加到原型对象中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function Person(){
}
Person.prototype.name = 'Yi';
Person.prototype.age = 29;
Person.prototype.job = 'Developer';
Person.prototype.sayName = function(){
alert(this.name);
}
var person1 = new Person();
person1.sayName();
var person2 = new Person();
person2.sayName();
alert(person1.sayName == person2.sayName); //true
无论什么时候创建了一个新函数,都会为函数指定一个prototype属性,prototype属性指向函数的原型对象。所有原型对象都会自动获得一个constructor,constructor包含一个指向prototype属性所在的函数的指针。 Person.prototyp.constructor指向Person
可以通过isPrototypeOf()方法来确定对象之间是否有这种关系
Person.isPrototypeOf(person1)
Object.getPrototype(person1) == Person.prototype
hasOwnProperty()可以检测一个属性是存在于实例中,还是存在于原型中。 但是 in 操作符会在通过对象能够访问给定属性时返回true. 无论该属性存在于实例中还是原型中
for-in循环时,返回的是所有能够通过对象访问的,可枚举的属性,无论在实例中还是原型中
要获得对象中所有可枚举的实例属性,可以用Object.keys()方法,该方法接受一个对象参数
更简单的原型语法1
2
3
4
5
6
7
8
9function Person(){}
Person.prototype = {
name:'Yi',
age:29,
job:'Software Engineer',
sayName: function(){
alert(this.name);
}
}
但是上述语法有一个例外,constructor不在指向Person,因为本质上,重写了prototype,如果需要
constructor可以这样写1
2
3
4
5
6
7
8
9
10function Person(){}
Person.prototype = {
constructor:Person
name:'Yi',
age:29,
job:'Software Engineer',
sayName: function(){
alert(this.name);
}
}
但是这种方法会使得constructor默认为enumerable,所以可以用Object.defineProperty()1
2
3
4
5
6
7
8
9
10
11
12
13function Person(){}
Person.prototype = {
name:'Yi',
age:29,
job:'Software Engineer',
sayName: function(){
alert(this.name);
}
};
Object.defineProperty(Person.prototype,'constructor',{
enumberalbe:false,
value: Person
});
原型具有动态性
在原型中查找值得过程是一次搜索,
因此我们队源性对象所做的任何修改都能够立即从实例上
反映出来,即使是先创建了实例,后修改原型也照样如此.
但是重写整个原型对象,那么情况就不同了1
2
3
4function Person(){}
var person = new Person();
Perosn.prototype = { name : 'Yi'};
person.name // 会报错
因为person指向的原型中不包含以改名字命名的属性
原型对象的问题是
- 省略了为构造函数传递初始化参数这一环节,结果就是所有实例在默认情况下都将取得相同的值
- 最大的问题是由于其共享属性导致的,对于包含引用类型的值的属性来说,问题就比较突出,因为一处修改,处处修改
例如1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18function Person(){}
Person.prototype = {
name:'Yi',
age:29,
friends:['f1','f2','f3'];
job:'Software Engineer',
sayName: function(){
alert(this.name);
}
};
Object.defineProperty(Person.prototype,'constructor',{
enumberalbe:false,
value: Person
});
var person1 = new Person();
person1.friends.push('f4');
var person2 = new Person();
person2.friends // ['f1','f2','f3'];
组合使用构造函数模式和原型模式
创建自定义类型最常见的方式就是组合使用构造函数模式和原型模式
构造函数用于定义实例属性,原型模式用于定义方法和共享的属性
动态原型模式
通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型1
2
3
4
5
6
7
8
9
10
11function 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);
}
}
}
使用动态原型模式时不能使用对象字面量来重写原型,如果在已经创建了市里的
情况下重写原型,那么就会切断现有实例与新原型之间的联系
寄生构造函数模式
这个模式可以再特殊的情况下来为对象创建构造函数,假设我们想创建一个具有
额外方法的特殊数组,由于不能直接修改Array构造函数,因此我们可以使用这个模式1
2
3
4
5
6
7
8
9
10
11function SpecialArray()
{
var values = new Array();
values.push.apply(values,arguments);
values.toPipString(){
return values.join('|');
}
return values;
}
var arr = new SpecialArray('1','2','3');
arr.toPipString();