Please enable Javascript to view the contents

从头学习js-14-创建对象

 ·  ☕ 5 分钟

这个系列是我读冴羽老师博客的感悟,
加入了个人的解读和练习题的解答

本文会介绍不同创建对象的方法,来探究不同解决方案的优缺点

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操作符调用构造函数

稳妥对象适合在一些安全的环境中,
稳妥构造函数和工厂模式一样,无法识别实例对象所属类型

分享

Llane00
作者
Llane00
Web Developer