一文搞懂JavaScript中原型与原型链

目录
  • 1.构造函数原型prototype
  • 2.对象原型__proto__
  • 3.constructor构造函数
  • 4.原型链
  • 5.原型对象中的this指向
  • 6.扩展内置对象(原型对象的应用)

在ES6之前,我们面向对象是通过构造函数实现的。我们把对象的公共属性和方法放在构造函数里

像这样:

function student(uname,age) {
    this.uname = uname;
    this.age = age;
    this.school = function() {
        console.log('深圳理工大学');
    }
}
var stu1 = new student('小红',18);
var stu2 = new student('小紫',20);

但是构造函数方法虽然好用,可他存在浪费内存的问题。比如在上例中在构造函数里我们有一个函数,函数属于复杂数据类型,他会单独在内存中开辟一个空间。在这里我们创建了两个实例化对象,那么就会开辟两个内存空间来存放同一个函数,那就造成了内存浪费的问题。

1.构造函数原型prototype

构造函数通过原型分配的函数是所有对象所共享的。

JavaScript规定,每一个构造函数都有一个prototype属性, 指向另一个对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。

我们可以把那些不变的方法,直接定义在prototype对象上,这样所有对象的实例就可以共享这些方法。

所以在上例中我们就可以把公共函数放在原型对象的里面,这样就不会造成内存浪费

function student(uname,age) {
    this.uname = uname;
    this.age = age;
}
student.prototype.school = function() {
    console.log('深圳理工大学');
}
var stu1 = new student('小红',18);
var stu2 = new student('小紫',20);

那么我们就明白了原型是什么,他就是一个对象,我们称prototype为原型对象

原型的作用是什么呢?四个字,共享方法

2.对象原型__proto__

对象都会有一个属性__proto__指向构造函数的 prototype原型对象,之所以我们对象可以使用构造函数prototype原型对象的属性和方法,就是因为对象有_ proto_ 原型的存在。

在对象身上系统自己添加了一个__proto__指向我们构造函数的原型对象,所以__proto__对象原型和原型对象prototype是等价的。

我们验证一下,看看会输出什么:

console.log(stu1.__proto__ === student.prototype);

最后输出是true证明对象原型__proto__和原型对象protptype是等价的

所以方法的查找规则就是首先看stu1对象身上是否有school方法,如果有就执行这个对象上的school,如果没有school这个方法, 因为有__proto__的存在,就去构造函数原型对象prototype身上去查找school这个方法

如果觉得还是不太懂,一图搞懂你的疑问:

3.constructor构造函数

对象原型(__proto__) 和构造函数( prototype )原型对象里面都有一个属性 constructor属性, constructor我们称为构造函数,因为它指回构造函数本身。

我们打印一下student.prototype和stu1.__type__看看是否存在constructor属性:

console.log(student.prototype);
console.log(stu1.__proto__);

可以看到这里面都有constructor,那constructor有什么作用呢?

constructor主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数

我们知道在原型对象中我们可以定义那些公共的方法,但是如果公共的方法很多呢,我们通过对象的形式存储就方便多了:

student.prototype = {
    school:function() {},
    location:function() {}
}

然后我们输出一下原型对象的constructor看看有没有变化:

console.log(student.prototype.constructor);
console.log(stu1.__proto__.constructor);

最后的输出结果是这样:

这是为什么呢,constructor不应该指向我们的student构造函数吗

因为我们通过student.prototype.school这样属于往原型对象里添加方法,但是我们刚才属于赋值,这样就把原先prototype里的东西都覆盖掉了,这样student.prototype就没有constructor了,没有constructor自然就指回不了student构造函数了

这个时候我们就需要手动的利用constructor这个属性指回原来的构造函数

student.prototype = {
    constructor:student,
    school:function() {},
    location:function() {}
}

我们再输出一下原型对象的constructor:

这样我们就知道这个对象到底是通过哪个构造函数创建出来的了

现在我们更新一下构造函数、实例、原型对象三者关系图:

4.原型链

因为student原型对象也是一个对象,我们之前说了只要是对象就有对象原型的存在

那我们打印一下原型对象看看里面是否有原型:

function student(uname,age) {
    this.uname = uname;
    this.age = age;
}
student.prototype.school = function() {
    console.log('深圳理工大学');
}
var stu1 = new student('小红',18);
console.log(student.prototype);

输出结果:

可以看到原型对象里也有一个原型,又因为原型指向的是原型对象,那么我们这个student.prototype里面的__proto__指向的是谁呢?

我们打印一下:

console.log(student.prototype.__proto__);

可以看到指向的是这个constructor指向的是Object原型对象

Object原型对象是由谁创建出来的呢,毫无疑问是Object构造函数创建出来的。那么我们继续,Object原型对象也是一个对象,那它也有一个原型,这个原型指向的又是谁呢?

我们输出一下:

console.log(Object.prototype.__proto__);

最后的结果为空:

这样就到了最顶层了,这样我们把这些串起来就能得到一个原型链:

5.原型对象中的this指向

我们知道在构造函数中的this指向的是对象实例,那么原型对象里的函数,这个函数里的this指向的是谁呢?

我们声明一个全局变量that,把原型对象里的this赋给that,看看这个that指向的是不是实例对象:

function student(uname,age) {
    this.uname = uname;
    this.age = age;
}
var that;
student.prototype.school = function() {
    that = this;
    console.log('深圳理工大学');
}
var stu1 = new student('小红',18);
stu1.school();
console.log(that === stu1);

输出结果:

所以原型对象函数里面的this指向的也是实例对象stu1

6.扩展内置对象(原型对象的应用)

我们可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组增加自定义求和的功能

我们输出一下数组的原型对象,看看里面有什么方法:

console.log(Array.prototype);

这里没有给数组自定义求和的函数,那么我们往数组的原型对象里添加这个方法:

Array.prototype.sum = function() {
    var sum = 0;
    for(var i = 0;i<this.length;i++)
    {
        sum += this[i];
    }
    return sum;
}
var ss = new Array(4,5,3,6);
console.log(ss.sum());

我们自定义的sum方法里,this.length指的就是调用这个方法的数组的长度,因为在上一节中我们知道原型对象函数里面的this指向的也是实例对象

我们通过new方法创建一个数组实例对象,我们向数组的原型对象添加sum方法,那么我们的实例对象就可以调用它。

输出结果:

我们再打印一下数组的原型对象,看看里面有没有sum方法:

可以看到sum成功的添加到数组的原型对象里了,这样我们继续用到数组求和时,就可以直接调用sum方法了。

到此这篇关于一文搞懂JavaScript中原型与原型链的文章就介绍到这了,更多相关JS原型与原型链内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 一文彻底理解JavaScript原型与原型链

    目录 前言 new对函数做了什么? 原型和原型链 借用原型方法 实现构造函数之间的继承 方案一 方案二 class extends实现继承 结语 前言 JavaScript中有许多内置对象,如:Object, Math, Date等.我们通常会这样使用它们: // 创建一个JavaScript Date实例 const date = new Date(); // 调用getFullYear方法,返回日期对象对应的年份 date.getFullYear(); // 调用Date的now方法 //

  • JavaScript原型链及常见的继承方法

    目录 原型链 原型链的概念 原型链的问题 几种常见的继承方法 盗用构造函数 组合继承 原型式继承 寄生式继承 寄生组合式继承 原型链 原型链的概念 在JavaScript中,每一个构造函数都有一个原型,这个原型中有一个属性constructor会再次指回这个构造函数,这个构造函数所创造的实例对象,会有一个指针(也就是我们说的隐式原型__proto__或者是浏览器中显示的[[Prototype]])指向这个构造函数的原型对象.如果说该构造函数的原型对象也是由另外一个构造函数所创造的实例,那么该构造

  • JavaScript原型和原型链与构造函数和实例之间的关系详解

    目录 原型 原型链 原型 如图所示: 1.instanceof检测构造函数与实例的关系: function Person () {.........} person = new Person () res = person instanceof Person res  // true 2.实例继承原型上的定义的属性: function Person () {........} Person.prototype.type = 'object n' person = new Person () re

  • JavaScript三大重点同步异步与作用域和闭包及原型和原型链详解

    目录 1. 同步.异步 2. 作用域.闭包 闭包 作用域 3. 原型.原型链 原型(prototype) 原型链 如图所示,JS的三座大山: 同步.异步 作用域.闭包 原型.原型链 1. 同步.异步 JavaScript执行机制,重点有两点: JavaScript是一门单线程语言 Event Loop(事件循环)是JavaScript的执行机制 JS为什么是单线程 最初设计JS是用来在浏览器验证表单操控DOM元素的是一门脚本语言,如果js是多线程的,那么两个线程同时对一个DOM元素进行了相互冲突

  • JavaScript原型与原型链深入探究使用方法

    目录 原型(prototype) 显示原型与隐式原型 原型链 原型链属性问题 原型链 instanceof 使用 练习 原型(prototype) 每一个函数都有一个 prototype 属性,它默认指向一个Object空对象(即称为:原型对象). <script> console.log(Date.prototype, typeof Date.prototype); function fun(){ } fun.prototype.test = function(){ //给原型对象添加一个方

  • Javascript 原型与原型链深入详解

    目录 前言 对象 原型 原型链 javascript中的类 new的实现 instanceof的实现 javascript的继承 总结 前言 在前端这块领域,原型与原型链是每一个前端 er 必须掌握的概念.多次在面试或者一些技术博客里面看见这个概念.由此可见,这个玩意对于前端来说有多重要.其实它本身理解起来不难,但是很多刚入行前端的同学,看到prototype.__proto__理解起来还是有点吃力,然后脑子里面就乱成一锅粥,就像我一样. 但是这是很正常的事情,没什么大不了的,就像我们想要学会跑

  • 三张图带你搞懂JavaScript的原型对象与原型链

    对于新人来说,JavaScript的原型是一个很让人头疼的事情,一来prototype容易与__proto__混淆,二来它们之间的各种指向实在有些复杂,其实市面上已经有非常多的文章在尝试说清楚,有一张所谓很经典的图,上面画了各种线条,一会连接这个一会连接那个,说实话我自己看得就非常头晕,更谈不上完全理解了.所以我自己也想尝试一下,看看能不能把原型中的重要知识点拆分出来,用最简单的图表形式说清楚. 我们知道原型是一个对象,其他对象可以通过它实现属性继承.但是尼玛除了prototype,又有一个__

  • JavaScript原型链中函数和对象的理解

    目录 __ proto__ prototype.__ proto__ 理解 __ proto__ 最近在看高程4,原型链肯定是绕不过的,本瓜之前一直认为,只要记住这句话就可以了: 一个对象的隐式原型(__proto__)等于构造这个对象的构造函数的显式原型(prototype) 确实,所有对象都符合这句真理,在控制台打印一试便知: const str = new String("123") str.__proto__ === String.prototype // true const

  • 一文搞懂JavaScript中原型与原型链

    目录 1.构造函数原型prototype 2.对象原型__proto__ 3.constructor构造函数 4.原型链 5.原型对象中的this指向 6.扩展内置对象(原型对象的应用) 在ES6之前,我们面向对象是通过构造函数实现的.我们把对象的公共属性和方法放在构造函数里 像这样: function student(uname,age) { this.uname = uname; this.age = age; this.school = function() { console.log('

  • 一文搞懂JavaScript中bind,apply,call的实现

    目录 bind.call和apply的用法 bind call&apply 实现bind 实现call和apply 总结 bind.call和apply都是Function原型链上面的方法,因此不管是使用function声明的函数,还是箭头函数都可以直接调用.这三个函数在使用时都可以改变this指向,本文就带你看看如何实现bind.call和apply. bind.call和apply的用法 bind bind()方法可以被函数对象调用,并返回一个新创建的函数. 语法: function.bin

  • 一文搞懂JavaScript中的内存泄露

    目录 什么是内存泄漏 怎么检测内存泄漏 Performance Memory 内存泄漏的场景 垃圾回收算法 引用计数 循环引用 标记清除 闭包是内存泄漏吗 总结 以前我们说的内存泄漏,通常发生在后端,但是不代表前端就不会有内存泄漏.特别是当前端项目变得越来越复杂后,前端也逐渐称为内存泄漏的高发区.本文就带你认识一下Javascript的内存泄漏. 什么是内存泄漏 什么是内存?内存其实就是程序在运行时,系统为其分配的一块存储空间.每一块内存都有对应的生命周期: 内存分配:在声明变量.函数时,系统分

  • 一文带你搞懂JavaScript中的进制与进制转换

    目录 进制介绍 进制转换 parseInt(str, radix) Number() +(一元运算符) Number.prototype.toString(radix) 自定义转换 十进制与十六进制转换 十进制和二进制转换 进制介绍 JavaScript 中提供的进制表示方法有四种:十进制.二进制.十六进制.八进制. 对于数值字面量,主要使用不同的前缀来区分: 十进制(Decimal):取值数字 0-9:不用前缀. 二进制(Binary):取值数字 0 和 1 :前缀 0b 或 0B. 十六进制

  • 一文搞懂ES6中的Map和Set

    Map Map对象保存键值对.任何值(对象或者原始值) 都可以作为一个键或一个值.构造函数Map可以接受一个数组作为参数. Map和Object的区别 •一个Object 的键只能是字符串或者 Symbols,但一个Map 的键可以是任意值. •Map中的键值是有序的(FIFO 原则),而添加到对象中的键则不是. •Map的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算. •Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突.

  • 一文搞懂Spring中的Bean作用域

    目录 概述 Singleton prototype request session application 概述 scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其 相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象. Spring容器bean的作用域类型: singleton:Spring IoC 容器的单个对象实例作用域都默认为singleton prototype:针对声明为拥有prototyp

  • 一文搞懂Spring中的注解与反射

    目录 前言 一.内置(常用)注解 1.1@Overrode 1.2@RequestMapping 1.3@RequestBody 1.4@GetMapping 1.5@PathVariable 1.6@RequestParam 1.7@ComponentScan 1.8@Component 1.9@Service 1.10@Repository 二.元注解 @Target @Retention @Documented @Inherited 三.自定义注解 四.反射机制概述 4.1动态语言与静态语

  • 一文搞懂JavaScript如何实现图片懒加载

    目录 实现思路 准备知识 data-* getBoundingClientRect() throttle window.innerHeight 完整代码 js部分 CSS部分 运行结果 总结 图片懒加载,往往作为减少首页白屏时间的一个解决方案而出现.直观的来说,就是不要直接加载所有图片,而是满足一定条件后才加载,也就是”惰性加载“.实现图片懒加载的方式有很多,如果要简单点那就直接使用第三方插件:vue-lazyload,如果想探究一下别人的插件是怎么实现图片懒加载的,那么可以看看本文是如何实现的

  • 一文搞懂C++中string容器的构造及使用

    目录 string容器 string基本概念 string构造函数 string赋值操作 string拼接操作 string查找替换 string字符串比较 string字符读取 string插入和删除 string求子串 string容器 string基本概念 本质: string是c++风格的字符串,不同于c语言的 char*,他本质是一个类 string 和 char*的区别: char*是一个指针 string是一个类,类内部封装了char*来管理字符串,是一个char&型的容器 特点:

  • 彻底搞懂JavaScript中的apply和call方法(必看)

    call和apply都是为了改变某个函数运行的context上下文而存在的,即为了改变函数体内部this的指向.因为JavaScript的函数存在定义上下文和运行时上下文以及上下文是可以改变的概念. 回到目录定义 fun.apply(thisArg, [argsArray]) fun.call(thisArg, arg1,arg2, ...) 其中thisArg可以为null或undefined,此时表示全局对象,更详细见MDN:apply().call() 二者的作用完全一样,只是接受参数的方

随机推荐