这个系列是我读冴羽老师博客的感悟,
加入了个人的解读和练习题的解答
本文会介绍不同创建对象的方法,来探究不同解决方案的优缺点
1.工厂模式
创建一个全新的对象,然后给它加上属性和方法,最后返回这个对象
1
2
3
4
5
6
7
8
|
function createPerson(name){
var obj = new Object();
obj.name = name;
obj.getName = function () {
console.log(this.name)
};
return obj
}
|
优点:简单直接
缺点:对象无法识别,所有实例的原型全都直接指向Object.prototype
2.构造函数模式
创建一个构造函数,然后利用new来创建其实例
1
2
3
4
5
6
7
8
|
function Person(name) {
this.name = name;
this.getName = function (){
console.log(this.name);
}
}
let Neo2 = new Person("Neo");
|
优点:所有实例能归类为一个特定的原型了
缺点:每次创建实例时,每个方法都要被创建一次,而实际上每个实例用的方法是一样的,这造成了不必要的数据重复
2.1构造函数模式优化
我们尝试解决2中的缺点,很容易想到去把构造函数里的方法抽出来,然后只要把构造函数的方法指向抽出的方法就好了
1
2
3
4
5
6
7
8
9
10
|
function getName() {
console.log(this.name)
}
function Person(name) {
this.name = name;
this.getName = getName;
}
let Neo2 = new Person("Neo")
|
优点:不用额外生成重复的方法对象了,方法可以抽出去了
缺点:因为方法被抽出去了,失去了封装性
3.原型模式
尝试把属性和方法都加到构造函数的原型上去,再利用new 构造函数来批量创建对象
1
2
3
4
5
6
7
8
|
function Person(name){}
Person.prototype.name = "Neo";
Person.prototype.getName = function(){
console.log(this.name) //
}
let Neo3 = new Person()
|
优点:解决了在创建多个对象的时候不会创建重复的方法对象
缺点:构造函数不能接受实例初始化参数name,且所有在原型上的属性和方法都会被共享(没有封装性)
3.1原型模式优化
1
2
3
4
5
6
7
8
9
10
|
function Person(name){}
Person.prototype = {
name: "Neo",
getName: function(){
console.log(this.name)
}
}
let Neo3 = new Person()
|
优点:把对原型的赋值过程封装化了
缺点:直接赋值构造函数的原型,使得原型的constructor属性丢失
3.2原型模式再优化
1
2
3
4
5
6
7
8
9
10
11
|
function Person(name) {}
Person.prototype = {
constructor: Person,
name: "Neo",
getName: function() {
console.log(this.name)
}
}
let Neo3 = new Person()
|
优点:这次可以补全丢失了constructor属性了
缺点:依旧不能初始化参数,对new出来的实例也没有解决必须共享所有属性和方法问题
4.组合模式
结合构造模式和原型模式的优点
1
2
3
4
5
6
7
8
9
10
11
12
|
function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person,
getName: function() {
console.log(this.name)
}
}
let Neo4 = new Person("Neo")
|
优点:可以初始化,不用重复生成相同的函数对象
缺点:结构上必须分为两个部分
4.1动态原型模式
1
2
3
4
5
6
7
8
9
10
|
function Person(name){
this.name = name;
if(typeof this.getName != "function") {
Person.prototype.getName = fucntion() {
console.log(this.name)
}
}
}
var person1 = new Person("Neo");
|
注意:使用动态原型模式时,不能用对象字面量重写原型
如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
function Person(name) {
this.name = name;
if (typeof this.getName != "function") {
//此处直接用用对象字面量重写原型,将会引发错误
Person.prototype = {
constructor: Person,
getName: function () {
console.log(this.name);
}
}
}
}
var person1 = new Person('kevin');
var person2 = new Person('daisy');
// 报错 并没有该方法
person1.getName();
// 注释掉上面的代码,这句是可以执行的。
person2.getName();
|
我们来一步步分析原因,
new Person(‘kevin’)后发生的事:
1.创建一个新对象
2.把对象的原型指向Person.prototype
3.执行 Person.apply(obj)
4.返回对象
其中第3步,Person.apply(obj),
将会执行obj.Person
由于首次时obj.getName不是function,于是执行if语句
此时obj.prototype存的是Person.prototype的地址指针x(第2步的执行结果)
把Person.prototype重新赋值意味着Person.prototype重新赋值了一个新对象的地址y
动态原型模式为什么不能直接赋值–示例demo:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
let obj = {
name: "obj"
};
let per = obj; //这里per存的是对象obj的地址
//obj重新赋值为一个新对象的地址
obj = {
age: 20
};
// obj { age:20 } //新对象的地址
// per { name:"obj" } //还是指向旧obj的地址
|
结果:
obj.prototype 还是指向旧原型的地址,没有getName
Person.prototype 指向新的对象,有getName方法
// 报错 person1的原型指向旧原型,并没有getName方法
person1.getName();
// person2的原型指向新原型对象,有getName方法
person2.getName();
那可以修改代码为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function Person(name) {
this.name = name;
if (typeof this.getName != "function") {
Person.prototype = {
constructor: Person,
getName: function() {
console.log(this.name)
}
}
}
//用最新的Person.property再次生成对象返回,构造函数如果返回到是对象,直接返回这个对象
return new Person(name);
}
|
5.1 寄生构造函数模式
先看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function Person(name) {
var o = new object();
o.name = name;
o.getName = function() {
console.log(this.name)
};
return o; //构造函数如果返回到是对象,直接返回这个对象,o是object的一个实例和Person类没有关系,https://www.cnblogs.com/kenanyah/p/13246934.html
}
var person1 = new Person("Neo")
console.log(person1 instanceOf Person) // false 实例无法指向构造函数
console.log(Person1 instanceOf Object) //true
|
创建出来的实例对象无法指向构造函数,只是寄生在构造函数里的工厂模式,和工厂模式不同的是它可以调用new来创建实例
5.2 稳妥构造函数模式
1
2
3
4
5
6
7
8
9
10
11
12
13
|
function person(name) {
var o = new Object();
o.getName = function() {
console.log(name);
}
return o;
}
var person1 = person("Neo")
person1.getName(); //Neo
person1.name = "Llane";
person1.getName(); //Neo
console.log(person1.name); //Llane
|
所谓稳妥对象,指的是没有公共属性,其方法也不引用this的对象
与寄生构造函数模式有两点不同:
1.新创建的实例方法不引用this
2.不使用new操作符调用构造函数
稳妥对象适合在一些安全的环境中,
稳妥构造函数和工厂模式一样,无法识别实例对象所属类型