首页/文章/javascript

JavaScript 原型与原型链,继承

2024-07-09
20427 分钟
...

github.com/CongYao1993…

1. 原型

  • prototype:每个构造函数都有一个原型对象 prototype
    • prototype 属性是一个指针,指向一个对象,该对象包含所有实例共享的属性和方法。
  • constructor:原型对象都包含一个指向构造函数的指针 constructor
  • __proto__:当调用构造函数创建一个新实例,该实例的内部将包含一个 __proto__ 属性,指向构造函数的原型对象
    • 所有的引用类型(数组、对象、函数)都有一个 __proto__ 属性

2. 使用构造函数创建对象

创建自定义类型最常见的方式,就是组合使用构造函数模式与原型模式。

  • 构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。
  • 每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.friends = ["Bob"];
}


Person.prototype = {
  constructor: Person,
  sayName: function () {
    alert(this.name);
  },
};


Person.statciFunc = function () {
  console.log("我是静态方法,与实例无关");
};

var person1 = new Person("Tom", 29);
var person2 = new Person("Mary", 32);

person1.friends.push("Van");
alert(person1.friends); 
alert(person2.friends); 
alert(person1.friends === person2.friends); 
alert(person1.sayName === person2.sayName); 

3. new 的实现原理

MDN new

new 操作符用于创建对象的实例。

new 关键字会进行如下的操作:

  1. 创建一个空的 JavaScript 对象(即{});
  2. 为空对象添加属性 __proto__,将该属性指向构造函数的原型对象;
  3. 将空对象作为 this 的上下文;
  4. 如果构造函数的返回值为对象,则返回构造函数的返回值,否则返回新对象。

如果你没有使用 new 运算符,构造函数会像其他的常规函数一样被调用,并不会创建一个对象。在这种情况下,this 的指向也是不一样的。

function myNew(func, ...args) {
  
  const obj = {};

  
  obj.__proto__ = func.prototype;

  
  const result = func.call(obj, ...args);

  
  const isObject = typeof result === "object" && result !== null;
  const isFunction = typeof result === "function";
  if (isObject || isFunction) {
    return result;
  }

  
  return obj;
}
function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.sayHello = function () {
  console.log(this.name);
};

const xiaoming1 = new Person("xiaoming", 12);
console.log(xiaoming1);

const xiaoming2 = myNew(Person, "xiaoming", 12);
console.log(xiaoming2);

4. 原型链

实例对象都有 __proto__ 属性,指向它的原型对象;原型对象也是对象,也有 __proto__ 属性,指向原型对象的原型对象,直到最顶层对象 Object 的原型 Object.prototype.__proto__===null,这样一层一层形成的链式结构,称为原型链。

当获取一个对象的某个属性时,如果这个对象本身没有这个属性,就会沿着原型链往上查找,直到 Object.prototype。

原型与实例的关系可以通过两种方式来确定:

  1. instanceof 操作符:如果一个实例的原型链中出现过相应的构造函数,则 instanceof 返回 true;
  2. isPrototypeOf() 方法:用于检查一个原型对象是否存在于另一个对象的原型链中,只要原型链中包含这个原型对象,这个方法就返回 true。
function Person(name) {
  this.name = name;
}
const person = new Person("yc");
console.log(person instanceof Person); 
console.log(person instanceof Object); 
console.log(Person.prototype.isPrototypeOf(person)); 
console.log(Object.prototype.isPrototypeOf(person)); 

5. 继承

继承,即让一个引用类型继承另一个引用类型的属性和方法。

原型链继承和构造函数继承了解即可,主要是组合继承和寄生式组合继承。

5.1 原型链继承

  • 优点:无
  • 缺点:
    • 创建子类实例时,不能向父类的构造函数中传递参数
    • 父类中所有引用类型的属性会被所有子类实例共享,也就说一个子类实例修改了父类中的某个引用类型的属性时,其他子类实例也会受到影响
function Parent() {
  this.name = "parent";
  this.hobby = ["sing", "rap"];
}

function Child() {
  this.type = "child";
}
Child.prototype = new Parent();

let child1 = new Child();
let child2 = new Child();
child1.name = "Bob";
console.log(child1.name); 
console.log(child2.name); 
child1.hobby.push("basketball");
console.log(child1.hobby); 
console.log(child2.hobby); 

5.2 构造函数继承(经典继承)

  • 优点:
    • 在子类构造函数中可以向父类的构造函数中传递参数
    • 避免了父类中的引用类型属性在子类中共享的问题
  • 缺点:父类原型对象上的方法子类继承不到
function Parent(age) {
  this.name = "parent";
  this.age = age;
  this.hobby = ["sing", "rap"];
}
Parent.prototype.sayHi = function () {
  console.log("Hi");
};

function Child(age) {
  Parent.call(this, age);
  this.type = "child";
}

let child1 = new Child(15);
let child2 = new Child(15);
child1.hobby.push("basketball");
console.log(child1.name); 
console.log(child1.age); 
console.log(child1.type); 
console.log(child1.hobby); 
console.log(child2.hobby); 
child1.sayHi(); 

5.3 组合继承(原型链继承+构造函数继承)

使用最多的继承模式是组合继承,使用原型链继承共享的属性和方法,而通过借用构造函数继承实例属性。既通过在原型上定义方法实现了函数复用,又能够保证每个实例都有它自己的属性。

  • 优点:
    • 在子类构造函数中可以向父类的构造函数中传递参数
    • 避免了父类中的引用类型属性在子类中共享的问题
    • 父类原型对象上的方法子类也可以继承到
  • 缺点:父类构造函数被调用了两次

SubType.prototype 上会有两个属性:name 和 colors。它们都是 SuperType 的实例属性,但现在成为了 SubType 的原型属性。在调用 SubType 构造函数时,也会调用 SuperType 构造函数,这一次会在新对象上创建实例属性 name 和 colors。这两个实例属性会遮蔽原型上同名的属性。

function SuperType(name) {
  this.name = name;
  this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
  console.log(this.name);
};

function SubType(name, age) {
  
  SuperType.call(this, name); 
  
  this.age = age;
}


SubType.prototype = new SuperType(); 


SubType.prototype.constructor = SubType;


SubType.prototype.sayAge = function () {
  console.log(this.age);
};

将子类的原型指向父类的实例,子类原型不仅拥有父类实例所拥有的全部属性和方法,其内部还有 __proto__ 指针指向父类的原型。

function Parent(age) {
  this.name = "parent";
  this.age = age;
  this.hobby = ["sing", "rap"];
}
Parent.prototype.sayHi = function () {
  console.log("Hi");
};

function Child(age) {
  Parent.call(this, age); 
  this.type = "child";
}
Child.prototype = new Parent(); 
Child.prototype.constructor = Child;

let child1 = new Child(15);
let child2 = new Child(15);
child1.hobby.push("basketball");
console.log(child1.name); 
console.log(child1.age); 
console.log(child1.type); 
console.log(child1.hobby); 
console.log(child2.hobby); 
child1.sayHi(); 

5.4 寄生式组合继承(实现继承最有效的方式!)

  • 优点:实现继承最有效的方式!
  • 缺点:无
function Parent(age) {
  this.name = "parent";
  this.age = age;
  this.hobby = ["sing", "rap"];
}
Parent.prototype.sayHi = function () {
  console.log("Hi");
};

function Child(age) {
  Parent.call(this, age);
  this.type = "child";
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

let child1 = new Child(15);
let child2 = new Child(15);
child1.hobby.push("basketball");
console.log(child1.name); 
console.log(child1.age); 
console.log(child1.type); 
console.log(child1.hobby); 
console.log(child2.hobby); 
child1.sayHi(); 

Object.create() 方法用于创建一个新对象,使用参数对象来作为新创建对象的原型(prototype)。原理如下:

function object(o) {
  function F() {}
  F.prototype = o;
  return new F();
}

6. 类 class

6.1 class 的基本用法

constructor() 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法,该方法默认返回实例对象 this。

实例属性现在除了可以定义在 constructor()方法里面的 this 上面,也可以定义在类内部的最顶层。

类的属性和方法,除非显式定义在 this 上,否则都是定义在原型上。

class Point {
  count = 0; 

  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {}

  toValue() {}

  static staticProp = 1; 

  static staticMethod() {
    
  }

  #privateProp = 0; 

  #privateMethod() {
    
  }
}

6.2 class 的继承

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y); 
    this.color = color;
  }

  toString() {
    return this.color + " " + super.toString(); 
  }
}

7. instanceof 的实现原理

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

function myInstanceof(obj, className) {
  
  if (!obj || !className || !obj.__proto__ || !className.prototype) {
    return false;
  }

  let curProto = obj.__proto__;

  while (curProto) {
    
    if (curProto === className.prototype) {
      return true;
    }
    curProto = curProto.__proto__;
  }

  return false;
}
function SubType(name, age) {
  this.name = name;
  this.age = age;
}
const instance = new SubType("yc", 18);
console.log(myInstanceof(instance, SubType)); 
console.log(myInstanceof(instance, Object)); 

8. 函数的原型

  • 函数实例(不是实例对象)的 __proto__ 指向 Function.prototype
  • Function.prototype.__proto__ 指向 Object.prototype
  • 函数实例的原型的 __proto__ 指向 Object.prototype,函数实例的原型就是构造函数创建对象的原型
function func() {}
console.log(func.__proto__ === Function.prototype); 
console.log(Function.prototype.__proto__ === Object.prototype); 
console.log(func.prototype.__proto__ === Object.prototype); 

console.log(Object.__proto__ === Function.prototype); 
console.log(Array.__proto__ === Function.prototype); 
console.log(Date.__proto__ === Function.prototype); 

f = new func();
console.log(f.__proto__ === func.prototype); 

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
javascript2025-09-18
JavaScript 的多线程能力:Worker
如果你写过一些计算量稍大的 JavaScript 代码,比如图像处理、大量数据排序或者复杂的算法,你几乎肯定遇到过浏览器“卡死”的现象。点击页面没反应,动画也停了,就像整个世界都静止了。 这就是主线程被阻塞的典型后果。因为主线程既要负责执行 JavaScript,又要负责渲染页面、响应用户操作,一旦它被繁重的计算任务占满,就无暇顾及其他,用户体验便直线下降。 这个问题的根源,正是“主线程是单线程的”。那么,如何解决呢? 答案很简单:把这...
学习面试
javascript2025-09-10
手把手教你实现一个EventEmitter
你是不是也遇到过这种情况:代码里各种事件监听和触发,回调函数套了一层又一层,最后自己都理不清哪个事件先触发、哪个后触发了? 别担心,今天我就带你从零开始实现一个自己的事件触发器(EventEmitter),让你彻底掌握事件驱动的精髓,代码清晰度直接提升200%! 为什么要懂EventEmitter? 先说说为什么我们要关心这个。Node.js的核心就是事件驱动,比如文件读写、网络请求,都是通过事件来处理的。不会EventEmitter,...
学习面试
javascript2025-08-09
Web Worker 多线程魔法
你是否曾遇到过网页因为复杂计算而卡顿的情况?今天,让我们一起探索Web Worker的神奇世界,看看如何用多线程技术解决性能瓶颈,并实现流畅的图片压缩功能! 为什么需要Web Worker? 想象一下,你在一个繁忙的餐厅里,只有一位服务员(主线程)负责点餐、上菜、结账所有工作。当客人突然暴增时,这位服务员就会手忙脚乱,其他客人只能干等着… 这就是JavaScript的现状—— 单线程 !所有任务都在一个线程中排队执行,一旦遇到复杂计算(...
学习面试
javascript2025-08-07
🌐AST抽象语法树
AST 是前端开发中一个非常重要的概念,它就像是代码的“编译器”,能够帮助我们理解和操作代码。本文将深入探讨 AST 的原理和应用,带你从零开始了解 AST。 一、什么是 AST? AST(抽象语法树)是一种树形结构,用于表示程序代码的语法结构。它将代码分解为一系列的节点,每个节点代表代码中的一个元素,如变量、函数、表达式等。AST 是编译器和解释器的基础,也是代码分析和转换的关键工具。 1\. AST 的结构 AST 的结构通常包括以...
学习面试
javascript2025-08-05
打字机效果是如何实现的
分析效果 使用 AI 对话的时候,文字是一个字一个字蹦出来的?它不是等所有内容生成完毕后一次性展示,而是以一种流畅、实时的“打字机”效果呈现,这种体验的背后,隐藏着一种高效的Web技术。它就是 服务器推送事件(Server Sent Events,简称 SSE) 什么是 SSE 从根本上说,SSE 是一种允许服务器 单向 、 持续 地向客户端(浏览器)推送数据的Web技术。 可以把它想象成一个 电台广播 : 服务器 是广播电台,持续不断...
学习面试
javascript2025-08-04
Promise 从原理到实现
前言 是 异步编程的核心概念之一,它为我们提供了一种更优雅的方式来处理异步操作。 本文将全面剖析 的工作原理,并逐步实现一个符合 规范的 。 正文 一、Promise 基础概念 代表一个异步操作的最终完成(或失败)及其结果值。它有 种状态: 1. pending : ,既不是成功,也不是失败状态 2. fulfilled :意味着操作 完成 3. rejected :意味着操作 状态一旦改变就不能再变,只能从 变为 ,或从 变为 。 二...
学习面试

评论

请登录后发表评论

去登录
加载评论中...

目录