数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。

原型和原型链:

老规矩,先说概念:

先明确构造函数的概念:

在JavaScript中,构造函数是用于创建对象的特殊函数。它们通常用于定义对象的属性和方法,并在创建对象时初始化这些属性。
构造函数使用new关键字来创建对象。当使用new关键字调用构造函数时,会创建一个新的对象,并将该对象的原型设置为构造函数的prototype属性。构造函数内部的this关键字指向新创建的对象。

简单来说,构造函数是用来构造实例的,比如字符串是由String所构造出来的,函数是由Function构造出来的.

原型对象

原型(原型对象):在JavaScript中,每个函数都有一个prototype属性,这个属性指向函数的原型对象。

数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第1张图片

对象原型

对象都会有一个__proto__属性,指向构造函数的prototype原型对象,之所以我们对象可以使用原型对象的属性和方法,就是因为对象有__proto__原型的存在。 其中指向构造函数的意思就是指向构造自己的那个函数.

数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第2张图片

如上图:有一个构造函数Man, “张三”,李四是通过Man构造出来的实例. 而通过构造函数构造出来的实例可以通过_proto_属性(也就是对象原型)访问到此构造函数的原型对象.

作用

这全是概念性的东西,那原型对象的作用是什么呢?

原型对象,是用来解决构造函数在创建实例的时候,防止重复执行所导致的性能的降低(这里主要指占用内存),来为复用带来方便

唉? 还是很概念啊,到底作用是什么啊,能不能举个例子说一下? 行,如下:数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第3张图片
在这个例子中,我通过Man构造出来两个实例,张三和李四,里面不同的是他们的名字,但是我想到他们都是人,都有相同的共性:“说话”,那我在构造函数中加一个"Talk"的方法,每次构造出来实例的时候都给这个实例附上"Talk"的方法。
那按道理说,那最省事办法就是只创建一次这个打印文本方法,然后每次创建实例的时候都把实例中的Talk属性指向那个已经创建好的方法的地址就可以了. 但是实际上这样子创建出来的实例,每次创建一次实例都新开辟了一个地址保存了一个 function(){ console.log(‘人会说话’)} 方法. 所以上图中最后打印出来的是false。说明两个方法的内存地址不同。那如果这个实例创建了一百个呢? 一千个呢? 这时候就会出现内存的浪费.
按照上述最理想的方法如下图:
数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第4张图片

图中,两个实例中的Talk方法都指向同一个地址的.而这样怎么实现呢? 可以通过原型对象实现,将所有实例共用的方法放到原型对象中,而之前介绍了,实例可以访问到构造函数的原型对象,就实现了一个地址,多个实例共同使用一个方法的需求 !

验证下结论:
数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第5张图片
打印出true,说明实例中的Talk都指向了同一个内存地址. 奈斯

但是,我所说的树形结合, 理论上通关了,咱们心里也要有这一幅图
数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第6张图片
由图再看上面的代码,我们执行:

console.log(zs.Talk==ls.Talk)

建议按照图分析,到最后自己遇到题目的时候也能心里有图。

  1. js执行首先会查找zs实例自身中有没有Talk方法, 发现zs中没有Talk方法会自动通过_proto_属性往自己家的构造函数的原型上查找
    数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第7张图片

  2. 然后在Man的原型对象(prototype)上发现了Talk方法 。
    数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第8张图片

  3. 再查找实例ls中的Talk方法,同理,最后也在Man构造函数的原型上找到了Talk方法.

  4. 而此时,实际上他们找到的都是Man构造函数的原型上的Talk方法,所以此时打印的结果为true.

综上,证明了原型能够解决占用内存的问题.

原型链

原型链: 通过对象__proto__属性指向函数的原型对象(函数.prototype)一层一层往上找,直到找到Object的原型对象(Object.prototype)为止,层层继承的链接结构叫做原型链(通过proto属性形成原型的链式结构,专业术语叫做原型链)

上述概念其实不够严谨,其实原型链的尽头是null,下面是我自己画的一个原型链

数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第9张图片
乍一看这也太复杂了吧,但是简单的描述原型链只需要说:

每一个构造函数构造出来的实例都会有一个_proto_属性指向其原型对象,然后原型对象实际上也是一个"对象",而对象是由Object所创建出来的实例,所以自然会指向Obect的Prototype(原型),然后Object的原型再往上指向null。这从下往上查找的“链”就是原型链。

下面有几个经典的题目,弄懂后保证你把原型链吃得透透的! 最后一题一定弄懂!!!

建议一次性把六个题目复制到一个js文件中,然后把答案删掉. (因为一次复制一个题目删一个做一个就会记得答案啦,我们要让自己忘记答案哈哈)

题目1:

function F() {
  this.a = 1;
}
var obj = new F();
console.log(obj.prototype);//打印undefined   对象只有 __proto__属性没有prototype属性,函数才有

看图; obj是F构造函数所构造的一个实例,而实例是没有prototype属性的,他是最底层的东西.
数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第10张图片

题目2:

Object.prototype.a = 1;
var obj = {
    b: 2
};
for(var i in obj) {
    console.log(i); //能迭代出原型链里面的属性,所以会打印出 b和a
    }

拓展:

需要注意的是,虽然for…in循环可以迭代原型链上的属性,但它也会迭代对象的所有可枚举属性,包括从原型链继承的属性和对象自身的属性。如果你只想迭代对象自身的属性,可以使用Object.hasOwnProperty()方法来过滤掉原型链上的属性。例如:

for (var i in obj) {
    if (obj.hasOwnProperty(i)) {
        console.log(i);
    }
}

这样就只会打印出obj对象自身的属性b,而不会打印出原型链上的属性a。

题目3:

Object.prototype.a = 1; 
var obj = {
    b: undefined
};
 
console.log(obj.a);//1
console.log('b' in obj);//true  //能找到处于原型链里面的b属性
 
console.log(obj.hasOwnProperty('a'));//false 不是自身的属性
console.log(obj.hasOwnProperty('b'));//true  是自身的属性

题目4:

function A(){
}
function B(a){
  this.a = a;
}
function C(a){
  if(a){
		this.a = a;
  }
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;
 //实例对象都能访问构造函数的原型对象,可以明确的是以上3个都是能拿到a的
 
console.log(new A().a);//1   拿到的是原型上的a=1
console.log(new B().a);//undefined  this.a = a;将new B()这个实例对象的a属性这是为undefined,所以拿到的是undefined
console.log(new C(2).a);//2   将new C(2)这个实例对象的a属性这是为2,所以拿到的是2

题目5:

function A () {
}
A.prototype.n = 1;
 
var b = new A();//b实例对象已经建立原型连接
 
//原型对象指向被改变,不会切断b实例对象的
A.prototype = {
    n: 2,
    m: 3
}
var c = new A();//c实例对象将根据新的原型建立连接
 
console.log(b.n, b.m); //1 undefined  这里拿到是改变prototype之前的
console.log(c.n, c.m); //2 3		这里拿到是改变prototype之后的

题目6:

var F = function(){};
Object.prototype.a = function(){
    console.log('a')
}
Function.prototype.b = function(){
    console.log('b')
}
var f = new F();
 
F.a();  //打印a  对象都能访问Object.prototype中的属性和方法
F.b();  //打印b  F是函数,原型链  F => F.__proto__ => Function.prototype => Function.prototype.__proto__ => Object.prototype
 
f.a();  //打印a  对象都能访问Object.prototype中的属性和方法
f.b();  //报错f.b is not a function   因f是F实例对象,原型链 f=>f.__proto__=>F.prototype=>F.prototype.__proto__ => Object.prototype

是不是有人以为f.b()会打印 b ?
如果对最后一题很懵逼的同学,可以跟着我的思路来画图:

  1. 第一行代码:有一个F构造函数,函数内部为空:
    数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第11张图片
    2.最大的对象Object的原型上有一个属性a,a的内容是打印一个a;
    数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第12张图片
    3.最大的Function的原型上有一个属性b,b的内容是打印一个b
    数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第13张图片
    4.通过第一行的F构造函数创建出一个实例:f
    数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第14张图片
    此时我们要知道他们的上下级关系:

要明确:构造函数F 是最大的Function所创建的实例,(可以理解成:函数肯定是要归最大的函数管)于是可以连接两幅图:
数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第15张图片
再明确一点:

Object 是原型链的顶端,是几乎所有JS中的东西的大哥 ! ! !

在JavaScript中,几乎所有的东西都可以被视为对象,并且大部分对象都可以继承Object的原型上的方法。
在JavaScript中,基本数据类型(如字符串、数字、布尔值等)在使用时会被自动转换为对应的包装对象(如String、Number、Boolean等)。这些包装对象是Object的实例,因此它们可以继承Object的原型上的方法。
除了基本数据类型的包装对象,函数对象、数组对象、日期对象等也都是Object的实例,它们同样可以继承Object的原型上的方法。
需要注意的是,虽然大部分对象都可以继承Object的原型上的方法,但并不是所有对象都是直接继承自Object。例如,通过Object.create(null)创建的对象是没有原型的,因此它不会继承Object的原型上的方法。
总的来说,在JavaScript中,大部分对象都可以继承Object的原型上的方法,但也有一些特殊情况需要注意。

我们只需要知道Function、String,等等这些数据类型其实都是Object的实例,所以,Function也需要连接到Object,那我们现在可以把这三个东西连接起来了:
数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第16张图片

此时,要知道,构造函数F的prototype,也就是F的原型对象,既然他都叫"原型对象"了,而对象都是由Obect所创建出来的实例,于是,F的prototype需要和Object的prototype链接,然后整幅图就完成了 ! ! !

数形结合搞懂原型与原型链,真正理解原型链,面试官直呼内行。一文搞懂原型和原型链的作用。_第17张图片

仔细看红色的那条线,可以说就是从实例f触发的一条原型链,现在看图,能知道为什么f.b()会报错了吗? 红色线上能找到b?

这只是举个例子,在其他的情况下也一样,所有的函数的prototype都会指向object的prototype. 而在此题中因为F是一个构造函数,构造函数的"大哥"是大Function,也就是说F 是Function的实例,所以F.b()能打印出b.

举一反三,字符串实例,比如’1’,‘2’,‘3’,他们的大哥是String.

不知不觉文章有这么长了,希望对读者有帮助 !

欢迎指正!!

你可能感兴趣的:(面试题,原型模式,javascript,开发语言)