js系列-原型和原型链继承

原型链与继承 每个实例对象都有一个私有属性(proto)指向它的构造函数的原型对象prototype;该原型对象也有一个自己的原型对象,层层向上直到一个对象的原型对象为null;根据定义,null没有原型,并作为这个原型链中的最后一个环节 1.原型链继承 基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法 核心:原型链对象变成父类实例,子类就可以调用父类方法和属性 优点:实现简单 缺点:引用类型值的原型属性会被所有实例共享;不能向父类传递参数 2.借用构造函数继承 核心:子类构造函数内部调. 阅读详情

前戏

世界上是先有的鸡,还是先有的蛋?这个世界难题,可能无从考究了。但如果现在问是先有的函数,还是先有的对象,那么,我的答案是先有的函数(构造函数)。那知道了这个,我们讲解原型,就从函数说起。

原型

每个函数都会创建一个prototype对象属性,它就是原型对象,也是我们调用构造函数生成的对象的原型,我们直接打印看下:

function Person(name) {
  this.name = name;
}
console.dir(Person);

我们看上面的图片发现一个特别有意思的事情,我们自定义的Person()构造函数的prototype原型对象默认只有一个constructor属性,而且onstructor属性指回了Person()构造函数,即Person.prototype.constructor === Person。

有了构造函数,我们就可以创建实例对象:

function Person(name) {
  this.name = name;
}
let tom = new Person("Tom");
console.log(tom);

创建的实例对象,我们发现它除了有个name属性外,还有个__proto__,这就是对象的原型,指向Person.prototype原型对象。__proto__是浏览器自身实现的,不是规范写法,我们要求使用Object.getPrototypeOf()替换__proto__,获取原型。

原型链

上面讲原型,只讲了实例与构造函数的关系,实例对象原型就是构造函数prototype指向的原型对象,即:Object.getPrototypeOf(tom) === Person.prototype;那既然Person.prototype也是对象,那也有原型,它的原型是什么呢?

 function Person(name) {
    this.name = name;
  }
  let tom = new Person("Tom");
  console.log(Object.getPrototypeOf(Person.prototype) === Object.prototype);//true

可以看到Person.prototype的原型指向Object.prototype原型对象

原型链继承

前面讲到了原型链,那原型链能解决什么问题呢,我们下面要讲的继承就可以通过原型链实现,原理就是修改子构造函数原型对象即prototype属性,指向父级构造函数new的实例:

//定义父构造函数
function Animal() {
  this.Arr = [1, 2, 3];
}
//往父构造函数原型上添加run方法
Animal.prototype.run = function (target) {
  console.log(target + '会跑');
}
//定义子构造函数
function People(target) {
  this.target = target;
  this.age = 20;
}
//修改子构造函数prototype属性原型对象指向父构造函数实例对象
People.prototype = new Animal();
//new出的子构造函数实例对象就可以调用父构造函数的属性包括父构造函数原型上的方法
let tom = new People('Tom');
console.log(tom.Arr);//[1, 2, 3]
tom.run('Tom');//Tom会跑

上面这种继承方式有两个问题,第一个问题其实在我们书写的时候就发现了,我们最终new出的实例tom可以给构造函数传参,但没办法给父构造函数传参,有人可能会说,可以在People.prototype = new Anima("参数");时传参,但这种方式使得new出的所有People都有这个属性,并不是每个最终的实体单独占有的。第二个问题就是父构造函数的Arr属性也是所有实例共有的,当一个实例改变Arr,会同时影响所有实例,我们用代码证明下:

//上面的代码我们不再重复书写
//我们在new一个jerry实例
let jerry = new People('Jerry');
//修改tom实例
tom.Arr.push(4);
console.log(tom.Arr);//[1, 2, 3, 4]
//jerry实例中的Arr也发生改变,说明两个实例共用一份Arr
console.log(jerry.Arr);//[1, 2, 3, 4]

好,既然有问题,那怎么解决呢,在这之前我想再讲一种继承方式:

经典继承

他有个更形象的名字,“盗用构造函数”,顾名思义,就是盗用父构造函数里的属性,那怎么盗用呢,上代码:

//定义父构造函数
function Animal(target) {
  this.Arr = [1, 2, 3];
  this.jump = function () {
    console.log(target + '会跳');
  }
}
//往父构造函数原型上添加run方法
Animal.prototype.run = function (target) {
  console.log(target + '会跑');
}
//定义子构造函数
function People(target) {
  //继承Animal(盗用父构造函数里的属性)
  Animal.call(this, target);
  this.target = target;
  this.age = 20;
}
//new 实例的时候可以传参
let tom = new People('Tom');
console.log(tom.Arr);//[1, 2, 3]
tom.jump();//Tom会跳
//我们在new一个jerry实例
let jerry = new People('Jerry');
//修改tom实例
tom.Arr.push(4);
console.log(tom.Arr);//[1, 2, 3, 4]
//jerry实例中的Arr不变,说明两个实例分别有一份Arr
console.log(jerry.Arr);//[1, 2, 3]
jerry.run();//Uncaught TypeError: jerry.run is not a function

经典继承很好的解决了原型链继承的两个问题(上面讲到的),但,代码最后一行的报错也暴露了经典继承的一个问题,定义在父构造函数原型上的属性无法继承,那就有了下面的继承方式:

组合继承

即原型链继承和经典继承组合

//定义父构造函数
function Animal(target) {
  this.Arr = [1, 2, 3];
  this.jump = function () {
    console.log(target + '会跳');
  }
}
//往父构造函数原型上添加run方法
Animal.prototype.run = function (target) {
  console.log(target + '会跑');
}
//定义子构造函数
function People(target) {
  //继承Animal(盗用父构造函数里的属性)
  Animal.call(this, target);
  this.target = target;
  this.age = 20;
}
//原型链继承
People.prototype = new Animal();
//new 实例的时候可以传参
let tom = new People('Tom');
console.log(tom.Arr);//[1, 2, 3]
tom.jump();//Tom会跳
//我们在new一个jerry实例
let jerry = new People('Jerry');
//修改tom实例
tom.Arr.push(4);
console.log(tom.Arr);//[1, 2, 3, 4]
//jerry实例中的Arr不变,说明两个实例分别有一份Arr
console.log(jerry.Arr);//[1, 2, 3]
jerry.run('Jerry');//Jerry会跑

组合继承解决了原型链继承,经典继承的问题,那组合继承是不是就是完美的呢,有没有什么问题呢?

是的,组合继承也是有问题的:我们使用Animal.call()会调用一次父构造函数,使用new Animal();时还会调用一次构造函数,

那调用两次父构造函数造成的后果是什么呢?

子构造函数People中执行Animal.call(this, target);这样People中就保存了一份Arr和jump;实例化后就会保存在实例上

子构造函数原型属性People.prototype = new Animal();这样在People.prototype 上也保存了一份Arr和jump;

那最终的实例使用的是哪个呢?

我们知道调用属性时,是从下往上查找,即先看实例上有没有,再看原型 上有没有,所以用到的是子构造函数里的,People.prototype的原型上保存的不需要,代码证明下:

//复用上面的代码

//修改原型上Arr
People.prototype.Arr.push(4);
console.log(People.prototype.Arr);//[1, 2, 3, 4]
//我们在new一个jerry实例
let Tony = new People('Tony');
console.log(Tony.Arr);//[1, 2, 3]

也就是说People.prototype = new Animal();这行代码是有问题的,我们是不需要在People.prototype上面也增加父构造函数中的属性比如Arr和jump的,我们只需要People.prototype继承父构造函数原型Animal.prototype,用到的就是Object.create();

即:

//定义父构造函数
function Animal(target) {
  this.aType = '哺乳类';
  this.Arr = [1, 2, 3];
  this.jump = function () {
    console.log(target + '会跳');
  }
}
//往父构造函数原型上添加run方法
Animal.prototype.run = function (target) {
  console.log(target + '会跑');
}
//定义子构造函数
function People(target) {
  //继承Animal(盗用父构造函数里的属性)
  Animal.call(this, target);
  this.target = target;
  this.age = 20;
}
console.log(People.prototype.constructor)//People函数
//原型继承
People.prototype = Object.create(Animal.prototype);
console.log(People.prototype.constructor)//Animal函数
//解决上面由于重写子构造函数原型导致constructor指向了父构造函数问题
People.prototype.constructor = People;
console.log(People.prototype.constructor)//People函数
//new 实例的时候可以传参
let tom = new People('Tom');
console.log(tom.Arr);//[1, 2, 3]
tom.jump();//Tom会跳
console.log(People.prototype.Arr);//undefined

这种继承方式被称为寄生式组合继承,也是继承的最佳模式。

继承的问题解决了,但留下个疑问,使用new和使用Object.create()区别是啥?

new和Object.create()区别

new

  1. 创建一个空对象{}
  2. 将对象{}的原型指向构造函数prototype原型属性
  3. 将对象{}赋值给构造函数中this
  4. return这个this(默认)

在原型链继承代码中:

//原型链继承
People.prototype = new Animal();

这行代码就会执行new这个过程,所以在父构造函数Animal中,this指向的就是People.prototype,这也就是为什么People.prtotype上也会存有一份父构造函数属性的原因,同时将People.prototype的原型指向Animal.prototype。

Object.create()

内部实现原理:

function create(o){
  let f = function(){}
  f.prototype = o;
  return new f();
}

我们会发现,在内部创建一个临时构造函数,并将函数prototype指向传进来的对象,最后返回临时构造函数实例。

所以

//原型继承
People.prototype = Object.create(Animal.prototype);

这行代码只是将People.prototype的原型指向了Animal.prototype,并不会像new一样,在People.prototype上添加父构造函数属性。

JavaScript中的原型链 在JavaScript中,如果一个对象需要访问另一个对象的属性或方法,它会先查找自身的属性和方法,如果找不到,就会沿着__proto__链接找到其原型对象,并继续查找,直到找到目标属性或方法或者到达原型链的末端。原型链是JavaScript中实现继承的一种方式,它描述了对象之间的继承关系,通过沿着__proto__链接查找属性和方法,实现了对象之间的属性和方法的共享和继承。原型链是JavaScript中一个重要的概念,它不仅是实现继承的主要方法之一,也是理解JavaScript中对象和类之间关系的关键。 阅读详情

相关推荐

原型链和原型链的继承

所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype,其包含了 JavaScript 中许多通用的功能 为什么能创建 “类”,借助一种特殊的属性:所有的函数默认都会拥有一个名为 prototype 的共有且不可枚举的属性,它会指向另外一个对象,这个对象通常被称为函数的原型 functionPerson(name){ this.name=name; } Person.prototype.constructor=Person ...

liuhao9999的博客 731

js原型与常见的继承方式

本文主要是以图文的方式去理解js原型的指向关系以及js的常见继承方式。 前言 任何函数在被创建时,都会被附带一个新创建的原型对象。 原型和实例的关系:每个构造函数附带一个原型对象prototype,原型有一个constructor指向构造函数,实例对象有一个__proto__属性指向原型prototype。 普通函数的原型具有两面性,当作为实例对象时,它的原型(__proto__)指向Function构造函数的原型对象,当作为构造函数时,它的原型(prototype)指向自身创建的原型对象。 一.

Derlta的博客 798

环境科研人必备:手把手教你用EPISuite 4.1批量预测化合物毒性(附ECOSAR实战)

本文详细介绍了EPISuite 4.1在环境毒理学研究中的应用,重点解析了ECOSAR模块的化合物毒性预测技术。通过批量处理实战和科研案例,帮助环境科研人员快速评估化学物质对水生生态系统的风险,提升工作效率。文章还涵盖了EPISuite核心模块的功能分类及ECOSAR模型的技术原理与算法特点。

weixin_28738165的博客 540

JavaScript:原型、原型链、继承

每个函数都有一个 prototype(原型)属性,这个属性是一个指针,指向一个对象, 而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法,使用原型对象的好处是可以 让所有对象实例共享它所包含的属性和方法。当你创建一个对象时,Javascript会从该对象的构造函数的原型对象中查找属性和方法。结合第四种中提及的继承方式,解决普通对象的继承问题的 Object.create 方法,我们在前面这几种继承方式的优缺点基础上进行改造,得出了寄生组合式的继承方式,这也是所有继承方式里面相对最优的继承方式。

snow的博客 1625

自适应动态规划硕士博士论文学习

强化学习关注随机过程中的序贯决策问题,而自适应动态规划关注动态系统的控制问题。吸引域:系统渐近稳定的区域。

DarlingLSA的博客 1423

Python 爬虫:获取网页数据的 5 种方法

requests:适用于简单的静态网页抓取,易于使用。requestslxml:适合需要高效解析大规模网页内容的情况,支持 XPath 和 CSS 选择器。Selenium:适用于动态网页(JavaScript 渲染)的抓取,模拟浏览器行为获取动态数据。Scrapy:强大的爬虫框架,适合大规模的网页抓取任务,支持异步请求和高级功能。PyQuery:基于 jQuery 语法,适合快速开发,提供简洁的 CSS 选择器语法。

想看什么文章都可以私信给我,综合性博客。 5513

prototype的六种继承方式以及优缺点

prototype的六种继承方式以及优缺点

Ak47a7的博客 1441

Basler相机和pylon viewer 的使用

文章目录1 线路连接2 安装和使用pylon 1 线路连接 电源线接到电源插座,网线接到电脑(电脑没有网口的话,可用转换器转到type-c接口)。 2 安装和使用pylon 参考 Basler pylon安装及对网口相机基本设置 相机和电脑以太网IP设置参考:balser相机IP设置 关于相机参数和pylon viewer的详细设置,可以参考pylon Viewer安装路径下的文档(Documentation) ...

Deephome的博客 1万+

JS | JS中原型链的常见继承模式

每个对象拥有一个原型对象,对象以其原型为模板,从原型继承属性和方法。当然 原型对象 也可能拥有原型,并从中继承属性和方法,依此类推,这种关系被称为原型链。准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非对象实例本身。

青春木鱼的博客 1478

I2S时序及音频驱动

I2S、音频驱动

qq_41882586的博客 3945

DeepSeek + 番茄小说:AI辅助小说开发最全指南

DeepSeek与番茄小说合作推出AI辅助小说创作系统,通过DeepSeek的文本生成能力(包括情节构建、角色塑造等)结合番茄小说的市场数据,为作者提供从创意到发布的全流程支持。系统包含智能创意生成器、角色设计、章节自动生成等核心模块,采用数据驱动优化策略,支持128K长文本处理和多风格模仿。开发架构采用Python+FastAPI技术栈,提供分层审核机制确保质量。该系统可帮助作者突破创作瓶颈,提升效率,同时提供订阅制、内容分成等多元变现模式。需注意版权问题和原创性保证。

weixin_41445009的博客 1671

JavaScript学习系列之初识原型继承和原型链

我们将JavaScript对象分为函数对象和普通对象。函数对象用function关键字定义的对象或者对其使用typeof运算符返回”function”的对象。 * JavaScript引擎内置的函数对象(构造器)typeof Array//"function" typeof Number//"function" typeof Boolean//"function" typeof String//"

merry_tea的博客 395

Unity四部曲(核心)

需要先改成Cube复杂的展开留到unity进阶再说。

ultraman1234566的博客 2797

Javascript基础系列16:Javascript的原型链和继承详解

一、继承的概念​ 继承是所有的面向对象的语言最重要的特征之一。大部分的oop语言的都支持两种继承:接口继承和实现继承。比如基于类的编程语言Java,对这两种继承都支持。从接口继承抽象方法 (只有方法签名),从类中继承实例方法。​ 但是对JavaScript来说,没有类和接口的概念(ES6之前),所以只支持实现继承,而且继承在 原型链 的基础上实现的。等了解过原型链的概念之后,你会发现继承其实

完善飞跃 791

深入NAND Flash信号与接口:结合ONFI 5.0协议,聊聊DBI、ODT和差分信号到底在解决什么实际问题

本文深入探讨了NAND Flash信号与接口设计,结合ONFI 5.0协议,详细解析了DBI、ODT和差分信号等技术如何解决高速数据传输中的信号完整性问题。通过实际案例和数据,展示了这些技术在降低功耗、减少噪声和提升抗干扰能力方面的显著效果,为存储工程师提供了宝贵的实践参考。

weixin_33736649的博客 341

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

js原型和原型链_JS 系列二:原型与原型链

引言JS系列暂定 27 篇,从基础,到原型,到异步,到设计模式,到架构模式等,本篇是 JS系列中最重要的一章,花费 3 分钟即可理解,如果你已了解,快速浏览即可。本篇文章主讲构造函数、原型以及原型链,包括 Symbol 是不是构造函数、constructor 属性是否只读、prototype 、__proto__ 、[[Prototype]] 、原型链。一、基础入门1. 对象在JS中...

weixin_39953356的博客 114

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

JS-原型、原型链

* 原型对象也是普通的对象,是对象一个自带隐式的 __proto__ 属性, 原型也有可能有自己的原型,如果一个原型对象的原型不为null的话,我们就称之为原型链。 * 原型链是由一些用来继承和共享属性的对象组成的(有限的)对象链。

qq_36631168的博客 274

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

JavaScript系列——原型、原型链、继承

原型是JavaScript 对象的一个内置属性,我们称为prototype,它本身就是一个对象。一般地,这个原型对象值指向 其构造函数的prototype。实际上prototype名称不是标准的,所有浏览器都是用__proto__来表示对象原型的名称(普通对象和函数都有),但prototype属性是构造函数专有的,如Date.prototype。

1089

JavaScript 原型链和继承面试题

JavaScript 原型链和继承问题 JavaScript 中没有类的概念的,主要通过原型链来实现继承。通常情况下,继承意味着复制操作,然而 JavaScript默认并不会复制对象的属性,相反,JavaScript只是在两个对象之间创建一个关联(原型对象指针),这样,一个对象就可以通过委托访问另一个对象的属性和函数,所以与其叫继承,委托的说法反而更准确些。 原型 当我们 new 了一个新的对象实例,明明什么都没有做,就直接可以访问 toString 、valueOf 等原生方法。那么这些方法是从哪里来的

木木木森林 3803

JavaScript 原型链与继承

原型链与继承 系列文章 这是JavaScript系列文章: 类 promise async & await 原型链与继承 闭包 this CommonJS ES6 Module 文章目录原型链与继承系列文章

一出好剧 9505

js原型、原型链和继承

继承是指将特性从父代传递给子代,以便新代码可以重用并基于现有代码的特性进行构建。JavaScript 使用对象实现继承。每个对象都有一条链接到另一个称作原型的对象的内部链。该原型对象有自己的原型,依此类推,直到原型是 null 的对象。

hole_diss的博客 1237

javascript原型继承与原型链

当访问一个对象的属性或方法时,首先查找这个对象自身有没有该属性如果没有就查找它的原型(也就是 __proto__ 指向的 prototype 原型对象)如果还没有就查找原型对象的原型(Object的原型对象)以此类推一直找到 Object 为止(null)__proto__ 对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线可以使用 instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上三、实现继承的方式。

2301_82023822的博客 1426

js原型继承和原型链继承以及注意事项

* 就是子对象自动拥有父对象的属性和方法, 继承可以提高代码的复用性。 * JS里的继承主要依靠是的原型链。让原型对象(每一个构造函数都有一个原型对象)的值,等于另一个类型的实例,即实现了继承;另外一个类型的原型再指向第三个类型的实例,以此类推,也就形成了一个原型链 function Animal(newName,newAge){ this.name = newName; ...

素描本里的男子的博客 3774

JavaScript 中基于原型和原型链的继承方式详解

继承方式是否共享引用属性能否传参能否继承原型方法父构造函数调用次数是否推荐原型链继承是否是1(设置原型时)❌构造函数继承否是否1(子构造中)⚠️ 局限组合继承否是是2⚠️ 可用但非最优寄生组合式继承(空对象中介)否是是1✅ 强烈推荐(ES5)原型式继承(Object.create)是否是(来自源对象)0✅ 特定场景适用。

2501_92872533的博客 937

javascript原型、原型链、继承详解

在JavaScript中,call和apply是两个用于调用函数的方法。call。它接收一个参数列表,并将每个参数传递给函数。第一个参数thisArg是可选的,用于指定函数中的this值。如果不传递thisArg,默认为全局对象(在浏览器中是window对象)。call方法会立即调用函数。${${this在这个例子中,call方法将person对象作为第一个参数传递给了greet函数。函数执行后,this.name将会是person对象的name属性。输出将会是。

jieyucx的博客 2037

深入JS原型、原型链和继承

深入JS原型、原型链和继承

weixin_50926010的博客 1196
上一篇: js系列-Object对象
下一篇: js系列-class类,继承
前端蜗牛君
博客等级 码龄10年 274粉丝 158原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值