js-原型与原型链

105 阅读6分钟

有bug我不说,测试提了再改!大家好,我是以一己之力养活全公司测试的狗前端---曹八哥

前端面试老是被问到什么是原型,什么是原型链哈! 一直搞不明白的童鞋们,跟我一起来扒一扒。 我将学习原型和原型链的过程写成了 <<找对象5部曲>>

21.jpeg

  1. 了解对象(object),追对象第一件事得去了解她,不然怎么投其所好?
  2. 对象怎么来的(创建对象)?对象不是天上掉的,或相亲,或自由恋爱,或网恋奔现?
  3. 了解未来丈母娘(原型prototype),要搞定对象,还得搞定丈母娘,毕竟她手里拽着户口本呢😜
  4. 怎么和未来老丈人丈母娘保持联系? 得记住地址、联系方式呀!(__proto__prototypeconstructor)
  5. 了解对象的家庭史(原型链) ok,当一切尽在掌握之中,结果必然是...

22.jpeg

结果我TM怎么知道?我又没有谈过恋爱😭

哈哈哈...

言归正传,正文开始


1.什么是JS对象(object)

1)对象是属性(变量)和方法(函数)的容器

2)对象通过运算符点【.】来调用它的属性和方法,如

访问对象:objectName.propertyName  

访问方法:objectName.mathodName() 3)js中万物皆对象,数字是对象,字符串是对象、函数是对象,类也是对象

23.png

记住上面的三点定义,通过上述图片中的方法可以说明:js中所有事物都是对象。

标准的对象,都有很多隐式的属性和方法,包括 __proto__constructor

本文主要讲的内容都是关于标准的对象

难道还有不标准的对象?

这里简单的提一下,比如说nullundefinedGlobal

  • null表示无,即此处此时不应该有值
  • undefined是指此处应该有个值,但是尚未定义
  • Global对象是js内置对象,也是js中最特殊的对象,因为它本身是不存在的,不能通过new创建出来,有且只有一个,还不能直接拿出来调用

在ECMAScript中,不存在独立的函数,所有函数都必须是某个对象的方法

因此在js中将一些内置的构造函数: Object、Array、Function、Boolean、String、Number、Date、RegExp、Error等等,一些特殊属性:undenfined、NaN、Infinity等 和本地对象的构造函数、全局变量等都放到了对象Global里,不需要通过对象来调用,直接使用就行

如:new Array()parseInt()

因此,凡是没有通过 【对象.方法】 的方式来调用的方法都是Global对象的方法

2.如何创建对象

网上一搜人家能给你列举出7、8、9、10种,这种死扣区别列举出一大堆方法,个人感觉真的很没有必要,其实大致分为三种(或4种)就行

  1. 字面量方式(最常用)
  2. new关键字动态创建方式
    • 构造函数创建
    • 类(ES6引入)创建
  3. Object.create() 方式
//1.字面量方式 
let a = {name:'a'} 

//2.new关键字动态创建方式 
//2.1 构造函数创建方式 
function B(name) { 
    this.name = name 
} 
let b = new B("b") 

//2.2 类创建方式 
class C { 
    constructor(name){//类中的构造函数名字必须是constructor 
        this.name = name 
    } 
} 
let c = new C("c") 

//3. Object.create() 方式 
let d = Object.create({ name:"d" })

浏览器控制台展示:

24.png

3.什么是原型prototype

ES6之前,JS是没有类class的概念的,但是又想要类这样的继承功能,于是在声明构造函数时默认实例化了一个对象,这个对象就是原型Prototype,在Prototype上定义的属性将会被该构造函数实例化的对象所继承。相当于这一系列对象的公用变量的容器

25.png

4.对象的__proto__ 、 constructor属性 和 构造函数的属性prototype

一个对象创建成功后,默认存在很多属性和方法

26.png

其中:

constructor: 指向该对象的构造函数,对象不是凭空出现的,标准对象都由其对应的构造器(也叫构造函数)去实例化它,该属性就是该对象指向构造器的引用

__proto__: 一般指向该对象的构造器的原型

使用Object.create方法创建的对象,其 __proto__ 属性不是指向其构造函数原型,后面再详细讲解

构造函数也是对象,但同时它又是函数,因此,它具有对象的所有属性,但自己也有函数特有的一些属性和方法,如prototype,这是指向原型对象Prototype的引用

27.png

将对象、构造函数(构造器)、构造原型有的属性整理一下:

32.png

28.png 根据上图我们可以得知构造函数A对象a原型对象Prototype之间有两种关系:实例化关系(蓝线)和引用关系(红线)

  • 构造函数A —— 实例化 ——> 原型对象Prototype
  • 构造函数A —— 实例化 ——> 对象a
  • 构造函数A —— prototype引用 ——> 原型对象Prototype
  • 原型对象Prototype —— constructor引用 ——> 原型对象Prototype
  • 对象a —— constructor引用 ——> 构造函数A
  • 对象a —— __proto__ 引用 ——> 原型对象Prototype

根据以上关系我们可以得出以下结论:

a.constructor === A 
a.__proto__ === A.prototype 
a.__proto__.constructor === A 
A.prototype.constructor === A

浏览器控制台展示:

29.png

注: 原型对象不能直接拿出来使用,它不是独立存在的,要想调用它,得通过它的构造函数来引用

a.__proto__ === Prototype ❌

a.__proto__ === A.prototype ✔️

5.对象__proto__的追根溯源(原型链)

动手在浏览器控制台输入以下代码:

//#构造函数声明 
//1. 构造函数A 
function A(name){ 
    this.name = name 
} 
//2. 类B 
class B{ 
    constructor(name){ 
        this.name = name 
    } 
} 

//3.构造函数Object,js的内置构造函数之一,直接使用即可 

//#对象创建 
//1. A 实例化对象a 
let a = new A("a") 
//2. B 实例化 对象b 
let b = new B("b") 
//3. 拷贝对象a 
let c = Object.create(a) 
c.name = "c" 
//4. 拷贝对象b 
let d = Object.create(b) 
d.name = "d" 
//5. 拷贝对象 {name:'e'} 
let o = {name:'e'} 
let e = Object.create(o) 

/* 
* 1.每个对象都有一个原型引用__proto__和构造函数引用constructor 
* 2.构造函数对象则多一个prototype引用 
* 3.接下来的任务是,帮每一个对象(实例对象、构造函数、原型对象)的引用属性都找到对应的对象 
* 4.不清楚引用指向谁的,可以直接打在控制台查看 
* 5.原型对象都是匿名对象,需要赋值给变量,方便操作 
**/ 

//A:(prototype、__proto__、constructor)
let pa = A.prototype 
let pf = A.__proto__ 
A.constructor === Function 

//B:(prototype、__proto__、constructor) 
let pb = B.prototype 
B.constructor === Function //true 
//A和B的构造函数相同,那么其构造函数的原型应该也相同 
B.__proto__ === pf 

//a:(__proto__、constructor) 
a.__proto__ === pa 
a.constructor === A 

//b:(__proto__、constructor) 
b.__proto__ === pb 
b.constructor === B 

//c:(__proto__、constructor) 
c.__proto__ === a 
//之前A和B的构造函数相同,__proto__指向同一个对象,但是这里有区别,c是由a拷贝而来,其__proto__指向了拷贝的对象 
c.constructor === A 

//d:(__proto__、constructor) 
d.__proto__ === b 
d.constructor === B 

//e:(__proto__、constructor) 
e.__proto__ === o 
e.constructor === Object 

//o:(__proto__、constructor) 
let po = o.__proto__ 
o.constructor === Object 

//pa:(__proto__、constructor) 
pa.constructor === A 
Object.prototype === po 

//pb:(__proto__、constructor)
pb.constructor === B 
pb.__proto__ === po 

//po:(__proto__、constructor) 
po.__proto__ === null 
po.constructor === Object 

//Function:(prototype、__proto__、constructor) 
Function.prototype === pf 
let p = Function.__proto__ 
Function.constructor === Function 

//Object:(prototype、__proto__、constructor) 
Object.prototype === po 
Object.constructor === Function 
Object.__proto__ === p 

//p:(__proto__、constructor) 
p.__proto__ === po 
p.constructor === Function 

//pf:(__proto__、constructor) 
pf.__proto__ === po 
pf.constructor === Function 

//将上面对象整理下: 
/* 
*构造函数(3个引用属性): A、B、Object、Function 
*原型对象(2个引用属性): pa、pb、po、pf、p 
*实例对象(2个引用属性): a、b、c、d、e、o 
*其它(无引用属性):null 
*/

30.png

根据上面代码整理出来的对象,可以画出一个关系图:

  • 绿色线:实例化
  • 蓝色线:构造函数引用
  • 黄色线:原型引用

31.png