😁深入JS(三): 一文让你完全了解原型,继承

1、原型

1.1 、原型的作用

为实现对象之间共享属性方法,允许对象通过原型访问另一个对象的属性和方法

js 复制代码
function fn() {
  this.name = "tom";
}
fn.prototype = { age: 11 };
let obj = new fn();
console.log(obj.age);//11
js 复制代码
function fn() {
  this.name = "tom";
}
// fn.prototype = { age: 11 };
let obj = new fn();
obj.__proto__ = { age: 11 };
console.log(obj.age);
  • 可以发现fn.prototypeobj.__proto__是同一个对象

1.2、对象原型与函数原型

1.2.1、对象原型

  • 对象中存在一个原型属性[[Prototype]]
  • 早期是无法访问这个[[Prototype]]
  • 后续一些浏览器实现了非标准的 __proto__ 访问器
  • 在 2015 年,Object.setPrototypeOfObject.getPrototypeOf 被加入到标准中

1.2.2、函数原型

  • 函数(构造函数)中存在一个原型属性prototype

  • 并且函数是一种特殊的对象,因此函数也具有原型属性[[Prototype]]

  • prototype中的属性直接共享给实例

1.2、对象的原型怎么来的?

F.prototype 是一个对象, new 操作符会使用它为新对象设置 [[Prototype]]

  • 可以简单在了解下new的实现
js 复制代码
function _new(fn, ...args) {
  let obj = {};
  obj.__proto__ = fn.prototype;
  fn.apply(obj, args);
  return obj;
}

1.3、原型链

当访问一个对象的属性或方法时,JavaScript 首先会在该对象本身查找,如果找不到,就会顺着 [[Prototype]] 所指的原型对象继续查找,以此类推,直到找到该属性或方法,或者到达原型链的末尾(即 [[Prototype]]null)。这种通过 [[Prototype]] 连接起来的层次结构就是原型链

js 复制代码
function F(){}
const f = new F()
console.log(F.__proto__)
console.log(F.prototype.__proto__)
console.log(f.__proto__)
console.log(Function.prototype.__proto__)
console.log(Object.prototype.__proto__)

2、继承

了解原型与原型链的最终目的,就是为了实现继承

2.1、原型链继承

js 复制代码
// 父类构造函数
function Father() {
  this.name = "father";
}
Father.prototype = { a: 1 };
// 子类构造函数
function Son() {
  this.age = 16;
}
// 子类的prototype指向父类的实例
Son.prototype = new Father();
var son = new Son();
console.log(son.name); // father
console.log(son.age); // 16
console.log(son.a); //1

优点

  • 可以继承父类的私有属性和原型属性。

缺点

  • 无法向父类构造函数传参。

2.2、盗用构造函数

js 复制代码
function parent(name) {
  this.name = name;
  this.age = 12;
}
parent.prototype = { a: 1 };
function child(name) {
  parent.call(this, name);
}
console.log(new child("tom")); 
console.log(new child('tonm').a)//undefined

优点

  • 在子类中可以给父类传参。
  • 可以继承父类的私有属性。

缺点

  • 不能继承父类的原型

2.3、原型链+盗用构造函数

js 复制代码
function parent(name) {
  this.name = name;
  this.age = 12;
}

parent.prototype = { a: 1 };

function child(name) {
  parent.call(this, name);
}
child.prototype = new parent();
console.log(new child("tom"));
console.log(new child("tom").a);

缺点

  • 父类构造函数被调用两次
  • 数据的冗余:数据即存在于私有属性,又存在prototype中

2.4、create继承

js 复制代码
let parent = {
  name: "parent",
  friends: ["p1", "p2", "p3"],
  getName: function () {
    return this.name;
  },
};
let personA = Object.create(parent);
personA.name = "tom";
personA.friends.push("jerry");

let personB = Object.create(parent);
personB.friends.push("lucy");

console.log(personA.name); // tom
console.log(personA.name === personA.getName()); // true
console.log(personB.name); // parent
console.log(personA.friends); // ["p1", "p2", "p3","jerry","lucy"]
console.log(personB.friends); // ["p1", "p2", "p3","jerry","lucy"]

缺点

  • 原型为浅拷贝

2.5、盗用构造函数+create

js 复制代码
function inheritPrototype(child, parent) {
  let prototype = Object.create(parent.prototype); // 创建对象
  child.prototype = prototype; // 赋值对象
}//通过这个函数,将父元素的Prototype移到子元素的Prototype上

function Parent(name) {
  this.name = name;
  this.friends = ["rose", "lily", "tom"];
}
Parent.prototype.sayName = function () {
  console.log(this.name);
};
function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}
inheritPrototype(Child, Parent);
Child.prototype.sayAge = function () {
  console.log(this.age);
};
let child1 = new Child("yhd", 23);
child1.sayAge(); // 23
child1.sayName(); // yhd
child1.friends.push("jack");
console.log(child1.friends); // ["rose", "lily", "tom", "jack"]

let child2 = new Child("yl", 22);
child2.sayAge(); // 22
child2.sayName(); // yl
console.log(child2.friends); // ["rose", "lily", "tom"]
  1. 子类继承父类的私有属性和原型属性。
  2. 子类可以向父类传递参数。
  3. 继承后没有冗余属性。

2.5、extends(最完美的继承)

js 复制代码
class Person {
  constructor(name) {
    this.name = name
  }
  // 原型方法
  // 即 Person.prototype.getName = function() { }
  // 下面可以简写为 getName() {...}
  getName = function () {
    console.log('Person:', this.name)
  }
}
class Gamer extends Person {
  constructor(name, age) {
    // 子类中存在构造函数,则需要在使用"this"之前首先调用 super()。
    super(name)
    this.age = age
  }
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法

3、class

3.1、function与class区别

js 复制代码
// 用纯函数重写 class User
// 1. 创建构造器函数
function User(name) {
  this.name = name;
}
// 函数的原型(prototype)默认具有 "constructor" 属性,
// 所以,我们不需要创建它
// 2. 将方法添加到原型
User.prototype.sayHi = function() {
  alert(this.name);
};
// 用法:
let user = new User("John");
user.sayHi();
js 复制代码
class User {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    alert(this.name);
  }
}
// 用法:
let user = new User("John");
user.sayHi();
相关推荐
ygming1 分钟前
Hashmap/ Hashset- Q39~Q42内容
前端·算法
多啦C梦a2 分钟前
【前端必修】闭包、`this`、`箭头函数`、`bind`、节流,一篇文章全懂!
前端·javascript·html
归于尽2 分钟前
为什么别人用闭包那么溜?这 8 个场景照着用就对了
前端·javascript·面试
Hilaku4 分钟前
Vue 2与Vue 3响应式原理的对比与实现
前端·javascript·vue.js
自出洞来无敌手(曾令瑶)9 分钟前
浏览器 实时监听音量 实时语音识别 vue js
前端·javascript·vue.js·语音识别
在钱塘江26 分钟前
《你不知道的JavaScript-上卷》-笔记-5-作用域闭包
前端
搬砖码26 分钟前
Vue病历写回功能:实现多输入框内容插入与焦点管理🚀
前端
不简说31 分钟前
史诗级更新!sv-print虽然不是很强,但却是很能打的设计器组件
前端·产品
用户952511514015532 分钟前
最常用的JS加解密场景MD5
前端
Hilaku32 分钟前
“虚拟DOM”到底是什么?我们用300行代码来实现一个
前端·javascript·vue.js