Javascript的原型和原型链你了解吗

 更新时间:2022年3月3日 20:57  点击:747 作者:与宇宙对视

一、为什么要使用原型?怎样去理解原型的出现

1、对象字面量创建对象的缺点

想要介绍原型,就不得不提为什么我们要使用原型,在js早期,我们创建一个对象,比较流行的做法是使用对象字面量去创建一个对象,例如:

const person = {
            name: "wywy",
            age: 21,
            hobby: "听周杰伦"
}

用这种方式去创建对象,虽然简洁明了,但是我们如果需要大批量的创建这一类的对象,就像person这个对象,我们可能需要去创建多个不同的人,那么每一次都需要去声明创建,这样工作量是巨大的,为了解决这个问题我们引入了工厂函数的概念。

2、工厂函数

什么是工厂函数,顾名思义就可以把工厂函数看作是一个流水线的工厂,这个工厂的作用就是批量生产person对象,就像下面这样:

function createPerson(name,age,hobby){
	const obj = {};
	obj.name = name;
	obj.age = age;
	obj.hobby = hobby;
	return obj;
}
const person1 = createPerson("zs", 23, "滑板");
const person2 = createPerson("ls", 22, "听歌");
console.log(person1);
console.log(person2);

在这里插入图片描述

这里我们在创建person对象的时候只需要调用这个函数即可,并把每个对象对应的属性值传入就好了,这样相对于用对象字面量去创建一个个的person确实简化了代码量,但是工厂函数也有自身的缺点,就是我们不能去判断出这个对象的类型,按我们知道的在 js 中复杂引用数据类型进行细分,有 Array,Function,String 等,但是通过工厂函数模式创建的这些对象用控制台打印去全部都是 Object 类型,假如你有多个工厂函数,用多个工厂函数创建了多个实例,但是你却并不知道这些对象属于哪个工厂。为此又推出了构造函数这个概念。

3、构造函数

关于构造函数其实它和工厂函数非常相似,在js的函数中其实并没有单独的一类函数叫做构造函数,构造函数总是和new关键字一起使用,更准确地说一个函数被构造调用了。当一个构造函数不使用new关键字调用时,他和普通的函数无异。

function CreatePerson(name, age, hobby) {
            this.name = name;
            this.age = age;
            this.hobby = hobby;
}
const person1 = new CreatePerson("zs", 23, "滑板");
const person2 = new CreatePerson("ls", 22, "听歌");
console.log(person1);
console.log(person2);

在这里插入图片描述

仔细观察,不难发现我们对上面的工厂函数进行了以下几点改造:

1、我们将函数名的首字母大写了,在 js 中有个规定如果你以后打算将一个函数作为构造函数去使用那么最好把它的函数名首字母大写,来提醒使用者这是一个构造函数。其实不大写也不会有什么语法错误。

2、我们取消了在函数内部显示的声明一个对象 “const obj = {}”,并一并取消了最后返回这个对象的操作 “return obj”。

3、在调用这个这个 CreatePerson 函数时在前面加上了 new 关键字。

在这里插入图片描述

然后我们看这个结果,这个时候我们打印的对象不在是 Object 了,而是我们自己创建的函数 CreatePerson。看来构造函数确实解决了对象无法判定类型的问题。
那么神奇的new关键字在后台做了什么呢?其实它做了下面的五件事

1、在内存中创建一个新的对象。

2、让新对象的内部特性 [[Prototype]] 保存函数 CreatePerson 的 prototype 的属性值,也就是把函数的原型的指针保存到了 [[Prototype]] 中。

3、把函数内部的 this 指向这个新创建的对象。

4、执行函数内部的代码。

5、如果函数本身没有返回对象,那么就把这个新对象返回。

我们看构造函数原来在后台为我们做了这么多事。那么构造函数就完美了吗?并不是的,我们想一个 person 是不是应该也该给他加一个方法呢?那么我们就给加一个说话的方法吧:

function CreatePerson(name, age, hobby) {
     this.name = name;
     this.age = age;
     this.hobby = hobby;
     // 添加一个方法
     this.sayHi = function() {
         console.log("你好,我叫" + this.name)
     }
 }
const person1 = new CreatePerson("zs", 23, "滑板");
const person2 = new CreatePerson("ls", 22, "听歌");
 console.log(person1);
 console.log(person2);

但是我们发现这样做无疑为每一个实例对象都添加了一个 sayHi 方法,而且每个实例上的方法都不相等,但我们的目的是让每个实例都有这么一个功能就好了,不必创建这么多的 sayHi 方法而去浪费内存空间。说的直白一点,比如家里有五个孩子,每个孩子都想玩 switch 游戏,那么家长要给每个孩子买一台 switch 吗?当然家里有矿的当我没说,一般家庭是不是就买一台,然后哪个孩子想玩就管家长去要就行了是不是。

同样的,代码就是对现实生活的抽象,那么我们是不是也可以这样做,把方法添加到这些实例都拥有的一个爸爸是不是就好了,而仔细想想在 new 的五步中第二步是不是做了这么一件事,没错他就是 js 中的原型。这个原型就是这些实例的爸爸。

说了这么多我们总算要讲原形了!!!

二、使用原型

通过上面的讲解我们似乎对原型的作用有了大致的理解,就是把实例对象上需要用到的共有方法添加到原型上,而实例对象的自己的私有属性写在构造函数内部。
接下来我们对构造函数进行再一次改造:

function CreatePerson(name, age, hobby) {
     this.name = name;
     this.age = age;
     this.hobby = hobby;
     // 添加一个方法
     this.sayHi = function() {
         console.log("你好,我叫" + this.name)
     }
 }
const person1 = new CreatePerson("zs", 23, "滑板");
const person2 = new CreatePerson("ls", 22, "听歌");
 console.log(person1);
 console.log(person2);

首先向大家说明一点我并没有按着定义一个构造函数,然后在构造函数的原型上添加 sayHi 方法,接着使用 new 创建实例的顺序来写代码。而是先 new 创建了实例,然后再在原型上添加方法,这样的目的是想告诉大家,原型是具有动态性的,即你先创建了实例,在实例之后给原型添加了方法那么实例依然是可以访问的。而且可以看到通过比较 person1 和 person2 的 sayHi 方法我们发现这是同一个方法。这样我们完美的解决了构造函数的问题。

三、原型概念辨析

首先清楚两个概念:

  • 引用类型,都具有对象特性,即可自由扩展属性。(引用类型:Object、Array、Function、Date、RegExp)
  • 每个函数function都有一个显示原型prototype,每个实例对象都有一个隐式原型__proto__

function Fn() { // 内部语句:this.prototype = {}
}
// 1、每个函数function都有一个prototype,即显示原型(属性)
console.log(Fn.prototype);
// 2、每个实例对象都有一个__proto__,可称为隐式原型(属性)
var fn = new Fn(); // 内部语句: this.__proto__ = Fn.prototype
console.log(fn.__proto__);

两个准则:

在设计js原型原型链的时候遵循以下两个准则:

准则一: 原型对象(即Fn.prototype)的 constructor 指向构造函数本身。

准则二: 实例对象(即 fn )的__proto__ 指向其构造函数的显示原型。

function Fn() {}
var fn = new Fn();
// 原型对象的 constructor 指向构造函数本身
console.log(Fn.prototype.constructor === Fn); // true
// 对象的隐式原型的值为其对应构造函数的显示原型的值
console.log(Fn.prototype === fn.__proto__); // true

理解Function与Object特例

每个函数都是 Function 的实例,所以每个函数既有显示原型又有隐式原型,所有函数的隐式原型指向 Function.prototype; 构造器Function的构造器是它自身。

// function Foo() {} 相当于 var Foo = new Function()
// Function = new Function() => Function.__proto__ = Function.prototype
// Object 作为构造函数时,其 __proto__ 内部属性值指向 Function.prototype
// Object.__proto__ = Function.prototype
// Function.constructor=== Function;//true

Object构造函数创建一个对象包装器。JavaScript中的所有对象都来自 Object,所有对象都是Object的实例;所有对象从Object.prototype继承方法和属性,尽管它们可能被覆盖。

// Fn的原型对象(Fn.prototype)也来自Object,故Fn.prototype.__proto__ = Object.prototype
function Fn() {}

原型链:

读取某个对象的属性时,会自动找到原型链中查找。

  • 1、现在自身属性中查找,找到返回
  • 2、找不到则继续沿着__proto__这条链向上查找,找到返回
  • 3、如果最终没有找到。返回undefined设置对象的属性值时,不会查找原型链,如果当前对象中没有此属性,直接添加此属性并设置其值方法一般定义在原型中,属性一般通过构造函数定义在对象本身

原型链就是一个过程,原型是原型链这个过程中的一个单位,贯穿整个原型链

图解

在这里插入图片描述

四、原型链练习

//练习题1
function A(){
}
A.prototype.n = 1;
var b = new A();
A.prototype = {
	n:2,
	m:3
}
var c = new A();
console.log(b.n,b.m,c.n,c.m)
// 1 undefined 2 3

// 测试题2
var F = function() {
};
Object.prototype.a = function() {
    console.log('a()');
};
Function.prototype.b = function() {
    console.log('b()');
};
var f = new F();
f.a(); // a()
f.b(); // 报错:Uncaught TypeError: f.b is not a function
F.a(); // a()
F.b(); // b()

原型、原型链的意义与使用场景:

原型对象的作用,是用来存放实例中共有的那部分属性、方法、可以大大减少内存消耗。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注猪先飞的更多内容!       

原文出处:https://blog.csdn.net/weixin_45740510/article/details/123191

[!--infotagslink--]

相关文章

  • 使用PHP+JavaScript将HTML页面转换为图片的实例分享

    这篇文章主要介绍了使用PHP+JavaScript将HTML元素转换为图片的实例分享,文后结果的截图只能体现出替换的字体,也不能说将静态页面转为图片可以加快加载,只是这种做法比较interesting XD需要的朋友可以参考下...2016-04-19
  • 关于JavaScript中name的意义冲突示例介绍

    在昨天的《Javascript权威指南》学习笔记之十:ECMAScript 5 增强的对象模型一文中,对于一段代码的调试出现了一个奇怪现象,现将源代码贴在下面: 复制代码 代码如下: <script type="text/javascript"> function Person(){}...2014-05-31
  • C#和JavaScript实现交互的方法

    最近做一个小项目不可避免的需要前端脚本与后台进行交互。由于是在asp.net中实现,故问题演化成asp.net中jiavascript与后台c#如何进行交互。...2020-06-25
  • javascript自定义的addClass()方法

    复制代码 代码如下: //element:需要添加新样式的元素,value:新的样式 function addClass(element, value ){ if (!element.className){ element.className = value; }else { newClassName = element.className; newClas...2014-05-31
  • JavaScript中的this关键字使用方法总结

    在javascritp中,不一定只有对象方法的上下文中才有this, 全局函数调用和其他的几种不同的上下文中也有this指代。 它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式。JavaScript 中函数的调用有以下...2015-03-15
  • 详解javascript数组去重问题

    首先,我想到的是另建一个结果数组,用来存储原始数组中不重复的数据。遍历原始数组依次跟结果数组中的元素进行比较,检测是否重复。于是乎,我写出了如下代码A: Array.prototype.clearRepetitionA = function(){ var resul...2015-11-08
  • JavaScript中逗号运算符介绍及使用示例

    有一道js面试题,题目是这样的:下列代码的执行结果是什么,为什么? 复制代码 代码如下: var i, j, k; for (i=0, j=0; i<10, j<6; i++, j++) { k = i+j; } document.write(k); 答案是显示10,这道题主要考察JavaScript的逗...2015-03-15
  • javascript的事件触发器介绍的实现

    事件触发器从字面意思上可以很好的理解,就是用来触发事件的,但是有些没有用过的朋友可能就会迷惑了,事件不是通常都由用户在页面上的实际操作来触发的吗?这个观点不完全正确,因为有些事件必须由程序来实现,如自定义事件,jQue...2014-06-07
  • Javascript类型转换的规则实例解析

    这篇文章主要介绍了Javascript类型转换的规则实例解析,涉及到javascript类型转换相关知识,对本文感兴趣的朋友一起学习吧...2016-02-27
  • ActiveX控件与Javascript之间的交互示例

    1、ActiveX向Javascript传参 复制代码 代码如下: <script language="javascript" for="objectname" event="fun1(arg)"> fun2(arg); </script> objectname为ActiveX控件名,通过<object>标签里的id属性设定,如下; 复制...2014-06-07
  • 详解JavaScript操作HTML DOM的基本方式

    通过 HTML DOM,可访问 JavaScript HTML 文档的所有元素。 HTML DOM (文档对象模型) 当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model)。 HTML DOM 模型被构造为对象的树: 通过可编程的对象模型,Java...2015-10-23
  • JavaScript获取浏览器信息的方法

    Window有navigator对象让我们得知浏览器的全部信息.我们可以利用一系列的API函数得知浏览器的信息.JavaScript代码如下:function message(){ txt = "<p>浏览器代码名: " + navigator.appCodeName + "</p>";txt+= "<p>...2015-11-24
  • 跟我学习javascript的最新标准ES6

    虽然ES6都还没真正发布,但已经有用ES6重写的程序了,各种关于ES789的提议已经开始了,这你敢信。潮流不是我等大众所能追赶的。潮流虽然太快,但我们不停下学习的步伐,就不会被潮流丢下的,下面来领略下ES6中新特性,一堵新生代JS...2015-11-24
  • javascript设计模式之解释器模式详解

    神马是“解释器模式”?先翻开《GOF》看看Definition:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。在开篇之前还是要科普几个概念: 抽象语法树: 解释器模式并未解释如...2014-06-07
  • JavaScript预解析,对象详解

    这篇文章主要介绍了JavaScript预解析,对象的的相关资料,小编觉得这篇文章写的还不错,需要的朋友可以参考下,希望能够给你带来帮助...2021-11-10
  • 学习JavaScript设计模式之装饰者模式

    这篇文章主要为大家介绍了JavaScript设计模式中的装饰者模式,对JavaScript设计模式感兴趣的小伙伴们可以参考一下...2016-01-21
  • JavaScript操作URL的相关内容集锦

    ---恢复内容开始---1.location.href.....(1)self.loction.href="http://www.cnblogs.com/url" window.location.href="http://www.cnblogs.com/url" 以上两个用法相同均为在当前页面打开URL页面 (2)this.locati...2015-10-30
  • javascript实现tab切换的四种方法

    tab切换在网页中很常见,故最近总结了4种实现方法。 首先,写出tab的框架,加上最简单的样式,代码如下: <!DOCTYPE html> <html> <head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><style> *{ pa...2015-11-08
  • JavaScript学习笔记整理_setTimeout的应用

    下面小编就为大家带来一篇JavaScript学习笔记整理_setTimeout的应用。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2016-10-03
  • 基于JavaScript如何实现私有成员的语法特征及私有成员的实现方式

    前言在面向对象的编程范式中,封装都是必不可少的一个概念,而在诸如 Java,C++等传统的面向对象的语言中, 私有成员是实现封装的一个重要途径。但在 JavaScript 中,确没有在语法特性上对私有成员提供支持, 这也使得开发人员使...2015-10-30