创建对象
通过Object构造函数或对象字面量创建单个对象
这些方式有明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。为了解决这个问题,出现了工厂模式。
工厂模式
考虑在ES中无法创建类(ES6前),开发人员发明了一种函数,用函数来封装以特定接口创建对象的细节。(实现起来是在一个函数内创建好对象,然后把对象返回)。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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 person1=createPerson( "Nicholas" ,29, "Software Engineer" );
var person2=createPerson( "Greg" ,27, "Doctor" );
|
构造函数模式
像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
1
2
3
4
5
6
7
8
9
10
11
|
function Person(name,age,job){
this .name=name;
this .age=age;
this .job=job;
this .sayName= function (){
alert( this .name);
};
}
var person1= new Person(...);
var person2= new Person(...);
|
与工厂模式相比,具有以下特点:
- 没有显式创建对象;
- 直接将属性和方法赋给了this对象;
- 没有return语句;
- 要创建新实例,必须使用new操作符;(否则属性和方法将会被添加到window对象)
- 可以使用instanceof操作符检测对象类型
构造函数的问题:
构造函数内部的方法会被重复创建,不同实例内的同名函数是不相等的。可通过将方法移到构造函数外部解决这一问题,但面临新问题:封装性不好。
原型模式
我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。(prototype就是通过调用构造函数而创建的那个对象实例的原型对象)。
使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中。
1
2
3
4
5
6
7
8
9
10
11
12
|
function Person(){
}
Person.prototype.name= "Nicholas" ;
Person.prototype.age=29;
Person.prototype.job= "..." ;
Person.prototype.sayName= function (){
...
};
var person1= new Person();
person1.sayName(); //"Nicholas"
|
更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象,并重设constructor属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
function Person(){
}
Person.prototype={
name: "..." ,
age:29,
job: "..." ,
sayName: function (){
...
}
};
Object.defineProperty(Person.prototype, "constructor" ,{
enumerable: false ,
value:Person,
});
|
原型对象的问题:
他省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值,虽然这会在一定程度带来一定的不便,但不是最大的问题,最大的问题是由其共享的本性所决定的。
对于包含基本值的属性可以通过在实例上添加一个同名属性隐藏原型中的属性。然后,对于包含引用数据类型的值来说,会导致问题。
组合使用构造函数模式和原型模式
这是创建自定义类型的最常见的方式。
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。所以每个实例都会有自己的一份实例属性的副本,但同时共享着对方法的引用,最大限度的节省了内存。同时支持向构造函数传递参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function Person(name,age,job){
this .name=name;
this .age=age;
this .job=job;
this .friends=[ "S" , "C" ];
}
Person.prototype={
constructor:Person,
sayName: function (){
alert( this .name);
}
};
var person1= new Person(...);
|
动态原型模式
1
2
3
4
5
6
7
8
9
10
11
|
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);
};
}
}
|
这里只有sayName()不存在的情况下,才会将它添加到原型中,这段代码只会在初次调用构造函数时才执行。这里对原型所做的修改,能够立刻在所有实例中得到反映。
Object.create()
ES5定义了一个名为Object.create()的方法,它创建一个新对象,其中第一个参数是这个对象的原型,第二个参数对对象的属性进行进一步描述。
Object.create()介绍
Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法,例如:toString(), hasOwnProperty()等方法
Object.create()方法接受两个参数:Object.create(obj,propertiesObject) ;
obj:一个对象,应该是新创建的对象的原型。
propertiesObject:可选。该参数对象是一组属性与值,该对象的属性名称将是新创建的对象的属性名称,值是属性描述符(这些属性描述符的结构与Object.defineProperties()的第二个参数一样)。注意:该参数对象不能是 undefined,另外只有该对象中自身拥有的可枚举的属性才有效,也就是说该对象的原型链上属性是无效的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
var o = Object.create(Object.prototype, {
// foo会成为所创建对象的数据属性
foo: {
writable: true ,
configurable: true ,
value: "hello"
},
// bar会成为所创建对象的访问器属性
bar: {
configurable: false ,
get: function () { return 10 },
set: function (value) {
console.log( "Setting `o.bar` to" , value);
}
}
});
console.log(o); //{foo:'hello'}
var test1 = Object.create( null ) ;
console.log(test1); // {} No Properties
因为在bar中设置了configurable 使用set,get方法默认都是不起作用,所以bar值无法赋值或者获取
这里的o对象继承了 Object.prototype Object上的原型方法
我们可以 对象的 __proto__属性,来获取对象原型链上的方法 如:
console.log(o.__proto__); //{__defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, __lookupSetter__: ƒ, …}
console.log(test1.__proto__); //undefined
|
通过打印发现, 将{}点开,显示的是 No Properties ,也就是在对象本身不存在属性跟方法,原型链上也不存在属性和方法,
new object()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var test1 = {x:1};
var test2 = new Object(test1);
var test3 = Object.create(test1);
console.log(test3); //{}
//test3等价于test5
var test4 = function (){
}
test4.prototype = test1;
var test5 = new test4();
console.log(test5);
console.log(test5.__proto__ === test3.__proto__); //true
console.log(test2); //{x:1}
|
1
2
3
4
5
6
7
8
9
10
|
var test1 = {};
var test2 = new Object();
var test3 = Object.create(Object.prototype);
var test4 = Object.create( null ); //console.log(test4.__proto__)=>undefined 没有继承原型属性和方法
console.log(test1.__proto__ === test2.__proto__); //true
console.log(test1.__proto__ === test3.__proto__); //true
console.log(test2.__proto__ === test3.__proto__); //true
console.log(test1.__proto__ === test4.__proto__); //false
console.log(test2.__proto__ === test4.__proto__); //false
console.log(test3.__proto__ === test4.__proto__); //false
|
总结:使用Object.create()是将对象继承到__proto__属性上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
var test = Object.create({x:123,y:345});
console.log(test); //{}
console.log(test.x); //123
console.log(test.__proto__.x); //3
console.log(test.__proto__.x === test.x); //true
var test1 = new Object({x:123,y:345});
console.log(test1); //{x:123,y:345}
console.log(test1.x); //123
console.log(test1.__proto__.x); //undefined
console.log(test1.__proto__.x === test1.x); //false
var test2 = {x:123,y:345};
console.log(test2); //{x:123,y:345};
console.log(test2.x); //123
console.log(test2.__proto__.x); //undefined
console.log(test2.__proto__.x === test2.x); //false
|
继承
我这里就介绍一种吧,剩下的可以去权威指南里看去
原型链
ECMAScript 中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原 型让一个引用类型继承另一个引用类型的属性和方法。简单回顾一下构造函数、原型和实例的关系:每 个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型 对象的内部指针。那么,假如我们让原型对象等于另一个类型的实例,结果会怎么样呢?显然,此时的 原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数 的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实 例与原型的链条。这就是所谓原型链的基本概念。
实现原型链有一种基本模式,其代码大致如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
function SuperType(){
this .property = true ;
}
SuperType.prototype.getSuperValue = function (){
return this .property;
};
function SubType(){
this .subproperty = false ;
}
//继承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this .subproperty;
};
var instance = new SubType();
alert(instance.getSuperValue());
//true
|
以上代码定义了两个类型:SuperType 和 SubType。每个类型分别有一个属性和一个方法。它们 的主要区别是 SubType 继承了 SuperType,而继承是通过创建 SuperType 的实例,并将该实例赋给 SubType.prototype 实现的。实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原 来存在于 SuperType 的实例中的所有属性和方法,现在也存在于 SubType.prototype 中了。在确立了 继承关系之后,我们给 SubType.prototype 添加了一个方法,这样就在继承了 SuperType 的属性和方 法的基础上又添加了一个新方法。这个例子中的实例以及构造函数和原型之间的关系如图 6-4 所示。
