JavaScript中的原型链

news2024/10/6 6:44:58

本文作者为奇舞团前端开发工程师

概述

JavaScript 是 Web 的编程语言,简单易学,功能强大,但由于过于灵活设计理念,导致初学者经常一脸懵,本文要谈的是JavaScript中难点之一原型链。

原型链的前世

JavaScript的诞生

要理解Javascript的原型链的设计思想,必须从它的诞生说起。

1994年,网景公司(Netscape)发布了Navigator浏览器0.9版。这是历史上第一个比较成熟的网络浏览器,当时轰动一时。但是这个版本的浏览器只能用来浏览,不具备与访问者互动的能力。比如,如果网页上有一栏"用户名"要求填写,浏览器就无法判断访问者是否真的填写了,只能让客户端将网页全部传回服务端,让服务器端判断是否填写。如果没有填写,服务器端就返回错误,要求用户重新填写,这太浪费时间和服务器资源了。

因此,网景公司急需一种网页脚本语言,使得浏览器可以与网页互动。工程师Brendan Eich负责开发这种新语言。他觉得,没必要设计得很复杂,这种语言只要能够完成一些简单操作就够了,比如判断用户有没有填写表单。

1994年正是面向对象编程(object-oriented programming)最兴盛的时期,C++是当时最流行的语言,而Java语言的1.0版即将于第二年推出,Sun公司正在大肆造势。

Brendan Eich无疑受到了影响,Javascript里面所有的数据类型都是对象(object) ,这一点与Java非常相似。但是,他随即就遇到了一个难题,到底要不要设计"继承"机制呢?

JavaScript是一门脚本语言,是为了操作网页的,如果只将其作为简易的脚本语言,其实不需要有"继承"机制。但是Javascript里面都是对象,必须有一种机制,可以将对象之间关联起来。所以Brendan Eich最后还是设计了"继承"。

但是他不打算引入"类"(class)的概念,因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这好像有点太正式了,Brendan Eich考虑到C++和Java语言都使用new命令生成实例。

C++的写法是:

ClassName *object = new ClassName();

Java的写法是:

ClassName object = new ClassName();

因此,他把new命令引入了Javascript,用来从类(JavaScript中叫原型对象)生成一个实例对象。但是Javascript没有"类",怎么来表示类(原型对象)呢?

这时,他想到C++和Java使用new命令时,都会调用"类"的构造函数(constructor)。他就做了一个简化的设计,在Javascript语言中,new命令后面跟的不是类,而是构造函数。

举例来说,现在有一个叫做Person的构造函数,表示人对象的原型(可以理解成java中的类)。

function Person(name){

   this.name = name;

}

对这个构造函数使用new,就会生成一个人对象的实例。

var pA = new Person('老王');

alert(pA.name); // 老王

注意构造函数中的this关键字,它就代表了新创建的实例对象。

prototype属性的由来

对于面向对象编程语言比如java或者c++来说,用构造函数生成实例对象是无法共享属性和方法,都有其独立的内存区域,互不影响。

比如,在Person对象的构造函数中,设置一个实例对象的共有属性race。

function Person(name){

    this.name = name;

     this.race = '汉族';

}

然后,生成两个实例对象:

var pA = new Person('老王');
var pB = new Person('老张');

这两个对象的race属性是独立的,修改其中一个,不会影响到另一个。

pA.species = '苗族';
alert(pB.species); // 显示"汉族",不受pA的影响

每一个实例对象,都有自己的属性和方法的副本。

按前面new运算符所述,每一个实例对象,都有自己的属性和方法的副本。但此时如果我们想在同类但不同对象间共享数据(继承)怎么办呢,解决此问题的方法就是prototype。

考虑到共享数据的问题,Brendan Eich决定为JavaScript的构造函数设置一个prototype属性。

这个属性包含一个对象(以下简称"prototype对象"),所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。这里prototype对象有点像C++基类。

实例对象一旦创建,将自动引用prototype对象的属性和方法。也就是说,实例对象的属性和方法,分成两种,一种是本地的,另一种是引用的。

还是以Person构造函数为例,现在用prototype属性进行改写:

function Person(name){

  this.name = name;

}

Person.prototype = { race : '汉族' };


var pA = new Person('老王');

var pB = new Person('老张');


alert(pA.race); // 汉族

alert(pB.race); // 汉族

现在,race属性放在prototype对象里,是两个实例对象共享的。只要修改了prototype对象,就会同时影响到两个实例对象。

Person.prototype.race = '苗族';
alert(pA.race); // 苗族
alert(pB.race); // 苗族

综上所述,由于所有的实例对象共享同一个prototype对象,那么从外界看起来,而实例对象则好像"继承"了prototype对象一样。

这就是Javascript继承机制的设计思想。

重写prototype属性、方法

用过java、c++类语言的都知道,既然有继承,必然有重写。举例说明JavaScript的重写

Person.prototype.hairColor = 'black';
Person.prototype.eat = function(){
    console.log('Person eat')
}
console.log(pA)
console.log(pB)

控制台输出:

29d592802b2660dd88214e88ea8b968d.png

此时我们打印pA、pB,我们惊喜的发现,他们有了属性hairColor和eat方法;实例动态的获得了Person构造函数之后添加的属性、方法,这就是原型意义所在!可以动态获取,这样可以节省内存。

另外我们还要注意:如果pA将头发染成了黄色,那么hairColor会是什么呢?

pA.hairColor = 'yellow';
console.log(pA)
console.log(pB)

控制台输出:

19de728228f0b9ecff09cc24e610c2fc.png

可以看到,pA的hairColor = 'yellow', 而pB的hairColor = 'black';实例对象重写原型上继承的属性、方法,相当于 “属性覆盖、属性屏蔽” ,这一操作不会改变原型上的属性、方法,自然也不会改变由统一构造函数创建的其他实例,只有修改原型对象上的属性、方法,才能改变其他实例通过原型链获得的属性、方法。

继承与原型链

JavaScript 中一切皆对象。每个实例对象都有一个私有属性,称之为 proto,指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(proto),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

  • 原型链的经典图:

8495e7e4fcda9e1152875d8e601daca3.png

这张图详细的描述了构造函数Function,Object以及它们实例之间的原型关系。

首先得记住并理解几个概念

  1. 属性__proto__是一个对象,它有两个属性,constructor和__proto__;

  2. 原型对象prototype有一个默认的constructor属性,用于记录实例是由哪个构造函数创建;

  3. 除了Object的原型对象(Object.prototype)的__proto__指向null,其他内置函数的原型对象和自定义构造函数的原型对象的 __proto__都指向Object.prototype

Object.prototype.__proto__ === null;
Array.prototype.__proto__ === Object.prototype;


结合一张pB对象的结构图看就更容易理解了:

4296624c377913ccc8d09372b579bff9.png

创建对象的方法

使用语法结构创建

var p = {name: "老三"};

// p 这个对象继承了 Object.prototype 上面的所有属性
// Object.prototype 的原型为 null
// 此对象原型链如下:
// p ---> Object.prototype ---> null

var arr = [1,2,3,4,5];
// 数组都继承于 Array.prototype
// 原型链如下:
// arr ---> Array.prototype ---> Object.prototype ---> null

使用构造器创建

在 JavaScript 中,构造器其实就是一个普通的函数。当使用 new 操作符 来作用这个函数时,它就可以被称为构造方法(构造函数)。

function Animal(name,age) {
  this.name = name;
  this.age = age;
}

Animal.prototype.eat = function(){
    console.log('Animal eat')
};

var a = new Animal("cat",1);
// a 是生成的对象,他的自身属性有 'name' 和 'age'。
// 在 a 被实例化时,a.__proto__ 指向了 Animal.prototype。

使用 Object.create 创建

ECMAScript 5 中引入了一个新方法:Object.create()。可以调用这个方法来创建一个新对象。新对象的原型就是调用 create 方法时传入的第一个参数:

var a = {name: "老三"};// a ---> Object.prototype ---> nullvar b = Object.create(p);
// b ---> p ---> Object.prototype ---> null
console.log(b.name); // 老三 (继承而来)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined,因为 d 没有继承 Object.prototype

class关键字

ECMAScript6 引入了一套新的关键字用来实现 class。使用java、swift等面向对象的语言的开发人员会对这些结构感到熟悉,但它们是不同的。JavaScript 仍然基于原型。这些新的关键字包括 class, constructor,static,extends 和 super。

class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Person extends Animal {
  constructor(name,sex) {
    super(name);
    this.sex = sex;
    this.arrs = [1,2,3,4];
  }
  
}

var pC = new Person("老王","男");

注意:类的本质还是一个函数,类就是构造函数的另一种写法,JavaScript 中并没有一个真正的 class 原始类型, class 、extends 仅仅只是对原型对象运用语法糖,这样便于程序员理解。

function Person(){}
console.log(typeof Person); //function

class Person extends Animal {
  constructor(name,sex) {
    super(name);
    this.sex = sex;
  }
  get mysex() {
    return this.sex;
  }
  set mysex(sex) {
    this.sex = sex;
  }
}

console.log(typeof Person); //function

参考

轻松理解JS 原型原型链 https://juejin.cn/post/6844903989088092174

Javascript继承机制的设计思想 https://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html

继承与原型链 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

ed82835ac9e60d446fa5ef35e7fcc1d6.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/169853.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nessus介绍与安装

Nessus介绍与安装 1.Nessus简介 Nessus号称是世界上最流行的漏洞扫描程序,全世界有超过75000个组织在使用它。该工具提供完整的电脑漏洞扫描服务,并随时更新其漏洞数据库。Nessus不同于传统的漏洞扫描软件,Nessus可同时在本机或远端上遥控&…

测试开发 | Dubbo 接口测试原理及多种方法实践总结

image1080478 86.9 KB 1、什么是 Dubbo? Dubbo 最开始是应用于淘宝网,由阿里巴巴开源的一款优秀的高性能服务框架,由 Java 开发,后来贡献给了 Apache 开源基金会组织。 下面以官网的一个说明来了解一下架构的演变过程&#xff0…

初学Java中的方法,看这篇就够了

本篇介绍了Java中方法的概念以及方法的使用(方法的定义和调用,实参和形参的关系).方法重载的介绍和使用,编译器如何实现方法重载- -方法签名,介绍和使用方法调用自身解决问题的技巧–递归 对比递归和循环的优缺点 掌握Java中的方法一.方法的概念及使用1.什么是方法2.方法的使用…

【C++】AVL树

​🌠 作者:阿亮joy. 🎆专栏:《吃透西嘎嘎》 🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根 目录👉AVL树&am…

【Linux】CentOS、CentOS Stream、RedHat 和Fedora 之间的关系

目录 简单说明 详细说明 红帽(Red Hat)系和德班(Debian)系 简单说明 在centos8之前: Fedora 》RedHat 》CentOS Fedora 是RedHat的“试验场”,很多新功能和特性先加入Fedora 稳定后再加入RedHat&…

YOLOv5 引入 最新 BiFusion Neck | 附详细结构图

YOLO 社区自前两次发布以来一直情绪高涨!随着中国农历新年2023兔年的到来,美团对YOLOv6进行了许多新的网络架构和训练方案改进。此版本标识为 YOLOv6 v3.0。对于性能,YOLOv6-N在COCO数据集上的AP为37.5%,通过NVIDIA Tesla T4 GPU测…

99.恢复二叉搜索树

99.恢复二叉搜索树 1、题目描述 题目的额外要求是: 使用 O(n) 空间复杂度的解法很容易实现。你能想出一个只使用 O(1) 空间的解决方案吗? 2、解题思路 二叉搜索树中某两个节点被交换了数值,那么对中序序列的影响如下: 假设没有交换之前二叉…

活动星投票千人共读一本书网络评选微信的投票方式线上免费投票

“千人共读一本书”网络评选投票_视频投票评选_投票统计小程序_微信不记名投票用户在使用微信投票的时候,需要功能齐全,又快捷方便的投票小程序。而“活动星投票”这款软件使用非常的方便,用户可以随时使用手机微信小程序获得线上投票服务&am…

正则表达式和re模块

目录 一.基础匹配 1.什么是正则表达式 re模块三个基础方法 re.match(匹配规则,被匹配字符串) search(匹配规则,被匹配字符串) findall(匹配规则,被匹配字符串) 小结 二.元字符匹配 单字符匹配: 示例: 数量匹配 边界匹配 分组匹配…

【Java】【系列篇】【Spring源码解析】【三】【体系】【BeanFactory体系】

BeanFactory体系BeanFactory整体结构体系图顶层接口-BeanFactory1.1、描述1.2、方法解析(15个)1.2.1、属性1.2.2、获取bean实例1.2.3、获取bean的提供者(对象工厂)1.2.4、判断是否包含bean1.2.5、单例,原型,bean类型的判断1.2.6、…

SAP ABAP——SAP包(二)【CTS | 传输请求】

💂作者简介: THUNDER王,一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学会计专业大二本科在读,阿里云社区专家博主,华为云社区云享专家,CSDN SAP应用技术领域新兴创作者。   在学习工…

CentOS7.9配置Nginx反向代理+NodeJS部署上线

Nginx配置 Nginx是一个高性能的HTTP和反向代理服务,许多的大型网站都会采用Nginx来进行HTTP服务器托管 安装编译环境gcc g 进入到root目录: yum -y install make zlib zlib-devel gcc-c libtool openssl openssl-devel 安装PCRE PCRE功能时让Ngi…

ue4c++日记3(碰撞报告碰撞位置|力和扭矩)

目录 代码速查 根据世界方向/局部方向移动 根据世界方向/局部方向旋转 加力加扭矩 1碰撞并报告碰撞位置 两个设为阻挡才会阻挡,其中一个是重叠就会重叠 2例子:旋转前进!/原地踏步? 3增加力和扭矩 1.头文件 2.cpp文件 …

(14)go-micro微服务服务层Handle开发

文章目录一 Handle层开发功能说明需要完成的服务开发功能:从哪找需要开发的功能二 代码编写三 最后一 Handle层开发功能说明 需要完成的服务开发功能: 登录注册查询用户信息修改信息发送注册邮件发送重置密码邮件重置密码获取权限修改权限退出账号删除…

【计算机视觉】梯度消失和爆炸以及解决方法

问题 梯度消失无论是笔试还是面试都是常客了,其实对应于梯度消失,还有一个梯度爆炸的概念,这又是什么导致的呢?下面我们将根据公式推导来解释何为梯度消失与梯度爆炸。 梯度消失和梯度爆炸的表现 网络层数越多,模型训练的时候便越容易出现 梯度消失(gradient vanish) 和…

史上最全| 14种自动化分拣系统合集

导语大家好,我是智能仓储物流技术研习社的社长,你的老朋友,老K。新书上市《智能物流系统构成与技术实践》2023年度-厂商宣传合作位--->点击详情本文为研习社原创,违规转载必究01移栽式02偏转轮式03扫臂式04滑靴式05侧向翻转06推…

C++设计模式(3)——抽象工厂模式

抽象工厂模式 亦称: Abstract Factory 意图 抽象工厂模式是一种创建型设计模式, 它能创建一系列相关的对象, 而无需指定其具体类。 问题 假设你正在开发一款家具商店模拟器。 你的代码中包括一些类, 用于表示: …

Vue3系列二:如何实现对响应式数据的代理

上一篇文章中,我们讲解了 Vue3 中对响应式系统的实现,本章节会更进一步的从数据层面分享 Vue3 中对响应式数据是如何进行代理的,本文主要从引用类型数据和基本类型数据两个方面进行讲解。 实现数据代理的基础 理解 Proxy 和 Reflect 首先&…

26.Isaac教程--导航算法

导航算法 本节详细介绍导航算法。 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录导航算法全局路径规划器规划器模型可见性图算法优化器轨迹规划器全局路径规划器 Isaac 框架中的全局规划器问题被分解为三类:规划器模型、…

SpringBoot使用Swagger2

SpringBoot使用Swagger21.引入swagger依赖2.添加swagger配置类3.测试Controller4.测试5.swagger的注解Api注解ApiOperation注解ApiImplicitParam、ApiImplicitParams注解ApiParam注解ApiResponse、ApiResponses注解ResponseHeader注解ApiModel、ApiModelProperty注解6.更多1.引…