记录--JS原型链
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
引子
对于初学者学习原型链,还是有很大的困难。一方面是函数与对象分不太清楚;另一方面,不懂原型链的继承等。本人曾今也深受困惑,并且把疑惑的地方都记录下来,为大家做出解释。明明CSDN搜索关于原型链的文章一大堆,为啥我还要写?个人觉得,写下这篇原型链文章,不仅表达我对原型链理解的程度,也算是总结自己了自己的学习心得;更多的,此篇文章,或多或少会为大家理解原型链,又多出了一些新的感悟。同时,在讲解原型链之前,要补充关于函数和对象的一些知识,加强大家的理解。总体围绕原型链展开,其它内容为辅。希望大家能够静下心来,认真阅读,最终能够有所收获!
对象与函数的区别
- 对象包含函数,函数是特别的对象,也就是说对象的范围更大,而函数的范围更加小。举个简单例子,985,211等知名大学是一定是本科,而本科不一定是985,211,本科可能是普通一本,甚至是普通二本,三本等。
- 对象特别拥有*** --proto–(因为使用Markdown语法写文章,下划线的_会转译成别的作用,所以用-代替,接下里的文章内容都将如此) *** 和 constructor 两个属性,而函数特别拥有 prototape 这个属性。这个属性因为函数属于特殊的对象,因此函数也可以有 –proto– 和 constructor 两个属性。也就是说,对象有的,我都有,对象没有的,我还有。换句话说,你的就是我的,我的还是我的。
- Object函数和Function函数是已经内置好了的,除此之外,还有Array,Date等等。Object这个函数,也是由 Function 构造的。并且,所有函数都是 Function的实例。换句话说,我们声明的普通函数 *function name(){} *, 更加像是由new Function实例生成的,如
1 2 3 4 5 6 7 8 | var ff = new Function( "a" , "b" , "console.log(a,b); return a+b;" ); 就相当于function ff(a,b){ console.log(a+b); return a+b; } 值得注意的是,Function最后一个 "" 中,是函数的主体,在它之前的都是参数。 并且,由Function实例生成的,都是函数。这是与其它普通函数实例出来的 最大不同,普通函数 new 生成的,是一个对象,而不是函数。 |
通过 函数名.–proto– 可以得到 Function.prototape。通过 函数名.constructor这个属性,可以得到它的构造函数是Function。比较巧的是,Function的constructor就是它自身,Function的–proto–也是它的 Function.prototape。但是规定,一切函数的prototape的–proto– 指向 Object.prototape,即函数名.prototape.–proto–等于Object.prototape
对象的创建
- 通过new +构造函数,比如
var obj=new Object()
- 通过字面量的形式,比如var obj={},这个是JOSN对象的简写。JOSN是由Object构造的,也就是说 JOSN不是函数,也就不拥有prototape这个属性,JOSN.–proto–为Object.prototape,而不是Function.prototape。JOSN.constructor为Object。Math对象和JOSN一样,也是由Object实例的。那么,Math.constructor也为Object,Math.–proto–也为Object.prototape值得注意的是,JOSN和Math已经是实例对象了,不可以再new一个实例对象。也就是不能new JOSN()和new Math()等 ,这些都是错误的操作,这是很多新学者会出错的地方。
函数的创建
- 通过函数声明式,如
function name(){}
- 通过函数表达式,如
let name=function(){}
- 通过Function实例,如
let name=new Function()
.凡事由Function实例的,都是函数
函数的静态属性和方法,以及函数的实例属性和方法
-
静态属性和方法
通过函数名.属性或者方法就是静态属性或者方法。
上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | function Son(){} ; Son.age=18; Son.sex= "男" ; Son.play=function(){ console.log( "我会玩游戏" ); } Son.sing=function(){ console.log( "我会唱歌" ); } 要想得到age,sex属性以及play(),sing()方法,只能通过函数名.属性或者方法使用 如 console.log(Son.age,Son.sex) //18,男 Son.play() //我会玩游戏 Son.sing() //我会唱歌 不能通过 new 一个对象得到 比如 var son= new Son(); console.log(Son.age,Son.sex) //undefined,undefined son.sing() //son.sing is not a function |
- 实例属性和方法
- 通过在函数内部,用this.属性或者this.方法的就是实例属性或方法
- 上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | function Son(name,age,phone){ this .name1=name; this .age=age; this .phone=phone; this .dance=function(){ console.log( "我会跳舞" ) } this .getAge=function(){ return this .age; } } 要想得到age,sex,phone属性以及dance(),getAge()方法,只能 new 一个实例对象, 然后通过实例对象.属性或者方法使用 如 var son1= new Children( "小强" , "18" , "苹果" ); console.log(son1.name1); //小强 console.log(son1.age); //18 console.log(son1.phone); //苹果 console.log(son1.getAge); //18 son1.dance(); //我会跳舞 不能通过函数名.属性或者方法使用 如 console.log(Children.name1); //undefined Children.dance(); //Uncaught TypeError: Parent.say is not a function |
–proto–属性的例子
–proto–属性,指向构造它的函数的prototape属性。比如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | function Son() { } var son= new Son(); console.log( son.__proto__==Son.prototape) //true console.log( Son.__proto__==Function.prototape) //true var obj= new Object()或者 var obj= new Object({})或者 var obj={} console.log( obj.__proto__==Object.prototape) //true console.log(Object.__proto__==Function.prototape) //true console.log(Array.__proto__==Function.prototape) //true console.log(JOSN.__proto__==Object.prototape) //true console.log(Math.__proto__==Object.prototape) //true console.log(JOSN.__proto__==Function.prototape) //false console.log(Math.__proto__==Function.prototape) //false console.log(Function.__proto__==Function.prototape) //true 特殊 上述表明,函数的__proto__属性指向的,都是Function.prototape,而实例对象指向的 是构造函数的prototape属性。 |
constructor属性的例子
constructor属性,指向构造它的函数
1 2 3 4 5 6 7 8 9 10 11 12 | function Son() { } var son= new Son(); console.log(son.constructor==Son) //true console.log(Son.constructor==Function) //true console.log(Object.constructor==Function) //true console.log(Date.constructor==Function) //true console.log(Math.constructor==Object) //true console.log(JOSN.constructor==Object) //true console.log(Function.constructor==Function) //true 上述表明,函数的constructor属性指向的,都是Function,而实例对象指向它的构造函数 |
prototape属性的例子
prototape属性 函数特别拥有,对象没有这个属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | var obj={} console.log(obj.prototape) //undefined function Son() { } var son= new Son(); console.log(obj.prototape) //undefined console.log(son.prototape) //undefined console.log(Date.prototape) //undefined console.log(JOSN.prototape) //undefined console.log(Object.prototape) //{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ,...} console.log(Son.prototape) //{constructor: ƒ} console.log(Function.prototape) //ƒ () { [native code] } 由此可见,对象没有这个属性,并且,prototape的值为对象 我们可以通过prototape.constructor,可以知道这个原型对象的构造函数是谁 console.log(Object.prototape.constructor) //ƒ Object() { [native code] } console.log(Son.prototape.constructor) //ƒ Son() { } console.log(Son.prototape.constructor) //ƒ Function() { [native code] } 我们可以向prototape中添加方法或者属性,但一般添加方法,代表这个链上的公共方法,个人认为, 这个方法如果很多对象都用到了,不妨放在Object.prototape中。 Object.prototape.sing=function(){ console.log( "我会唱歌" ); } Object.prototape.sing() //我会唱歌 首先 new 一个实例对象 var son= new Son(); 然后通过实例对象.方法名就可以使用 son.sing(); //我会唱歌 |
原型链
-
没有改变prototape的原型链
-
如图
-
-
箭头指向它们的方向,上述关系(–proto–,constructor,prototape)都在例子中讲解了,就不再赘述,这里关注的是原型链。(备注:本图的JOSN和Math对象不好画在图的左边,因此又画了一个Object.prototape原型对象在右边。)我们看Son的实例对象,为了方便,我写一个var son=new Son()。通过son.–proto–,可以得到Son.prorotape。如果继续通过链式,即son.–proto–.–proto–可以得到Object.prototape。我们已经知道了,son.–proto–就是为Son.prorotape,那么我们通过Son.prorotape.–proto–也可以得到Object.prototape。也就是说,–proto–能够让我们顺藤摸瓜,一直向上查找,这就是原型链(图中已经用红色大边框包裹起来了)。当我们son.–proto–.–proto–.–proto–(相当于Object.prototape–proto–,也相当于Son.prorotape.–proto–.–proto–)时,得到null,这说明,原型链是有终点的。
-
为什么要原型链?
当我们需要一些方法时,别的函数已经有了这些方法,那么我们不需要自己再次造轮子,可以通过原型链,查找各个父级的prototape属性的值,得到想要的方法。比如:我们想要让Object的实例对象可以使用sing()方法,同时也想让Son的实例对象使用一个sing()方法,那么我们就可以通过在Object的原型对象上,写上这个sing()共有方法,不需要让Son的原型对象上,再写下这个sing()方法。
1 2 3 4 5 6 7 | Object.prototape.sing=function(){ console.log( "我会唱歌" ); } 首先 new 一个实例对象 var son= new Son(); 然后通过实例对象.方法名就可以使用 son.sing(); //我会唱歌 |
- 所以,通过原型链,可以让我们少些一些相同的代码。我们只需要把相同的方法放在父级上,就不必自己重写一个;除此之外,如果我们这个实例对象的构造函数如果没有这个方法,那么这个实例对象会依次向原型链查找,直到查找到Object.prototape为止,如果还没有找到,那就终止,并且报错。如果这个构造函数有这个方法,那么按照构造函数的来,而不是父级的prototape里面的方法。这说明,存在一定的优先级,我们需要注意。
- 改变了prototape的值原型链
-
-
如图
-
如上图所示,即
Son.prototape=new Parent()
。我们试着打印一下Son.prototape的值,console.log(Son.prototape)//Parent {}
,显示是parent的实例对象,再试着打印console.log(Son.prototape.constructor)//function Parent(){}
,发现竟然是Parent,你会心想,这就不合理了。我应该指向Son才对啊。这里我做出解释,因为我们是将Son.prototape重新赋值了,将原本的对象覆盖了,即地址发生了变化。现在的地址和之前的地址不是同一个地址(对象的重新赋值,会改变地址),那么大家是不是瞬间清楚了。同时,这也说明了,Son.prototape.constructor继承Parent.prototape.constructor,我相信这下子大家都清楚了,说明constructor也可以继承。为了解决这种问题,我们必须让Son.prototape.constructor重新指向Son,所以Son.prototape.constructor=Son,这样子,console.log(Son.prototape.constructor)//function Son(){}
就解决问题了。之后通过–proto–与没有改变prototape的原型链是一样的,只不过Son.prototape.–proto–为Parent.prototape。那么,我们new 一个Son的实例对象,有1 2 3 4 5 6 7 8 9 10 11 | var son= new Son(); console.log(son.--proto--) //Son.prototape console.log(son.--proto--.--proto--) //Parent.prototape console.log(son.--proto--.--proto--.--proto--) //Object.prototape console.log(son.--proto--.--proto--.--proto--.--proto--) //null console.log(Son.prototape.--proto--) //Parent.prototape console.log(Son.prototape.--proto--.--proto--) //Object.prototape console.log(Son.prototape.--proto--.--proto--.--proto--) //null console.log(Patent.prototape.--proto--) //Object.prototape console.log(Patent.prototape.--proto--.--proto--) //null console.log(Object.prototape.--proto--) //null |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2021-07-02 TP6框架--EasyAdmin学习笔记:数据表添加新参数,如何强制清除缓存