一道面试题详解原型和原型链的基础知识

先看下这道面试题,

function Fn() {
    this.x = 100;
    this.y = 200;
    this.getX = function () {
        console.log(this.x);
    }
}
Fn.prototype.getX = function () {
    console.log(this.x);
};
Fn.prototype.getY = function () {
    console.log(this.y);
};
let f1 = new Fn;
let f2 = new Fn;
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.__proto__.getX === f2.getX);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY();

1.每一个类(函数)都具备prototype,并且属性值是一个对象
2.对象上天生具备一个属性:constructor,指向类本身
3.每一个对象(普通对象、prototype、实例、函数等)都具备:proto,属性值是当前实例所属类的原型

  • 函数数据类型:普通函数、类(内置类/自定义类)、箭头函数(没有prototype属性) (都是Function的实例)
  • 对象数据类型:普通对象、数组对象、正则对象、日期对象、实例对象、函数对象(函数也是一个对象,也像普通对象一样,有自己的键值对)、类的prototype也是对象 (都是Object的实例)
  1. 每一个函数(除ES6的箭头函数外)都有一个内置的属性prototype(原型属性),属性值是一个对象,在对象中会存储当前类的公共属性和方法(普通函数也有属性prototype,但是没有实例所以没用)
  2. 在prototype的堆内存中,如果是浏览器为其默认开辟的堆内存,会存在一个内置的属性:constructor构造函数,属性值就是当前类本身
  3. 每一个对象都有一个内置属性:proto(原型链属性),属性值是当前实例所对应类的prototype

原型链查找机制:调用当前实例对象的某个属性(成员访问),先看自己私有属性中是否存在,存在调用的就是自己私有的;不存在,则默认按照__proto__找所属类prototype上的公有属性和方法;如果还没有,再基于prototype上的__proto__继续向上级查找,直到找到Object.prototype为止

针对以上的知识点用例题和画图的方式来依次讲解:

function Fn() {
	this.x = 100;
	this.y = 200;
	this.getX = function () {
		console.log(this.x);
	}
}

// 都是把Fn执行了,创造了实例(区别就是运算符优先级)
let f1 = new Fn(); //有参数new   19
let f2 = new Fn; //无参数new     18

// Fn() 函数执行
// Fn  函数本身

在这里插入图片描述

结合代码分析上图第一步:(创建EC(G)和VO(G))

代码执行的时候,首先创建一个全局上下文EC(G),这个EC(G)中有一个存储区南距变量的VO(G)
1.变量提升阶段【function Fn(){…}】先创建一个函数堆AAAFFF000,把这个堆地址放在VO(G)中,然后创建变量名Fn,最后把两者关联起来
2.代码执行【function Fn(){…}】,此步在变量提升阶段已经执行。【let f1 = new Fn();】先创建【 new Fn()】,再创建f1,最后两者关联。

结合代码分析上图第二步:(创建函数堆和函数的原型堆)

1.每一个函数(除ES6的箭头函数外)都有一个内置的属性prototype(原型属性),属性值是一个对象,在对象中会存储当前类的公共属性和方法
2. 在prototype的堆内存中,如果是浏览器为其默认开辟的堆内存,会存在一个内置的属性:constructor。构造函数属性值就是当前类本身

函数Fn也是对象,有自己的健值对,在控制台查看此函数
在这里插入图片描述
函数作为对象时比较常用的属性:name–函数名;length–形参个数;prototype–函数原型;proto–原型链属性
prototype指向堆内存Fn.prototype,Fn.prototype是一个对象,这个对象有一个属性constructor指向函数Fn【Fn.prototype.constructor===Fn】

结合代码分析上图第三步:(构造函数执行)

【function Fn() 】此步在变量提升阶段已经执行;
【let f1 = new Fn();】先执行【 new Fn()】得出结果;new Fn()创建的是类的实例。
构造函数执行的时候会执行普通函数都执行的步骤,除此之外还会在初始化作用域链后默认创建一个普通对象即当前这个实例对象【BBBFFF000】,在初始化this的时候将指针指向这个实例对象【BBBFFF000】,代码执行阶段所有的this.xxx=xxx都是给当前这个实例设置私有的属性和方法,默认把当前创建的实例对象返回【BBBFFF000】。此实例对象【BBBFFF000】一定是构造函数Fn的实例,所以此对象实例的__proto__是Fn.prototype。

结合代码分析上图第四步:Object内置类

Object内置类也是一个函数堆,存储一些内置的代码,所有的函数都是对象。都有属性__proto__和prototype。Object内置类比较常用的属性有:assign、create、defineProperty、entries、freeze、getPrototypeOf、is、keys···
在这里插入图片描述
Object.prototype也是一个堆一个对象
在这里插入图片描述

结合代码分析上图第四步:(proto
  1. 每一个对象都有一个内置属性:proto(原型链属性),属性值是当前实例所对应类的prototype。

原型链查找机制:调用当前实例对象的某个属性(成员访问),先看自己私有属性中是否存在,存在调用的就是自己私有的;不存在,则默认按照__proto__找所属类prototype上的公有属性和方法;如果还没有,再基于prototype上的__proto__继续向上级查找,直到找到Object.prototype为止

所有的函数都是Function这个类的实例,所以所有的函数的__proto__都指向Function.prototype。

所有的实例对象的__proto__都指向自己构造函数的prototype,如果一个对象不是new出来的实例,那么这个对象的__proto__指向Object.prototype。对于实例对象f1来说,f1时Fn这个构造函数/类new出来的,所以f1.proto===Fn.prototype,对于某个对象比如Fn.prototype,它也是一个普通对象。所有的对象,如果不知道它是new谁出来的,那么它就一定是内置类Object的实例:
Fn.prototype.proto===Object.prototype。Object.prototype也是一个普通对象,是这个内置类Object的实例,按照理论讲Object.prototype.__proto__本来应该指向自己即Object.prototype.proto===Object.prototype,但是这样没有意义,所以Object.(对象的基类)的Object.prototype.__proto__是null。

通过例题来验证自己是否掌握以上内容

写出下面打印出的内容

function Fn() {
    this.x = 100;
    this.y = 200;
    this.getX = function () {
        console.log(this.x);
    }
}
Fn.prototype.getX = function () {
    console.log(this.x);
};
Fn.prototype.getY = function () {
    console.log(this.y);
};
let f1 = new Fn;
let f2 = new Fn;
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.__proto__.getX === f2.getX);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY();

在这里插入图片描述

分析:Fn这个构造函数中通过this添加的是私有属性,通过Fn.prototype添加的是公有属性。f1和f2都是new Fn出来的Fn的实例,查看某个实例对象的属性和方法时按照【原型链查找机制】来,先看是否为自己私有的,私有的没有再找公有的,如果还没有就沿着__proto__再接着向上找。

  1. 构造函数函数中this都属性和方法是new出来的实例的私有的属性和方法,所以f1和f2都有自己的私有属性【x、y】和私有方法【getX】。
//f1和f2都有自己的私有方法【getX】
console.log(f1.getX === f2.getX);//=>false
  1. getY这个函数是定义在Fn.prototype上,f1和f2私有中都没有此方法,沿着原型链查找到Fn.prototype上。
console.log(f1.getY === f2.getY);//=>true
  1. f1是Fn这个类new出来的实例,所以f1.proto===Fn.prototype
console.log(f1.__proto__.getY === Fn.prototype.getY);//=>true
  1. 因为f1.proto.getX===Fn.prototype.getX,此处的方法getX是公有的方法,f2.getX中的getX是f2的私有方法,这两个getX不是同一个
console.log(f1.__proto__.getX === f2.getX);//=>flase
  1. f1.getX中的getX是f1的私有方法,Fn.prototype.getX中的getX是公共的方法。
console.log(f1.getX === Fn.prototype.getX);//=>flase
  1. f1私有属性中没有constructor,顺着原型链__proto__向上查找到Fn.prototype上,Fn.prototype有此属性且Fn.prototype.constructor===Fn
console.log(f1.constructor);//=>Fn
  1. Fn.prototype是一个堆,一个对象当不知道是谁new出来的时候一定是Object的实例,所以 Fn.prototype.proto===Object.prototype 。

Object.prototype.constructor===Object

console.log(Fn.prototype.__proto__.constructor);//=Object

f1.getX();

  1. 先确定执行的是哪一个方法?公有的还是私有的
  2. 执行方法(方法中的this:看函数执行前面是否有电,有的话,点前面的是谁this就是谁,没有就是window/undefined)
  3. 确定出来this是谁后,直接执行代码看结果即可
  1. f1.getX()先看自已私有的方法,有getX()这个方法,直接调用,this直接指f1这个实例对象,这个实例对象的属性x是100,所以直接打印出100
f1.getX();//=>100
  1. f1.proto.getX();此处的getX是指f1公有属性上的方法,此方法中的this指向f1.__proto__即Fn.prototype,Fn.prototype没有属性x,所以打印出undefined
//跳过查找自己私有的,直接基于原型链找所属原型上的方法(IE中不会允许我们使用__proto__)
f1.__proto__.getX();//=>undefined
  1. f2的私有方法中没有getY,顺着原型链找到公有属性即Fn.prototype上,Fn.prototype中有方法getY(),f2.getY()调用时getY中的this指的是f2,f2的属性y为200,所以打印出200。
f2.getY();//=>200
  1. Fn.prototype.getY()调用时,getY中的this指的是Fn.prototype,Fn.prototype中没有属性Y,所以打印出undefined
Fn.prototype.getY();//=>undefined

最后在控制台输出结果,与上面的分析结果一致
在这里插入图片描述

某个属性的公有和私有是相对的

检测某个属性是否为对象的私有属性hasOwnProperty()
Fn.prototype 是公有的【相对概念】,形对于Fn的实例是公有的,相对于自己是私有的,hasOwnProperty:检测某个属性是否为对象的私有属性

f1.hasOwnProperty('getY');//=>false
  1. f1是基于原型链查找机制一层层向上找,最后找到Object.prototype上的hasOwnProperty,并且把它执行
  2. 'getY’属性并不是f1的私有属性,是它所属类原型上的属性,所以相对于f1来说’getY’说公有属性 =>false
Fn.prototype.hasOwnProperty('getY');//=>true

Fn.prototype用的也是Object原型上的hasOwnProperty方法,此方法执行时,'getY’相对于Fn.prototype是私有的 =>true

hasOwnProperty相对于Object.prototype是私有的,相对于f1、f2、Fn.prototype都是公有的。

tip:补充面试题
Object.prototype.hasOwnProperty('hasOwnProperty');//=>true

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值