原型链万能公式,关于__proto__和prototype的那些事

本文探讨了JavaScript中的原型链概念,通过实例解析了__proto__和prototype的区别。当使用f.a()和f.b()时,由于f的原型链中不存在Function.prototype,导致f.b()报错。同时,分析了Object和Function的原型链,揭示了在原型链查找过程中的错误认知,并提供了一个终极面试题来巩固原型链的理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

昨天在一个技术群里,有个小伙伴提出了这么个问题:

Object.prototype.a = function () {
    console.log('a')
}

Function.prototype.b = function () {
    console.log('b')
}

function F(){}
var f = new F();

f.a();
f.b();
F.a();
F.b();

如果用万能公式,结果一下子就出来了,结果分别是a,报错(f.b is not a function),a,b。接着我们就来分析为什么会得出这些结果。

首先,我们要知道,实例化一个对象(new)到底做了什么?

function Base() {
    this.a = function () {
        console.log('我是Base中的a')
    }
}

Base.prototype.b = function () {
    console.log('我是Base prototype上的b')
}

var obj = new Base();
// 实际上做了以下几件事
// var obj = {};
// obj.__proto__ = Base.prototype;
// Base.call(obj);
// 第一行,我们创建了一个空对象obj
// 第二行,我们将这个空对象的__proto__成员指向了Base函数对象prototype成员对象
// 第三行,我们将Base函数中this上的成员赋值给obj

控制台显示如图:

好,可以开始解题,回顾一下问题:

Object.prototype.a = function () { console.log('a') }
Function.prototype.b = function () { console.log('b') }
function F(){}
var f = new F();
f.a();f.b();F.a();F.b();

先展开f的原型链:

//f是F new的实例对象,即f是F的实例
f.__proto__ === F.prototype;

// 因为prototype本质也是对象,继承自Object,所以F.prototype.__proto__ === Object.prototype
f.__proto__.__proto__ === Object.prototype;

// 按上一条,会得出Object.prototype.__proto__ = Object.prototype,那样会永无止境,因此js中把Object.prototype.__proto_直接赋值成null
f.__proto__.__proto__.__proto__ === null;

我们会发现,f的原型链中,根本没有Function.prototype什么事,所以答案出来了,f.a()输出a,f.b会报错;

再展开F的原型链:

F.__proto__ === Function.prototype;

// 因为prototype本质也是对象,继承自Object,所以Function.prototype.__proto__ === Object.prototype
F.__proto__.__proto__ === Object.prototype;

f.__proto__.__proto__.__proto__ === null;

可以看到,Function.prototype和Object.prototype都在原型链上,所以都会有输出,F.a()输出a,F.b()输出b;

如果就这个问题再延伸一下,就刚才的条件Object.a();Object.b();Function.a();Function.b();会输出什么?

很多人会得出答案a,b,a,b。

1. Object.a();  直接prototype取值;

2. Object是Function.prototype的实例,可以继承b;

3. Function.__proto__.__proto__ === Object.prototype;;

4. Function.b();直接prototype取值;

答案是正确的,不过得出答案的过程是这样的吗?

其实1和4的过程都是错误的。Object.a第一步就直接到Object.prototype上找?这可能也是很多人弄不清原型链的一大问题,那么摊开Object的原型链:

// Object是函数,继承自Function
Object.__proto__ === Function.prototype;

Object.__proto__.__proto__ === Object.prototype;

Object.__proto__.__proto__.__proto__ === null;

在控制台打印看看上面的结果如图:

因此,1的过程错了的原因是:Object.a其实是访问到Object.__proto__.__proto__时才从Object.prototype上找到相应的a();

同样展开Function的原型链:

// Function自身也是个函数,因此继承自己
Function.__proto__ === Function.prototype;

Function.__proto__.__proto__ === Object.prototype;

Function.__proto__.__proto__.__proto__ === null;

4的过程错也是因为不是直接读取prototype,而是访问到Function.__proto__时从Function.prototype上找到对应的b();

如果再延伸一下呢,

// 在刚才的前提下
var c = 1;
console.a();// a
console.b();// console.b is not a function
c.a();// a
c.b();// c.b is not a function
console.log.a();// a
console.log.b();// b

如果学会了展开原型链,上面这些答案都可以推导出来的。那么万能公式就是:

在js中,万物皆对象,只要在Object.prototype上写的方法,万物都能访问到;而Function.prototype上的方法,只有能通过()方式调用的函数,才能访问到;

因此只需要查看这个对象可不可以通过函数()的形式调用,就能确定他是否能访问Function.prototype。再次回顾出题人的问题,f仅仅是个对象,f()是会报not is a function错误的,而F()是可以调用的函数,一下子得出 a 报错 a b的答案。

文章结尾来一个终极面试题,这个题目答案都出来了的话,原型链就完全ojb98k了。(ps:原型链调用顺序是f.a -> f.__proto__.a -> f.__proto__.__proto__.a,直到访问到null)

Object.prototype.a = function () {
    console.log('我是Object中的a')
}
Object.prototype.b = function(){
    console.log('我是Object中的b')
}
Function.prototype.a = function () {
    console.log('我是Function中的a')
}
Function.prototype.b = function () {
    console.log('我是Function中的b')
}
function F(){
    this.a = function () {
        console.log('我是F中的a')
    }
}
F.prototype.a = function () {
    console.log('我是F的prototype中的a')
}
var f = new F();
f.a();
f.b();
F.a();
F.b();
Object.a();
Object.b();
Function.a();
Function.b();

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值