当前位置: 首页 > news >正文

原型链和原型

一、原型

原型与原型链,首先我们要知道什么是原型。在开始了解原型之前,我们先认识下js中的对象

1.对象

对象是一种复合数据类型,它是一种无序的键值对集合。对象用于存储和传递多个值,每个值都有一个键(key)与之关联。

对象的键是字符串类型,值可以是任意数据类型(数字,字符串,布尔)和其他对象。总之就是我们经常用到的 键值对 {key:value}

(对象又分为函数对象与普通对象)

对象的创建方式:以下是几种常见的对象创建方式:

// 第一种: 对象字面量方式
var obj1 = {name: "Jack",age: 26,
};
​
// 第二种: Object构造函数模式
var obj2 = new Object();
obj2.name = "Jack";
obj2.age = 26;
​
// 第三种: 构造函数模式
function Test(name, age) {this.name = name;this.age = age;this.say = function () {console.log("我能说话");};
}
var obj3 = new Test("Jack", 26);
var obj4 = new Test("Rose", 25);

日常中我们最常用的应该是字面量方式,那么为什么会出现第三种构造函数方式呢?

想想看我们需要多个obj1,obj2的时候,字面量方式我们需要重复代码去创建对象;而使用构造函数的方式只需要写一遍属性和方法,我们既可以通过new关键字,new出多个不同的对象

试试在控制台打印如下obj3.say === obj4.say,结果为false,看起来调用的是同一个函数方法,实际并不相等,因为他们的内存地址是不同的,每个new出来的obj3和obj4都包含一份独立的属性和方法(可能导致浪费内存)

function Test() {this.name = "rose";this.say = function () {console.log("我能说话");};
}
var obj3 = new Test();
var obj4 = new Test();
​
console.log(obj3.say === obj4.say); // false;
console.log(obj3.name === obj4.name); // true;

你可能会问为什么obj3.name和obj4.name事项等的呢?刚才不是说的内存不同独立的属性和方法吗?要理解这个行为,可以大致参考下面这种情况:

const a = { value: 10 };
const b = { value: 10 };
​
console.log(a.value === b.value); // 输出 true,因为属性值相同
console.log(a === b); // 输出 false,因为是不同的对象

同样的逻辑,对比obj3.name的时候比较的是name这个属性值,而obj3.name输出值是rose,所以rose比较值obj4.name也是rose,是相同的

现在我们来说说构造函数方式,上面的例子中的obj3和obj4都是Test的实例(也叫实例对象),而Test是obj3和obj4的构造函数。实例都有一个构造函数属性(constructor)指向构造函数,通过构造函数创建的对象用于构造函数内部定义的属性和方法

function Test(name, age) {this.name = name;this.age = age;
}
​
Test.prototype.say = function () {console.log("我能说话");
};
var obj3 = new Test("Jack", 26);
var obj4 = new Test("Rose", 25);
​
// constructor属性指向构造函数
console.log(obj3.constructor === Test); // true
console.log(obj4.constructor === Test); // true
console.log(obj4.constructor === obj3.constructor); // true

记住以下两个概念:

1.Test是构造函数

2.obj3 和 obj4 是构造函数Test的实力,实例的属性constructor指向构造函数Test

2.原型(原型对象)

上面的例子中,obj3和obj4都要调用Test中的say()方法,我们有没有办法将公共方法放到一个公共的地方呢?这时候有请公共的原型(prototype)登场

在js中,每一个对象(函数也是对象)都有一个特殊的属性叫做原型(prototype),它指向另一个对象,这个对象(Test.prototype)被称为原型对象,原型对象是用来共享属性和方法的

Test.prototype 就叫做原型对象

打印Test.prototype可以看到上图中原型对象

Test.prototype.constructor === Test
// true

原型对象:

1.原型对象有一个constructor属性指向构造函数本身(Test)

2.原型对象是一个普通的对象,它包含属性和方法

3.原型对象的属性和方法会被继承到所有通过原型链与它相连的对象

简单来说,原型对象是一个普通的对象,属性和方法会被继承到其它对象,而每一个对象都有一个原型(prototype),用来继承其他对象的属性和方法。

此时我们就可以吧say方法放到这个原型对象上,obj3和obj4就可以访问这个方法,再不用写到Test中去重复占用内存,所有new出来的实例都可以使用此方法

我们再来打印obj3.say === obj4.say,为true,证明obj3和obj4调用的就是同一个方法

function Test(name, age) {this.name = name;this.age = age;
}
​
Test.prototype.say = function () {console.log("我能说话");
};
var obj3 = new Test("Jack", 26);
var obj4 = new Test("Rose", 25);
​
obj3.say(); // 我能说话
obj4.say(); // 我能说话
console.log(obj3.say === obj4.say); // true

构造函数和实例之间就初步构成了这样一个关系,如图:

二、隐式原型 proto

1.proto

在js中,每个对象都有一个"_proto"属性(左右两边两个短下划线),这个proto就被称为隐式原型(记住这点)

实例对象也是对象,也存在proto属性

console.log(obj3.__proto__ === Test.prototype)
// true

打印以上obj3.proto === Test.prototype结果为true,所以:

1.每个js对象都有一个隐藏的原型对象属性proto,它指向创建它的构造函数的原型对象(Test.prototype)

2.*proto__存在的意义是在于为原型链查找提供方向,原型链查找靠的是***proto,而不是prototype(重点!!!!)

实例对象obj3通过proto指向了Test的原型对象(Test.prototype),如图:(Test.prototype.constructor:从Test先指向原型对象Test.prototype在.constructor指回Test,饶了一圈)

2.考你一下

function Test(name, age){this.name = namethis.age = age
}Test.prototype.say = function(){console.log('我能说话')
}
var obj3 = new Test('Jack', 26)1, 构造函数是? 实例是?
2, obj3.constructor === Test   true or false?
3, obj3.__proto__ === Test ?
4, Test.prototype === obj3.__proto__ ?
5, obj3.__proto__.constructor === Test ?// 1, Test  obj3  2,true  3,false  4,true  5,true

三、原型链

1.Object.prototype

在上面第二点中,每个js对象都有一个隐藏的原型对象属性proto

那Test的原型对象Test.prototype会不会也有一个隐式原型proto呢?控制台输出如下:

Test.prototype当然也存在一个属性proto,而这个Test.prototype.proto到底是谁呢?

Test.prototype.__proto__ === Object.prototype
// true

1.Test.prototype的隐式原型(proto)就是Object.prototype

2.所有的对象,包括构造函数的原型对象,最终都继承自Object.prototypr,这是js原型链的顶点

Object.prototype是从哪里来的呢?当然是由Object的属性prototype指向来的。Object.prototype同样也会存在属性constructor指向Object,

此时的关系图:

2.链

在控制台打印Object.prototype,会发现Object.prototype也是一个对象

既然它也是对象,他也存在隐式属性proto。想想看,如果Object.prototype.proto再去指向某个对象的原型(prototype),那么整条线就显得无穷无尽,一直找下去

js代码在创建时我们的开发者当然考虑到了,Object.prototype作为原型链的顶端,位于原型链的最末端。因此,他不再有自己的原型,所以Object.prototype.proto指向null,表示原型链的终点

原型链的终点是null

Object.prototype.proto === null

这个时候终于到达了终点,形成了这样的一个关系图(一整个链接在一起):

每个对象都有一个原型(prototype)。它指向另外一个对象,而指向的对象又存在属性(proto)指向另外一个对象。当我们访问对象obj3的属性时,会先在对象定义的属性中进行查找,没找到就会沿着proto一路向上查找,最终形成一个链式结构,这整个链式结构就叫做原型链

如果在原型链中找到了这个属性,就返回找到的属性值;如果整个原型链都没找到这个属性值,则返回undefined,没找到方法直接报错(not a function)

四、练习一下

到了这里应该对整个原型链有了自己的认知,其实只要记住以下几个概念,就可以试着自己画出整个关系图

1,在js中,每一个对象(函数也是对象)都有一个特殊的属性叫做原型(prototype),它指向另一个对象,这个对象被称为原型对象, 原型对象是用来共享属性和方法的2,对象有一个属性(__proto__)指向构造函数的原型对象,构造函数的原型对象也存在__proto__3,原型链的顶端是Object.prototype4,原型链的终点是null

练习一下:

function Test(name, age){this.name = namethis.age = age
}
Test.prototype.say = function(){console.log('我能说话')
}
var obj3 = new Test('Jack', 26)
var obj4 = new Test('Rose', 24)1, Test.prototype === ( ) ?
2, obj3.__proto__.__proto__ === ( ) ?
3, obj3.__proto__ === obj4.__proto__ ?
4, Test.prototype.__proto__ === ( ) ?
5, obj4.__proto__.constructor === ( ) ?
6, Object.prototype.__proto__ === ( ) ?
7, obj3.say === obj4.say ?// 1, obj3.__proto__ 或 obj4.__proto    2,Object.prototype    3, true (二者都由Test new出来,在原型链上都指向 Test.prototype)
// 4, Object.prototype    5, Test    6, null (终点)    7,true (同问题3)

五、进阶

1.普通对象与函数对象

在js中,有两种主要类型的对象:普通对象和函数对象。普通对象最常见 ,通过"{}"创建的就是普通对象;通过new Function()出来的就是函数对象(函数声明、函数表达式创建的为函数对象),我们可以用typeof来区分,(注意:这里函数声明式和表达式不要和对象字面量方式混淆)

f1,f2,f3都是函数对象,Object也是函数对象,b1,b2,b3为普通对象

简单理解,普通对象就是我们最常见的{}键值对;函数对象通常包含了一个function

function f1(){}var f2 = function(){}var f3 = new Function('name')var b1 = new f1()var b2 = {name: 'Rose'}var b3 = new Object()typeof f1    // 'function'
typeof f2    //'function'
typeof f3    //'function'
typeof b1    //'object'
typeof b2    //'object'
typeof b3    //'object'
typeof Object // 'Function'

这是再来看我们上面的例子就很清晰了, obj3 为普通对象, Test为函数对象 (不信F12控制台打开 typeof试试)

function Test(name, age){this.name = namethis.age = age
}
var obj3 = new Test('Jack', 26)

在上面【2 原型对象】我们提到过每一个对象(函数也是对象)都有一个特殊的属性叫做原型(prototype) obj3是对象,但是它没有prototype的这个原型属性(不信控制台试试)

所以这话不够完整,只有 函数对象才具有 prototype 这个原型属性

2.原型链机制

在【二、隐式原型】中,提到过:_**_proto__存在的意义在于为原型链查找提供方向**

为什么会说提供查找方向呢,看看下面两个例子:

左边构造函数Test的原型对象(Test.prototype)上定义了sex,Test.sex为undefined(注意是Test.sex不是obj.sex);右边在Object.prototype上定义了sex,Test.sex能取到值;为什么在Test.prototype上定义,Test.sex不能通过原型链到Test.prototype上去找到sex属性呢;而定义在顶点Object.prototype上,又能通过原型链找到了

看了大佬的一些解答分析,定义到Test.prototype上的时候,Test.sex并没有通过原型链查找,而是检查Test自身是否定义该属性,没有所以是undefined。感觉解释会有点说不通,定义到Object.prototype,不也应该是Test自身检查,也会检查不到,但我们能输出值,证明的确是顺着原型链去查找到了Object.prototype

3.Function的原型

在第二点【隐式原型proto】中,我们提到proto指向创建它的构造函数的原型对象

function Test(name, age){this.name = namethis.age = age
}
var obj = new Test('Jack', 26)Test.__proto__ === Function.prototype  // true

(1)构造函数Test的隐式原型( proto**)指向 Function.prototype, 函数对象的proto指向Function.prototype**

至于为什么,js在设计时决定了构造函数本身是函数,当然也可以通过指向 Function.prototype来访问原型链上的属性和方法,让构造函数也能参与到原型链中来(虽然不建议通过构造函数访问)

Function会有原型(prototype),当然也有隐式原型(__proto__),打印这两个原型,会发现二者互相相等  Function.prototype === Function.__proto__  是不是很神奇? 看起来像是自己创造了自己
​针对 Function.prototype === Function.__proto__, 诸多大佬对此有各种不同的解释,以下抛出两个观点仅供参考:
​1,Function 也是对象, 由new Function创建,所以自己创造自己       
​2,Function作为一个内置对象,代码运行前就存在,并非自己创造自己,先有的Function,然后实现上把原型指向了Function.prototype  以此来保持和其他函数一致,表明一种关系 

对象都拥有隐式原型(proto),Function.prototype当然也存在proto,打印出来看看

对象都拥有隐式原型(proto),Function.prototype当然也存在proto,打印出来看看

好像有点眼熟? 是不是和Object.prototype打印的有点像 Function.prototype是函数对象,按照刚得出的结论,函数对象的proto应该指向Function.prototype(即Function.prototype.__proto === Function.prototype),但是自己指向自己并没有意义。 别忘记Object.prototype才是原型链的顶点,Function.prototype存在于原型链中必然会与Object.prototype存在关联,指向Object.prototype能保证原型链存在终点,所以Function.prototype.proto === Object.prototype

再来看原型链关系,这时候就成了这样:

(2)如果在深究一点,Object也是函数对象,Object.proto 也会指向Function.prototype

(3)构造函数Test也有constructor属性,这个属性指向创建该函数的构造函数;如果自己没有定义构造函数,会指向到 Function (Test.constructor === Function)

原型链关系就成了这样:

针对上面 【2,原型链机制】 中的问题也有了答案,在构造函数Test上访问Object.prototype中的属性时,其实是顺着Test.proto这条路径从Function去访问 空口无凭,证据如下,看看会输出什么

function Test(name, age){this.name = namethis.age = age
}
var obj = new Test('Jack', 26)Object.prototype.price = 2000Function.prototype.price = 300Test.price

Test在自身没有找到price,顺着Test的proto到Function.prototype上找到了price = 300,所以直接返回 300;若Function.prototype上没有price,才会进一步顺着proto找到Object.prototype

在实际使用中,要获取定义到Test.prototype上的属性,还可以用原型对象Test.prototype.price访问;不过建议还是通过实例(obj)来访问具体的属性(obj.name),而不是构造函数Test.name访问,毕竟实例new出来的目的就是为了调用构造函数上的方法属性

真相大白,再一次证明proto存在的意义在于为原型链查找提供方向,原型链查找靠的是proto,而不是prototype

六、总结

1,每个对象均存在隐式原型(proto),函数对象才有prototype属性

2,proto存在的意义在于为原型链查找提供方向,原型链查找靠的是proto,而不是prototype

3,函数对象的proto都指向Function.prototype

4,每个对象都有一个隐式原型属性(proto),多个原型通过proto链接在一起形成的链式结构就是原型链


文章转载自:

http://MvdvgeDx.qptbn.cn
http://qmZyp21y.qptbn.cn
http://bswM57g1.qptbn.cn
http://BnX1VPcU.qptbn.cn
http://Q8xx3CKt.qptbn.cn
http://hHaOo24L.qptbn.cn
http://PNDRdZP8.qptbn.cn
http://H5PGLr8Y.qptbn.cn
http://3OPydQyH.qptbn.cn
http://I5IxehSi.qptbn.cn
http://oZ4Rte30.qptbn.cn
http://6QSzvL1W.qptbn.cn
http://ONWrMXho.qptbn.cn
http://YR5keYwc.qptbn.cn
http://2Ifrljui.qptbn.cn
http://DQZuApkz.qptbn.cn
http://cfeKD5qz.qptbn.cn
http://e78poEKM.qptbn.cn
http://6zAAB2UI.qptbn.cn
http://7GJEgDSY.qptbn.cn
http://0n4vS5Cm.qptbn.cn
http://aJsT6cs2.qptbn.cn
http://LwxKzdzC.qptbn.cn
http://2y6eRFeX.qptbn.cn
http://Wf2EtP87.qptbn.cn
http://G11b99SG.qptbn.cn
http://LtTdVbFn.qptbn.cn
http://H5jYb4rW.qptbn.cn
http://4eNTsb6o.qptbn.cn
http://DVqUkXUQ.qptbn.cn
http://www.dtcms.com/a/366945.html

相关文章:

  • 嵌入式学习 51单片机(3)
  • 详细学习计划
  • 深度解读《实施“人工智能+”行动的意见》:一场由场景、数据与价值链共同定义的产业升级
  • CLIP模型
  • 深度学习篇---SENet网络结构
  • JS初入门
  • 大数据开发计划表(实际版)
  • TypeScript 增强功能大纲 (相对于 ECMAScript)
  • LLAMAFACTORY:一键优化大型语言模型微调的利器
  • DeepSeek文献太多太杂?一招制胜:学术论文检索的“核心公式”与提问艺术
  • Android AI客户端开发(语音与大模型部署)面试题大全
  • hutool的EnumUtil工具类实践【持续更新】
  • 从基础到实践:Web核心概念与Nginx入门全解析
  • 深度学习:基于自定义 ResNet 的手写数字识别实践(MNIST 数据集)
  • Day35 网络协议与数据封装
  • Vue 3 学习路线指南
  • C语言基础:内存管理
  • 大模型应用开发框架 LangChain
  • Deeplizard深度学习课程(六)—— 结合Tensorboard进行结果分析
  • 小程序:12亿用户的入口,企业数字化的先锋军
  • 【C++题解】关联容器
  • 15,FreeRTOS计数型信号量操作
  • PMP新考纲练习题10道【附答案解析】
  • 开源技术助力企业腾飞,九识智能迈入‘数据驱动’新纪元
  • Docker(①安装)
  • [Windows] PDF工具箱 PDF24 Creator 11.28.0
  • 阿里云轻量应用服务器部署-WooCommerce
  • Java全栈开发面试实战:从基础到高并发的深度解析
  • 并非银弹,而是利器:对软件开发工具的深度探讨与理性思考
  • 使用 Sentry 为 PHP 和 Web 移动小程序提供多平台错误监控