前言

  • 原型、原型链、类与承继好像无时无刻的呈现在咱们身边,不管你是在面试中亦或是往常学习和工作中都有它的身影。那么这个是又是什么东西呢 ? 我曾经过 avaScript高档程序设计你不知道的JavaScript、MDN文档以及教学视频。但好像仍是半知半解,但我依然信任能经过这篇文章能让咱们以及我搞懂这玩意究竟是什么神仙。js版的哈希表完成

什么是目标?

  • 在ECMAScript中,目标是一组特点无序的集合,目标的每个特点或办法都有一个称号来标识,这个称号映射到一个值,你能够将其想像成一张散列表。

散列表(Hash table,也叫哈希表),是根据键(Key)而直接拜访在内存贮存方位的数据结构。也便是说,它经过计算出一个键值的函数,将所需查询的数据映射到表中一个方位来让人拜访,这加快了查找速度。这个映射函数称做散列函数,寄存记载的数组称做散列表。

创立目标

  • 创立自界说目标
// 创立一个Object的新实例,然后在实例上面添加特点和办法
const person = new Object();
person.name = "moment";
person.age = 18;
person.running = function () {
  console.log("我会游水哦,你会吗");
};
  • 目标字面量
const person = {
  running: function () {
    console.log("你会我也会啊");
  },
  name: "moment",
  age: 7,
  dance: function () {
    console.log("我还会跳舞呢");
  },
};
  • 两个实例中,每创立一个目标,都要为其创立一个name,age的特点以及running的办法,那么有没有什么办法能够让咱们能够减少一些重复的代码呢,答案是有的。咱们能够把办法抽取出来界说成一个函数,再给目标赋值。可是……
const running = (info) => {
  console.log(info);
};
const person = new Object();
person.name = "moment";
person.age = 18;
person.running = running;
const object = {
  running,
  name: "moment",
  age: 7,
  dance: function () {
    console.log("我还会跳舞呢");
  },
};
// ,好像只能封装公共的办法,特点无法动态传值,只能是固定的一个值
person.running("咱们咱们都会游水哦"); // 咱们咱们都会游水哦
object.running("咱们咱们都会游水哦"); // 咱们咱们都会游水哦

工厂函数

抽象工厂形式(英语:Abstract factory pattern)是一种软件开发设计形式。抽象工厂形式提供了一种方式,能够将一组具有同一主题的独自的工厂封装起来。在正常运用中,客户端程序需要创立抽象工厂的详细完成,然后运用抽象工厂作为接口来创立这一主题的详细目标。客户端程序不需要知道(或关心)它从这些内部的工厂办法中获得目标的详细类型,因为客户端程序仅运用这些目标的通用接口。抽象工厂形式将一组目标的完成细节与他们的一般运用分离开来。

  • 按我个人的理解,工厂形式就像是一个模具,你能根据这个模具去出产产品,你只能改动其外观色彩,可是你不能改动其形状和大小。
function createObject(name, age, info) {
  const obj = new Object();
  obj.name = name;
  obj.age = age;
  obj.running = function () {
    console.log(info);
  };
  return obj;
}
const person = createObject("moment", 18, "我会跑步");
const student = createObject("supper", 16, "我会跑步,我比你还年青呢");
person.running(); // 我会跑步
student.running(); // 我会跑步,我比你还年青呢
  • 经过工厂形式,咱们能够快速创立很多相似目标,没有重复代码。迎面走来走来的又是一个新问题,工厂形式创立的目标归于Object,无法区别目标类型,这也是工厂形式没有广泛运用的原因。

结构函数形式

  • 没有显式地创立目标
  • 特点和办法直接赋值给了this
  • 没有return
function Person(name, age, info) {
  this.name = name;
  this.age = age;
  this.running = function () {
    console.log(info);
  };
}
const person = new Person("moment", 18, "我会跑步");
const student = new Person("supper", 16, "我会跑步,我比你还年青呢");
person.running(); // 我会跑步
student.running(); // 我会跑步,我比你还年青呢
  • 以上代码实际上履行的是这样的操作
person.name = "moment";
person.age = 18;
person.running = function () {
  console.log("我会跑步");
};
student.name = "moment";
student.age = 16;
student.running = function () {
  console.log("我会跑步,我比你还年青呢");
};
  • 要创立Person实例,应运用new操作符。
  1. 在内存创立一个新目标。
  2. 这个新目标内部的[[Prototype]] (proto)特性被赋值为结构函数的prototype特点, 即 person.proto=Person.prototype
  3. 结构函数内部的this被赋值为这个新目标(即this指向新目标)。
  4. 履行结构函数内部的代码. 如 person.proto.name=’moment’
  5. 假如结构函数回来非空目标,则回来该目标;不然,回来刚创立的新目标。
  • new的进程能够参阅以下代码
function myNew(func, ...args) {
  // 判断办法体
  if (typeof func !== "function") {
    throw "第一个参数有必要是办法体";
  }
  // 创立新目标
  // 这个目标的[[prototype]](隐式原型 __proto__)指向 func 这个类的原型目标 prototype
  // 即实例能够拜访结构函数原型 obj.constructor === Person
  const object = Object.create(func.prototype);
  // 结构函数内部的this被赋值为这个新目标
  const result = func.apply(object, args);
  // 假如结构函数回来的结果是引证数据类型,则回来运转后的结果
  // 不然回来新创立的 obj
  const isObject = typeof result === "object" && result !== null;
  const isFunction = typeof result === "function";
  return isObject || isFunction ? result : object;
}
  • 经过控制台打印不难发现,前面两个目标都是Object的实例,同时也是Person的实例
// instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上
// person和student的隐式原型都指向Person的显式原型;
console.log(person.__proto__ === student.__proto__); //true
console.log(person instanceof Object); //true
console.log(person instanceof Person); //true
console.log(student instanceof Object); //true
console.log(student instanceof Person); //true
// 以上代码的代码实际上履行的该比如
console.log(student.__proto__.__proto__.constructor.prototype === Object.prototype);
console.log(student.__proto__.constructor.prototype === Person.prototype);
  • 结构办法尽管有用,可是也存在问题,尽管person和student都有一个办法running,但这两个办法不是同一个Function的实例,指向的内存也各自不同。
console.log(student.running === person.running); //false
  • 都是做着相同的事情,可是每个实例都创立出一个办法,这就造成了没必要的内存损耗,那么有没有什么办法能够很好的处理这个问题呢,这时候,原型形式就呈现了。

原型形式

  • 每个函数都会创立一个prototype特点,这个特点是一个目标,该目标能够给每个实例同享特点和办法。
  • 咱们对之前的代码进行改造。
function Person(name, age) {
  this.name = name;
  this.age = age;
}
// 在这儿,把办法running界说在了Person的prototype特点上
// person和student同享同一个原型办法running,指向的是同一快内存空间
Person.prototype.running = function (info) {
  console.log(info);
};
const person = new Person("moment", 18);
const student = new Person("supper", 16);
// 在结构函数中这儿输出的是false
console.log(person.running === student.running); //true
person.running("我会跑步"); // 我会跑步
student.running("我会跑步,我比你还年青呢"); //我会跑步,我比你还年青呢
  • 在前面的new操作符能够知道,personstudent的隐式原型等于Perosn的显式原型
  1. 首先person和student现在自己身上查找有没有running办法,没有找到。
  2. 去原型里查找,也便是经过person.__proto__或许student.proto,该办法,因为person.proto=Person.prototype,所以调用person.running()实际上调用的是Person.prototype.running()办法

目标、原型、原型链与承继?这次我懂了!

  • 详细的内存表现形式如下图所示。

目标、原型、原型链与承继?这次我懂了!

  • 经过上图,一下代码的输出就能理解了。
console.log(Person.prototype.constructor === Person); // true
console.log(student.__proto__.constructor === Person); // true

原型层级

  • 在讲之前先祭出两张神图

目标、原型、原型链与承继?这次我懂了!

目标、原型、原型链与承继?这次我懂了!

  • 在讲原型之前,咱们先来认识一下认识一下FunctionObject的联系!
  1. JavaScript中,每个JavaScript函数实际上都是一个Function目标。
  2. JavaScript 中,简直一切的目标都是Object类型的实例,它们都会从Object.prototype承继特点和办法。
  3. 从原型链上讲,Function承继了Object。
  4. 从结构器上讲,Function结构了Object。
  5. 好了,看到这儿,晕了没?
  6. 接下来经过代码的展现,应该能更清楚的说明了上面两张图中的所讲的意思了。
function Person(name, age) {
  this.name = name;
  this.age = age;
}
// 在这儿,把办法running界说在了Person的prototype特点上了
Person.prototype.running = function (info) {
  console.log(info);
};
const obj = {};
const person = new Person("moment", 18);
const student = new Person("supper", 16);
console.log(obj.__proto__ === Object.prototype); // true
console.log(Object.constructor === Function); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
console.log(Function.constructor === Function); // true
console.log(Person.__proto__.constructor.__proto__ === Function.prototype); // true
console.log(student.__proto__.__proto__.constructor.__proto__ === Function.prototype); // true
console.log(Function.constructor.__proto__ === Function.prototype); // true
console.log(Object.constructor === Function.constructor); // true
console.log(Object instanceof Function); // true
console.log(Function instanceof Object); // true
console.log(Function.prototype.__proto__ === Object.prototype); // true
  • 原型查找机制,首先在实例上查找,有则回来,没有则往上查找,这时候查找到了原型上了,假如能找到有该特点或办法,则回来,假如依然没找打,就在Object的原型上查找,找到了则回来,没有就回来undefined或许报错。
function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayName = function () {
    console.log("我是在实例身上的");
  };
  this.memory = "我是归于实例的";
}
// 在这儿,把办法running界说在了Person的prototype特点上了
Person.prototype.running = function () {
  console.log("我是原型上的办法");
};
Object.prototype.牛逼 = "这是真的";
Person.prototype.memory = "我是归于原型的";
const person = new Person("moment", 18);
console.log(person.name); // 来自实例
console.log(person.memory); // 来自实例
person.sayName(); // 来自实例
person.running(); // 来自原型
console.log(person.牛逼); // 这是真的
console.log(person.六六六); // undefined

原型形式的弊端

  • 在承继之前,咱们每个类都要给其界说归于它自己的特点和办法,可是假如呈现相同的办法,咱们都要给他重新界说一遍,这样未免呈现过多重复代码。
function Student() {}
function Teacher() {}
Student.prototype.running = function () {
  console.log("学生");
};
Student.prototype.eating = function () {
  console.log("吃");
};
Student.prototype.study = function () {
  console.log("学习");
};
Teacher.prototype.running = function () {
  console.log("教师
};
Teacher.prototype.teach = function () {
  console.log("");
};

原型链承继

  • 父类的原型直接赋值给子类的原型
  1. 父类和子类同享同一个原型目标,修改了任意一个,另外一个也被修改。
  2. 这是一个错误的做法。
function Student() {}
function Teacher() {}
Teacher.prototype.running = function () {
  console.log("教师");
};
Teacher.prototype.teach = function () {
  console.log("吃");
};
Student.prototype = Teacher.prototype;
const student = new Student();
student.running(); // 教师
student.__proto__.running = function () {
  console.log("我被修改了");
};
const teacher = new Teacher();
teacher.running();// 我被修改了
  • 正确的原型链承继
function Student() {}
function Teacher() {}
Teacher.prototype.running = function () {
  console.log("教师");
};
Teacher.prototype.teach = function () {
  console.log("教");
};
const teach = new Teacher();
Student.prototype = teach;
const student = new Student();
student.running = function () {
  console.log("我被修改了");
};
const smallStudent = new Student();
smallStudent.running(); // 教师 承继于Teacher原型
student.running(); // 我被修改了  来自于实例自身
  • 经过一张图来展现子类的两个实例和两个结构函数及其对应的原型之间的联系

目标、原型、原型链与承继?这次我懂了!

  • 原型链承继的问题
  1. 在运用原型完成承继时,原型实际上变成了另一个类型的实例。这意味着原先的实例特点和办法摇身一变成了原型特点和办法。
  2. 子类型在实例化时不能给父类型的结构函数传参。

盗用结构函数

  • 经过call或许apply改动this指向
function Teacher(nickname, age, height) {
  this.nickname = nickname;
  this.age = age;
  this.height = height;
}
function Student(nickname, age, height) {
  Teacher.call(this, nickname, age, height);
  this.hobby = ["唱", "跳", "rap"];
}
Teacher.prototype.running = function () {
  console.log("教师");
};
Teacher.prototype.teach = function () {
  console.log("教");
};
const student = new Student("moment", "18", "1米59");
console.log(student.height); // 1米59
console.log(student.hobby); //  ["唱", "跳", "rap"]
  • 借用结构函数存在问题
  1. 有必要在结构函数中界说办法,因而函数不能重用。
  2. 子类也不能拜访父类原型上界说的办法。

组合承继

  • 归纳了原型链和盗用结构函数,将两者的长处集中了起来。
function Teacher(nickname, age, height) {
  this.nickname = nickname;
  this.age = age;
  this.height = height;
}
function Student(nickname, age, height) {
  Teacher.call(this, nickname, age, height);
  this.hobby = ["唱", "跳", "rap"];
}
Teacher.prototype.running = function () {
  console.log("教师");
};
Teacher.prototype.teach = function () {
  console.log("教");
};
Student.prototype = new Teacher();
const student = new Student("moment", "18", "1米59");
console.log(student.height); // 1米59
console.log(student.hobby); //  ["唱", "跳", "rap"]
  • 组合承继存在的问题 1.父类结构函数至少被调用两次。

    引证类型承继终极处理方案 寄生式组合承继

function inheritPrototype(superType, children) {
  const prototype = Object(superType.prototype); // 创立目标
  prototype.constructor = children; // 增强目标
  children.prototype = prototype; // 赋值目标
}
function Teacher(nickname, age, height) {
  this.nickname = nickname;
}
function Student(nickname) {
  Teacher.call(this, nickname);
  this.hobby = ["唱", "跳", "rap"];
}
inheritPrototype(Student, Teacher);
Teacher.prototype.running = function () {
  console.log("教师会跑步");
};
Student.prototype.running = function () {
  console.log("学生也会跑步");
};
const student = new Student("moment");
student.running(); // 学生也会跑步
console.log(student.hobby); // ['唱', '跳', 'rap']
console.log(student.nickname); // comment
  • 这儿只调用了一次Teacher结构函数,避免了Student.prototype上不必要也用不到的特点。
  • 原型链依然坚持不变。

结束

  • 本来会打算在这篇文章中连着class一起写的了,内容太多了,那就分成两篇吧。

  • 已然都看到这儿了,还不点个关注吗?信任我,会给到你不一样的惊喜!