本文已收录于专栏 ⭐️ 《JavaScript》⭐️

学习指南:

  • 对象的原型
  • 函数的原型
    • new操作符
    • 将方法放原型里
    • constructor
  • 总结梳理
    • 原型对象
    • 内存表现
  • 完结散花
  • 参考文献

对象的原型

JavaScript 当中每个对象都有一个特殊的内置属性[[prototype ]] ,这个特殊的对象可以指向另外一个对象。
那这个对象有什么用呢?

  • 当我们通过引用对象的属性Key 来获取一个value时,它会触发[[Get]]的操作;
  • 这个操作会首先检查该对象是否有对应的属性,如果有点话就使用它。
  • 如果对象中没有该属性,那么会访问对象的内置属性[[prototype]],在此属性指向的对象中查找是否有该属性。
  • 只要是对象都会都有这样的一个内置属性。

属性 [[Prototype]] 是内部的⽽且是隐藏的,所有我们可获取的方式有两种:

  • 方式一:通过对象的_proto_属性可以获取到(早期路由器自己添加的,存在一定的兼容性问题)
  • 方式二:通过Object.getPrototypeOf 方法可以获取到。

函数的原型

  1. 将函数看成是一个普通函数的对象时,它是具备[[Prototype]]或者说是_proto_(隐式原型)。
var obj = {}
function fun(){}console.log(obj._proto_)
console.log(fun._proto_)
  1. 将函数看成是一个普通函数时,它是具备prototype属性的。(显式原型)
var obj = {}
function fun(){}console.log(fun.prototype)
// console.log(obj.prototype) 对象是没有prototype属性的。

所以 只有函数才具有prototype属性。
注意这里的prototype和[[prototype]]是完全不同的两个概念。

new操作符

new Person();

new完 之后会发生什么?

var obj = {};
  • 创建空对象。
this = obj;
  • 将这个空对象赋值给this
obj._proto_ = Person.prototype
  • 将函数的显示原型赋值给创建的这个空对象的_proto_属性作为它的隐式原型。
  • 执行函数体中的代码。
  • 将这个对象默认返回。

再举一个例子来理解一下。

function foo(){}
console.log(foo.prototype);// 先打印一下 foo 属性
var fun = new foo();// 创建空的对象// 将foo的prototype原型(显示原型)赋值给空对象的_proto_(隐式原型)
console.log(fun._proto_);console.log(foo.prototype === fun._proto_)// true

将方法放原型里

function Student(name,age,sno){this.name = name;this.age = age;this.sno = sno;this.running = function(){console.log(this.name +" running");}this.eating = function(){console.log(this.name +" eating");}this.studying = function(){console.log(this.name +" studying");}
}var stu1 = new Student("why",18,111);
var stu2 = new Student("kobe",16,112);
var stu3 = new Student("jame",15,113);

之前我们编写多个函数方法的时候,会直接在对象中创建。
但我们发现在每调用一次函数时,都会同时创建多个相同的函数对象。
比如上面的例子中三个函数对象runningeatingstudying都分别创建了三次。

那有没有一种方法让我们每种对象只需要创建一次,然后共享这些属性呢?
答案就将函数方法放到显示原型里面!

function Student(name,age,sno){this.name = name;this.age = age;this.sno = sno;
}
Student.prototype.running = function(){console.log(this.name + "running")}
Student.prototype.eating = function(){console.log(this.name +" eating");}
Student.prototype.studying = function(){console.log(this.name +" studying");}
var stu1 = new Student("why",18,111);
var stu2 = new Student("kobe",16,112);
var stu3 = new Student("jame",15,113);
Student.prototype.running = function()
  • 由构造函数创建出来的所有对象,都会共享这些属性,而且每种只创建一次。

查找原理

  • 先在对象内部进行查找。
  • 如果没有找到,就去原型里查找。

那可不可以将其他属性也放到原型里面?
答案是否定的。
因为每个对象的属性值都是不一样的,而原型只有一个。

constructor

事实上原型对象上面是有一个属性的:constructor

  • 默认情况下原型上都会添加一个属性叫做 constructor,这个constructor指向当前的函数对象。
  • Person === Person.prototype.constructor
function Person{}
console.log(Person)//[Function:Person]
console.log(Person.prototype.constructor); //[Function:Person]
console.log(p1.__proto__.constructor);//[Function:Person]
console.log(p1.__proto__.constructor);// Person
function Person(name,age){this.name = name;this.age = age;
}

对应内存图如下:

  • 创建出来的对象中的prototype属性指向了显示原型对象的constructor

function Person(name,age){this.name = name;this.age = age;
}
var p1 = new Person("why",18);
var p2 = new Person("kobe",30);console.log(p1.name);
console.log(p2.name);
  • new出来的两个新的空对象的prototype属性指向了显示原型对象的constructor

function Person(name,age){this.name = name;this.age = age;
}
//新增方法
Person.prototype.running = function(){console.log("running");
}
var p1 = new Person("why",18);
var p2 = new Person("kobe",30);p1.running();
p2.running();
  • 在Person的原型上添加了running函数,于是新开辟的running这块内存也指向了Person显式原型对象。+

function Person(name,age){this.name = name;this.age = age;
}
Person.prototype.running = function(){console.log("running");
}
var p1 = new Person("why",18);
var p2 = new Person("kobe",30);console.log(p1.name);
console.log(p2.name);p1.running();
p2.running();//新增属性
Person.protptype.address = "中国"
p1.__proto__.info = "中国很美丽!"p1.height = 1.88p2.isAdmin = true;
  • 在原型上添加新的属性并赋值,Person显式原型对象的内存中同样也声明了新增的这些属性。
  • p1对象中新增了height属性,在p2对象中新增了isAdmin属性。

function Person(name,age){this.name = name;this.age = age;
}
Person.prototype.running = function(){console.log("running");
}
var p1 = new Person("why",18);
var p2 = new Person("kobe",30);console.log(p1.name);
console.log(p2.name);p1.running();
p2.running();//新增属性
Person.protptype.address = "中国"
p1.__proto__.info = "中国很美丽!"p1.height = 1.88p2.isAdmin = true;// 修改address
p1.address = "河北省"
console。log(p2.address)
  • Person显式原型对象中的 address属性并未被覆盖,而是被加到了p1对象里面。
  • p2.address打印的仍然是 Person显式原型对象中的 中国

Person.prototype.message = "Hello Person";Person.prototype.info = {name:"沈七",age:30};Person.prototype.running = function(){};Person.prototype.eating = function(){};

我们也可以通过直接赋值一个新的原型对象来简写上面代码。

Person.prototype = {message:"Hello Person",info:{name:"沈七",age:30},running:function(){}eating:function(){}constructor:Person
}

总结梳理

隐式原型

  • JavaScript每一个对象都有[[prototype]]属性,它的属性值是对某个特定对象的引用。
  • 我们把这里的“某个特定对象”称为该实例对象的原型,也称之为隐式原型。
  • [[prototype]]属性是内部且是隐藏的,所以我们需要__proto__属性来操作[[prototype]]属性,因为__proto__属性存在于每一个对象当中且允许被访问。

显式原型

  • 对于每一个函数对象(非箭头函数)其都有prototype属性,被叫做显式原型。
  • prototype也会指向一个对象,这个对象的所有属性和方法都会被构造函数的实例所继承,这对象被成为原型对象。

两者之间的关系
每一个实例对象都通过__proto_指向它的构造函数的原型对象。

这是因为new操作符的底层实现决定的。

  • 当一个对象通过构造函数 new出实例后
  • 该对象prototype显式原型会通过__proto__赋值给[[prototype]]的隐式原型。
obj._proto_ = Person.prototype

注意[[prototype]]prototype是完全不同的两个属性.

原型对象

原型对象里面都有两个属性:__proto__constructor

  • constructor:用来记录实例对象是由哪个构造函数创建的,所以它默认指向创建它的构造函数。
  • __proto__:原型对象也是对象,所以它也有__proto__属性。

内存表现

function Person(){}
var p1 = new Person();

  • Personprototype属性会指向它的显式原型,即Person函数的原型对象
  • Person函数的原型对象constructor属性会指向创建它的构造函数,即Person
  • new出来的实例对象p1的隐式原型__proto__会自动指向创建它的构造函数的显式原型,即Person函数的原型对象
console.log(p1._proto == Person.prototype)//true
  • 因为Person函数的原型对象本身也是一个对象,所以是由Object``new出来的。
  • 所以Person函数的原型对象的隐式原型__proto__属性指向Obejct的原型对象的显式原型。
console.log(Person.prototype.__proto__ == Obejct.prototype)//true
  • Object作为顶级父类,它的原型对象的隐式原型__proto__属性指向null
console.log(Object.prototype.__proto == null)// true

Person对象是由谁创建出来的呢?,它的隐式原型又指向谁?

我们来打印一下试试。

function Person(){}
console.log(Person.__proto__)


我们发现Person.__proto__实际上是指向Function.prototety的。
也就是说:

  • Function是所有直接声明的函数的构造函数。
  • 所有直接声明的函数都是Function的一个实例对象。
  • 所有直接声明的函数的__proto__都指向同一个地方那就是Function.prototety.

我们可以进一步验证:

function Person(){}
function foo(){}
console.log(Person.__proto__==Function.prototype);// true
console.log(foo.__proto__==Function.prototype);   // true
console.log(foo.__proto__==Person.prototype);     // true

因为Function函数的原型对象本身也是一个对象,所以Function函数的原型对象的隐式原型__proto__也指向Obejct的显式原型.

console.log(Function.prototype.__proto__  == Object.prototype);// true

Obejct本身也是一个函数,所以它的隐式原型__proto__指向Function函数的原型对象的显式原型。

console.log(Object.__proto__ == Function.prototype);// true

Function的本身也是一个函数,所以它的隐式原型__proto__指向它自己的Function函数的原型对象的显式原型。

console.log(Function.__proto__  == Function.prototype);// true

于是就有了下面这张图。

小结:

  • p1Person的实例对象。
  • objObject的实例对象。
  • Function/Object/FOO都是Function的实例对象。
  • 原型对象默认创建时,隐式原型都是指向Object的显式原型的。(Object指向null)

又因为:
当A类的原型对象的隐式原型__proto__指向B类的显式对象时,我们称之为A类是继承于B类的。

if(A.prototype.__proto__ == B.prototype){A 继承于 B
}

推到出的结论:

  • ObjectPerson/Function的父类

FunctionObject的关系:

  • ObjectFunction 的父类。
  • FunctionObject的构造函数。

完结散花

ok以上就是对 JavaScript高级 |彻底搞懂原型对象 的全部讲解啦,很感谢你能看到这儿。如果有遗漏、错误或者有更加通俗易懂的讲解,欢迎小伙伴私信我,我后期再补充完善。

参考文献

coderwhy老师JS高级视频教程

JavaScript高级 |彻底搞懂原型对象相关推荐

  1. java 原型图_一张图搞懂原型、原型对象、原型链

    基本概念 在javascript中,函数可以有属性. 每个函数都有一个特殊的属性叫作原型(prototype) 每个对象拥有一个原型对象 [[Prototype]] / __proto__ / Obj ...

  2. 猿创征文|在工作中彻底搞懂原型和原型链的原理

    前言 在前端开发过程中,涉及到JS原理相关的内容也就是常用的几大模块,不仅常用而且很重要,但是涉及到原理的话会有点难懂,尤其是对JS接触不太久的开发者来讲,甚至工作好几年的开发者也只是在平时开发中知道 ...

  3. JavaScript高级之构造函数和原型

    1.1 概述 在典型的 OOP 的语言中(如 Java),都存在类的概念,类就是对象的模板,对象就是类的实例,但在 ES6之前, JS 中并没用引入类的概念. ES6, 全称 ECMAScript 6 ...

  4. JavaScript——面向对象之继承(原型对象)与多态(重载、重写)

    继承与多态 引入问题 一.继承 1. 步骤 (1) 找到所有子对象共同的父对象 (2) 将所有子对象公共的方法定义添加到共同的父对象中 (3) 所有子对象因继承关系而直接使用父对象中公共的方法 2. ...

  5. 弄懂原型对象和原型链

    这些都是自己在学习的过程中整理出来的笔记,希望能帮到大家.因为自己也是个前端菜鸟,只是最前端有很浓厚的兴趣,想往这个方面发展,如果有整理不对的地方,请大家斧正. 首先我们需要知道原型是什么? Java ...

  6. 一篇文章让你搞懂原型和原型链

    每一个构造函数在被创建的时候,会自动创建一个相应的对象,这个对象就是原型对象,这个函数有一个指向该对象的指针.举个例子: 下面创建了一个函数person. function person () { } ...

  7. Javascript深入理解构造函数和原型对象

    1.在典型的oop的语言中,如java,都存在类的概念,类就是对象的模板,对象就是类的实例.但在js中不存在类的概念,js不是基于类,而是通过构造函数(constructor)和原型链(propoty ...

  8. JavaScript 进阶 35 -- 构造函数、原型对象、实例之间的关系详解

    说到继承 ,先要弄明白 :构造函数.原型对象.实例之间的关系. 先来了解几个简单的概念: 构造函数:通过 关键字 new 来调用的 函数,var p = new Person():Person就称为构 ...

  9. 设计模式系列:搞懂原型模式,你也会分身

    原型(Prototype)模式的定义:用一个已经存在的对象实例作为原型,通过复制该原型对象来创建一个和原型对象相同或相似的新对象.属于建造型模式. 原型模式的结构:原型模式主要包含3种角色. 抽象原型 ...

最新文章

  1. How to use nheqminer in RedHat based systems (CentOS/Fedora)
  2. some requirement checks failed
  3. asp.net 的page 基类页面 做一些判断 可以定义一个基类页面 继承Page类 然后重写OnPreLoad事件...
  4. 听说,他用报表关联数据库表,运维效率提升70%?
  5. 直播实录丨十年主导15个产品从0到1,她的经验与思考现场拆解
  6. 计算机网络怎样连手机软件,玩够了手机投屏?了解一下手机如何直接连智能电视...
  7. arcgis 批量计算几何_GIS中的计算几何
  8. 利用js完成根据excel填充网页表单
  9. tp5 微信分享朋友或朋友圈
  10. 学校运动会开幕式邀请函
  11. 解决k8s中的长连接负载均衡问题
  12. 自定义注解校验List集合数据
  13. 新手如何学习java
  14. 获取网络图片或本地图片的长宽的方法
  15. 几计算机网络特,湛江理工职业学校1级MS0ffice了解计算机网络的基本概念和因特网...
  16. (十八)师大放假了 - 7
  17. h5 or web缅甸语乱码问题
  18. 寄存器某一位置位或者清零
  19. php易信短信接口,易信公众平台demo代码php(含验证接口)
  20. 图片采集器-网页图片批量采集器免费

热门文章

  1. PHP_Ajax编程(1)
  2. 简单了解事件冒泡和阻止事件冒泡
  3. J-Link配合S32DS IDE使用时遇到的问题总结
  4. 函数(Fuctions)
  5. 一文搞懂极大似然估计
  6. 时间的对错 人的对错
  7. python 删除文件夹下的所有文件
  8. 视觉里程计的重定位问题1——SVO的重定位部分
  9. ubuntu自带游戏_Ubuntu安装游戏Wesnoth的步骤
  10. RabbitMQ-ack、nack、reject、unacked