理解prototype和__proto__,原型与原型链

670 阅读6分钟

前言

文章只作为本人的学习笔记和分享,有借鉴有补充,有错误,也会有进步,待更。

Ⅰ、需要理解的

  1. 每个构造函数都有一个prototype(显示原型)属性,他是一个指针,指向一个对象,这个对象的用途是包含所有实例共享的方法和属性我们把这个对象叫做原型对象)。
  2. 每个引用类型都有一个__proto__(隐式原型)属性。
  3. 对象__proto__属性,指向它的构造函数的原型对象(Function.prototype)。
  4. 每个构造函数prototype原型对象里的constructor指向构造函数本身。
  5. 特殊:Object.create() 创建的对象没有__proto__属性

注意:在现在的浏览器中你看不到__proto__,而是[[prototype]]。在MDN文档中你可以看到他的提示是:虽然Object.prototype.__proto__现在大多数浏览器都支持它,但它的存在和确切行为仅在 ECMAScript 2015 规范中标准化为遗留功能,以确保与 Web 浏览器的兼容性。为了获得更好的支持,请Object.getPrototypeOf()改用。

    let a = {};
    console.log(Object.getPrototypeOf(a) === a.__proto__); //true

Ⅱ、通过代码理解

代码示例:理解prototype

function Person() {
    console.log('这是a函数输出');
}
console.log(Person.prototype); //输出空对象
Person.prototype.showName = function(){
    console.log('这是Person的 showName 的输出');
}
Person.prototype.city = '深圳'
var watson =  new Person() 
var jack = new Person()
watson.showName() //这是Person的 showName 的输出
console.log(jack.city); //深圳

上述代码实例中,构建函数Person中的prototype一个指针,他默认指向一个空对象原型对象,我们在Person上面添加方法showName和属性city,继而通过Person创建的实例对象watson和jack,就可以都能获取到共showName 方法和city属性,这就是第一点所说的,我们也可以拿其他的函数查看

var a = new Object()
console.log(Object.toString())  //function Object() { [native code] } 表示他是程序自带的一个函数
console.log(a.toString())  //function Object() { [native code] } 表示他也是程序自带的一个
  • 我们知道对象是有toString方法的,这是在我们使用js的时候,已经被添加到其原型对象上了,所以我们是使用 new Object() 创造出来的对象,其都带有一些一定的公共方法如 toString(),hasOwnProperty()...等

代码示例:理解 __proto__

var info = {
    name:'watson',
    age:100
}
console.log(info);
console.log(info.__proto__);

对象有__proto__.png

  • 我们在对象info里面可以看到 [[prototype]]其实就是__proto__,称为对象的隐式原型,即info.__proto__
  • 每一个对象都有__proto__属性

代码示例:理解 prototype__proto__关系

var obj = {} 
var arr = []
var fn = function () {}

obj.__proto__ === Object.prototype // true
arr.__proto__ === Array.prototype // true
fn.__proto__ === Function.prototype // true
  • 在之间定义变量为{}的时候其实也是一个变量的创建过程,是通过 new Object() 创建的, 所以对象的隐式原型指向它的构造函数显示原型,即obj.__proto__ === Object.prototype会得到true
  • 数组和函数是特俗的对象,也是存在相同的指向关系的。

代码示例:理解 constructor构造函数关系

function Person() {
    console.log('这是a函数输出');
}
console.log(Person.prototype);
console.log(Person.prototype.constructor === Person);

Snipaste_2022-07-21_11-23-31.png

  • 在理解数据类型判断的时候会用到constructor,也存在这个每个构造函数prototype原型对象里的constructor指向构造函数本身。

在第五点需要理解的点中,可以查阅上诉链接的文档,这里不做详解。

Ⅲ、原型基本关系

原型基本关系.png

上诉的基本代码示例,我们可以理解一个基本的几者的关系。

Ⅳ、原型链

1. 先上一个总结图:

stickPicture.png

  • 访问一个对象的属性时,先在自身属性中查找,找到返回,如果没有, 再沿着__ proto __这条链向上查找, 找到返回,如果最终没找到, 返回undefined
function Foo() {
    this.test1 = function(){
        console.log('test1()');
    }
}
Foo.prototype.test2 = function(){
    console.log('test2()');
}
var f1 = new Foo()
f1.test1() //test1()
f1.test2() //test2()
console.log(f1.toString()) //[object Object]
console.log(f1.test3) //undefined,他一直寻找到Object.prototype上都没有,再继续通过__proto__寻找test3属性都没有找到
f1.test3() //f1.test3为undefined,所以被当作函数执行的时候会报错,Uncaught TypeError: f1.test3 is not a function
  1. 首先通过构造Foo函数创建的实例对象f1,他是一个对象。
  2. 对象f1的对象属性自身上有test1属性时候,便使用自身的test1
  3. 对象f1的对象属性自身上没有test2属性时候,便会在构造函数Foo的原型对象上面寻找test2属性,发现有则会使用Foo.prototype.test2
  4. 对象f1的对象属性自身上和向上的构造函数Foo的原型对象上都没有test3属性时候,便通过__proto__继续向上一直寻找(此时的继续寻找是以Foo.prototype为起点,因为构造函数的原型对象也是一个对象,所以Foo.prototype也是有他的__proto__属性的,而他是怎么来的呢? 对象---> 通过new Objecte() 而来的,所以他会一直找到函数Object的显示原型上去),形成一条隐式原型链,如果最终都没有找到则返回undefined,防止无限循环。
  5. 可以理解为原型链的作用:查找对象的属性(方法)
  6. 结合上图理解上面 4 点。

2. 通过instanceof来理解剩余部分

  1. instance是如何判断的?
  • 表达式: A instanceof B
  • 表达式理解(关键): 如果 B函数的显式原型在A的原型链上的话(原型链是一个隐式原型,__proto__寻找的过程),则返回true,否则返回false
  • 通俗理解:instanceof 字面意思便是: 关键字,实例的意思,那么表达式就看看作来判断:1.A 是B的实例?----> 2.A是 B 构造函数创建的实例对象--->3. B.prototype === A.__proto__ , A.__proto__ 非最终,不为null的情况下
function Foo() { } f
var f1 = new Foo() 
console.log(f1 instanceof Foo) // true f1是由构建函数创建的实例对象
console.log(f1 instanceof Object) // true f1---> Foo --->Object (继续向上寻找的话是,便是Object)
  1. FunctionObject
console.log(Object instanceof Function) // true 
console.log(Object instanceof Object) // true 
console.log(Function instanceof Function) // true 
console.log(Function instanceof Object) // true 

function Foo() {} 
console.log(Object instanceof Foo) // false
  • 在图中我们可以看到 Object created by Function,构造函数 Object 是由Function 创建的, Object原型链是一个隐式原型(__proto__)寻找的过程,即Object的显示原型 Object.ptototype会寻找到 Function.prototype
  • 由上一点在 Object的原型链中 Object.prototype 指向其构造函数的原型对象Object.prototype,那么这个对象(引用类型)也有其隐式原型--->Object.prototype.__proto__是存在的,即使最终为null
  • 构造函数Function的原型对象为显式原型的指向 Function.prototype, 原型对象是一个对象属性 ,也有其隐式原型属性,所以存在 Function.prototype.__proto__,返回 true
  • 函数式通过 new 他本身产生的实例,那么便存在 Function.__proto__指向其构造函数Function的 显式原型 Function.prototype,所以返回 true
  • 函数是特殊的对象,而对象不一定是函数,所以Object instanceof Object ---> false。

需要注意的点帮助理解的是:

  1. 显示原型指向的该 x.prototype 也是一个对象,那么他便是存在 .__proto__ 属性的。
  2. 函数是特殊的对象。
  3. Object created by Function 构造函数 Object() 也是 Function() 创建出来的。
  4. Function是通过new自己产生的实例,即 Function.proto 指向---> Function.prototype。

Ⅴ、测试题

/* 测试题1 */ 
function A () {} A.prototype.n = 1 
let b = new A() 
A.prototype = { n: 2, m: 3} 
let c = new A() 
console.log(b.n, b.m, c.n, c.m) 
// 1 undefined 2 3
function F (){} 
Object.prototype.a = function(){ 
    console.log('a()') 
} 
Function.prototype.b = function(){ 
    console.log('b()') 
} 
let f = new F() 
f.a() //a() 
f.b() //f.b is not a function -->找不到 
F.a() //a() 
F.b() //b() 
console.log(f) 
console.log(Object.prototype) 
console.log(Function.prototype)

image-20210723173855550.png