从工厂模式到原型链

137 阅读5分钟

一、工厂模式

缺点:不知道新创建的对象是什么类型

function createPerson(name, age, job) {
     // this 是 window
  let o = new Object();
  o.name = name;
  o.age = age;
  o.job = job;
  o.sayName = function() {
    console.log(this.name);
  };
  return o;
}
 console.log(createPerson('tina', 18, 'engineer'));
//{name: 'tina', age: 18, job: 'engineer', sayName: ƒ}

二、构造函数模式

function Person(name, age, job){
   this.name = name;
   this.age = age;
   this.job = job;
   this.sayName = function() {
   console.log(this.name);
   };
}
  const p1 = new Person('tina', 18, 'engineer');
 //Person {name: 'tina', age: 18, job: 'engineer', sayName: ƒ}

虽然内部代码一样,但是有如下区别

没有显式地创建对象。

属性和方法直接赋值给了this

没有return 。

注意: 构造函数名称 的首字母都是要大写的,非构造函数则以小写字母开头

调用构造函数会执行如下操作

(1) 在内存中创建一个新对象。

(2) 这个新对象内部的[[Prototype]] 特性被赋值为构造函数的 prototype 属性。

(3) 构造函数内部的this 被赋值为这个新对象(即this 指向新对象)。

(4) 执行构造函数内部的代码(给新对象添加属性)。

(5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象。


2.1 构造函数流程图

2.2 原型对象

验证图中的两条红线

原型对象: 本身就是一个对象,作用是为其他对象提供共享属性

  1. [[prototype]]隐式原型属性

当构造函数创建一个对象后,该对象隐式引用构造函数的 prototype 属性被赋值为构造函数的 prototype 属性

JavaScript中任意对象都有一个内置属性[[prototype]]通过__proto__或者Object.getPrototypeOf()来访问

Object.getPrototypeOf() 方法返回指定对象的原型(内部[[Prototype]]属性的值)。

console.log(p1.__proto__===Object.getPrototypeOf(p1)); //true
  1. prototype(显式原型属性)

每一个函数都会拥有一个名为prototype的属性,指向原型对象,而构造函数的prototype就是原型对象,所以如下

console.log(Person.prototype===p1.__proto__); //true

这两个对象都有一个constructor 属性指向构造函数Person

 person1.__proto__.constructor ===Person  //true
person1.constructor == Person  //true
console.log(person2.constructor == Person); // true

// 构造函数的原型对象的构造器 就等于自身
Person.prototype.constructor === Person
// true

2.3 原型链与查找机制

可理解为一条链条,连接实例对象和原型对象,每个函数都有一个prototype属性指向原型对象。如图 p1.__proto__指向它构造函数的原型对象,而构造函数的原型对象也是对象所以p1.proto.__proto__指向了Object.prototype,原型链的尽头是null所以最后打印null

所谓查找机制就是对象中没有的属性或者方法会到原型链上去查找,顺着原型链直到Object,如果找到立即返回,如果对象中的属性与原型对象的属性重名,则直接返回本对象的属性不去查找原型链

// p1.proto -->Person-->Object -->null

  1. 比如Array.prototype.filter()

 数组构造函数所创建的实例可以直接调用,原因是因为会去查找原型链

instanceof 运算符 检查实例的原型链中是否包含指定构造函数的原型:

console.log(person1 instanceof Object); // true
console.log(person1 instanceof Person); // true
console.log(person2 instanceof Object); // true
console.log(person2 instanceof Person); // true

在实例化时,如果不想传参数,那么构造函数后面的括号可加可不加。只 要有new 操作符,就可以调用相应的构造函数:

function Person() {
 this.name = "Jake";
 this.sayName = function() {
 console.log(this.name);
 };
}
let person1 = new Person();
let person2 = new Person;

2.4 构造函数的问题

构造函数的主要问题在于, 其定义的方法会在每个实例上都创建一遍。 但是它们的功能都是一样的,如果我需要new100个这样的对象,就会创建100个sayName方法,而且地址都不一样,这无疑是对空间的浪费。

console.log(person1.sayName == person2.sayName); // false

这个问题可以通过原型模式来解决


三、原型模式

每个函数都会创建一个prototype 属性,这个属性是一个对象,包含应 该由特定引用类型的实例共享的属性和方法。 使用原型对象的好处是,在它上面定义 的属性和方法可以被对象实例共享,也就是我上面所说的原型链查找机制。

3.1理解原型

摘自红宝书

无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建 一个prototype 属性(指向原型对象)。默认情况下,所有原型对 象自动获得一个名为constructor 的属性,指回与之关联的构造函 数。对前面的例子而言,Person.prototype.constructor 指向 Person 。然后,因构造函数而异,可能会给原型对象添加其他属性 和方法。

在自定义构造函数时,原型对象默认只会获得constructor 属性, 其他的所有方法都继承自Object 。每次调用构造函数创建一个新实 例,这个实例的内部[[Prototype]] 指针就会被赋值为构造函数的 原型对象。脚本中没有访问这个[[Prototype]] 特性的标准方式, 但Firefox、Safari和Chrome会在每个对象上暴露__proto__ 属性, 通过这个属性可以访问对象的原型。在其他实现中,这个特性完全被 隐藏了。关键在于理解这一点:实例与构造函数原型之间有直接的联 系,但实例与构造函数之间没有。

function Person(name, age, job) {
            this.name = name;
            this.age = age;
            this.job = job;
        }
        Person.prototype.sayName = function () {
                console.log(this.name);
            };
        const p1 = new Person('tina', 18, 'engineer');
        const p2 = new Person('sulin',19,'dancer');
        console.log(p1.sayName === p2.sayName); //true

结论

  1. 每个实例对象都有隐式原型属性,即__proto__,值为对应的构造函数的显式原型的值
  2. 实例通过__proto__链接到原型对象, 构造函数通过prototype属性链接到原型对象
  3. 如果成员没有就会找原型对象如果还没则往上找,直到Object为止
  4. 构造函数的原型对象的构造器 就等于自身
  5. 构造函数、原型对象和实例是3个完全不同的对象:
console.log(person1 !== Person); // true
console.log(person1 !== Person.prototype); // true
console.log(Person.prototype !== Person); // true

若有写得不恰当的地方,还请指教!!!谢谢你的观看,Bye!